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 <noreply@anthropic.com>
This commit is contained in:
Vaclav Tvrdik
2026-09-10 00:12:15 +02:00
parent 1e31990e71
commit ab501b8cb5
4 changed files with 7 additions and 29 deletions

View File

@@ -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 it. A one-row scroll lands exactly on the tolerance boundary, so one Up closes the gate
and one Down re-opens it. 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 ### How colour is actually drawn
`view.add_regions(key, regions, scope)` can only name a **scope**. It cannot take a `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 **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), 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 `syntax` (the empty one), `scroll_past_end` (off — the view never scrolls below the last
disables — `gutter`, `highlight_line`, `word_wrap`, `auto_complete`, `draw_white_space`, line), `scroll_context_lines`, and a list of disables — `gutter`, `highlight_line`,
`draw_indent_guides`, `spell_check`, `translate_tabs_to_spaces`, `command_mode` (which `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 defuses Vintage mode inside a terminal). The view is also set scratch, so closing it never
prompts to save. prompts to save.

View File

@@ -587,8 +587,6 @@ terminal.viewport_y = 400
command = make_command(terminal.view) command = make_command(terminal.view)
fold_end = terminal.view.text_point(3, 0) - 1 fold_end = terminal.view.text_point(3, 0) - 1
terminal.view.settings().set("scroll_past_end", True)
stream.feed("\x1b[?1049h") stream.feed("\x1b[?1049h")
command.sync_alt_fold(terminal) command.sync_alt_fold(terminal)
assert terminal.view.folds == [(0, fold_end)], terminal.view.folds 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") stream.feed("\x1b[?1049l")
command.sync_alt_fold(terminal) command.sync_alt_fold(terminal)
assert terminal.view.folds == [], terminal.view.folds 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 # without scrollback there is nothing to fold
terminal, stream = make_terminal(rows=5, cols=20) terminal, stream = make_terminal(rows=5, cols=20)

View File

@@ -218,8 +218,9 @@ class VtermRenderCommand(sublime_plugin.TextCommand):
if want and view.fold(sublime.Region(0, end)) and entering: if want and view.fold(sublime.Region(0, end)) and entering:
# entering collapses the layout; a stale viewport anchor would # entering collapses the layout; a stale viewport anchor would
# stop the follow-scroll gate from re-engaging. re-asserting a # stop the follow-scroll gate from re-engaging. re-asserting a
# disturbed fold keeps the anchor: apps repaint the boundary row # disturbed fold keeps the anchor instead: apps repaint the
# every scroll, and resetting per repair thrashes the poller # boundary row on every scroll, and resetting per repair would
# drop the user's place each time
terminal.viewport_y = 0 terminal.viewport_y = 0
def sync_cursor_style(self, terminal): def sync_cursor_style(self, terminal):

View File

@@ -280,7 +280,6 @@ class Terminal:
def renderer(): def renderer():
"""Own the emulator: parse, then ask sublime to redraw, once per tick.""" """Own the emulator: parse, then ask sublime to redraw, once per tick."""
settled_y = None
while True: while True:
# a hidden tab is not drawn at all: the screen keeps the state # 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 # 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 # due first: _need_to_render consumes the cursor move it reports
if self.is_render_due() and self._need_to_render(): if self.is_render_due() and self._need_to_render():
self.view.run_command("vterm_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() self._sync_wheel_flag()
if done[0] or not self.is_hosted(): if done[0] or not self.is_hosted():
LOG.debug("[renderer] stops") LOG.debug("[renderer] stops")