Skip to content

feat(gpui): open a folder in a chosen shell from the Explorer menu - #145

Merged
Kuddev merged 1 commit into
Kuddev:mainfrom
AnxForever:feat/open-in-wsl-distribution
Sep 17, 2026
Merged

Kuddev merged 1 commit into
Kuddev:mainfrom
AnxForever:feat/open-in-wsl-distribution

Conversation

@AnxForever

Copy link
Copy Markdown
Contributor

Result / 用户结果

Right-clicking a folder in Explorer can now open it in a chosen shell. The
installer registers one context-menu entry per registered WSL distribution —
"Open in Pebrel (Ubuntu)" next to the existing "Open in Pebrel" — and the
distribution's own prompt lands in that folder (/mnt/<drive>/…).

Previously the menu only ever opened the configured default shell, usually
PowerShell, with no way to ask for anything else.

Design / 设计边界

  • Responsibility: cli.rs takes the option; workspace/shell_launch.rs (new)
    owns id → launch resolution; the startup paths carry the id; the installer owns
    the menu entries.
  • Why a new module: main.rs has to validate --shell before it opens a
    window, and none of the resolution needs NebulaWorkspace state. It also keeps
    workspace.rs from growing — that file is at its budget in
    architecture/file-budgets.txt, and this change makes it marginally smaller.
  • --shell <id> uses the same ids as the shell setting (pwsh, cmd,
    wsl:Ubuntu, or an imported profile's settings id) and applies to the first
    tab this process creates. An id, not a path: every launch re-resolves the
    executable. It conflicts with -e, which the GPUI path does not consume anyway
    and which would bypass the shell integration (DetectedShell::shell()).
  • Both startup paths carry it — the cold start (run_shell
    WorkspaceStartup::NewTerminal) and the resident hand-off
    (tab.new/window.create gained an optional shell field). Without the
    latter, clicking the menu while Pebrel is already running would silently open
    the default shell in a second process.
  • Compatibility / fallback: an explicit request never silently degrades. An
    unresolvable id fails (invalid_shell over IPC, a startup error before any
    window for the CLI), and a wsl:<distro> the registry no longer lists still
    launches wsl.exe -d <distro> so the distribution reports the problem itself.
    The shell setting keeps its documented fallback to the platform default, and
    the winit shell rejects a shell request rather than ignoring it. WindowParams
    is deny_unknown_fields: an upgraded client asking an older resident for a
    shell gets invalid_params and falls back to a cold start — worth a release
    note ("restart the resident instance after upgrading").
  • Installer: one verb per distro (PebrelWsl<n>, docker-desktop* skipped,
    mirroring shell_detect::find_wsl_distros), reclaimed on uninstall by key
    prefix plus the command line it wrote. --shell is passed before
    --working-directory: a drive-root %V ends in \", which would swallow the
    rest of the line if the directory came first.

Evidence / 验证依据

  • cargo test -p nebula --bin pebrel for cli::tests (24),
    runtime_api:: (62), gpui_shell::workspace (98), shell_launch (1),
    residency (1) → all pass.
  • python scripts/check_architecture.py --base main → exit 0 (it failed with
    workspace.rs: 4460 lines > 4363 before the module extraction above — that is
    what prompted it).
  • scripts/tests/installer.tests.ps1 → the text assertions covering this change
    pass, including the argument-order regression (--shell before
    --working-directory). The script's final step compiles the packaging payload
    and needs a full release build tree (pebrel-hook.exe, conpty.dll,
    OpenConsole.exe); it fails on a working tree without one, on main as well.
    Inno Setup is not installed on this machine, so the Pascal was not compiled
    here
    — it is limited to RegGetSubkeyNames / RegQueryStringValue /
    RegWriteStringValue / RegDeleteKeyIncludingSubkeys and needs CI to confirm.
  • Regression tests: the argv-order case (shell_flag_parses_ahead_of_the_working_directory)
    and the IPC field (tab_and_window_requests_take_an_optional_shell, including
    the no-shell form that older clients send), plus wsl_launch_args for the id
    semantics (wsl:Team Linux, bare wsl, wsl: with no name, non-WSL ids).
  • Not verified here: the Unix completion snapshots were regenerated by hand
    (this machine can only run the Windows generator) and diffed character-for-character
    against the generated Windows output; the byte comparison test runs on Linux.
    The end-to-end menu click was exercised on the author's machine (cold start and
    resident hand-off); no CI coverage exists for Explorer integration.

