diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/internal/event.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/internal/event.lua new file mode 100644 index 000000000..d0696cc8d --- /dev/null +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/internal/event.lua @@ -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 } diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/programs/edit.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/programs/edit.lua index 05c35d956..e05ec1766 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/programs/edit.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/programs/edit.lua @@ -88,6 +88,7 @@ for i = 1, #wrapped do term.write(wrapped[i]) end os.pullEvent('key') +require "cc.internal.event".discard_char() ]] -- Menus diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/paint.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/paint.lua index 5e53367de..8b28a0851 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/paint.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/paint.lua @@ -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, } diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/programs/help.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/programs/help.lua index 3ae6ac7f7..1dba69ae0 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/programs/help.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/programs/help.lua @@ -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 diff --git a/projects/core/src/test/resources/test-rom/spec/modules/cc/internal/event_spec.lua b/projects/core/src/test/resources/test-rom/spec/modules/cc/internal/event_spec.lua new file mode 100644 index 000000000..1e9ebd2f1 --- /dev/null +++ b/projects/core/src/test/resources/test-rom/spec/modules/cc/internal/event_spec.lua @@ -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)