Skip to content

feat: find-in-files sidebar, command bar with :cd re-root, terminal tabs + diff viewer - #46

Open
jaivial wants to merge 8 commits into
cloudmanic:mainfrom
jaivial:main
Open

feat: find-in-files sidebar, command bar with :cd re-root, terminal tabs + diff viewer#46
jaivial wants to merge 8 commits into
cloudmanic:mainfrom
jaivial:main

Conversation

@jaivial

@jaivial jaivial commented Aug 13, 2026

Copy link
Copy Markdown

1. Summary

SpiceEdit had no way to search across a project from the sidebar, no way to change the working directory once the editor was running, and no shell access. Users opening spiceedit in a directory had to quit and relaunch from another path to browse a different project, and grep-style cross-file search only existed as a centered modal that clobbered its state on every open.

This PR adds four user-facing features built on the existing modal and event-loop patterns. First, a persistent Find-in-files panel in the sidebar with a Files/Find header tab strip — a second grep surface whose query and results survive independently of the Esc-F modal, grouped into per-file accordions with click-to-jump. Second, a command bar (Esc-: or the menu) whose first built-in command is cd: it re-roots the file tree, the finder's project index, git status and every search surface at the target directory — nvim's :cd behavior — with bash-style Tab completion (common-prefix extension, then cycling; ~, absolute and relative paths; trailing-slash drill-down). Third, terminal tabs: a PTY-backed shell runs inside an editor tab, opened with Esc-`, the ≡ menu, or a "+" button pinned to the far right of the tab bar — keystrokes go straight to the child shell, and the tab closes with its usual × affordance. Fourth, a git-changes diff viewer, plus a fix so absolute-path completion splices the full directory prefix back into the token.

All six commits were developed and merged incrementally via fork PRs #1#3 with go test -race ./... green at every step.

2. Root Cause (bug fix df72919)

  • Symptom: typing cd /var/www/new and pressing Tab completed to bare newvillacarmen, dropping /var/www/ from the token.
  • Root cause: commandSearchBase in internal/app/commandbar.go returned an empty splice prefix for absolute tokens, so candidates were inserted without their directory part.
  • How the fix addresses it: the absolute branch now returns the dirname as the splice prefix (with a single-slash special case at filesystem root), so Tab yields /var/www/newvillacarmen.
  • Prevention: TestCommandCompletionAbsolute pins nested absolute completion, Tab-cycling prefix survival, and root-level tokens.

3. Flow

sequenceDiagram
    participant U as User
    participant CB as Command bar
    participant FS as Filesystem
    participant T as File tree
    participant F as Finder index

    U->>CB: Esc : cd /var/www/new + Tab
    CB->>FS: ReadDir(/var/www)
    FS-->>CB: matching directories
    CB-->>U: common prefix, then cycle
    U->>CB: Enter
    CB->>FS: Stat(target)
    FS-->>CB: valid dir
    CB->>T: rebuild at new root
    CB->>F: recreate + Rebuild (background)
    F-->>CB: finderRebuiltEvent
    CB-->>U: sidebar + finder point at new root
Loading

Screenshots

Command bar with directory completion — suggestion popup above the input, match count on the right:

command bar completion

Absolute-path completion fix/tmp/opencode/spicedemo/new + Tab completes the full path, not just the last segment:

absolute path completion

After cd + Enter — the sidebar re-roots at the new directory (nvim :cd behavior), open tabs survive, and the status bar flashes the new root:

cd re-root

Sidebar Find-in-files panel — Files/Find tab strip, per-file accordions with match lines:

sidebar find in files

Terminal tab — a shell running inside an editor tab (Esc-` or the ≡ menu), file tree and terminal side by side:

terminal tab

New-terminal "+" button — pinned to the far right of the tab bar; clicking it opens a second terminal tab:

new terminal button

4. Changes

App (event loop / UI)

File Change
internal/app/app.go Tab-bar terminal button geometry, sidebar-tab and search state, command-bar routing in key/mouse/draw chains, reroot support wiring, menu row for the command bar.
internal/app/commandbar.go New: command bar — input handling, bash-style directory completion (common prefix, cycling, ~/absolute/relative), cd validation, reroot of tree/finder/git status/search state.
internal/app/sidebarsearch.go New: sidebar Find-in-files panel — query input, per-file accordion groups, click-to-jump, independent of the Esc-F modal.
internal/app/searchfiles.go New: centered find-in-files modal the sidebar panel shares its grep backend with.
internal/app/diffviewer.go New: git-changes modal — dirty-file list plus scrollable per-file unified diffs.
internal/app/leader.go New Esc-leader bindings: : command bar, F find-in-files.
internal/app/modals.go closeAllModals / anyModalOpen cover the command bar and new modals.
internal/app/find.go Find bar no longer swallows keystrokes while the new surfaces own the keyboard.
internal/app/gitstatus.go Exposes the repo root and status snapshot the diff viewer renders.

Editor / Finder

File Change
internal/editor/terminal.go New: PTY-backed terminal tab with resize and scrollback handling.
internal/editor/terminal_unix.go, internal/editor/terminal_windows.go Platform PTY/ConPTY shims for the terminal tab.
internal/editor/tab.go Tab kinds for terminals and image previews; keyboard routing opt-out for terminal tabs; far-right "+" new-terminal button geometry in the tab bar.
internal/editor/comment.go Comment toggle no-op guard for non-text tabs.
internal/finder/grep.go New: project grep producing content matches for both search surfaces.
internal/icons/icons.go Terminal glyph for the tab bar.

