Skip to content

feat(open-with): configurable editors/IDEs instead of a hard-wired "Open in VS Code" - #186

Merged
mortenaslo merged 2 commits into
mainfrom
feat/178-open-with
Nov 6, 2026
Merged

mortenaslo merged 2 commits into
mainfrom
feat/178-open-with

Conversation

@mortenaslo

Copy link
Copy Markdown
Contributor

Closes #178

Summary

Replaces the single hard-wired Open in VS Code with a configurable Open with… list. Each entry has a label, a program, and an argument template for each session kind (local, WSL, SSH); one entry is the default.

  • Toolbar { } ▼, the same pattern as the ▶/▼ run button:
    • { } opens the default editor for this session's kind.
    • ▼ lists every entry that has a template for that kind, then Edit…. Entries without one are hidden.
  • Sidebar: right-click → Open with ▸.
  • Settings → Open with → Edit…:
    • add, remove, reorder and set the default;
    • label, and program with Browse…;
    • templates for each kind, with a preview of the fully resolved command.
  • WSL and SSH sessions now open remotely. VS Code-family editors use --folder-uri vscode-remote://wsl+… or ssh-remote+…, passed as one argument with percent-encoded {name:uri} placeholders. Before, a WSL folder was opened over the \wsl$ share.

Auto-detect

Detection seeds the list once; after that it is the user's.

  • VS Code (stable and Insiders), Cursor, Windsurf and VSCodium: the PATH shim resolved to its .exe, App Paths, standard install folders, and Scoop.
  • Visual Studio via vswhere.
  • JetBrains IDEs: standalone and Toolbox installs, using the *64.exe only.
  • Zed, Sublime Text and Notepad++.

Detection runs on the thread pool. code.cmd on PATH is still found first, so with no configuration it behaves as before. If nothing is found, detection runs again on the next launch.

Security

  • No shell. The program starts with UseShellExecute = false and ArgumentList.
    • The template is split into tokens before placeholders are filled, so each value stays inside one argument.
    • A test starts a real child process with that ArgumentList and splits its raw command line with CommandLineToArgvW.
  • No switch-shaped arguments. A value that starts an argument may not begin with - or /. Checked on this machine: Code.exe 1.143.1 does treat /js-flags=… as a Chromium switch.
  • BatBadBut. .cmd shims are resolved to the real .exe, but only when the script names exactly one program. If a batch file is used anyway, any argument containing a cmd metacharacter is refused, never escaped.
  • Programs:
    • Must be an absolute local .exe.
    • Nothing under %SystemRoot%, and no known shells or script hosts.
    • Checked against the real path on disk: 8.3 names, links and subst are resolved, network drives are refused.
    • The PE original name is checked, so a renamed shell is still caught.
  • Templates must contain a placeholder.
  • Imports:
    • The list is filtered on load and on import.
    • An import never replaces it silently: every imported command is listed for confirmation, and "keep current list" is the default.
    • Menu items show the program's file name and the fully resolved command, so a label can't hide what runs.

Review

A read-only review of the first commit found eight issues, all fixed in the second commit. The most serious was an imported backup being able to put an arbitrary command behind a harmless label. The others were an 8.3-name bypass of the shell list, /-prefixed values reaching Code.exe as switches, and file checks running on the UI thread.

Tests

  • 156 Open with unit tests (OpenWithServiceTests, OpenWithHardeningTests, OpenWithDetectorTests).
  • Full suite: 921/921.

Manual testing still needed (desktop was locked)

  • Toolbar { } ▼ layout and tooltips, the sidebar submenu, and Settings → Open with
  • Editor dialog: styling, Browse… on code.cmd, previews and warnings, Detect
  • Local folder opens in VS Code, and in Visual Studio from ▼
  • WSL session opens through Remote-WSL, including a path with spaces
  • SSH session opens through Remote-SSH (ssh-remote+user%40host; VS Code decoding %40 is the main assumption)
  • Importing a backup with a different list shows the confirmation, defaulting to No

🤖 Generated with Claude Code

https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX

mortenaslo and others added 2 commits November 6, 2026 16:51
…pen in VS Code" (#178)

Replace the single VS Code action with a user-owned "Open with" list:
label, program, and an argument template per session kind (local {folder};
WSL {distro} {linuxPath} {folder}; SSH {user} {host} {userAtHost} {port}
{remoteFolder}), one default.

- Toolbar { } becomes the run-button pattern: { } opens the default for the
  session's kind, the chevron lists every applicable entry plus Edit...;
  sidebar right-click gains "Open with >". Works for WSL and SSH sessions
  (VS Code family via --remote wsl+ / ssh-remote+).
- Seeded once by OpenWithDetector (off the UI thread): VS Code stable and
  Insiders, Cursor, Windsurf, VSCodium (PATH shim -> exe, App Paths,
  standard install dirs, Scoop), Visual Studio via vswhere, JetBrains IDEs
  (standalone + Toolbox), Zed, Sublime Text, Notepad++. PATH code.cmd is
  still found first, so the zero-config default is unchanged.
- OpenWithService (WPF-free): templates are tokenised before substitution,
  so each placeholder is one argv element; launch is UseShellExecute=false +
  ArgumentList. Programs must be absolute local .exe paths and never a
  shell; .cmd/.bat shims are resolved to the real exe, and a batch target
  refuses any argument with cmd metacharacters (BatBadBut). Option-shaped
  and control-character values are refused; a local folder must exist.
- state.json and imports are sanitised; imports reject batch files outright.
  The editor (Settings -> Open with, or Edit...) previews the full resolved
  command for each kind.
- 110 new unit tests (hostile folder names round-tripped through
  CommandLineToArgvW, BatBadBut refusal, import trust, detector layouts).

Closes #178

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX
…m checks (#178 review)

- Import never silently replaces the Open-with list: every imported
  entry's program and templates are listed and confirmed, default No.
- Programs: refuse anything under %SystemRoot%; on-disk check resolves the
  real path (8.3, links, subst), refuses network drives, and checks PE
  OriginalFilename so renamed shells are caught. Run at import, launch and
  (debounced, off-thread) in the editor.
- Templates must contain a placeholder; menu headers show the program file
  and tooltips the fully resolved command.
- No placeholder may start an argument with '-' or '/' (verified: Code.exe
  1.143.1 honours /js-flags=... as a Chromium switch). Seeded VS Code
  remote forms are now --folder-uri vscode-remote://... with {name:uri}
  percent-encoding.
- PrepareAsync validates before probing WSL $HOME; ResolveShim needs a
  single unambiguous candidate; detection finding nothing stays unseeded;
  toolbar state follows session edits and is released on teardown.
- Dialog disk I/O moved off the UI thread.
- Tests: real ArgumentList encoding round-tripped via a spawned child,
  8.3 / renamed-copy / SystemRoot / network-drive refusals, probe ordering,
  URI encoding (921 total).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX
@mortenaslo
mortenaslo merged commit e4ab258 into main Nov 6, 2026
1 check passed
mortenaslo added a commit that referenced this pull request Nov 6, 2026
Conflict in MainWindow.OnLoaded: kept EnsureOpenWithSeeded(); the old
RestoreWindowState call stays removed (bounds are applied before show).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX
@mortenaslo
mortenaslo deleted the feat/178-open-with branch November 6, 2026 18:21
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.

Open with…: configurable editors/IDEs instead of a hard-wired "Open in VS Code"

1 participant