From ab501b8cb53e2cbca438acb79be40cb336445864 Mon Sep 17 00:00:00 2001 From: Vaclav Tvrdik Date: Thu, 10 Sep 2026 00:12:15 +0200 Subject: [PATCH] remove viewport pin left over from touchpad handling The renderer pinned a viewport dragged into sublime's past-end room. With scroll_past_end off there is no such room: sublime clamps to bottom_y, so the trigger can only fire against a stale anchor, which following() already resolves on the next frame. Co-Authored-By: Claude Opus 5 --- Architecture.md | 12 ++++-------- tests/test_render.py | 3 --- vterm/render.py | 5 +++-- vterm/terminal.py | 16 ---------------- 4 files changed, 7 insertions(+), 29 deletions(-) diff --git a/Architecture.md b/Architecture.md index a05cc45..0dab940 100644 --- a/Architecture.md +++ b/Architecture.md @@ -196,11 +196,6 @@ the same way, and scrolling down clamps to the anchor `bottom_y` returns, which it. A one-row scroll lands exactly on the tolerance boundary, so one Up closes the gate and one Down re-opens it. -**Pinning waits for rest.** The renderer pins a viewport dragged into sublime's past-end -room on a terminal too idle to draw a frame, but only once two ticks agree it has come to -rest: macOS keeps scrolling after the fingers lift, and a hard `set_viewport_position` -against that momentum yanks the view back for as long as the glide lasts. - ### How colour is actually drawn `view.add_regions(key, regions, scope)` can only name a **scope**. It cannot take a @@ -367,9 +362,10 @@ palette and restyles open terminals in place. **Stamped on every terminal view** by `vterm_initialize_view`: `vterm_view` (the marker every context and hook keys off), `is_widget`, `color_scheme` (the generated scheme), -`syntax` (the empty one), `scroll_past_end`, `scroll_context_lines`, and a list of -disables — `gutter`, `highlight_line`, `word_wrap`, `auto_complete`, `draw_white_space`, -`draw_indent_guides`, `spell_check`, `translate_tabs_to_spaces`, `command_mode` (which +`syntax` (the empty one), `scroll_past_end` (off — the view never scrolls below the last +line), `scroll_context_lines`, and a list of disables — `gutter`, `highlight_line`, +`word_wrap`, `auto_complete`, `draw_white_space`, `draw_indent_guides`, `spell_check`, +`translate_tabs_to_spaces`, `command_mode` (which defuses Vintage mode inside a terminal). The view is also set scratch, so closing it never prompts to save. diff --git a/tests/test_render.py b/tests/test_render.py index e39fd30..7518d55 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -587,8 +587,6 @@ terminal.viewport_y = 400 command = make_command(terminal.view) fold_end = terminal.view.text_point(3, 0) - 1 -terminal.view.settings().set("scroll_past_end", True) - stream.feed("\x1b[?1049h") command.sync_alt_fold(terminal) assert terminal.view.folds == [(0, fold_end)], terminal.view.folds @@ -610,7 +608,6 @@ assert terminal.viewport_y == 60, terminal.viewport_y stream.feed("\x1b[?1049l") command.sync_alt_fold(terminal) assert terminal.view.folds == [], terminal.view.folds -assert terminal.view.settings().get("scroll_past_end") is True, "left untouched" # without scrollback there is nothing to fold terminal, stream = make_terminal(rows=5, cols=20) diff --git a/vterm/render.py b/vterm/render.py index 6d7444a..ace85a9 100644 --- a/vterm/render.py +++ b/vterm/render.py @@ -218,8 +218,9 @@ class VtermRenderCommand(sublime_plugin.TextCommand): if want and view.fold(sublime.Region(0, end)) and entering: # entering collapses the layout; a stale viewport anchor would # stop the follow-scroll gate from re-engaging. re-asserting a - # disturbed fold keeps the anchor: apps repaint the boundary row - # every scroll, and resetting per repair thrashes the poller + # disturbed fold keeps the anchor instead: apps repaint the + # boundary row on every scroll, and resetting per repair would + # drop the user's place each time terminal.viewport_y = 0 def sync_cursor_style(self, terminal): diff --git a/vterm/terminal.py b/vterm/terminal.py index eae1552..b0d05bb 100644 --- a/vterm/terminal.py +++ b/vterm/terminal.py @@ -280,7 +280,6 @@ class Terminal: def renderer(): """Own the emulator: parse, then ask sublime to redraw, once per tick.""" - settled_y = None while True: # a hidden tab is not drawn at all: the screen keeps the state # and one render on unhide catches the view up. The tick stays at @@ -298,21 +297,6 @@ class Terminal: # due first: _need_to_render consumes the cursor move it reports if self.is_render_due() and self._need_to_render(): self.view.run_command("vterm_render") - # a frame can move the viewport: a sample from - # before it says nothing about coming to rest - settled_y = None - else: - # the wheel can drag the screen off the top into - # sublime's past-end room; a frame undoes that, and - # an idle terminal draws none. Pinning while the - # view is still gliding fights the scroll's - # momentum, which reads as shaking, so wait for two - # ticks to agree that it has come to rest - y = self.view.viewport_position()[1] - if y == settled_y and self.viewport_y < y: - from .render import request_scroll # module-level import would cycle - request_scroll(self.view, self) - settled_y = y self._sync_wheel_flag() if done[0] or not self.is_hosted(): LOG.debug("[renderer] stops")