Standardise how we discard "char" events

One common issue we get when a program exits after handling a "key"
event is that it leaves the "char" event on the queue. This means that
the shell (or whatever program we switch in to) then receives the "char"
event, often displaying it to the screen.

Previously we've got around this by doing sleep(0) before exiting the
program. However, we also see this problem in edit's run handler script,
and I'm less comfortable doing the same hack there.

This adds a new internal discard_char function, which will either
wait one tick or return when seeing a char/key_up event.

Fixes #1705
This commit is contained in:
Jonathan Coates 2024-01-31 20:49:39 +00:00
parent ebeaa757a9
commit 033378333f
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
5 changed files with 83 additions and 2 deletions

View File

@ -0,0 +1,37 @@
-- SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
--
-- SPDX-License-Identifier: MPL-2.0
--[[- Utilities for working with events.
> [!DANGER]
> This is an internal module and SHOULD NOT be used in your own code. It may
> be removed or changed at any time.
@local
]]
--[[-
Attempt to discard a [`event!char`] event that may follow a [`event!key`] event.
This attempts to flush the event queue via a timer, stopping early if we observe
another key or char event.
We flush the event queue by waiting a single tick. It is technically possible
the key and char events will be delivered in different ticks, but it should be
very rare, and not worth adding extra delay for.
]]
local function discard_char()
local timer = os.startTimer(0)
while true do
local event, id = os.pullEvent()
if event == "timer" and id == timer then break
elseif event == "char" or event == "key" or event == "key_up" then
os.cancelTimer(timer)
break
end
end
end
return { discard_char = discard_char }

View File

@ -88,6 +88,7 @@ for i = 1, #wrapped do
term.write(wrapped[i])
end
os.pullEvent('key')
require "cc.internal.event".discard_char()
]]
-- Menus

View File

@ -300,7 +300,7 @@ local menu_choices = {
return false
end,
Exit = function()
sleep(0) -- Super janky, but consumes stray "char" events from pressing Ctrl then E separately.
require "cc.internal.event".discard_char() -- Consume stray "char" events from pressing Ctrl then E separately.
return true
end,
}

View File

@ -257,7 +257,7 @@ while true do
offset = print_height - content_height
draw()
elseif param == keys.q then
sleep(0) -- Super janky, but consumes stray "char" events.
require "cc.internal.event".discard_char()
break
end
elseif event == "mouse_scroll" then

View File

@ -0,0 +1,43 @@
-- SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
--
-- SPDX-License-Identifier: MPL-2.0
local timeout = require "test_helpers".timeout
describe("cc.internal.event", function()
local event = require "cc.internal.event"
describe("discard_char", function()
local function test(events)
local unique_event = "flush_" .. math.random(2 ^ 30)
-- Queue and pull to flush the queue once.
os.queueEvent(unique_event)
os.pullEvent(unique_event)
-- Queue our desired events
for i = 1, #events do os.queueEvent(table.unpack(events[i])) end
timeout(0.1, function()
event.discard_char()
-- Then read the remainder of the event queue, and check there's
-- no char event.
os.queueEvent(unique_event)
while true do
local event = os.pullEvent()
if event == unique_event then break end
expect(event):ne("char")
end
end)
end
it("discards char events", function()
test { { "char", "a" } }
end)
it("handles an empty event queue", function()
test {}
end)
end)
end)