Skip to content

Latest commit

 

History

History
214 lines (162 loc) · 11.6 KB

File metadata and controls

214 lines (162 loc) · 11.6 KB

Current state

Last verified against feature/ui-redesign on 2026-10-09, Swift 6.4, macOS 27.0.1.

swift build    → Build complete
swift test     → 73 tests in 17 suites passed
swift-format lint --strict → 0 warnings

Tagged 0.1.0. The interface has been redesigned since that tag; see Redesigned interface and #14.

Start here: the highest-priority next step is not one of the six issues — it is guarding unsaved work on quit, which is the only remaining path to data loss. See Next.

Redesigned interface

The window was rebuilt around native macOS chrome. The design tokens live in CodeEditorThemes, the metrics scale in CodeEditorCore.

Area What it does now
Theme Theme carries colorScheme, a Chrome group (accent, sidebar, rows, bars, panels, overlays, borders) and a Semantic group (danger, success, warning). Three palettes: Dark, Light, High Contrast. High Contrast sets usesMaterials = false.
Typography Typography maps roles to semantic styles, so chrome scales with the system text size. .system(size:) is banned from chrome. The canvas stays a fixed-size SF Mono — code reflowing mid-edit is worse than the accessibility it buys.
Metrics One 4pt spacing scale, radii keyed by surface, fixed chrome heights. Enforced by MetricsTests.
Toolbar Unified: breadcrumb principal, create menu and quick-open/terminal toggles trailing. The sidebar's hand-rolled header is gone.
Sidebar Click or arrow to a row to activate: files open, folders expand. The active document is revealed by expanding its ancestors, shown bold with an accent icon. Delete asks first.
Tabs 32pt strip, active tab filled with the canvas colour and rounded so it merges with the editor. Dirty dot beside the title; close button always present.
Status bar Caret, selection count, UTF-8, line ending, language. The byte count is gone.
Empty state One, in the canvas. With a workspace open it points at the sidebar; without one it offers Open Folder as the single prominent action.
Find/replace ⌘F opens CodeEdit's own panel through SourceEditorState.findPanelVisible.
Quick open ⌘P over a bounded filesystem index. Matched characters are emphasised.
Terminal Drag-resized 120–600pt, persisted. Semantic status dot, icon buttons, selectable output.
Session Workspace root, open tabs and active tab restore on launch.

Working

File explorer. Recursive tree, lazily loaded — only expanded branches are enumerated. Create file, create folder, rename, delete, reveal in Finder, copy path. Dotfiles hidden. New items land beside the active document, or at the workspace root.

Editing. SourceEditor from CodeEditSourceEditor 0.15, bound two-way to Document.content. Three defects found in visual review of #14 were fixed and all three are confirmed fixed in the running app: every tab showed the first file opened (#15 — upstream does not diff text on update), a selected folder's chevron did nothing (#16), and the gutter drew through the translucent tab strip while rubber-band scrolling (#17). Tree-sitter highlighting across roughly 25 languages, line-number gutter, bracket-pair emphasis, indentation and reformat-at-column, word wrap, undo stack shared with the document model. Peripherals are configured explicitly: gutter on, minimap off by default with a toggle, folding ribbon and invisibles toggleable, smart quotes and invisible spaces flagged as warnings.

Documents. Tabs with dirty-state dots. Save, Save All, close with a save prompt. Untitled documents get a location prompt. Line endings detected on load, preserved on save.

Terminal. Runs $SHELL -i in a subprocess. Line-buffered scrollback capped at 2000 lines, command history on ↑/↓, stdout and stderr colored separately.

Themes. Dark, Light, High Contrast. Selection persists to UserDefaults, along with font size, tab width, word wrap, minimap, folding ribbon and invisibles.

Quit guard. Quitting with modified documents prompts first. Save saves everything and abandons the quit if any save did not take — a cancelled panel keeps the work; Don't Save discards; Cancel stays.

