Report a failed spawn, drop the paste wrapper and the unbound close command, sync docs

- vterm_open shows an error dialog when the command cannot start,
  instead of a tab that silently vanishes
- on_text_command hands paste straight to vterm_paste_text;
  VtermPasteCommand only forwarded
- VtermCloseCommand was bound nowhere; closing the tab already kills
  the process
- wheel_lines fallback matches the shipped 1; README said three
- Architecture.md: per-theme scheme files and managers, command table
This commit is contained in:
Vaclav Tvrdik
2026-10-25 10:41:29 +01:00
parent a556dab638
commit 2715ecddbb
7 changed files with 14 additions and 35 deletions

View File

@@ -11,7 +11,7 @@
| `vterm/render.py` | Screen → view text, colour overlays, caret, folding, trimming. |
| `vterm/scheme.py` | `SchemeManager`: generates one theme's colour scheme, the scopes the renderer paints with. `get_scheme_manager` caches one instance per theme name, each terminal frozen to the instance it was opened or rethemed with. |
| `vterm/themes.py` | The built-in palettes (`default`, `homebrew`, `pro`, `slate`, `red-alert`). |
| `vterm/commands.py` | The Sublime commands: open, keypress, copy/paste, theme picker, clear, cleanup, close. |
| `vterm/commands.py` | The Sublime commands: open, keypress, copy/paste, theme picker, clear, cleanup. |
| `vterm/listeners.py` | `VtermCoreEventListener`: focus, IME capture, copy/paste rerouting, the mousemap context. |
| `vterm/mouse.py` | Wheel and click handling. |
| `vterm/keys.py` | Key name + modifiers → escape sequence tables. |
@@ -21,14 +21,15 @@
| File | Role |
|---|---|
| `vterm.sublime-settings` | Defaults for the eleven plugin settings. |
| `vterm.sublime-settings` | Defaults for the twelve plugin settings. |
| `vterm.sublime-commands` | Four palette entries: open, open terminal in project folder, clear screen, select theme. |
| `vterm.sublime-syntax` | An intentionally empty hidden syntax, so terminal text is never tokenized. |
| `Default.sublime-keymap` | 245 generated bindings. **Do not edit by hand** — regenerate with `tools/gen_keymap.py`. |
| `Default (OSX).sublime-mousemap` | Two wheel bindings and the middle button. |
| `dependencies.json` | Package Control libraries. |
Generated at runtime, not in the repo: `Packages/User/vterm/vterm.hidden-color-scheme`.
Generated at runtime, not in the repo: `Packages/User/vterm/vterm-<theme>.hidden-color-scheme`,
one per theme (`vterm-system` for "no theme").
## Classes
@@ -50,8 +51,8 @@ Generated at runtime, not in the repo: `Packages/User/vterm/vterm.hidden-color-s
into the next one. Parity is enforced by a differential test (`run_fast_path_cases`).
- **`Cursor`** (`emulator.py`) — replaces pyte's so the default attribute set can be a
`Char` carrying the extra `linefeed` field.
- **`SchemeManager`** (`scheme.py`) — module singleton `scheme_manager`. Owns the
generated scheme file and the mapping from colour combination to scope name.
- **`SchemeManager`** (`scheme.py`) — one per theme name, cached by `get_scheme_manager`.
Owns the generated scheme file and the mapping from colour combination to scope name.
- **`VtermRenderCommand`** (`render.py`) — the per-frame logic. Sublime rebuilds every
`TextCommand` instance whenever any plugin loads, so the view's render state
(`RenderState`: `colored_lines`, `row_cache`, `key_counter`, the scrollback chunk
@@ -68,12 +69,11 @@ Generated at runtime, not in the repo: `Packages/User/vterm/vterm.hidden-color-s
| `vterm_keypress` | `VtermKeypressCommand` | 244 keymap bindings |
| `vterm_render` | `VtermRenderCommand` | renderer thread |
| `vterm_show_cursor` | `VtermShowCursorCommand` | internal |
| `vterm_copy` / `vterm_paste` / `vterm_paste_text` | `commands.py` | `on_text_command`, IME capture |
| `vterm_copy` / `vterm_paste_text` | `commands.py` | `on_text_command`, IME capture |
| `vterm_paste_selection` | `commands.py` | mousemap, middle button |
| `vterm_select_theme` | `VtermSelectThemeCommand` | palette |
| `vterm_clear_screen` | `VtermClearScreenCommand` | palette, `super+shift+k` |
| `vterm_cleanup` | `VtermCleanupCommand` | renderer, on process exit |
| `vterm_close` | `VtermCloseCommand` | nothing — available for a user keybinding |
| `vterm_clear_undo_stack` / `vterm_noop` | `commands.py` | internal |
| `vterm_scroll` / `vterm_mouse_press` / `vterm_mouse_release` | `mouse.py` | mousemap |

View File

@@ -87,8 +87,8 @@ fullscreen UI before it enables mouse reporting).
Sublime raises no wheel event at all for the trackpad (sublime_text#1838): set
a utility such as [LinearMouse](https://linearmouse.app/) to discrete scrolling
for Sublime Text and it arrives as wheel notches like any other. A notch sends the
app three scroll lines, as iTerm2 does; `wheel_lines` lowers that when a swipe
scrolls too far. While an app owns the
app one scroll line; `wheel_lines` raises that (iTerm2 sends 3) when the wheel
feels slow. While an app owns the
alternate screen the scrollback above it is folded away, kitty-style, and
unfolds when the app leaves it.

View File

@@ -12,14 +12,12 @@ from .vterm.commands import (
VtermClearScreenCommand,
VtermClearUndoStackCommand,
VtermCleanupCommand,
VtermCloseCommand,
VtermCopyCommand,
VtermInitializeViewCommand,
VtermKeypressCommand,
VtermNoopCommand,
VtermOpenCommand,
VtermOpenTerminalProjectFolderCommand,
VtermPasteCommand,
VtermPasteSelectionCommand,
VtermPasteTextCommand,
VtermSelectThemeCommand,

View File

@@ -455,6 +455,8 @@ Terminal._terminals[id(terminal)] = terminal
terminal.view.id = lambda: id(terminal)
scroll = VtermScrollCommand.__new__(VtermScrollCommand)
scroll.view = terminal.view
# three per notch, so "one report per line" is visible
sublime.load_settings(SETTINGS_FILE).set("wheel_lines", 3)
stream.feed("\x1b[?1049h")
scroll.run(None, up=True)

View File

@@ -60,6 +60,7 @@ class VtermOpenCommand(sublime_plugin.WindowCommand):
except Exception:
LOG.exception("failed to start terminal: cmd=%s", cmd)
view.close()
sublime.error_message("vterm: could not start {}".format(" ".join(cmd)))
return
window.focus_view(view)
@@ -189,14 +190,6 @@ class VtermCopyCommand(sublime_plugin.TextCommand):
sublime.set_clipboard(selection_text(self.view))
class VtermPasteCommand(sublime_plugin.TextCommand):
"""Pastes the clipboard into the app; replaces sublime's own paste."""
def run(self, edit):
"""Hand the clipboard to vterm_paste_text."""
self.view.run_command("vterm_paste_text", {"text": sublime.get_clipboard()})
class VtermPasteTextCommand(sublime_plugin.TextCommand):
"""Sends arbitrary text to the app, bracketed when it asked for that."""
@@ -334,20 +327,6 @@ class VtermCleanupCommand(sublime_plugin.TextCommand):
view.sel().clear()
class VtermCloseCommand(sublime_plugin.TextCommand):
"""Kills the process and closes the tab. Registered but bound nowhere; for user keybindings."""
def run(self, _):
"""Kill the process, then close the view."""
view = self.view
if not view.settings().get("vterm_view"):
return
terminal = Terminal.from_id(view.id())
if terminal:
terminal.kill()
view.close()
class VtermClearUndoStackCommand(sublime_plugin.TextCommand):
"""Drops the undo history, so rendered output can never be undone into view."""

View File

@@ -104,6 +104,6 @@ class VtermCoreEventListener(sublime_plugin.EventListener):
if name == "copy":
return ("vterm_copy", None)
elif name in ("paste", "paste_and_indent"):
return ("vterm_paste", None)
return ("vterm_paste_text", {"text": sublime.get_clipboard()})
elif name == "undo":
return ("vterm_noop", None)

View File

@@ -119,7 +119,7 @@ class VtermScrollCommand(sublime_plugin.TextCommand):
cell = None
if terminal.screen.mouse_reporting_enabled() and event:
cell = _event_to_cell(self.view, terminal, event, clamp=True)
lines = max(0, int(settings().get("wheel_lines", 3)))
lines = max(0, int(settings().get("wheel_lines", 1)))
send_scroll(terminal, up, lines, cell)
def want_event(self):