fixes
This commit is contained in:
@@ -200,6 +200,14 @@ 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.** 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.
|
||||||
|
|
||||||
### 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
|
||||||
@@ -269,6 +277,8 @@ keymap binding (context: setting.vterm_view)
|
|||||||
and Up/Down a row; stops here, never on the alt screen
|
and Up/Down a row; stops here, never on the alt screen
|
||||||
→ Terminal.send_key() adds application_mode (DECCKM), new_line_mode (LNM)
|
→ Terminal.send_key() adds application_mode (DECCKM), new_line_mode (LNM)
|
||||||
→ keys.get_key_code() modified nav keys → CSI 1;<mod><final>
|
→ keys.get_key_code() modified nav keys → CSI 1;<mod><final>
|
||||||
|
enter/tab/backspace/space, modified, while the app
|
||||||
|
asked for modifyOtherKeys → CSI 27;<mod>;<code>~
|
||||||
ctrl → control byte; alt → ESC prefix
|
ctrl → control byte; alt → ESC prefix
|
||||||
plain → _KEY_MAP / _APP_MODE_KEY_MAP
|
plain → _KEY_MAP / _APP_MODE_KEY_MAP
|
||||||
→ Terminal.send_string(normalized=False)
|
→ Terminal.send_string(normalized=False)
|
||||||
@@ -446,7 +456,13 @@ how an app discovers what the terminal supports.
|
|||||||
- **XTVERSION** ✅ (`CSI > q`) — the terminal's name and version. Naming ourselves is what
|
- **XTVERSION** ✅ (`CSI > q`) — the terminal's name and version. Naming ourselves is what
|
||||||
lets apps enable 2026 on a generic `TERM`.
|
lets apps enable 2026 on a generic `TERM`.
|
||||||
- **Kitty keyboard protocol** — a richer key encoding with release events. vterm answers
|
- **Kitty keyboard protocol** — a richer key encoding with release events. vterm answers
|
||||||
the probe with "unsupported", so apps fall back to the legacy encodings it does send.
|
the probe (`CSI ? u`) with zero flags, so apps fall back to something else — for pi that
|
||||||
|
is modifyOtherKeys, which it then enables unprompted.
|
||||||
|
- **modifyOtherKeys** ✅ (`CSI > 4 ; n m`) — xterm's pre-kitty answer to the same problem:
|
||||||
|
keys whose modified form has no legacy encoding are sent as `CSI 27 ; <mod> ; <code> ~`.
|
||||||
|
While an app has it on, vterm uses that form for modified enter, tab, backspace and space —
|
||||||
|
Shift+Enter is `CSI 27;2;13~`, since a bare `ESC CR` is indistinguishable from Alt+Enter.
|
||||||
|
Ctrl+letters keep their control bytes, so Ctrl+C stays `\x03`.
|
||||||
|
|
||||||
**Other terms**
|
**Other terms**
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ takes `cmd`, `cwd`, `env` and `title`:
|
|||||||
{ "caption": "vterm: pi", "command": "vterm_open", "args": {"cmd": ["pi"], "title": "pi"} }
|
{ "caption": "vterm: pi", "command": "vterm_open", "args": {"cmd": ["pi"], "title": "pi"} }
|
||||||
```
|
```
|
||||||
|
|
||||||
Keys: Shift+Enter sends `ESC CR` (newline in both agents). Shift+PgUp/PgDn page
|
Keys: Shift+Enter sends `ESC CR`, or `CSI 27;2;13~` while the app has xterm
|
||||||
|
modifyOtherKeys on — pi turns it on and reads a bare `ESC CR` as Alt+Enter,
|
||||||
|
which submits instead of breaking the line. Shift+PgUp/PgDn page
|
||||||
the view over the scrollback and Shift+Up/Down move it one line, instead of
|
the view over the scrollback and Shift+Up/Down move it one line, instead of
|
||||||
reaching the app, as xterm and kitty do — `scroll_modifier` moves that to Ctrl,
|
reaching the app, as xterm and kitty do — `scroll_modifier` moves that to Ctrl,
|
||||||
or turns it off. Copy/paste use the normal Sublime keys, and pastes are
|
or turns it off. Copy/paste use the normal Sublime keys, and pastes are
|
||||||
@@ -122,7 +124,9 @@ terminal protocols — see `Architecture.md`.
|
|||||||
|
|
||||||
Supported: alt screen (1049), synchronized output (2026), bracketed paste (2004),
|
Supported: alt screen (1049), synchronized output (2026), bracketed paste (2004),
|
||||||
mouse tracking 1000/1002/1003 + SGR 1006 (wheel and left click are reported; no
|
mouse tracking 1000/1002/1003 + SGR 1006 (wheel and left click are reported; no
|
||||||
drag, no right or middle button), focus events (1004), cursor styles (DECSCUSR —
|
drag, no right or middle button), focus events (1004),
|
||||||
|
xterm modifyOtherKeys (`CSI > 4 ; n m`, for the modified keys with no
|
||||||
|
unambiguous legacy encoding: enter, tab, backspace, space), cursor styles (DECSCUSR —
|
||||||
block/bar and blink/steady via the view's caret settings; underline falls back
|
block/bar and blink/steady via the view's caret settings; underline falls back
|
||||||
to bar, 0 is the default blinking block), scroll regions, autowrap,
|
to bar, 0 is the default blinking block), scroll regions, autowrap,
|
||||||
50 000 lines of scrollback that ED 3 does not erase (agents repaint with it;
|
50 000 lines of scrollback that ED 3 does not erase (agents repaint with it;
|
||||||
|
|||||||
@@ -54,10 +54,18 @@ def run_case(char_by_char):
|
|||||||
# and nothing leaked to the screen
|
# and nothing leaked to the screen
|
||||||
assert screen.buffer[0][0].data == " ", (tag, screen.buffer[0][0])
|
assert screen.buffer[0][0].data == " ", (tag, screen.buffer[0][0])
|
||||||
|
|
||||||
# modifyOtherKeys must not corrupt SGR state
|
# modifyOtherKeys: the level is tracked (pi enables it and reads shift+enter
|
||||||
|
# no other way), and the sequence must not corrupt SGR state
|
||||||
proc, screen, stream = make()
|
proc, screen, stream = make()
|
||||||
|
assert screen.modify_other_keys == 0, tag
|
||||||
feed(stream, "\x1b[>4;2mhello", char_by_char)
|
feed(stream, "\x1b[>4;2mhello", char_by_char)
|
||||||
|
assert screen.modify_other_keys == 2, (tag, screen.modify_other_keys)
|
||||||
assert screen.buffer[0][0].data == "h" and not screen.buffer[0][0].underscore, tag
|
assert screen.buffer[0][0].data == "h" and not screen.buffer[0][0].underscore, tag
|
||||||
|
feed(stream, "\x1b[>4;0m", char_by_char)
|
||||||
|
assert screen.modify_other_keys == 0, (tag, screen.modify_other_keys)
|
||||||
|
# another resource, and the bare form, leave it alone
|
||||||
|
feed(stream, "\x1b[>4;2m\x1b[>1;2m\x1b[>m", char_by_char)
|
||||||
|
assert screen.modify_other_keys == 2, (tag, screen.modify_other_keys)
|
||||||
|
|
||||||
# OSC 11 color query answered; OSC 0 sets title; OSC 133 dropped
|
# OSC 11 color query answered; OSC 0 sets title; OSC 133 dropped
|
||||||
proc, screen, stream = make()
|
proc, screen, stream = make()
|
||||||
|
|||||||
@@ -52,6 +52,15 @@ for char in string.ascii_letters + string.digits + string.punctuation:
|
|||||||
assert produced == char, "{!r} would send {!r}".format(char, produced)
|
assert produced == char, "{!r} would send {!r}".format(char, produced)
|
||||||
|
|
||||||
assert get_key_code(key="space") == " ", "space must send a space"
|
assert get_key_code(key="space") == " ", "space must send a space"
|
||||||
|
|
||||||
|
# legacy encodings by default; while the app has modifyOtherKeys on, the keys
|
||||||
|
# whose modified form is ambiguous take the CSI 27 form instead. pi reads a
|
||||||
|
# bare ESC CR as alt+enter and submits on it
|
||||||
|
assert get_key_code(key="enter", shift=True) == "\x1b\r"
|
||||||
|
assert get_key_code(key="enter", shift=True, modify_other_keys=2) == "\x1b[27;2;13~"
|
||||||
|
assert get_key_code(key="space", ctrl=True, modify_other_keys=2) == "\x1b[27;5;32~"
|
||||||
|
assert get_key_code(key="enter", modify_other_keys=2) == "\r", "unmodified keys stay legacy"
|
||||||
|
assert get_key_code(key="c", ctrl=True, modify_other_keys=2) == "\x03", "ctrl+c stays a control byte"
|
||||||
load_sublime_json("Default (OSX).sublime-mousemap")
|
load_sublime_json("Default (OSX).sublime-mousemap")
|
||||||
|
|
||||||
# scheme manager produces scopes and rules
|
# scheme manager produces scopes and rules
|
||||||
|
|||||||
@@ -519,10 +519,26 @@ assert not terminal._trackpad_engaged
|
|||||||
assert terminal.view.viewport == (0, ANCHOR - extent), "browsing must not snap"
|
assert terminal.view.viewport == (0, ANCHOR - extent), "browsing must not snap"
|
||||||
assert displace(-30) == "", "still browsing"
|
assert displace(-30) == "", "still browsing"
|
||||||
terminal.view.viewport = (0, ANCHOR)
|
terminal.view.viewport = (0, ANCHOR)
|
||||||
tick() # back at the anchor: re-engage
|
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 terminal._trackpad_engaged
|
||||||
assert displace(20) == "\x1b[<65;1;3M"
|
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
|
# on the alt screen without tracking the same displacement turns into arrows
|
||||||
stream.feed("\x1b[?1002l\x1b[?1006l\x1b[?1049h")
|
stream.feed("\x1b[?1002l\x1b[?1006l\x1b[?1049h")
|
||||||
terminal._sync_wheel_flag()
|
terminal._sync_wheel_flag()
|
||||||
@@ -537,7 +553,7 @@ tick()
|
|||||||
assert not terminal._trackpad_running
|
assert not terminal._trackpad_running
|
||||||
assert sublime._timeouts == [], sublime._timeouts
|
assert sublime._timeouts == [], sublime._timeouts
|
||||||
|
|
||||||
print("trackpad tests passed (displacement harvested, fractions kept, jumps browse, poller unhooks)")
|
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
|
# with the configured modifier PgUp/PgDn page the view and Up/Down move it one
|
||||||
@@ -641,6 +657,25 @@ plugin_settings.set("scroll_modifier", "shift")
|
|||||||
print("view scroll tests passed (a screen or a row per press, clamped to the anchor, configurable modifier)")
|
print("view scroll tests passed (a screen or a row per press, clamped to the anchor, configurable modifier)")
|
||||||
|
|
||||||
|
|
||||||
|
# shift+enter through the whole keypress path: ESC CR by default, and the CSI 27
|
||||||
|
# form once the app turned modifyOtherKeys on. pi enables it (its kitty probe
|
||||||
|
# gets no flags from us) and reads a bare ESC CR as alt+enter, which submits
|
||||||
|
press(key="enter", shift=True)
|
||||||
|
assert terminal.process.written == "\x1b\r", repr(terminal.process.written)
|
||||||
|
stream.feed("\x1b[>4;2m")
|
||||||
|
press(key="enter", shift=True)
|
||||||
|
assert terminal.process.written == "\x1b[27;2;13~", repr(terminal.process.written)
|
||||||
|
press(key="enter")
|
||||||
|
assert terminal.process.written == "\r", "unmodified enter still submits"
|
||||||
|
press(key="c", ctrl=True)
|
||||||
|
assert terminal.process.written == "\x03", "ctrl+c stays a control byte"
|
||||||
|
stream.feed("\x1b[>4;0m")
|
||||||
|
press(key="enter", shift=True)
|
||||||
|
assert terminal.process.written == "\x1b\r", repr(terminal.process.written)
|
||||||
|
|
||||||
|
print("modifyOtherKeys tests passed (shift+enter as CSI 27 while the app asks for it)")
|
||||||
|
|
||||||
|
|
||||||
# a click in an unfocused group must focus the tab instead of reporting: we
|
# a click in an unfocused group must focus the tab instead of reporting: we
|
||||||
# replaced drag_select, which is what sublime focuses the group from
|
# replaced drag_select, which is what sublime focuses the group from
|
||||||
class FakeWindow:
|
class FakeWindow:
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ class VtScreen(pyte.Screen):
|
|||||||
super().reset()
|
super().reset()
|
||||||
self.cursor = Cursor(0, 0)
|
self.cursor = Cursor(0, 0)
|
||||||
self.cursor_style = 0
|
self.cursor_style = 0
|
||||||
|
self.modify_other_keys = 0
|
||||||
self.history.clear()
|
self.history.clear()
|
||||||
self._clear_callback()
|
self._clear_callback()
|
||||||
|
|
||||||
@@ -192,6 +193,15 @@ class VtScreen(pyte.Screen):
|
|||||||
# DECSCUSR: 0 default, 1/2 block, 3/4 underline, 5/6 bar; odd = blinking
|
# DECSCUSR: 0 default, 1/2 block, 3/4 underline, 5/6 bar; odd = blinking
|
||||||
self.cursor_style = style
|
self.cursor_style = style
|
||||||
|
|
||||||
|
def set_modify_other_keys(self, resource=0, value=0):
|
||||||
|
"""xterm CSI > 4 ; n m: send modified keys as CSI 27 ; mod ; code ~.
|
||||||
|
|
||||||
|
pi turns this on whenever the kitty probe answers with no flags, and
|
||||||
|
reads shift+enter no other way.
|
||||||
|
"""
|
||||||
|
if resource == 4:
|
||||||
|
self.modify_other_keys = value
|
||||||
|
|
||||||
def resize(self, lines=None, columns=None):
|
def resize(self, lines=None, columns=None):
|
||||||
"""Resize the screen, pushing scrolled-off lines into scrollback.
|
"""Resize the screen, pushing scrolled-off lines into scrollback.
|
||||||
|
|
||||||
@@ -617,7 +627,8 @@ class VtScreen(pyte.Screen):
|
|||||||
|
|
||||||
def report_kitty_keyboard(self):
|
def report_kitty_keyboard(self):
|
||||||
"""Answer the kitty keyboard protocol probe."""
|
"""Answer the kitty keyboard protocol probe."""
|
||||||
# 0 = unsupported, so apps fall back to legacy key encodings
|
# answering at all means "supported"; 0 flags means none are enabled,
|
||||||
|
# which is what steers pi onto its modifyOtherKeys fallback
|
||||||
self.write_process_input("\x1b[?0u")
|
self.write_process_input("\x1b[?0u")
|
||||||
|
|
||||||
def handle_osc(self, code, payload, terminator):
|
def handle_osc(self, code, payload, terminator):
|
||||||
@@ -912,7 +923,9 @@ class VtStream(pyte.Stream):
|
|||||||
listener.report_xtversion()
|
listener.report_xtversion()
|
||||||
elif final == "c":
|
elif final == "c":
|
||||||
listener.report_secondary_device_attributes(*params)
|
listener.report_secondary_device_attributes(*params)
|
||||||
# '>m' modifyOtherKeys, '>u' kitty push: ignore
|
elif final == "m":
|
||||||
|
listener.set_modify_other_keys(*params)
|
||||||
|
# '>u' kitty push: ignore
|
||||||
return
|
return
|
||||||
if prefix:
|
if prefix:
|
||||||
# kitty pop ('<u'), DECKPAM-style ('='): ignore
|
# kitty pop ('<u'), DECKPAM-style ('='): ignore
|
||||||
|
|||||||
@@ -94,19 +94,26 @@ _SHIFT_KEY_MAP = {
|
|||||||
"enter": "\x1b\r",
|
"enter": "\x1b\r",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# keys whose modified form has no unambiguous legacy encoding: ESC CR reads as
|
||||||
|
# alt+enter, ctrl+space as NUL. An app that turned modifyOtherKeys on gets them
|
||||||
|
# as CSI 27 ; <mod> ; <code> ~ instead
|
||||||
|
_OTHER_KEYS = {"enter": 13, "tab": 9, "backspace": 127, "space": 32}
|
||||||
|
|
||||||
|
|
||||||
def _modifier_code(ctrl, alt, shift):
|
def _modifier_code(ctrl, alt, shift):
|
||||||
"""xterm modifier parameter: 1 plus a bitmask of shift/alt/ctrl."""
|
"""xterm modifier parameter: 1 plus a bitmask of shift/alt/ctrl."""
|
||||||
return 1 + (1 if shift else 0) + (2 if alt else 0) + (4 if ctrl else 0)
|
return 1 + (1 if shift else 0) + (2 if alt else 0) + (4 if ctrl else 0)
|
||||||
|
|
||||||
|
|
||||||
def get_key_code(key, ctrl=False, alt=False, shift=False, application_mode=False, new_line_mode=False):
|
def get_key_code(key, ctrl=False, alt=False, shift=False, application_mode=False, new_line_mode=False,
|
||||||
|
modify_other_keys=0):
|
||||||
"""Escape sequence for a key name and its modifiers.
|
"""Escape sequence for a key name and its modifiers.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key: key name from the keymap, or a single literal character.
|
key: key name from the keymap, or a single literal character.
|
||||||
application_mode: DECCKM is set, so cursor keys take their SS3 form.
|
application_mode: DECCKM is set, so cursor keys take their SS3 form.
|
||||||
new_line_mode: LNM is set, so enter sends CR LF.
|
new_line_mode: LNM is set, so enter sends CR LF.
|
||||||
|
modify_other_keys: the level the app asked for with CSI > 4 ; n m.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: the bytes to write to the pty.
|
str: the bytes to write to the pty.
|
||||||
@@ -116,6 +123,9 @@ def get_key_code(key, ctrl=False, alt=False, shift=False, application_mode=False
|
|||||||
if (ctrl or alt or shift) and key_lo in _MODIFIABLE_CSI:
|
if (ctrl or alt or shift) and key_lo in _MODIFIABLE_CSI:
|
||||||
return _MODIFIABLE_CSI[key_lo].format(_modifier_code(ctrl, alt, shift))
|
return _MODIFIABLE_CSI[key_lo].format(_modifier_code(ctrl, alt, shift))
|
||||||
|
|
||||||
|
if modify_other_keys and (ctrl or alt or shift) and key_lo in _OTHER_KEYS:
|
||||||
|
return "\x1b[27;{};{}~".format(_modifier_code(ctrl, alt, shift), _OTHER_KEYS[key_lo])
|
||||||
|
|
||||||
if ctrl:
|
if ctrl:
|
||||||
base = key_lo
|
base = key_lo
|
||||||
if base in _CTRL_KEY_MAP:
|
if base in _CTRL_KEY_MAP:
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ def start_trackpad(terminal):
|
|||||||
terminal._trackpad_running = True
|
terminal._trackpad_running = True
|
||||||
terminal._trackpad_engaged = True
|
terminal._trackpad_engaged = True
|
||||||
terminal._trackpad_baseline = None
|
terminal._trackpad_baseline = None
|
||||||
|
terminal._trackpad_browse_y = None
|
||||||
sublime.set_timeout(lambda: _trackpad_tick(terminal), TRACKPAD_POLL_MS)
|
sublime.set_timeout(lambda: _trackpad_tick(terminal), TRACKPAD_POLL_MS)
|
||||||
|
|
||||||
|
|
||||||
@@ -143,9 +144,16 @@ def _trackpad_tick(terminal):
|
|||||||
terminal._trackpad_baseline = (anchor, extent)
|
terminal._trackpad_baseline = (anchor, extent)
|
||||||
terminal._trackpad_acc = 0.0
|
terminal._trackpad_acc = 0.0
|
||||||
elif not terminal._trackpad_engaged:
|
elif not terminal._trackpad_engaged:
|
||||||
if following(view, terminal):
|
# 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_engaged = True
|
||||||
terminal._trackpad_acc = 0.0
|
terminal._trackpad_acc = 0.0
|
||||||
|
view.set_viewport_position((0, anchor), False)
|
||||||
|
terminal._trackpad_browse_y = y
|
||||||
else:
|
else:
|
||||||
dy = view.viewport_position()[1] - anchor
|
dy = view.viewport_position()[1] - anchor
|
||||||
if dy == 0:
|
if dy == 0:
|
||||||
@@ -155,6 +163,7 @@ def _trackpad_tick(terminal):
|
|||||||
# scrollbar/minimap jump or page scroll, not a trackpad gesture:
|
# scrollbar/minimap jump or page scroll, not a trackpad gesture:
|
||||||
# let the user browse until they return to the anchor
|
# let the user browse until they return to the anchor
|
||||||
terminal._trackpad_engaged = False
|
terminal._trackpad_engaged = False
|
||||||
|
terminal._trackpad_browse_y = None
|
||||||
else:
|
else:
|
||||||
terminal._trackpad_acc += dy / view.line_height()
|
terminal._trackpad_acc += dy / view.line_height()
|
||||||
lines = int(terminal._trackpad_acc)
|
lines = int(terminal._trackpad_acc)
|
||||||
|
|||||||
@@ -110,11 +110,13 @@ class Terminal:
|
|||||||
self._wheel_captured = False
|
self._wheel_captured = False
|
||||||
self._scroll_pending = False
|
self._scroll_pending = False
|
||||||
# trackpad poller (mouse.start_trackpad): reschedule guard, fractional
|
# trackpad poller (mouse.start_trackpad): reschedule guard, fractional
|
||||||
# line remainder, harvesting vs. browsing, last seen (anchor, extent)
|
# line remainder, harvesting vs. browsing, last seen (anchor, extent),
|
||||||
|
# viewport of the previous browsing tick
|
||||||
self._trackpad_running = False
|
self._trackpad_running = False
|
||||||
self._trackpad_acc = 0.0
|
self._trackpad_acc = 0.0
|
||||||
self._trackpad_engaged = True
|
self._trackpad_engaged = True
|
||||||
self._trackpad_baseline = None
|
self._trackpad_baseline = None
|
||||||
|
self._trackpad_browse_y = None
|
||||||
self.pending_clear = False
|
self.pending_clear = False
|
||||||
self.pending_clear_screen = False
|
self.pending_clear_screen = False
|
||||||
# a forced frame (set on activation); rendering stays on the renderer
|
# a forced frame (set on activation); rendering stays on the renderer
|
||||||
@@ -290,6 +292,7 @@ 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
|
||||||
@@ -307,6 +310,21 @@ 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 trackpad'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")
|
||||||
@@ -348,6 +366,7 @@ class Terminal:
|
|||||||
"""Encode a key name plus modifiers for the app's current modes and send it."""
|
"""Encode a key name plus modifiers for the app's current modes and send it."""
|
||||||
kwargs["application_mode"] = APP_CURSOR_MODE in self.screen.mode
|
kwargs["application_mode"] = APP_CURSOR_MODE in self.screen.mode
|
||||||
kwargs["new_line_mode"] = mo.LNM in self.screen.mode
|
kwargs["new_line_mode"] = mo.LNM in self.screen.mode
|
||||||
|
kwargs["modify_other_keys"] = self.screen.modify_other_keys
|
||||||
self.send_string(get_key_code(**kwargs), normalized=False)
|
self.send_string(get_key_code(**kwargs), normalized=False)
|
||||||
|
|
||||||
def send_string(self, string, normalized=True):
|
def send_string(self, string, normalized=True):
|
||||||
|
|||||||
Reference in New Issue
Block a user