Tests

File Change
internal/app/commandbar_test.go New: completion (single/common-prefix/cycle/nested/absolute/tilde), cd validation, re-root wiring, Esc-: routing, drawing.
internal/app/sidebarsearch_test.go New: sidebar panel state and result grouping.
internal/app/searchfiles_test.go, internal/app/diffviewer_test.go, internal/app/terminal_test.go, internal/editor/terminal_test.go New coverage for the find-in-files modal, diff viewer and terminal tabs.
internal/finder/grep_test.go New: grep matching and gitignore behavior.
internal/app/app_test.go Menu-layout geometry expectations updated for the new row.

Config / Docs

File Change
README.md Documents the command bar, sidebar search, terminal tabs and diff viewer.
go.mod, go.sum PTY and gitignore dependencies for terminals and grep.
internal/version/version.go Version bump to 0.0.44.

5. Verification

  • go test -race ./... — all 12 packages pass on every commit (run before each fork PR merge).
  • gofmt -l — no new unformatted files.
  • go vet ./... — clean.
  • Manual smoke (real terminal, tmux, 120x34): Esc-: opens the bar; cd /tmp/…/spicedemo/ shows 4 directory candidates; …/new + Tab completes the full absolute path; Enter re-roots the sidebar (see screenshots); clicking the Find tab and typing func returns 4 per-file accordions; Esc-` opens a terminal tab, the far-right "+" button opens a second one, and both shells accept input in parallel with the editor.
  • Coverage of new UI logic: same-package tests beside every new source file, tcell.NewSimulationScreen for draw assertions, t.TempDir() for filesystem state.

Opens $SHELL in a real tab via "Open terminal in new tab" in the ≡ menu
(or Esc-`), so tests and git are one keystroke away instead of a second
tmux pane.

The shell runs on a PTY (creack/pty) and is rendered by an embedded VT
emulator (hinshun/vt10x) into the editor pane. We own the emulation
rather than passing escape codes through to the host terminal — that's
what lets the shell live in a sub-rectangle without fighting the editor
for cursor position and scroll region, and it means no tmux passthrough
config is needed. Both deps are pure Go, so the single static no-CGO
binary is preserved.

Notes on the less obvious decisions:

- Adds Tab.IsTextual() and moves the existing !IsImage() guards onto it.
  Nearly every one of those guards meant "is this a normal text tab";
  with only one alternate mode that was accidentally the same thing.
  Terminal tabs would otherwise have slipped through save, find, the git
  gutter, disk reconciliation, and the dirty-quit prompt.

- The PTY reader goroutine never touches UI state. It parses into the
  emulator (internally locked) and posts termOutputEvent so the main
  loop redraws, matching the existing autoScroll/treeRefresh pattern.

- Close() hangs up the child's process *group* with SIGHUP before
  escalating to SIGKILL. SIGKILLing the shell directly means bash never
  runs its exit path and every backgrounded job is orphaned. It also
  returns early if the child was already reaped, so a recycled PID can
  never be signalled.

- The Esc-leader table stands down inside a terminal. A shell prompt is
  where users hit Esc by reflex, and swallowing the next rune to run an
  editor action is destructive: Esc then q would quit the editor and
  hang up every running shell. Double-Esc still opens the menu, so
  nothing becomes unreachable.

- glyphStyle deliberately ignores the reverse-video bit: vt10x already
  swaps FG/BG into the stored cell while leaving the bit set, so
  honouring it double-swaps and cancels the highlight. Relatedly,
  termColor resolves the default-colour sentinels by meaning rather than
  by slot, or a reversed default cell collapses back to the normal pair.

Terminal tabs are unix-only; the menu row is disabled on Windows, where
creack/pty returns ErrUnsupported. Verified on linux/darwin/windows for
amd64/arm64.
Add persistent Find in files surface in the sidebar, grouped into
per-file accordions; matches jump to line on click. Independent
state from the Esc-F modal so both search surfaces coexist. Also
add far-right terminal tab button in the tab bar.
Esc-: (or the ≡ menu) opens a bottom command line. First built-in
command: cd, which re-roots the editor — file tree, project finder
index, git status and search surfaces all point at the new directory
(nvim :cd behavior). Tab completes directories with common-prefix
extension then cycles; Up/Down and click adopt a suggestion; invalid
paths keep the bar open with a hint. Open tabs survive the re-root.

Tests: commandbar_test.go (completion, cd validation, re-root wiring,
leader routing, drawing); menu layout expectations updated for the
new row. Full suite passes with -race.
commandSearchBase dropped the directory prefix for absolute tokens,
so '/var/www/new' + Tab completed to bare 'newvillacarmen' instead
of '/var/www/newvillacarmen'. The absolute branch now splices the
dirname back in (single slash at filesystem root). Adds a regression
test covering nested absolute completion, cycling, and root-level
tokens.
Draws a + button right after the last tab (or after the menu button
when no tabs are open) that opens and focuses a new unsaved scratch
tab. Styled like the far-right terminal button.
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.

1 participant