Skip to content

Enumerate and specify default actions, i.e. behavior which is prevented with preventDefault() #84

Description

@brainkim

A browser gives most keys a default action and lets a page opt out with preventDefault(). The engine has some of those defaults and not others, and the ones it lacks come up as soon as a document is taller than the terminal. This issue collects them so the set can be argued about in one place. The scrolling defaults are breaking, so the set would land together in a 0.2.0.

What exists

  • Tab and Shift+Tab move focus in document order. Past the last stop, focus rests on nothing for one press before wrapping.
  • Enter and Space activate buttons and summaries; Enter follows links; Space and Enter toggle checkboxes and radios; a label click forwards to its control.
  • Escape is a close request for the top popover or dialog.
  • Text inputs have the readline chords; number inputs step on Up and Down; selects take the arrows, Home, End, Enter, Space and Escape.
  • The wheel scrolls the nearest scroll box.
  • Ctrl+C closes the window. It never reaches the document as a key, but window.close() dispatches beforeunload, and a listener that cancels keeps the session.

Proposed

Keyboard scrolling (breaking)

Nothing scrolls a document or an overflow box by keyboard today, which is why the markdown example pages itself and a log has to reveal its own last line. The browser's set, targeting the nearest scrollable ancestor of the focused element and falling back to the document, when the keydown is not canceled:

  • PageUp, PageDown and Space: a page (Shift+Space sends the same byte as Space in a terminal, so there is no keyboard page-up besides PageUp)
  • Up and Down: a row
  • Home and End: the edges

The arrows and Space are the breaking half. A program that listens for them on the document without preventDefault(), while its document overflows, starts scrolling under its own handling. In this repository that is git-log, tree and hacker-news; each needs one preventDefault(), as the same program would in a browser. The page keys and Home/End break nothing, but on a Mac they are Fn+arrows, which Terminal.app and iTerm2 keep for their own scrollback, so shipping only those would help almost nobody.

Ctrl+C and Ctrl+Z are chrome, not keys (additive)

A terminal has no chrome: nothing outside the program to close the tab or background it with. The proposal is that the engine keeps two chords as that chrome, and neither is ever delivered to the document as a keydown.

  • Ctrl+C closes the window, as it does today: window.close() runs, beforeunload fires, and a listener that cancels keeps the session so the program can ask "are you sure". A second Ctrl+C within two seconds closes the session regardless of that listener. Today a program that cancels beforeunload unconditionally has trapped the user; the second press is the way out, and no program can take it away.
  • Ctrl+Z suspends, see below, and is likewise not a key and not cancelable.

Everything else a program can take with preventDefault(). These two it cannot, on purpose: a program's key handling can be wrong, and the user still has to be able to leave.

The alternative is the browser's model, where Ctrl+C is a keydown like any other, the close is its default action, and a program can cancel it, with the second press as the guarantee. It gives a program Ctrl+C for copy at the cost of the one hazard the chrome model rules out: a catch-all preventDefault() on keydown, one common line, makes the first press do nothing, and the user has to know about the second. The chrome model is the proposal; opinions on the alternative are what this section is for.

Implicit submission (additive)

Enter in a text input inside a form submits it when the form has a submit button or a single field, as in a browser. The engine declines this deliberately today on the grounds that a terminal has no implicit submission, but a form with a submit listener is how a web login or search box is written, and nothing fires now.

Radio groups (additive)

Up and Down move the checked radio through its group.

Ctrl+Z (additive)

Raw mode delivers Ctrl+Z as a key and the engine ignores it, so a program cannot be suspended. Leave raw mode, restore the screen, raise SIGTSTP; on SIGCONT re-enter raw mode and repaint. The program sees it as visibility: the document is hidden while suspended and visible again on resume, with visibilitychange on each edge, which is what a backgrounded browser tab reports, so a program that pauses its animation on that event behaves correctly in both places. Not cancelable.

Ctrl+L (additive)

Clear the screen, anchor the document at row 0 and repaint it from the engine's model, which is what readline's Ctrl+L does with a prompt. The diff renderer only writes the cells it believes changed, so after something else writes on the screen the engine and the terminal disagree until everything is repainted; repaintAll exists for the alternate-screen switch and is the primitive here. Starting at the top gives the document the whole screen as its viewport and makes the anchor known rather than detected.

The rows above the old anchor were the shell's and leave the screen, to scrollback in tmux and iTerm2 and nowhere in some terminals, exactly as a shell's Ctrl+L treats what is above its prompt. In tmux the erased document is archived into scrollback too, the double copy the frame renderer avoids by never issuing a full erase; under Ctrl+L the user asked for it, so it is acceptable here and nowhere else.

Left alone

  • Enter on a checkbox toggles it where a browser would submit the form.
  • Ctrl+D outside a field, Ctrl+, and the rest of readline. Each is a key a program could no longer use.

Open questions

  • Should the scrolling defaults ship at all, or should programs keep scrolling themselves? The argument for shipping is that every program with a long document reinvents it; the argument against is that it changes what existing programs see.
  • Two seconds for the Ctrl+C repeat window, or one? And should the engine give the program a way to render the "press again to exit" hint, since it has no chrome to print one in?
  • Should Ctrl+C be a key after all, with the close as its cancelable default and the second press as the guarantee?
  • Tab past the last focusable element rests on nothing for one press before wrapping. That is the browser's trip through its own chrome and the way a program takes keys off a text field, but a terminal has no chrome to visit, every other TUI wraps, and the common terminal program has a single input it wants focused forever: for it the first Tab is a dead press that sends the next keystrokes to the document. Wrap, or rest? A middle: wrap when there is one focusable element and rest otherwise, which is what a browser page with one field feels like, since the chrome trip is a single press there too. Focus itself is a browser idea being carried into a place that never had it, so the question is which parts of it earn their keep.

Drafted by Claude (Fable 5.1), reviewed by the maintainer.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions