From 2715ecddbbcbdb3ae21e5b0810406d55bf5d218d Mon Sep 17 00:00:00 2001 From: Vaclav Tvrdik Date: Sun, 25 Oct 2026 10:41:29 +0100 Subject: [PATCH] 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 --- Architecture.md | 14 +++++++------- README.md | 4 ++-- main.py | 2 -- tests/test_render.py | 2 ++ vterm/commands.py | 23 +---------------------- vterm/listeners.py | 2 +- vterm/mouse.py | 2 +- 7 files changed, 14 insertions(+), 35 deletions(-) diff --git a/Architecture.md b/Architecture.md index b5edc34..5f128b5 100644 --- a/Architecture.md +++ b/Architecture.md @@ -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-.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 | diff --git a/README.md b/README.md index db2596e..f439169 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/main.py b/main.py index 8aad758..9679e7b 100644 --- a/main.py +++ b/main.py @@ -12,14 +12,12 @@ from .vterm.commands import ( VtermClearScreenCommand, VtermClearUndoStackCommand, VtermCleanupCommand, - VtermCloseCommand, VtermCopyCommand, VtermInitializeViewCommand, VtermKeypressCommand, VtermNoopCommand, VtermOpenCommand, VtermOpenTerminalProjectFolderCommand, - VtermPasteCommand, VtermPasteSelectionCommand, VtermPasteTextCommand, VtermSelectThemeCommand, diff --git a/tests/test_render.py b/tests/test_render.py index b29d02f..cf3639b 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -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) diff --git a/vterm/commands.py b/vterm/commands.py index fc9e336..6ae5fdb 100644 --- a/vterm/commands.py +++ b/vterm/commands.py @@ -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.""" diff --git a/vterm/listeners.py b/vterm/listeners.py index 6fc19d6..8b64ed8 100644 --- a/vterm/listeners.py +++ b/vterm/listeners.py @@ -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) diff --git a/vterm/mouse.py b/vterm/mouse.py index ea8394f..0c38cdf 100644 --- a/vterm/mouse.py +++ b/vterm/mouse.py @@ -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):