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/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/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/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/listeners.py` | `VtermCoreEventListener`: focus, IME capture, copy/paste rerouting, the mousemap context. |
| `vterm/mouse.py` | Wheel and click handling. | | `vterm/mouse.py` | Wheel and click handling. |
| `vterm/keys.py` | Key name + modifiers → escape sequence tables. | | `vterm/keys.py` | Key name + modifiers → escape sequence tables. |
@@ -21,14 +21,15 @@
| File | Role | | 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-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. | | `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.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. | | `Default (OSX).sublime-mousemap` | Two wheel bindings and the middle button. |
| `dependencies.json` | Package Control libraries. | | `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 ## 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`). 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 - **`Cursor`** (`emulator.py`) — replaces pyte's so the default attribute set can be a
`Char` carrying the extra `linefeed` field. `Char` carrying the extra `linefeed` field.
- **`SchemeManager`** (`scheme.py`) — module singleton `scheme_manager`. Owns the - **`SchemeManager`** (`scheme.py`) — one per theme name, cached by `get_scheme_manager`.
generated scheme file and the mapping from colour combination to scope name. Owns the generated scheme file and the mapping from colour combination to scope name.
- **`VtermRenderCommand`** (`render.py`) — the per-frame logic. Sublime rebuilds every - **`VtermRenderCommand`** (`render.py`) — the per-frame logic. Sublime rebuilds every
`TextCommand` instance whenever any plugin loads, so the view's render state `TextCommand` instance whenever any plugin loads, so the view's render state
(`RenderState`: `colored_lines`, `row_cache`, `key_counter`, the scrollback chunk (`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_keypress` | `VtermKeypressCommand` | 244 keymap bindings |
| `vterm_render` | `VtermRenderCommand` | renderer thread | | `vterm_render` | `VtermRenderCommand` | renderer thread |
| `vterm_show_cursor` | `VtermShowCursorCommand` | internal | | `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_paste_selection` | `commands.py` | mousemap, middle button |
| `vterm_select_theme` | `VtermSelectThemeCommand` | palette | | `vterm_select_theme` | `VtermSelectThemeCommand` | palette |
| `vterm_clear_screen` | `VtermClearScreenCommand` | palette, `super+shift+k` | | `vterm_clear_screen` | `VtermClearScreenCommand` | palette, `super+shift+k` |
| `vterm_cleanup` | `VtermCleanupCommand` | renderer, on process exit | | `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_clear_undo_stack` / `vterm_noop` | `commands.py` | internal |
| `vterm_scroll` / `vterm_mouse_press` / `vterm_mouse_release` | `mouse.py` | mousemap | | `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 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 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 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 app one scroll line; `wheel_lines` raises that (iTerm2 sends 3) when the wheel
scrolls too far. While an app owns the feels slow. While an app owns the
alternate screen the scrollback above it is folded away, kitty-style, and alternate screen the scrollback above it is folded away, kitty-style, and
unfolds when the app leaves it. unfolds when the app leaves it.

View File

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

View File

@@ -455,6 +455,8 @@ Terminal._terminals[id(terminal)] = terminal
terminal.view.id = lambda: id(terminal) terminal.view.id = lambda: id(terminal)
scroll = VtermScrollCommand.__new__(VtermScrollCommand) scroll = VtermScrollCommand.__new__(VtermScrollCommand)
scroll.view = terminal.view 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") stream.feed("\x1b[?1049h")
scroll.run(None, up=True) scroll.run(None, up=True)

View File

@@ -60,6 +60,7 @@ class VtermOpenCommand(sublime_plugin.WindowCommand):
except Exception: except Exception:
LOG.exception("failed to start terminal: cmd=%s", cmd) LOG.exception("failed to start terminal: cmd=%s", cmd)
view.close() view.close()
sublime.error_message("vterm: could not start {}".format(" ".join(cmd)))
return return
window.focus_view(view) window.focus_view(view)
@@ -189,14 +190,6 @@ class VtermCopyCommand(sublime_plugin.TextCommand):
sublime.set_clipboard(selection_text(self.view)) 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): class VtermPasteTextCommand(sublime_plugin.TextCommand):
"""Sends arbitrary text to the app, bracketed when it asked for that.""" """Sends arbitrary text to the app, bracketed when it asked for that."""
@@ -334,20 +327,6 @@ class VtermCleanupCommand(sublime_plugin.TextCommand):
view.sel().clear() 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): class VtermClearUndoStackCommand(sublime_plugin.TextCommand):
"""Drops the undo history, so rendered output can never be undone into view.""" """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": if name == "copy":
return ("vterm_copy", None) return ("vterm_copy", None)
elif name in ("paste", "paste_and_indent"): elif name in ("paste", "paste_and_indent"):
return ("vterm_paste", None) return ("vterm_paste_text", {"text": sublime.get_clipboard()})
elif name == "undo": elif name == "undo":
return ("vterm_noop", None) return ("vterm_noop", None)

View File

@@ -119,7 +119,7 @@ class VtermScrollCommand(sublime_plugin.TextCommand):
cell = None cell = None
if terminal.screen.mouse_reporting_enabled() and event: if terminal.screen.mouse_reporting_enabled() and event:
cell = _event_to_cell(self.view, terminal, event, clamp=True) 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) send_scroll(terminal, up, lines, cell)
def want_event(self): def want_event(self):