Commands. ⌘N, ⌘O, ⇧⌘O, ⌘F, ⌘P, ⌘S, ⌥⌘S, ⌘W, ⌘B, ⌃`, ⇧⌘L. All wired; none are empty closures.

Tests. 73 swift-testing cases. FileSystemManager and QuickOpenIndex are tested against in-memory providers, so no test touches the real disk.

Not working

LSP is implemented but not connected. JSONRPCTransport does real Content-Length framing, correlates responses by id, routes notifications, and fails parked requests rather than hanging when the server dies. LSPClient completes the initialize handshake against sourcekit-lsp. Nothing calls either one. Completion, diagnostics, hover and go-to-definition are #1–#3.

The terminal has no PTY. Pipes, so no job control and no TTY. vim renders wrong, top exits immediately, Ctrl-C does not reliably interrupt. #5.

No external-change detection. If a file changes on disk while open, the editor does not notice and will overwrite it. #6.

The quick-open index is a snapshot, not a stream. It is a filesystem walk refreshed when the workspace root changes. Files created afterwards are not findable until the next reindex. Not FSEvents.

Session restore stores plain paths. Correct only because the app is not sandboxed. Under App Sandbox a path can go stale between launches and this would need security-scoped bookmarks. See MEMORY.md.

Untitled documents are not restored. They have no stable identity across launches.

Session restore reopens paths, not edits. The snapshot stores the workspace root, tab paths and active path — never buffer content. Quit with unsaved changes and the tab comes back showing what is on disk. Restoring buffers would mean writing them somewhere, and there is no untitled-document story yet either. The honest version of this feature is "reopens what you had open", not "resumes your session".

Quitting itself is guarded: a modified document prompts first (#18). Losing the buffer to a force-quit or a crash is still possible — nothing persists unsaved content anywhere.

No multi-cursor. TextSelectionManager models multiple selections and Document stores them, but the text view drives a single caret.

Single window only. Window replaced WindowGroup; a second window would have shared one AppState, one document set and one sidebar. Multi-window needs per-window state first.

CI does not build. Format and syntax only. See MEMORY.md.

Test coverage

Suite Cases Covers
LineEnding 1 detection precedence
Language 1 extension → language
TextSelection 2 inverted ranges, NSRange
Document 12 line splitting, offset math, selection clamping, insert/delete, line endings, line ranges, selected lines
Document persistence 2 save/reload round-trip, noURL
DocumentManager 3 reuse, neighbour focus, dirty tracking
TextSelectionManager 4 primary selection, joined text
FileSystemManager 10 root validation, sort order, create, rename, expansion, cache keys, reveal, clearError
FileSystemItem 2 hidden files, icons
Metrics 5 spacing grid, radii ordering, chrome heights
ThemeManager 4 appearance per palette, persistence round-trip, unknown name
SessionStore 4 round-trip, empty, corrupt data, clear
FileOutline 3 expansion and depth, collapsed branch, depth cap
QuickOpenMatcher 7 subsequence, ranking, highlight ranges, folding
QuickOpenIndex 4 relative paths, skipped directories, ranking, empty query
TerminalSession 4 history, failed connect, clear
saveAllModified 5 nothing to do, saved, abandoned, mixed outcomes, only-modified visited

Untested: the view layer in CodeEditorUI and CodeEditorApp beyond the pure logic extracted into CodeEditorCore, and the LSP transport.

Next

The redesign (#14, PR #13) changed what is cheap and what is not, so the old ordering no longer holds. Below is what I would pick next and why — this is a recommendation, not a commitment.

The data-loss guard recommended here is done — #18.

1. #1 — LSP completion

The client already works; nothing calls it. SourceEditor takes a completionDelegate, so this is wiring rather than design — the least code for the most visible gain, and the project's stated purpose (IDEA.md). The suggestion UI already exists upstream too; the same way ⌘F did not need building.

2. #6 — external change detection

A file changed on disk while open is silently overwritten. Also data loss, but narrower: it needs someone else editing the same file. A prompt on save when the modification date moved is enough to start; FSEvents is not required for correctness here.

3. #2 then #3 — diagnostics, hover, go-to-definition

Follow #1 naturally: they share the transport and lifecycle that #1 proves. Doing them together means the TextViewCoordinator wiring is designed once.

4. #4 — settings panel

Smallest of the six. Minimap, folding ribbon, invisibles, wrap, tab width and font size are already toggles in the Format menu and already persisted; the panel is moving existing state into a discoverable surface, not new behaviour.

5. #5 — PTY terminal

Largest and least user-visible until done. vim and top are broken today, which makes it feel urgent, but it is C interop behind a protocol and nothing else depends on it.

Deliberately not next

  • More themes. Each one is a palette someone maintains and reviews, for a choice most users make once. Three is enough.
  • Multi-window. Needs per-window AppState first. The Window scene is the honest single-window shape, not a compromise.
  • Finder file associations. Needs CFBundleDocumentTypes in App-Info.plist and .handlesExternalEvents. Small, but it is packaging rather than capability, and it should ride along with something that needs double-clicking files to work — which is completion via ⌘P, not the other way round.

Housekeeping before the next branch

  • Scripts/build-app.sh needs running from a clean checkout before it is believed again. See MEMORY.md #16.

feature/terminal-pty and PR #7 exist as a placeholder branch — plan only, no code.

Size

Module Lines Files
CodeEditorCore 1478 8
CodeEditorLSP 1143 3
CodeEditorUI 1904 11
CodeEditorApp 559 2
CodeEditorThemes 437 5
CodeEditorTerminal 245 1
Tests 1074 6

LSP is the largest module after CodeEditorUI and the least used. That is deliberate for now — it is the interesting part — but if the next three issues do not land, it becomes dead weight that should be deleted rather than kept aspirational.