Skip to content

feat: Add Open In modal to open projects in IDEs#200

Merged
marcus merged 4 commits into
marcus:mainfrom
EugeneOsipenko:feat/open-in-modal
Feb 27, 2026
Merged

feat: Add Open In modal to open projects in IDEs#200
marcus merged 4 commits into
marcus:mainfrom
EugeneOsipenko:feat/open-in-modal

Conversation

@EugeneOsipenko
Copy link
Copy Markdown
Contributor

@EugeneOsipenko EugeneOsipenko commented Feb 22, 2026

Summary

  • Add app-level "Open In" modal (^ key) that detects installed macOS IDEs and opens the current project/worktree directory in the selected app
  • Auto-detects 13 IDEs (VS Code, Cursor, Android Studio, IntelliJ, WebStorm, GoLand, PyCharm, Xcode, Sublime Text, Zed, Fleet, Terminal, Finder) via /Applications bundle scanning
  • Remembers last-used IDE per-project with global fallback
  • Full keyboard (j/k, enter, esc) and mouse support
Снимок экрана 2026-02-22 в 15 07 48

Test plan

  • go build ./... compiles cleanly
  • go test ./... all pass (10 new tests added)
  • Press ^ — modal appears with installed IDEs only
  • Navigate with j/k, select with enter — app launches
  • Reopen modal — last-used IDE pre-selected with (last) badge
  • esc closes modal without action
  • Binding visible in help modal (?) and command palette

🤖 Generated with Claude Code

EugeneOsipenko and others added 4 commits February 22, 2026 11:50
Add LastOpenInApp string field to both ProjectConfig (per-project) and
UIConfig (global fallback). Add SaveLastOpenInApp helper that persists
the last-used app ID to both the matching project and the global config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement the Open In modal that lets users open the current project
in their preferred IDE/editor. Includes:

- IDE registry with 13 known macOS apps (VS Code, Cursor, JetBrains
  IDEs, Xcode, Sublime Text, Zed, Fleet, Terminal, Finder)
- App detection via os.Stat on /Applications bundles
- Last-used app tracking (per-project with global fallback)
- Modal rendering with cursor navigation, scroll support, mouse handling
- Launch via exec.Command("open", "-a", appName, workDir)
- Model struct fields for Open In state

Tests cover: detectInstalledApps, openInItemID, findLastUsedIndex,
openInEnsureCursorVisible (all passing).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ModalOpenIn to the modal enum, wire keyboard (O key) and mouse
input routing, and connect the modal overlay rendering in View().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
O was conflicting with plugin-level bindings (open-in-file-browser
in git-status, open-in-git in workspace). Switch to ^ and register
in global keymap for discoverability in help modal and palette.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@marcus
Copy link
Copy Markdown
Owner

marcus commented Feb 22, 2026

Hey @EugeneOsipenko! Starling here. 👋

This is a really polished PR — scanning /Applications to auto-detect IDEs is the right approach (no config needed for the common case), and remembering the last-used IDE per-project is a nice UX touch. 13 IDEs covered is thorough. The ^ binding feels natural and the full keyboard + mouse support is consistent with the rest of sidecar's UI.

10 new tests with a fully checked test plan is exactly what we want to see. Flagging for @marcus to review — this one looks solid. ✦

@Amolith
Copy link
Copy Markdown
Contributor

Amolith commented Feb 22, 2026

I'd love this feature! But it will only work on macOS at the moment; neither Windows nor Linux nor any BSD flavour I'm aware of has any of the primitives the PR expects, like an /Applications directory or the open command. Those don't exist anywhere but macOS.

@marcus
Copy link
Copy Markdown
Owner

marcus commented Feb 23, 2026

Thanks @Amolith — you're absolutely right, this is macOS-specific as written: /Applications scanning and the open command are Apple-only primitives and don't exist on Linux, BSD, or Windows.

Flagging for @marcus to decide whether to merge as macOS-only (with a clear platform note in the docs and a build-tag or runtime check) or hold until there's a cross-platform path. @EugeneOsipenko — worth noting the macOS scope in the PR description if you haven't already, and it might be worth wrapping the OS-specific code in a //go:build darwin constraint so it compiles cleanly on other platforms in the meantime. ✦

Copy link
Copy Markdown
Owner

@marcus marcus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Approved — solid implementation

Reviewed commit range ce45fa0..10b2f56. Build passes, all relevant tests pass.

What's in this PR

  • New ^ keybinding opens an "Open In…" modal listing installed IDEs detected from /Applications
  • Detection is purely os.Stat on .app bundles — no shell-out, no shell injection surface
  • Launch uses open -a <bundle> <workDir> via exec.Command — correct macOS idiom, correctly uses FoundBundle (the actual bundle name) rather than display name, avoiding the lookup mismatch bug
  • Finder uses plain open <workDir> (no -a), which is correct
  • Last-used preference is stored per-project and as a global fallback in config.json; the two-tier lookup (project → global) is clean
  • Modal priority slot added correctly (ModalOpenIn between ModalThemeSwitcher and ModalIssueInput)

Security

The exec path is about as locked down as it gets here: the only binary called is /usr/bin/open, arguments are a fixed literal (-a) plus an app bundle name that was validated by os.Stat and a workDir that comes from existing config. No user-typed strings flow into exec args. No shell interpolation. 👍

Test coverage

Good unit coverage on all the pure functions: detectInstalledApps (including multi-bundle and empty-dir cases), findLastUsedIndex, openInEnsureCursorVisible, and config round-trip for SaveLastOpenInApp. The one area without tests is the UI rendering / key-event path, but that's consistent with the rest of the codebase.

Minor notes (non-blocking)

  • openInListUpdate handles "select" but the branch in update.go that receives it calls m.confirmOpenIn() and resets — that path works. Worth noting the action string is handled in two places (openInListUpdate returning "select" then handleOpenInMouse/handleKeyMsg acting on it), which is the existing modal pattern here.
  • m.openInModalWidth = 0 as a "force rebuild" trick on scroll works, but is a bit subtle. A comment already explains it — fine.
  • SaveLastOpenInApp does a full config load-modify-save. Same pattern as SaveGlobalTheme, consistent.

Pre-existing failure

internal/plugins/tdmonitor has two failing tests on main already — unrelated to this PR, verified by checking out main.

Installed locally via make install. Ready for Marcus to try with ^ in any project.

@marcus marcus merged commit 46653dc into marcus:main Feb 27, 2026
@marcus
Copy link
Copy Markdown
Owner

marcus commented Feb 27, 2026

Thanks for this! Really clean implementation — follows the existing modal patterns well, good test coverage, and the IDE detection approach is solid. Merged! 🪶

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

Labels

None yet

3 participants