fix(terminal+lsp): SHELL=$(POSIX-fallback) + skip selection-restore on empty buffer (v0.7.43)
All checks were successful
boundary-lint / PR boundary-claim (Lint (push) Has been skipped
boundary-lint / ban-list lint (Lint (push) Successful in 19s
boundary-lint / duplication-deadline (Layer 1/2) (push) Successful in 19s
Release Publish (Gitea session_helper) / verify-release-tag (push) Successful in 17s
Release Publish (Gitea session_helper) / publish-linux-x86_64 (push) Successful in 4m47s
ci / rust debug (push) Successful in 1m47s
ci / rust release (push) Successful in 2m24s
ci / test-health gate (push) Successful in 16s
ci / mutation test (broker) (push) Successful in 1m14s
ci / python (push) Successful in 1m41s
All checks were successful
boundary-lint / PR boundary-claim (Lint (push) Has been skipped
boundary-lint / ban-list lint (Lint (push) Successful in 19s
boundary-lint / duplication-deadline (Layer 1/2) (push) Successful in 19s
Release Publish (Gitea session_helper) / verify-release-tag (push) Successful in 17s
Release Publish (Gitea session_helper) / publish-linux-x86_64 (push) Successful in 4m47s
ci / rust debug (push) Successful in 1m47s
ci / rust release (push) Successful in 2m24s
ci / test-health gate (push) Successful in 16s
ci / mutation test (broker) (push) Successful in 1m14s
ci / python (push) Successful in 1m41s
Two follow-ups to v0.7.42 user reports.
1. Terminal: ``zsh:1: permission denied:`` exit 126
---------------------------------------------------
v0.7.42 dropped the ``${SHELL:-/bin/sh}`` fallback assuming sshd
populates ``$SHELL`` in every login shell, but ``ssh -t host cmd``
runs the user's login shell with ``-c`` (NON-login mode); on some
remotes ``$SHELL`` is unset there, so ``exec "$SHELL" -il`` becomes
``exec "" -il`` → ``permission denied:`` exit 126.
Reinstate the fallback via POSIX ``if [ -z "$SHELL" ]; then
SHELL=/bin/sh; fi`` instead of ``${SHELL:-...}`` so the parser-bug
class that produced ``zsh:1: unknown exec flag -/`` in v0.7.31+ is
still avoided.
2. LSP: cross-file goto-def to unhydrated placeholder lands at (0,0)
--------------------------------------------------------------------
When LSP-pyright / rust-analyzer return a definition target whose
local cache copy is still a 0-byte placeholder, Sublime's
``window.open_file(path:42:5, ENCODED_POSITION)`` cannot place the
caret at row 42 col 5 — that row doesn't exist in an empty buffer —
and clamps to ``(0, 0)``. ``_apply_hydrate_result`` then captured
that ``(0, 0)`` selection before revert and restored it after,
overriding whatever position Sublime defers / re-applies once the
buffer has content. Net result: user lands at the file top instead
of the definition.
Skip capture/restore entirely when the pre-revert buffer was empty.
For the empty-pre-revert case the captured selection is always
``(0, 0)`` — restoring it can only override a Sublime-side deferred
placement, never recover the LSP target — so dropping the restore
is at least as good as before and lets any deferred ENCODED_POSITION
take effect.
The non-empty branch (e21b3a4 cross-file caret fix for already-
hydrated buffers) is unchanged.
Tests
-----
Python 1368 pass; Rust 486 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "sessions-sublime"
|
name = "sessions-sublime"
|
||||||
version = "0.7.42"
|
version = "0.7.43"
|
||||||
description = "Sublime-facing Python code for Sessions."
|
description = "Sublime-facing Python code for Sessions."
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
license = {text = "MIT"}
|
license = {text = "MIT"}
|
||||||
|
|||||||
12
rust/Cargo.lock
generated
12
rust/Cargo.lock
generated
@@ -221,7 +221,7 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "local_bridge"
|
name = "local_bridge"
|
||||||
version = "0.7.42"
|
version = "0.7.43"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"glob",
|
"glob",
|
||||||
@@ -432,7 +432,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "session_helper"
|
name = "session_helper"
|
||||||
version = "0.7.42"
|
version = "0.7.43"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"notify",
|
"notify",
|
||||||
@@ -443,7 +443,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "session_protocol"
|
name = "session_protocol"
|
||||||
version = "0.7.42"
|
version = "0.7.43"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -452,14 +452,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sessions_askpass"
|
name = "sessions_askpass"
|
||||||
version = "0.7.42"
|
version = "0.7.43"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sessions_native"
|
name = "sessions_native"
|
||||||
version = "0.7.42"
|
version = "0.7.43"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"notify",
|
"notify",
|
||||||
@@ -773,7 +773,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "workspace_identity"
|
name = "workspace_identity"
|
||||||
version = "0.7.42"
|
version = "0.7.43"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ resolver = "2"
|
|||||||
[workspace.package]
|
[workspace.package]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
version = "0.7.42"
|
version = "0.7.43"
|
||||||
authors = ["Myeongseon Choi <key262yek@gmail.com>"]
|
authors = ["Myeongseon Choi <key262yek@gmail.com>"]
|
||||||
repository = "https://git.teahaven.kr/sublime-rs/sessions"
|
repository = "https://git.teahaven.kr/sublime-rs/sessions"
|
||||||
homepage = "https://git.teahaven.kr/sublime-rs/sessions"
|
homepage = "https://git.teahaven.kr/sublime-rs/sessions"
|
||||||
|
|||||||
@@ -1677,19 +1677,36 @@ def _apply_hydrate_result(
|
|||||||
# returns None / unexpected types) just let revert behave as
|
# returns None / unexpected types) just let revert behave as
|
||||||
# it always has — better to lose the cursor than to crash the
|
# it always has — better to lose the cursor than to crash the
|
||||||
# hydrate finish callback.
|
# hydrate finish callback.
|
||||||
|
#
|
||||||
|
# Empty-pre-revert guard: when the LSP target was a 0-byte
|
||||||
|
# placeholder (cache stub never fetched), Sublime can't place
|
||||||
|
# the caret at row 42 col 5 — that row doesn't exist in an
|
||||||
|
# empty buffer — so the caret clamps to (0, 0) and our
|
||||||
|
# captured selection becomes ``[(0, 0)]``. Restoring that
|
||||||
|
# after revert overrides whatever position Sublime defers /
|
||||||
|
# re-applies once the buffer has content, leaving the user at
|
||||||
|
# the file top. Skip capture/restore entirely when the buffer
|
||||||
|
# had no content; downstream LSP-side caret placement (if
|
||||||
|
# any) wins, and we are no worse than landing at (0, 0).
|
||||||
|
view_size_fn = getattr(current, "size", None)
|
||||||
|
try:
|
||||||
|
buffer_size = view_size_fn() if callable(view_size_fn) else 0
|
||||||
|
except (RuntimeError, AttributeError, TypeError):
|
||||||
|
buffer_size = 0
|
||||||
captured_selections: List[Tuple[int, int]] = []
|
captured_selections: List[Tuple[int, int]] = []
|
||||||
sel_fn = getattr(current, "sel", None)
|
if isinstance(buffer_size, int) and buffer_size > 0:
|
||||||
if callable(sel_fn):
|
sel_fn = getattr(current, "sel", None)
|
||||||
try:
|
if callable(sel_fn):
|
||||||
regions = sel_fn()
|
try:
|
||||||
if regions is not None:
|
regions = sel_fn()
|
||||||
for region in regions:
|
if regions is not None:
|
||||||
a = getattr(region, "a", None)
|
for region in regions:
|
||||||
b = getattr(region, "b", None)
|
a = getattr(region, "a", None)
|
||||||
if isinstance(a, int) and isinstance(b, int):
|
b = getattr(region, "b", None)
|
||||||
captured_selections.append((a, b))
|
if isinstance(a, int) and isinstance(b, int):
|
||||||
except (RuntimeError, AttributeError, TypeError):
|
captured_selections.append((a, b))
|
||||||
captured_selections = []
|
except (RuntimeError, AttributeError, TypeError):
|
||||||
|
captured_selections = []
|
||||||
run_command = getattr(current, "run_command", None)
|
run_command = getattr(current, "run_command", None)
|
||||||
if callable(run_command):
|
if callable(run_command):
|
||||||
run_command("revert")
|
run_command("revert")
|
||||||
@@ -6957,11 +6974,21 @@ class SessionsOpenRemoteTerminalCommand(sublime_plugin.WindowCommand):
|
|||||||
# form re-tripped some zsh setups in v0.7.31+ with
|
# form re-tripped some zsh setups in v0.7.31+ with
|
||||||
# ``zsh:1: unknown exec flag -/`` — the parameter expansion
|
# ``zsh:1: unknown exec flag -/`` — the parameter expansion
|
||||||
# split such that the literal ``-/bin/sh`` reached ``exec`` as
|
# split such that the literal ``-/bin/sh`` reached ``exec`` as
|
||||||
# a flag instead of expanding to ``/bin/sh``. sshd populates
|
# a flag instead of expanding to ``/bin/sh``.
|
||||||
# ``$SHELL`` from the user's passwd entry in every login
|
#
|
||||||
# session, so the fallback was redundant; quoting ``"$SHELL"``
|
# POSIX ``if [ -z "$SHELL" ]; then SHELL=/bin/sh; fi`` fallback
|
||||||
# also handles the rare path-with-spaces case.
|
# not ``${SHELL:-...}``: ``ssh -t host cmd`` runs the user's
|
||||||
remote_invocation = 'cd {}; exec "$SHELL" -il'.format(shlex.quote(remote_root))
|
# login shell with ``-c`` (NON-login mode), so depending on the
|
||||||
|
# remote ``/etc/passwd`` and any ``.zshenv`` quirks ``$SHELL``
|
||||||
|
# may be unset. v0.7.42 dropped the fallback assuming sshd
|
||||||
|
# always populates it, but that broke users with non-standard
|
||||||
|
# shell configs (``zsh:1: permission denied:`` exit 126 from
|
||||||
|
# ``exec ""``). Reinstate the fallback in a form that avoids
|
||||||
|
# the ``:-`` parser bug entirely.
|
||||||
|
remote_invocation = (
|
||||||
|
'cd {}; if [ -z "$SHELL" ]; then SHELL=/bin/sh; fi; '
|
||||||
|
'exec "$SHELL" -il'.format(shlex.quote(remote_root))
|
||||||
|
)
|
||||||
# ``panel_name`` makes Terminus open the shell as a panel
|
# ``panel_name`` makes Terminus open the shell as a panel
|
||||||
# docked at the bottom of the active window. Without it
|
# docked at the bottom of the active window. Without it
|
||||||
# Terminus defaults to a new tab in the editor pane group,
|
# Terminus defaults to a new tab in the editor pane group,
|
||||||
|
|||||||
@@ -452,13 +452,17 @@ def test_open_remote_terminal_opens_transient_terminus_pane(
|
|||||||
# prefix was dropped — it confused interactive zsh on some macOS →
|
# prefix was dropped — it confused interactive zsh on some macOS →
|
||||||
# Linux setups (``zsh: bad option: -/``). The ``${SHELL:-/bin/sh}``
|
# Linux setups (``zsh: bad option: -/``). The ``${SHELL:-/bin/sh}``
|
||||||
# default form re-tripped the same class of zsh setups in v0.7.31+
|
# default form re-tripped the same class of zsh setups in v0.7.31+
|
||||||
# (``zsh:1: unknown exec flag -/``); ``"$SHELL"`` quoted is enough
|
# (``zsh:1: unknown exec flag -/``). v0.7.42 dropped the fallback
|
||||||
# because sshd populates ``$SHELL`` from passwd in every login.
|
# entirely on the assumption sshd populates ``$SHELL``; that broke
|
||||||
|
# users where ``ssh -t host cmd`` runs the login shell in non-login
|
||||||
|
# ``-c`` mode and ``$SHELL`` is empty (``permission denied:`` exit
|
||||||
|
# 126). v0.7.43 reinstates the fallback via POSIX ``if`` instead of
|
||||||
|
# ``:-`` so the parser-bug class is avoided.
|
||||||
assert args["cmd"] == [
|
assert args["cmd"] == [
|
||||||
"ssh",
|
"ssh",
|
||||||
"-t",
|
"-t",
|
||||||
"prod",
|
"prod",
|
||||||
'cd /srv/app; exec "$SHELL" -il',
|
'cd /srv/app; if [ -z "$SHELL" ]; then SHELL=/bin/sh; fi; exec "$SHELL" -il',
|
||||||
]
|
]
|
||||||
# ``auto_close=False`` so an unexpected shell exit (dotfile error,
|
# ``auto_close=False`` so an unexpected shell exit (dotfile error,
|
||||||
# missing remote root, SSH drop) keeps the pane visible long enough
|
# missing remote root, SSH drop) keeps the pane visible long enough
|
||||||
|
|||||||
Reference in New Issue
Block a user