diff --git a/Architecture.md b/Architecture.md index fc95ce0..a05cc45 100644 --- a/Architecture.md +++ b/Architecture.md @@ -34,7 +34,7 @@ Constraints it works under: | `vterm/themes.py` | The built-in palettes (`default`, `homebrew`, `pro`, `slate`). | | `vterm/commands.py` | The Sublime commands: open, keypress, copy/paste, theme picker, clear, cleanup, close. | | `vterm/listeners.py` | `VtermCoreEventListener`: focus, IME capture, copy/paste rerouting, the mousemap context. | -| `vterm/mouse.py` | Wheel and click handling, and the trackpad poller. | +| `vterm/mouse.py` | Wheel and click handling. | | `vterm/keys.py` | Key name + modifiers → escape sequence tables. | | `vterm/utils.py` | `settings()`, the `responsive` throttle, the `intermission` pacer, `rev_wcwidth`. | @@ -133,8 +133,7 @@ child writes │ _need_to_render()? dirty rows, a pending clear, or a │ moved cursor │ view.run_command("vterm_render") - │ _sync_wheel_flag() publishes the mousemap gate, and starts the - │ trackpad poller while the app owns the wheel + │ _sync_wheel_flag() publishes the mousemap gate │ └─ UI thread ────────────────────────────────────────────────── VtermRenderCommand.run(edit) @@ -183,9 +182,6 @@ view row of screen row 0. When the emulator retires lines into `screen.history`, renderer drains them, advances `offset` by that many rows, and writes them at their now permanent view rows. **Once a line leaves `screen.history`, its only copy is the view text** — which is why the alternate screen *folds* the scrollback rather than deleting it. -A fresh view is given one blank scrollback row before anything else: Sublime lays out -nothing above row 0, and the trackpad poller needs that slack to see an upward gesture at -all — on the alt screen no line ever retires to provide it. **The row cache.** pyte's `dirty` set is a repaint *hint*, not a diff: scrolling one line marks every row dirty, and a TUI repaints whole frames. `update_line` compares @@ -200,13 +196,10 @@ 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.** Two pollers put the viewport back on the anchor: the renderer -tick, for a viewport dragged into sublime's past-end room on a terminal too idle to draw -a frame, and the trackpad poller, when a browsing gesture returns to the anchor. Both -compare the viewport against the position they saw one tick earlier and act only when the -two agree. macOS keeps scrolling after the fingers lift, and a hard -`set_viewport_position` against that momentum used to yank the view back thirty times a -second for as long as the glide lasted — a visible shake at the bottom of every scroll. +**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 @@ -253,14 +246,12 @@ Details worth knowing before touching `scheme.py`: ### The alternate screen While an app owns the alternate buffer, `sync_alt_fold` folds rows `0..offset-1` away, so -the view shows the app's screen alone. The scroll range is deliberately left in place: the -trackpad poller pins the viewport and needs the slack to read a gesture. The fold is -re-checked every frame, not just on transitions, because the screen's first row is -rewritten on every region scroll and an edit at the fold boundary can silently break a -fold. Entering the alt screen collapses the layout, so that transition also zeroes -`terminal.viewport_y`; a stale anchor would hold the follow-scroll gate shut. A fold -merely repaired keeps the anchor, or the boundary row apps rewrite on every scroll would -thrash the poller. The emulator side +the view shows the app's screen alone. The fold is re-checked every frame, not just on +transitions, because the screen's first row is rewritten on every region scroll and an +edit at the fold boundary can silently break a fold. Entering the alt screen collapses the +layout, so that transition also zeroes `terminal.viewport_y`; a stale anchor would hold +the follow-scroll gate shut. A fold merely repaired keeps the anchor, or the boundary row +apps rewrite on every scroll would reset it every frame. The emulator side (`switch_to_screen`) swaps in a `maxlen=0` history, so a full-screen app never pushes its frames into scrollback. @@ -314,15 +305,12 @@ which produces three cases: real scrollback text. **The trackpad** fires none of that: on macOS it never reaches a mousemap binding -(sublime_text#1838), and its only trace is the viewport moving. So while the wheel flag is -up, `start_trackpad` runs a 24 ms timer chain, and any displacement from the follow anchor -is a gesture — whole lines of it go out through the same `send_scroll` as the wheel, the -fraction is carried into the next tick, and the viewport is pinned back to the anchor. A -displacement of a viewport or more is a scrollbar drag or a page scroll instead: the poller -disengages and lets the user browse until they return to the anchor. `_sync_wheel_flag` -restarts the chain within a frame or two of it stopping on a hidden tab or a capture flap, -and `scroll_to_cursor` yields while the poller owns the pin — apps repaint mid-gesture, and -re-pinning there would discard displacement not yet harvested. +(sublime_text#1838) and dispatches no command, so a plugin cannot see it at all. Its only +in-API trace is the viewport moving, and reading a gesture that way means letting the view +move before pinning it back — visibly, at every poll. vterm does not try: the trackpad is +converted outside the plugin, by a utility such as LinearMouse set to discrete scrolling +for Sublime Text, and arrives as ordinary wheel notches on the path above. Nothing in the +plugin is trackpad-aware, and the viewport is never moved to measure input. Clicks: `VtermMouseEventListener` intercepts `drag_select` only when the app tracks the mouse, and only the plain single-button form, so shift-click and add-selection stay @@ -345,7 +333,7 @@ that normally does that — and subsequent clicks send a press/release pair. | Thread | Runs | Touches | |---|---|---| -| UI | commands, listeners, `Terminal.start`, `scroll_to_cursor`, `_trackpad_tick`, clipboard | Sublime API; small pty writes go out here too | +| UI | commands, listeners, `Terminal.start`, `scroll_to_cursor`, clipboard | Sublime API; small pty writes go out here too | | reader | `process.read`, decode, `chunks.append` | no Sublime API, no screen state | | renderer | `stream.feed`, then `run_command("vterm_render")` | owns the emulator | | paste drain | transient; empties the 4096-byte chunk queue | pty only | diff --git a/README.md b/README.md index 996f638..2ed7eb0 100644 --- a/README.md +++ b/README.md @@ -52,11 +52,9 @@ wheel scrolls the view normally; while the app tracks the mouse the wheel and left click go to it instead, and on the alternate screen without tracking each wheel tick sends arrow keys (alternate scroll, as kitty/iTerm2 do — this is what scrolls Claude Code's fullscreen UI before it enables mouse reporting). -Sublime raises no wheel event at all for the trackpad (sublime_text#1838), so -while the app owns the wheel a poller reads the viewport movement instead, -sends it as scroll lines and pins the view back. A jump of a whole screen or -more — the scrollbar, the page keys — is left alone, so the scrollback is still -yours to browse until you come back to the bottom. While an app owns the +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. While an app owns the alternate screen the scrollback above it is folded away, kitty-style, and unfolds when the app leaves it. @@ -118,9 +116,9 @@ terminal protocols — see `Architecture.md`. leave the character behind. `on_modified` in `vterm/listeners.py` remains the fallback for IME and composed input. - `vterm/mouse.py` — SGR mouse reporting (wheel, click) when the app enables - tracking; native selection and scrolling otherwise. The trackpad reaches no - mousemap on macOS, so while the app owns the wheel a 24 ms poller harvests - viewport displacement into the same reports. + tracking; native selection and scrolling otherwise. The trackpad is not + handled: it reaches no mousemap on macOS and is converted to wheel notches + outside the plugin. Supported: alt screen (1049), synchronized output (2026), bracketed paste (2004), mouse tracking 1000/1002/1003 + SGR 1006 (wheel and left click are reported; no @@ -150,8 +148,12 @@ instead), image protocols, Windows, panels. input, so close it and open a new one. - CJK and emoji rely on wcwidth agreeing with Sublime's font metrics; where they disagree the caret can drift. -- Trackpad scroll into an app is polled and rounded to whole lines, so it is - coarser than the wheel in a native terminal. +- Trackpad scrolling needs a helper on macOS: Sublime never delivers it to a + plugin (sublime_text#1838), so set a utility such as + [LinearMouse](https://linearmouse.app/) to discrete/linear scrolling for + Sublime Text. It then arrives as wheel notches and scrolls the app. Without + one, the trackpad does nothing in a full-screen app; the mouse wheel, + `shift+pageup/pagedown` and `shift+up/down` work regardless. ## Dev diff --git a/tests/test_render.py b/tests/test_render.py index 651d5dc..e39fd30 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -421,141 +421,6 @@ assert terminal.process.written == "", repr(terminal.process.written) print("alternate scroll tests passed (3 lines per tick: arrows on alt screen, SGR when tracking, inert otherwise)") -# the trackpad never fires the mousemap on macOS (#1838): the poller harvests -# viewport displacement from the follow anchor into wheel lines, snaps back. -# ticks are stepped one at a time — the poller reschedules itself, so a -# blanket sublime.run_timeouts() would loop forever -terminal, stream = make_terminal(rows=5, cols=20) -Terminal._terminals[id(terminal)] = terminal -terminal.view.id = lambda: id(terminal) -terminal.view.text = "\n".join("row {}".format(i) for i in range(8)) -terminal.offset = 3 -ANCHOR = 3 * LINE_HEIGHT -terminal.viewport_y = ANCHOR -terminal.view.viewport = (0, ANCHOR) -stream.feed("\x1b[?1002h\x1b[?1006h") - -del sublime._timeouts[:] -terminal._sync_wheel_flag() -assert terminal._trackpad_running -terminal._sync_wheel_flag() -assert len(sublime._timeouts) == 1, "one timer chain per terminal" - - -def tick(): - sublime._timeouts.pop(0)() - - -def displace(pixels): - terminal.process.written = "" - terminal.view.viewport = (0, ANCHOR + pixels) - tick() - return terminal.process.written - - -# the first tick only arms the baseline; a gesture on it must stay silent -assert displace(50) == "", repr(terminal.process.written) -assert terminal.view.viewport == (0, ANCHOR + 50), "arming must not snap" - -# 2.5 lines down: two wheel-down reports, snap back, the fraction kept -assert displace(50) == "\x1b[<65;1;3M" * 2 -assert terminal.view.viewport == (0, ANCHOR), terminal.view.viewport -assert terminal._trackpad_acc == 0.5, terminal._trackpad_acc - -# half a line more completes the kept fraction into one report -assert displace(10) == "\x1b[<65;1;3M" - -# a resting tick clears the remainder, so it cannot fire a gesture later -assert displace(10) == "" -assert displace(0) == "" -assert terminal._trackpad_acc == 0, terminal._trackpad_acc - -# upward displacement reports wheel-up -assert displace(-30) == "\x1b[<64;1;3M" - -# a renderer re-anchor (fold reset, follow-scroll) is not a gesture -displace(0) -terminal.viewport_y = 0 -terminal.view.viewport = (0, 0) -tick() -assert terminal.process.written == "", repr(terminal.process.written) -terminal.viewport_y = ANCHOR -terminal.view.viewport = (0, ANCHOR) -tick() - -# a queued follow-scroll must not starve the poller: apps repaint while -# scrolling (less redraws per line, claude constantly), so this races every -# gesture and used to read as shaking -terminal._scroll_pending = True -assert displace(40) == "\x1b[<65;1;3M" * 2 -terminal._scroll_pending = False - -# nor may the follow-scroll itself discard un-harvested displacement while -# the anchor is unchanged: the poller owns the pin -terminal.view.viewport = (0, ANCHOR + 30) -scroll_to_cursor(terminal.view, terminal) -assert terminal.view.viewport == (0, ANCHOR + 30), terminal.view.viewport -terminal.process.written = "" -tick() -assert terminal.process.written == "\x1b[<65;1;3M", repr(terminal.process.written) -assert terminal.view.viewport == (0, ANCHOR), terminal.view.viewport -displace(0) # drop the half-line remainder - -# a real anchor change still pins: that is the follow-scroll's job -terminal.view.text += "row 8\n" -scroll_to_cursor(terminal.view, terminal) -assert terminal.viewport_y == ANCHOR + LINE_HEIGHT, terminal.viewport_y -assert terminal.view.viewport == (0, ANCHOR + LINE_HEIGHT), terminal.view.viewport -terminal.view.text = terminal.view.text[:-len("row 8\n")] -terminal.viewport_y = ANCHOR -terminal.view.viewport = (0, ANCHOR) -tick() # settle back on the restored anchor - -# a jump of a viewport or more (scrollbar, page scroll) disengages: the user -# browses the scrollback natively until they come back to the anchor -extent = terminal.view.viewport_extent()[1] -assert displace(-extent) == "" -assert not terminal._trackpad_engaged -assert terminal.view.viewport == (0, ANCHOR - extent), "browsing must not snap" -assert displace(-30) == "", "still browsing" -terminal.view.viewport = (0, ANCHOR) -tick() # back at the anchor, but still gliding: nothing may pin it yet -assert not terminal._trackpad_engaged -tick() # at rest on the anchor: re-engage -assert terminal._trackpad_engaged -assert displace(20) == "\x1b[<65;1;3M" - -# momentum carrying past the anchor must glide unpinned — pinning it mid-glide -# is the shake — and be corrected in one step once it stops, without a report -assert displace(-extent) == "" -assert not terminal._trackpad_engaged -terminal.process.written = "" -terminal.view.viewport = (0, ANCHOR + 40) -tick() -assert terminal.view.viewport == (0, ANCHOR + 40), "gliding must not snap" -assert not terminal._trackpad_engaged -tick() -assert terminal._trackpad_engaged -assert terminal.view.viewport == (0, ANCHOR), terminal.view.viewport -assert terminal.process.written == "", repr(terminal.process.written) - -# on the alt screen without tracking the same displacement turns into arrows -stream.feed("\x1b[?1002l\x1b[?1006l\x1b[?1049h") -terminal._sync_wheel_flag() -assert len(sublime._timeouts) == 1, "capture never lapsed, no second chain" -tick() # alt entry left the anchor alone, but spend a tick anyway -assert displace(-20) == "\x1b[A" - -# capture released: the poller stops and unhooks itself -stream.feed("\x1b[?1049l") -terminal._sync_wheel_flag() -tick() -assert not terminal._trackpad_running -assert sublime._timeouts == [], sublime._timeouts - -print("trackpad tests passed (displacement harvested, fractions kept, jumps browse, momentum settles, poller unhooks)") - - # with the configured modifier PgUp/PgDn page the view and Up/Down move it one # row, instead of reaching the app; every other combination is sent as before. # 24 scrollback rows above an 8-row screen, viewport at the follow anchor @@ -728,16 +593,14 @@ stream.feed("\x1b[?1049h") command.sync_alt_fold(terminal) assert terminal.view.folds == [(0, fold_end)], terminal.view.folds assert terminal.viewport_y == 0, terminal.viewport_y -assert terminal.view.settings().get("scroll_past_end") is True, \ - "the scroll range must stay: the trackpad poller reads it" # a second render leaves the fold alone command.sync_alt_fold(terminal) assert terminal.view.folds == [(0, fold_end)], terminal.view.folds # a disturbed fold is re-asserted on the next render, keeping the anchor: -# apps repaint the boundary row every scroll, and a per-repair reset made the -# poller read the thrashing anchor as phantom down-lines (dead trackpad-up) +# apps repaint the boundary row every scroll, and a per-repair reset dropped +# the user's scrollback position on every frame terminal.viewport_y = 60 terminal.view.folds = [(0, fold_end - 2)] command.sync_alt_fold(terminal) @@ -760,23 +623,6 @@ assert terminal.view.folds == [], terminal.view.folds print("alt fold tests passed (scrollback folded on entry, restored on exit)") -# a fresh view gets one blank scrollback row: sublime has no layout above row -# 0, so without it the trackpad poller has no upward slack — and on the alt -# screen no line ever retires to provide it (trackpad-up was dead in less) -terminal, stream = make_terminal(rows=5, cols=20) -Terminal._terminals[id(terminal)] = terminal -terminal.view.id = lambda: id(terminal) -command = make_command(terminal.view) -stream.feed("hello") -command.run(None) -assert terminal.offset == 1, terminal.offset -assert terminal.view.text.startswith("\nhello"), repr(terminal.view.text) -command.run(None) # only a fresh view is seeded -assert terminal.offset == 1, terminal.offset -del sublime._timeouts[:] # drop the follow-scrolls the renders queued - -print("seed row tests passed (blank row above a fresh screen, seeded once)") - # a row whose first cells were never written: they read back as default cells # (StaticDefaultDict), so they form their own uncoloured run and the coloured diff --git a/vterm/commands.py b/vterm/commands.py index f3426e7..ece7d3d 100644 --- a/vterm/commands.py +++ b/vterm/commands.py @@ -94,8 +94,12 @@ class VtermOpenTerminalProjectFolderCommand(sublime_plugin.WindowCommand): parameters = [p.replace("%CWD%", cwd) for p in parameters] try: subprocess.Popen( - [terminal] + parameters, cwd=cwd, start_new_session=True, - stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, + [terminal] + parameters, + cwd=cwd, + start_new_session=True, + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, ) except OSError: LOG.exception("failed to start external terminal: terminal=%s", terminal) @@ -126,7 +130,7 @@ class VtermInitializeViewCommand(sublime_plugin.TextCommand): view_settings.set("draw_white_space", "none") view_settings.set("draw_unicode_white_space", False) view_settings.set("draw_indent_guides", False) - view_settings.set("scroll_past_end", True) + view_settings.set("scroll_past_end", False) view_settings.set("scroll_context_lines", 0) view_settings.set("color_scheme", SCHEME_PATH) view_settings.set("syntax", "Packages/vterm/vterm.sublime-syntax") diff --git a/vterm/mouse.py b/vterm/mouse.py index dd830c5..c5ca964 100644 --- a/vterm/mouse.py +++ b/vterm/mouse.py @@ -1,8 +1,12 @@ -""" SGR mouse reporting while the app tracks the mouse; otherwise sublime keeps drag_select """ -import sublime +""" SGR mouse reporting while the app tracks the mouse; otherwise sublime keeps drag_select + +The trackpad is not handled here. On macOS it never reaches a mousemap binding +(sublime_text#1838), so it needs a utility such as LinearMouse to convert its +continuous scrolling into discrete wheel events; those arrive as ordinary wheel +notches and take the path below. +""" import sublime_plugin -from .render import following from .terminal import Terminal # SGR button numbers for the wheel; 64 is the "scroll" bit plus direction @@ -13,10 +17,6 @@ WHEEL_DOWN = 65 # line per report and has no setting of its own WHEEL_LINES = 3 -# trackpad poll period: the snapped-back displacement grows with it, and the -# alt-screen up-scroll rate is capped at one line per tick (16 = one 60 Hz frame) -TRACKPAD_POLL_MS = 24 - def send_scroll(terminal, up, lines, cell=None): """Report `lines` scroll lines to the app, or fake arrow keys on the alt screen. @@ -111,64 +111,3 @@ class VtermScrollCommand(sublime_plugin.TextCommand): def want_event(self): """Ask sublime for the wheel coordinates.""" return True - - -def start_trackpad(terminal): - """Start the poller that turns trackpad scrolling into wheel lines. - - The trackpad never fires the mousemap scroll bindings on macOS - (sublime_text#1838); its only trace is viewport movement. While the app - owns the wheel the mousemap keeps the physical wheel off the viewport, so - any displacement from the follow anchor is trackpad scroll: harvest whole - lines from it, send them like wheel lines, and snap the viewport back. - - The caller checks _trackpad_running; the first tick arms the baseline.""" - terminal._trackpad_running = True - terminal._trackpad_engaged = True - terminal._trackpad_baseline = None - terminal._trackpad_browse_y = None - sublime.set_timeout(lambda: _trackpad_tick(terminal), TRACKPAD_POLL_MS) - - -def _trackpad_tick(terminal): - """One poll: harvest viewport displacement, reschedule while captured.""" - view = terminal.view - if Terminal.from_id(view.id()) is not terminal or not terminal.alive or not terminal._wheel_captured or not terminal.is_visible(): - terminal._trackpad_running = False - return - anchor = terminal.viewport_y - extent = view.viewport_extent()[1] - if (anchor, extent) != terminal._trackpad_baseline: - # the renderer moved the anchor (follow-scroll, fold, resize): re-arm - # around the new position instead of reading the move as a gesture - terminal._trackpad_baseline = (anchor, extent) - terminal._trackpad_acc = 0.0 - elif not terminal._trackpad_engaged: - # re-engaging mid-glide harvests the momentum as a gesture and snaps - # the view back on every tick, which reads as shaking. Wait for two - # ticks to agree that it has come to rest, then correct the overshoot - # in one step — it was browsing, not a scroll the app should hear about - y = view.viewport_position()[1] - if y == terminal._trackpad_browse_y and following(view, terminal): - terminal._trackpad_engaged = True - terminal._trackpad_acc = 0.0 - view.set_viewport_position((0, anchor), False) - terminal._trackpad_browse_y = y - else: - dy = view.viewport_position()[1] - anchor - if dy == 0: - # at rest a stale fraction must not fire on the next gesture - terminal._trackpad_acc = 0.0 - elif abs(dy) >= extent: - # scrollbar/minimap jump or page scroll, not a trackpad gesture: - # let the user browse until they return to the anchor - terminal._trackpad_engaged = False - terminal._trackpad_browse_y = None - else: - terminal._trackpad_acc += dy / view.line_height() - lines = int(terminal._trackpad_acc) - terminal._trackpad_acc -= lines - if lines: - send_scroll(terminal, up=lines < 0, lines=abs(lines)) - view.set_viewport_position((0, anchor), False) - sublime.set_timeout(lambda: _trackpad_tick(terminal), TRACKPAD_POLL_MS) diff --git a/vterm/render.py b/vterm/render.py index 192c049..6d7444a 100644 --- a/vterm/render.py +++ b/vterm/render.py @@ -172,14 +172,6 @@ class VtermRenderCommand(sublime_plugin.TextCommand): terminal.pending_clear_screen = False self.clear_view(edit, terminal, wipe_screen=True) - if terminal.offset == 0 and view.size() == 0: - # one blank scrollback row above a fresh screen: sublime has no - # layout above row 0, and the trackpad poller needs upward slack — - # without it, trackpad-up is dead until a line retires, which on - # the alt screen is never - view.insert(edit, 0, "\n") - terminal.offset = 1 - self.update_lines(edit, terminal) self.sync_alt_fold(terminal) @@ -208,8 +200,7 @@ class VtermRenderCommand(sublime_plugin.TextCommand): def sync_alt_fold(self, terminal): """Hide the scrollback while the app owns the alt screen: fold it away - (the view text is its only copy). The scroll range stays: the trackpad - poller pins the viewport and needs the slack to read the trackpad. + (the view text is its only copy). The fold is re-checked every render — the screen's first row is replaced on every region scroll, and an edit at the fold boundary can silently disturb a fold.""" @@ -545,11 +536,6 @@ def scroll_to_cursor(view, terminal): """Follow the output: pin the viewport to the bottom of the live screen.""" terminal._scroll_pending = False y = bottom_y(view, terminal) - if y == terminal.viewport_y and terminal.trackpad_pinning(): - # the anchor is unchanged and the trackpad poller owns the pin: - # re-pinning here would discard displacement it has not harvested yet, - # and apps repaint while scrolling, so this races every gesture - return terminal.viewport_y = y view.set_viewport_position((0, y), False) diff --git a/vterm/terminal.py b/vterm/terminal.py index 0fcc5f7..eae1552 100644 --- a/vterm/terminal.py +++ b/vterm/terminal.py @@ -109,14 +109,6 @@ class Terminal: self._send_lock = threading.Lock() self._wheel_captured = False self._scroll_pending = False - # trackpad poller (mouse.start_trackpad): reschedule guard, fractional - # line remainder, harvesting vs. browsing, last seen (anchor, extent), - # viewport of the previous browsing tick - self._trackpad_running = False - self._trackpad_acc = 0.0 - self._trackpad_engaged = True - self._trackpad_baseline = None - self._trackpad_browse_y = None self.pending_clear = False self.pending_clear_screen = False # a forced frame (set on activation); rendering stays on the renderer @@ -154,10 +146,6 @@ class Terminal: self._visible = group >= 0 and window.active_view_in_group(group) == self.view return self._visible - def trackpad_pinning(self): - """Whether the trackpad poller owns pinning the viewport to the anchor.""" - return self._trackpad_running and self._trackpad_engaged - def is_render_due(self): """Whether an unfocused terminal may draw again. @@ -317,7 +305,7 @@ class Terminal: # 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 trackpad's + # 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] @@ -421,11 +409,6 @@ class Terminal: if captured != self._wheel_captured: self._wheel_captured = captured self.view.settings().set("vterm_view.wheel_captured", captured) - if captured and not self._trackpad_running and self.is_visible(): - # checked every renderer tick, so a poller that stopped on a - # capture flap or a hidden tab restarts within a frame or two - from .mouse import start_trackpad # module-level import would cycle - start_trackpad(self) def send_focus(self, focused): """Report focus gain or loss, when the app asked for it (?1004)."""