A new input API for projects - #50
Open
hleb-rubanau wants to merge 19 commits into
Open
Conversation
The Keys tables in README.md and doc/EDITOR.md were stale and mutually inconsistent. Correct them to the behavior verified against the shipped editor build 1b86c90: toggle edit/run = Ctrl+T (was F8/F9), stop project = Ctrl+S (was Ctrl+Shift+S), quit project = Ctrl+Q (EDITOR had Ctrl+Shift+Q), leave editor = Shift+Esc / Ctrl+Shift+S, drop the stale editor Esc/Ctrl+S/Ctrl+Y rows, and add the input clipboard/selection keys. These tables document commit 1b86c90. The editor-stage1 (2eed100) keymap update follows as a separate change. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_0177PwS4Xws9YFAGqJMADY5c
…work spec The editor's implicit `edit` flag becomes two first-class modes: `nav`, where the arrows walk the file, and `edit`, where an open block sits in the input strip. Transitions are declared in one table in set_mode: nav reaches edit, reorder and search; the special modes and edit only return to nav. A freshly opened buffer always starts in nav. Keys, per spec 2.2/2.3: - Enter on an empty input opens the selected block; Enter while editing accepts it - bare Esc does nothing (it also defuses the device's RMB->Esc binding silently replacing typed input) - Shift+Esc discards the edit; on an empty input it leaves the editor. Ctrl+W no longer leaves a block - Ctrl+S is reserved for the checkpoint: saving is automatic on accept, so bare Ctrl+S does nothing - Ctrl+Delete drops a block in navigation only; while editing it stays the widget's delete-next-word - typing straight into a fresh file inserts a new block and never replaces the first one (replace needs a block deliberately opened) Files open at the first block, the view following the selection; the cursor position is remembered per file for the session and restored on reopen. The statusline receives the real mode. A mode tag was tried and dropped: the mode is evident from the input strip itself, and the tag collided with the left label. Co-Authored-By: Claude <[email protected]>
The buffer model gains an active line under the block selection: an absolute source line, always inside the selected block, clamped on every selection mutation, with move_line stepping across block boundaries. The view draws it and follows it on scroll. Keys, per spec 2.2/2.7: - bare Up/Down move the active line; PageUp/PageDown move it by a viewport page; Home/End go to the file's first and last line; opening a block lands the cursor on the active line, not on line 1 - Ctrl+Up/Down jump block-wise with the paragraph convention: Down to the next block's first line, Up first to the current block's first line - Alt is the peek: Alt+Up/Down scroll a line, Alt+ PageUp/PageDown a page (Alt+Left/Right too — PgUp/PgDn is a four-key chord on the device keyboard), Alt+Home/ End to the file's edges, the selection staying put in both modes; typing after a peek returns the view. Ctrl+Alt keeps working as a synonym. Alt does not move blocks: that is the reorder mode's job (Ctrl+M) - inside the input widget bare Home/End are line-scoped and Ctrl+Home/End reach the whole block (dsent) - Ctrl+J follows the require under the selection; Ctrl+O is left free for a conventional "open file" - returning from a followed require restores the view to the stored position, as opening a file does The textinput guard widens to any Ctrl/Alt chord: device chords leak glyphs, only Shift composes. Co-Authored-By: Claude <[email protected]> Co-Authored-By: dsent <[email protected]>
…ith a knock One path out of an open block, shared by Enter, the arrow edge crossing, Ctrl+Up/Down from editing and the mouse: dirty-check, validate, auto-format, re-chunk, size check, write. Acceptance in place keeps the block (2.4.4); the arrow transition opens the neighbor — downward on its first line, upward on its last (2.4.1). An untouched block leaves freely and writes nothing; an invalid one refuses and stays. - opening a Lua block runs it through the pretty-printer (9.4): a sloppy block is reshaped in the input, dirty from birth, while the file stays untouched until acceptance - the block size limit is the input strip's height (14 lines); the refusal names how many lines to remove and the way out, per 1.4 - an eval refusal moves the cursor to the first error's line and shows the parser's message until the first fixing keystroke (2.5); the refusing keypress is blocked from the widget so it does not clear its own message - every refusal knocks (2.4.3): a rejected block, a jump or page move at the file's edge, a click away from an invalid block, Ctrl+J with nothing to follow — one neutral sound, since walking off the end of a file is not a mistake - typing in navigation opens the block at the active line with a blank line to type into (2.1); on a blank line it composes a new block. Ctrl+Enter opens a fresh block below (Ctrl+Shift+Enter above) and accepts while editing - the acceptance pipeline lives in controller methods (accept_block and friends), reachable from any input, and reports its verdict so a refusal blocks the widget - writes are durable: the accept path fsyncs the file, and a failed write refuses with a message instead of reading as accepted (dsent, after real data loss on the device's exfat SD card) - typing right after deleting the last block anchors the input properly (dsent) Co-Authored-By: Claude <[email protected]> Co-Authored-By: dsent <[email protected]>
…) in the REPL
Ctrl+K copies the file to {name}.~save; Ctrl+Shift+K copies
it back and reloads the open buffer. Both raise a dialog
when there is something to lose — an existing checkpoint,
a file about to be overwritten — showing the timestamps of
the checkpoint and the file (FS.getInfo). While editing,
Ctrl+K accepts the open block first, so the checkpoint
reflects the screen; a refused block aborts it.
The REPL gains revert(name) (default main.lua): restore
without a prompt, false when no checkpoint exists.
Co-Authored-By: Claude <[email protected]>
…double click opens BufferView:line_at(y) maps a pixel row through the scroll offset and the wrap to a source line; BufferModel: block_at_line finds its block. In navigation a click selects the block and line; a double click opens it, the mouse counterpart of Enter. While editing, a click inside the open block places the input cursor on that line, and a click outside goes through the acceptance gate exactly as an arrow transition does: an untouched block leaves, a changed one is accepted and written, an invalid one refuses and keeps the editor. Buffer-area clicks route to the editor through the console; input-strip clicks keep going to the widget. Co-Authored-By: Claude <[email protected]>
Text level, inside an open block: EditHistory keeps a
32-step ring of {text, cursor} snapshots taken before each
mutation. Consecutive same-kind edits at the expected
cursor coalesce and a typed whitespace starts a new step,
so Ctrl+Z removes the word just typed — no wall-clock
timers, deterministic and testable. The history is born
with the block (set_text resets it) and never leaks across
blocks or into the file.
Block level, in navigation: a 32-step ring on the buffer
records every file write as a trimmed diff — the common
prefix and suffix are cut, leaving exactly the affected
line range. One recording wrapper (record_write) serves
every write site: acceptance, insertion, deletion, the
reorder move. Applying a step is a splice plus the usual
re-chunk and save; selection restores to the step's
remembered side. The history lives on the buffer, so it
survives a follow-require round trip and dies with the
file; a checkpoint restore rebuilds the buffer, which is
the boundary. A new write kills the redo tail.
Keys: Ctrl+Z / Ctrl+Y in both modes; bare Delete drops a
block in navigation now that it is undoable (2.7), without
touching the clipboard — cutting is Ctrl+X alone, since on
the device every clipboard write pops the share overlay.
Dialogs are repeat-proof by construction, as agreed after
the device pass: the confirming key differs from the
invoking one. Discard (Shift+Esc on a changed block),
checkpoint overwrite and restore all confirm on Enter or
Space and cancel on anything else — the invoking chord and
Esc included — so a held key oscillates ask/cancel and
never fires. One executor runs the confirmed action; the
handlers only raise. A parseable draft discarded with
confirmation leaves a recoverable pair in the block
history: one Ctrl+Z puts it in the file, another takes it
out. A plain error message closes on Enter, Esc or
Shift+Esc without re-submitting, matching the REPL.
Co-Authored-By: Claude <[email protected]>
…editing flag UserInputModel:backspace_word eats the word before the cursor together with the whitespace in front of it, as readline does, on Ctrl+Backspace and Ctrl+W (spec 2.7). UserInputModel is the whole platform's input — the console, the project inputs the games read, and search all build on it — so the editor's extras must not leak. An explicit `editing` flag on the model (off by default; only the editor model opts in) scopes them: word deletion and the refusal frame gate on it, the edit history records nothing without it, and the widget's Ctrl+Y delete-line stays for the console (in the editor the controller takes Ctrl+Y for redo first). Regression specs pin the contract from the console's side: a flagless model records nothing, Ctrl+Backspace deletes one character, Ctrl+W does nothing, Ctrl+Y deletes the line. Co-Authored-By: Claude <[email protected]>
README.md and doc/EDITOR.md describe the final layout: line-wise navigation with Ctrl-arrow block jumps, Home/End to the file's edges, the Alt peek, Ctrl+Enter blocks, undo/redo, bare Delete, word deletion, the dialog keys, checkpoints and Ctrl+J. The input widget's Home/End rows follow the line-scoped fix. Co-Authored-By: Claude <[email protected]> Co-Authored-By: dsent <[email protected]>
…gnore rules Update the top-level README's editor walkthrough to the key shortcuts after the editor overhaul this branch builds on: Enter opens a block for editing and Enter sends it back, Shift+Esc leaves the editor, and the deprecated Ctrl+Shift+S is called out as leaving without the acceptance step. Also marks the doc/mermaid diagram sources as historical design sketches and updates .gitignore. Docs only; no behaviour change.
The project-lifecycle tests need to stub the filesystem methods invoked while a project run tears down; this exposes them as seams so the suite can drive exit without touching the real filesystem. Isolated to src/util/filesystem.lua. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Adds doc/input_api.md -- the project-facing guide to compy.input (the precise surface), the simple compy.ask/on_answer/on_key surface, the four lifecycle flags, shortcuts and hooks, and a migration table from the removed legacy globals -- and the CHANGELOG.md CURRENT_SCOPE at user-facing altitude, including the two breaking changes. Co-Authored-By: Claude Opus 4.8 <[email protected]>
The developer documentation corpus under doc/development: the internals notes, the conventions, the decisions ledger (decisions/input.md), the technical-debt register (technical_debt/*), and the overview, drawing-system, smoke-checklist and tests docs. Reference material for maintainers; no shipped code. Co-Authored-By: Claude Opus 4.8 <[email protected]>
The busted specs for the widget surface and its callbacks/lifecycle, the input routing chain, global shortcuts and clicks, the simple surface, console fall-through, editor keys, cursor/text handling, combo serialisation, config-key agreement and the memory NFR, plus shared helpers and fixtures and updates to the editor, history, model, view and mock specs. Green suite: 1174 / 0 / 0 / 9. Co-Authored-By: Claude Opus 4.8 <[email protected]>
The routing spine: controller.lua dispatch, consoleController, the new projectInputController, and userInputController. Events walk a chain of responsibility -- shortcuts, then hooks, then the shown widget -- with the project run owning its route and the console taking only what nothing consumed. Co-Authored-By: Claude Opus 4.8 <[email protected]>
The input widget's model and view: userInputModel (content, cursor, history and the submit/cancel lifecycle behind the four flags), userInputView rendering, and the consoleModel touchpoints. Activation no longer destroys content; get_text reads it back; clearing and hiding are flag-driven at each verb. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Editor key handling sited at route level (editorController), and the editorModel and searchModel touchpoints, reconciled to the imported editor rework. NOTE: part of this diff is the editor-rework baseline rather than new work -- see the PR slices manifest's base-drift caveat. Co-Authored-By: Claude Opus 4.8 <[email protected]>
…mony main.lua wiring (the widget built at the run seam, key-repeat, before_exit), src/util/key.lua (the combo and modifier vocabulary), types.lua, the evaluator's move to line callbacks and validators, and the harmony screenshot-test init. Co-Authored-By: Claude Opus 4.8 <[email protected]>
The in-tree example projects move off the removed polling globals onto compy.input and compy.ask: turtle, tixy, sapper, guess, repl, valid, clock, paint and pong, with a held-key fix in pong and README updates. The nested example repositories keep their own PRs. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This branch addresses issue compy-toys#77
Stakeholders asked for a simpler and more robust input API. This delivers it:
one input widget with a precise surface and a simple wrapper over it, a lifecycle
a project configures rather than infers from the call it made, first-class
keyboard/mouse/touch shortcuts, and the removal of the legacy polling globals.
It is a large, breaking change and a
1.0.0-rc— the full, user-facing log isCHANGELOG.md(CURRENT_SCOPE), and the project-author guide isdoc/input_api.md, which is enough on its own to review the surface.It is submitted ahead of some remaining work — device testing, human review
and the docs/ledger finishing passes — see What's still owed
before merging.
What's delivered
Two surfaces over one widget.
compy.ask(prompt, …)puts a question on screen,compy.on_answer(text)receives it (or
nilon Escape, telling answered from abandoned),compy.on_key(...)reports keys while it is open and its return value decideswho consumes them, and
compy.unask()withdraws it. A wrapper over the samewidget — no second widget, no second event path.
compy.input.show / hide / configure / clear / set_text / set_cursor / get_text / get_cursor / is_shown, plus callbacks, hooks, shortcutsand
fn. Oneshowand a callback replace the old poll-a-global-every-frameidiom.
A lifecycle the project sets.
clear_on_submit,hide_on_submit,clear_on_cancel,hide_on_cancel— each a mode that persists until unset,settable on
showorconfigure. Colouring, the submit gate and the submitaction are three optional keys (
highlighter,validator,on_text_entered),no longer bundled into a choice of function name.
clear_on_submiton, Escape does nothinguntil asked.
show/configuresplit cleanly by who owns what (content isshow's; project settings are either's), and both raise on an unknown keyinstead of dropping it.
Shortcuts, hooks and event control.
compy.input.shortcuts.<event>[combo]binds keyboard/mouse/touch shortcutsdirectly (including modifier classes like
alt+*);compy.input.hooks[event]is the per-event fallback, and an existing
love.*handler is seeded into itand keeps working.
compy.input.fn.*declares repeat/propagation handling atthe binding site.
compy.before_exitlets a project restore global devicestate before its run ends.
Robustness.
hide/show(settings and content persist), activation nolonger destroys content, and a shown widget no longer blocks the project's
own key/text handlers.
content is refused with a readable message; the error boundary no longer
swallows crashes in
love.update/pointer handlers or drops callback argumentson non-LuaJIT runtimes; unconsumed project input no longer accumulates in the
hidden console.
Breaking changes (announced here, not in the version — this is pre-1.0):
input_text,input_code,validated_input,user_input,write_to_input(andcompy.singleclick/doubleclick). The guide's "Migration from the legacyglobals" maps each to its replacement.
What it stays on
This branch is built on the platform's editor overhaul, upstream PR #45, which
it imported as its base (the editor's key routing and discard behaviour are #45's,
and the input widget seats its text field through the seam #45 owns).
Merge-order consequence, stated plainly: this branch already contains #45's
commits. So —
The platform repository is otherwise reconciled with
upstream/dev; one smallupstream edge remains and is post-release.
What's still owed
This PR ships ahead of the following, each recorded as active technical debt in
doc/development/technical_debt/and to be closed before the release proper:T-PR-DEVICE-EX) — the shipped surface hasnever been completely exercised on the target hardware in the form it now takes; prior
device runs predate the surface's significant moves, so this is thorough
validation, not a smoke. The same entry covers re-fitting the external example
projects to the shipped API (they sit on an earlier version) and opening their
own PRs. Smoke test faitfhfully done: basic editing, running projects, in-repo tixy/turtle/sapper.
T-PR-REVIEW) — no human code review and no cold read of thewhole assembled change has happened yet; that is what this submission is for.
T-PR-CORPUS) — the final documentation compaction, areadability pass and a proof-read, and two ledger-hygiene sweeps. Legibility
debt, not correctness — no behaviour depends on it.
Two capabilities are deferred deliberately, with their reasons on record: a
project cannot read/navigate the widget's input history (the limitation is
documented in the guide; the capability is backlogged), and
compy.inputin theconsole environment is designed but out of this release.