Skip to content

Single-file mode for direct file invocations#38

Merged
cloudmanic merged 3 commits into
mainfrom
single-file-mode
Jul 23, 2026
Merged

Single-file mode for direct file invocations#38
cloudmanic merged 3 commits into
mainfrom
single-file-mode

Conversation

@cloudmanic

Copy link
Copy Markdown
Owner

Summary

  • spiceedit somefile.md no longer walks the surrounding directory just to render a sidebar the user didn't ask for. New app.NewSingleFile constructor skips the file tree, the project-wide finder index, and the 10s background tree-refresh goroutine.
  • The action menu's "Show / Hide file explorer" row is filtered out in this mode — there's no tree to show, so the row is nonsensical. Added a general visible predicate to menuItemDef so future tree-dependent rows can opt in the same way.
  • Tab bar still works normally, so opening additional files (via menu actions) stays sticky in single-file mode — multiple tabs, no sidebar.

Behavior matrix

Invocation Tree Finder Sidebar Sidebar toggle in menu
spiceedit ✓ built ✓ indexed shown shown
spiceedit . ✓ built ✓ indexed shown shown
spiceedit somedir/ ✓ built ✓ indexed shown shown
spiceedit somefile.md ✗ skipped ✗ skipped hidden hidden

Test plan

  • make test — all green locally with the race detector
  • spiceedit README.md opens the file with no sidebar visible
  • Open the ≡ menu — "Show file explorer" / "Hide file explorer" row is absent
  • On a large repo (~50k files), time spiceedit README.md startup is noticeably faster than time spiceedit .
  • spiceedit nonexistent.md still works (vim-style "create on first save")
  • spiceedit somedir/ still shows the sidebar and full tree
  • Inside single-file mode, the Find file / Rename / Delete folder menu rows are appropriately disabled (they were already gated by hasFinder / hasActiveSubfolder and continue to behave correctly with finder == nil / no subfolder)

When invoked as 'spiceedit somefile.md' the user is asking to look at
one file, not to open a project. Skip the file-tree walk, the
project-wide finder index, and the 10s background tree-refresh
goroutine — none of them are visible to the user in that mode and on
a large directory they're real CPU.

main.go routes to a new app.NewSingleFile(path) constructor when an
OpenFile is resolved; otherwise it stays on app.New(rootDir). The
constructor leaves tree and finder nil, hides the sidebar, and opens
the file as the first tab.

The action menu's 'Show / Hide file explorer' row is filtered out via
a new visibility predicate (hasTree) so the user can't try to show a
sidebar that doesn't exist. The same machinery (visible func on
menuItemDef) is general-purpose so future tree-dependent rows can
opt in the same way. Tree-Refresh sites in fileops are routed through
a small refreshTree() helper that no-ops when tree is nil.
Fresh-eyes review of the merged single-file-mode feature surfaced three
issues where post-merge code assumes a file tree that single-file mode
deliberately omits:

- Esc-t (menuToggleSidebar) bypassed the menu's hasTree gate and flipped
  sidebarShown true, sending draw() into a.tree.Render on a nil tree and
  panicking. Now a no-op with a flash.
- Esc-p (openFinder) similarly bypassed the gate and popped an always-empty
  finder modal. Now a no-op with a flash.
- Saving in single-file mode routes through refreshGitStatus, which bailed
  on nil tree and left the git gutter markers stale. It now refreshes the
  per-line markers (a file-scoped git diff) even without a tree.

Adds regression tests for all three.
@cloudmanic
cloudmanic merged commit 5c23541 into main Jul 23, 2026
2 checks passed
@cloudmanic
cloudmanic deleted the single-file-mode branch July 23, 2026 00:07
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