Required Review / 必须确认

  • I followed CONTRIBUTING.md, docs/architecture.md, and docs/project-constraints.md.
  • I split responsibilities, not arbitrary line ranges; no duplicate behavior authority was added.
  • python3 scripts/check_architecture.py --base main passes; budgets were not inflated to fit the change.
  • Tests cover success and failure; platform/feature coverage limitations are stated.
  • New messages use typed i18n IDs and matching placeholders; untranslated content has an explicit fallback.
  • Governance changes include a counterexample, corrected contract, tests, and a maintainer-reviewed decision. (n/a — no governance change)

Right-clicking a folder has always opened the configured default shell —
usually PowerShell — with no way to ask for a WSL distribution instead.

- `--shell <id>` takes the same ids the `shell` setting uses (`pwsh`, `cmd`,
  `wsl:Ubuntu`, or an imported profile's settings id) and applies to the
  first tab this process creates. It carries an id rather than a path so
  every launch re-resolves the executable.
- The id travels both startup paths: the cold start (`run_shell` →
  `WorkspaceStartup::NewTerminal`), and the resident hand-off, which is the
  common case on Windows. `tab.new`/`window.create` gained an optional
  `shell` field, so clicking the menu while Pebrel is already running adds a
  tab to the existing window instead of starting a second process.
- An explicit request never silently degrades. An unresolvable id fails
  (`invalid_shell`, or a startup error before any window opens), and a
  `wsl:<distro>` id that the registry no longer lists still launches
  `wsl.exe -d <distro>`, leaving the complaint to the distribution itself.
  The `shell` setting keeps its documented fallback to the platform default.
- The winit shell rejects a shell request instead of ignoring it, so the
  caller falls back to a cold GPUI start and still gets the requested shell.

The resolution lives in a new `workspace::shell_launch` module: `main.rs` has
to validate `--shell` before it opens a window, and none of it needs
`NebulaWorkspace` state — `workspace.rs` is also at its line budget, which
this keeps from growing (`architecture/file-budgets.txt`).

The installer registers one context-menu entry per registered WSL
distribution (`PebrelWsl<n>`, skipping `docker-desktop*`) and the uninstaller
reclaims them by key prefix plus the command line it wrote. `--shell` is
passed before `--working-directory`: a drive-root `%V` ends in `\"`, which
would swallow the rest of the line if the directory came first. Both the
string and the order are pinned by installer assertions.

Docs (runtime API schema + control API) and shell completions are updated.
@AnxForever
AnxForever requested a review from Kuddev as a code owner September 14, 2026 15:08
@AnxForever

Copy link
Copy Markdown
Contributor Author

The CI runs here (and on #142, #143, #144) are sitting in action_required: they need a maintainer to approve them before they start. Flagging it only so the runs are not left waiting — no rush on the review itself.

Two of those checks would cover things I could not verify locally, which is why I would rather not merge without them:

  • Cross-platform Preview packages compiles the installer, and Inno Setup is not installed on this machine — the Pascal added here (one context-menu verb per registered WSL distribution, plus their uninstall sweep) is uncompiled so far. It is limited to RegGetSubkeyNames / RegQueryStringValue / RegWriteStringValue / RegDeleteKeyIncludingSubkeys.
  • The same job runs the shell-completion snapshot comparison on Linux. This machine can only generate the Windows set, so the Unix files were updated by hand and diffed character-for-character against the generated Windows output; the byte comparison in CI is the real check.

Architecture contracts passes locally on all four branches (python scripts/check_architecture.py --base main, exit 0).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants