refactor(sublime/terminal): swap external new_terminal for transient Terminus pane
The v0.7.0 Open Remote Terminal command spawned an external OS terminal via Sublime's `new_terminal` (Terminal package). Walk that back: the in-package terminal is meant for short ad-hoc commands (`ls`, `git status`, running a script) and the embedded experience is preferable when scope is that narrow. Long-running shell workflows are explicitly an external-terminal job that the user runs themselves outside this package. Behavior: - terminus_open with cmd=['ssh', '-t', alias, 'cd <root> && exec $SHELL -l'] and auto_close=True. The pane closes when the shell exits; no view-reuse cache, no tmux, no persistent state across launches. - If Terminus is not installed the command surfaces an install hint via the status bar rather than silently doing nothing. - The `new_terminal` external-spawn path is removed. The Korean IME bug on Windows that motivated the v0.7.0 external switch is acceptable in this narrow ad-hoc-command scope: typed inputs are short, often pre-built (paste-from-clipboard or arrow-key history), and non-typed-Korean. For interactive shells where IME matters more, users go external. 1168 sublime tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6889,22 +6889,18 @@ def _write_connected_host_state(mapping: Dict[str, str]) -> None:
|
||||
|
||||
|
||||
class SessionsOpenRemoteTerminalCommand(sublime_plugin.WindowCommand):
|
||||
"""Open the workspace's remote root in an external OS terminal via ``ssh -t``.
|
||||
"""Open a transient Terminus pane SSH'd into the workspace's remote root.
|
||||
|
||||
The command spawns the host's preferred terminal (Sublime's own
|
||||
``new_terminal``, which the user-installed ``Terminal`` package
|
||||
provides) running ``ssh -t <alias> "cd <remote_root> && exec
|
||||
\\$SHELL -l"``. ``ssh -t`` allocates a tty so the remote shell
|
||||
behaves interactively, and ``exec`` ensures Ctrl-D / shell exit
|
||||
closes the terminal cleanly.
|
||||
|
||||
No tmux, no embedded view: the OS terminal owns the lifecycle so
|
||||
Windows IME, scrollback, copy/paste, and font rendering are all
|
||||
handled natively.
|
||||
Scope is intentionally narrow: a fresh ad-hoc shell for short, simple
|
||||
commands (``ls``, ``git status``, running a script). No view-reuse cache,
|
||||
no tmux session multiplexing, no persistence across pane closes — when
|
||||
the shell exits the pane closes (``auto_close=True``). For long-running
|
||||
or tmux-heavy workflows the user is expected to open their own external
|
||||
terminal.
|
||||
"""
|
||||
|
||||
def run(self) -> None:
|
||||
"""Open an external OS terminal SSH'd into the workspace's remote root."""
|
||||
"""Open a Terminus pane attached to the workspace's remote root."""
|
||||
settings = SessionsSettings()
|
||||
context = _workspace_context(self.window, settings)
|
||||
if context is None:
|
||||
@@ -6915,24 +6911,34 @@ class SessionsOpenRemoteTerminalCommand(sublime_plugin.WindowCommand):
|
||||
if not callable(run_command):
|
||||
_status_message("No terminal command is available in this Sublime build.")
|
||||
return
|
||||
|
||||
find_resources = getattr(sublime, "find_resources", None)
|
||||
has_terminus = False
|
||||
if callable(find_resources):
|
||||
try:
|
||||
has_terminus = bool(find_resources("Terminus.sublime-settings"))
|
||||
except (TypeError, ValueError, RuntimeError):
|
||||
has_terminus = False
|
||||
if not has_terminus:
|
||||
_status_message(
|
||||
"Sessions: install the Terminus package to use Open Remote Terminal."
|
||||
)
|
||||
return
|
||||
|
||||
remote_invocation = "cd {} && exec ${{SHELL:-/bin/sh}} -l".format(
|
||||
shlex.quote(remote_root),
|
||||
)
|
||||
ssh_cmd = "ssh -t {} {}".format(
|
||||
shlex.quote(host_alias),
|
||||
shlex.quote(remote_invocation),
|
||||
)
|
||||
run_command(
|
||||
"new_terminal",
|
||||
"terminus_open",
|
||||
{
|
||||
"cmd": ssh_cmd,
|
||||
"cmd": ["ssh", "-t", host_alias, remote_invocation],
|
||||
"cwd": str(context.local_cache_root),
|
||||
"title": "ssh {}:{}".format(host_alias, remote_root),
|
||||
"auto_close": True,
|
||||
},
|
||||
)
|
||||
_status_message(
|
||||
"Sessions: opening external terminal for {}:{}".format(
|
||||
host_alias, remote_root
|
||||
)
|
||||
"Sessions: opening terminal for {}:{}".format(host_alias, remote_root)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -398,22 +398,30 @@ def test_connect_selected_workspace_opens_remote_tree_view(
|
||||
assert refresh_calls == [1]
|
||||
|
||||
|
||||
def test_open_remote_terminal_invokes_new_terminal_with_ssh_cd_invocation(
|
||||
def test_open_remote_terminal_opens_transient_terminus_pane(
|
||||
tmp_path: Path, monkeypatch
|
||||
) -> None:
|
||||
"""Resolves workspace alias + remote root and dispatches to ``new_terminal``.
|
||||
"""Resolves workspace alias + remote root and dispatches to ``terminus_open``.
|
||||
|
||||
The terminal lifetime now lives in the OS terminal (no embedded view, no
|
||||
tmux convenience layer). The command's only job is to assemble the
|
||||
``ssh -t`` invocation that lands the user in their remote root and hand
|
||||
it off to Sublime's ``new_terminal`` (provided by the user-installed
|
||||
``Terminal`` package).
|
||||
The terminal is intentionally transient: ``auto_close=True`` so the pane
|
||||
closes when the shell exits, no view-reuse cache, no tmux. For long-lived
|
||||
or tmux-heavy workflows the user runs an external terminal themselves.
|
||||
"""
|
||||
ssh_config_path = tmp_path / "config"
|
||||
_write_ssh_config(ssh_config_path, "Host prod\n HostName prod.example.com\n")
|
||||
settings = SessionsSettings(ssh_config_path=ssh_config_path)
|
||||
monkeypatch.setattr(commands, "SessionsSettings", lambda: settings)
|
||||
monkeypatch.setattr(commands.sublime, "cache_path", lambda: str(tmp_path / "cache"))
|
||||
monkeypatch.setattr(
|
||||
commands.sublime,
|
||||
"find_resources",
|
||||
lambda pattern: (
|
||||
("Packages/Terminus/Terminus.sublime-settings",)
|
||||
if "Terminus" in pattern
|
||||
else ()
|
||||
),
|
||||
raising=False,
|
||||
)
|
||||
recent_store = commands._recent_store(settings)
|
||||
recent_store.save_index(
|
||||
RecentWorkspaceIndex(
|
||||
@@ -433,14 +441,57 @@ def test_open_remote_terminal_invokes_new_terminal_with_ssh_cd_invocation(
|
||||
|
||||
commands.SessionsOpenRemoteTerminalCommand(window).run()
|
||||
|
||||
new_terminal_calls = [c for c in window.window_commands if c[0] == "new_terminal"]
|
||||
assert len(new_terminal_calls) == 1
|
||||
args = new_terminal_calls[0][1]
|
||||
assert args["cmd"] == ("ssh -t prod 'cd /srv/app && exec ${SHELL:-/bin/sh} -l'")
|
||||
terminus_calls = [c for c in window.window_commands if c[0] == "terminus_open"]
|
||||
assert len(terminus_calls) == 1
|
||||
args = terminus_calls[0][1]
|
||||
assert args["cmd"] == [
|
||||
"ssh",
|
||||
"-t",
|
||||
"prod",
|
||||
"cd /srv/app && exec ${SHELL:-/bin/sh} -l",
|
||||
]
|
||||
assert args["auto_close"] is True
|
||||
assert args["title"] == "ssh prod:/srv/app"
|
||||
assert "cwd" in args
|
||||
assert any(
|
||||
"opening external terminal for prod:/srv/app" in m for m in status_messages
|
||||
assert any("opening terminal for prod:/srv/app" in m for m in status_messages)
|
||||
|
||||
|
||||
def test_open_remote_terminal_prompts_to_install_terminus_when_absent(
|
||||
tmp_path: Path, monkeypatch
|
||||
) -> None:
|
||||
"""Without Terminus installed the command surfaces an install hint, no spawn."""
|
||||
ssh_config_path = tmp_path / "config"
|
||||
_write_ssh_config(ssh_config_path, "Host prod\n HostName prod.example.com\n")
|
||||
settings = SessionsSettings(ssh_config_path=ssh_config_path)
|
||||
monkeypatch.setattr(commands, "SessionsSettings", lambda: settings)
|
||||
monkeypatch.setattr(commands.sublime, "cache_path", lambda: str(tmp_path / "cache"))
|
||||
monkeypatch.setattr(
|
||||
commands.sublime,
|
||||
"find_resources",
|
||||
lambda pattern: (),
|
||||
raising=False,
|
||||
)
|
||||
recent_store = commands._recent_store(settings)
|
||||
recent_store.save_index(
|
||||
RecentWorkspaceIndex(
|
||||
(
|
||||
RecentWorkspace(
|
||||
"prod",
|
||||
"/srv/app",
|
||||
"cache-123",
|
||||
"2026-04-12T03:00:00+00:00",
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
status_messages: List[str] = []
|
||||
monkeypatch.setattr(commands.sublime, "status_message", status_messages.append)
|
||||
window = FakeWindow(project_data={"settings": {PROJECT_SETTINGS_KEY: "cache-123"}})
|
||||
|
||||
commands.SessionsOpenRemoteTerminalCommand(window).run()
|
||||
|
||||
assert window.window_commands == []
|
||||
assert any("install the Terminus package" in m for m in status_messages)
|
||||
|
||||
|
||||
def test_connected_host_alias_recovers_from_persisted_state(
|
||||
|
||||
Reference in New Issue
Block a user