Skip to content

Add editor search, syntax highlighting, git/CI integration, undo/redo, and UX overhaul#1

Merged
bluestreak01 merged 19 commits intomasterfrom
vi_try2
Mar 22, 2026
Merged

Add editor search, syntax highlighting, git/CI integration, undo/redo, and UX overhaul#1
bluestreak01 merged 19 commits intomasterfrom
vi_try2

Conversation

@bluestreak01
Copy link
Copy Markdown
Member

@bluestreak01 bluestreak01 commented Mar 22, 2026

Summary

Major feature release that transforms Middle Manager from a basic file manager into a developer-focused tool with deep git, GitHub, and CI integration.

Editor

  • Undo/redo (Ctrl+Z / Ctrl+Shift+Z): operation-based with minimal deltas (~20 bytes per entry vs ~200KB for snapshot approach). Consecutive typing grouped into single undo entries, groups break on whitespace for word-level undo. 10K entry cap. Covers all operations: insert, delete, line joins, newlines, delete line.
  • Search (F7): forward/backward with wrap-around, case-sensitive toggle. Streaming byte-level search that seeks directly to cursor position — no full-file scan, handles multi-GB files efficiently
  • Syntax highlighting: tree-sitter integration for 10 languages (Rust, Java, Python, JS/TS, JSON, Go, C, Bash, TOML). Hybrid caching: files <10MB get a full cached parse; larger files use a 200-line context window
  • Mouse support: click to position cursor, scroll wheel
  • Word navigation: Ctrl+Left/Right (Linux), Option+Left/Right (Mac)
  • Unsaved changes dialog on all exit paths (Esc, F10, Ctrl+Q)
  • Dynamic line number width, configurable cursor shape

File Panels

  • Multi-file selection: Shift+Up/Down to extend, Insert to toggle. Copy/Move/Delete operate on selection
  • Git status: per-file markers (● modified, + added, - deleted, → renamed, ? untracked, ! conflict), branch name, ahead/behind counts, repo-wide summary in panel title
  • GitHub PR status: async CI check indicators (✓/✗/○) in panel title, F11 opens PR in browser
  • Filesystem watcher: kqueue (macOS) / inotify (Linux), zero syscalls when idle, auto-refreshes on external changes
  • Persistent state: panel paths, sort preferences, search queries saved to ~/.config/middle-manager/state.json

CI Panel (F2)

  • Tree view of CI checks with expand/collapse — no drill-down modes, everything inline
  • GitHub Actions + Azure DevOps support with automatic provider detection
  • Expand a check → async step loading with animated spinner
  • Enter on a step → downloads log to current directory and opens in the editor
  • GitHub: per-job log API (plain text, no zip extraction). Azure: direct per-step log URLs
  • Per-panel CI (each file panel can have its own CI view independently)
  • Tab cycles focus: file panel → CI → other file panel → CI
  • Download progress in title bar, prevents closing during active download
  • Auto-closes when navigating away from the repo/branch
  • Mouse click support, Left/Right tree navigation

Dialog System

  • All dialogs redesigned with consistent style: outer margin, inner padding, themed highlight colors, cursor navigation in input fields
  • Shared dialog helpers (dialog_helpers.rs) eliminate duplication
  • Make Folder dialog: "Process multiple names" checkbox, nested path support
  • Quit confirmation dialog (F10 from any panel)
  • Contextual footer changes based on active mode

Infrastructure

  • Event handler thread with atomic stop flag and graceful join on exit
  • Cursor shape restored to terminal default on exit (fixes shell corruption)
  • stdout flush on exit (fixes zsh arrow key corruption)
  • Shared git cache across panels (same repo queried once, 2-second TTL)
  • All git/gh subprocess stderr suppressed (prevents TUI corruption)
  • Path canonicalization for macOS symlink handling
  • Atomic counter for test temp files (fixes flaky tests)

Stats

  • 22 commits, 30+ files changed
  • ~8,500 lines added
  • 11 new modules: ci.rs, syntax.rs, state.rs, watcher.rs, panel/git.rs, panel/github.rs, ui/ci_view.rs, ui/dialog_helpers.rs, ui/mkdir_dialog.rs, ui/search_dialog.rs, ui/footer.rs (rewritten)
  • 99 tests (all passing), including large-file search tests, undo/redo edge cases, and selection tests

Test plan

  • Editor undo/redo: insert, delete, join, newline, delete line, grouping, whitespace breaks, full cycles, edge cases
  • Editor search: forward, backward, wrap-around, case-insensitive, multi-chunk, large files
  • Selection after search starts fresh (not from match anchor)
  • Syntax highlighting: tab color mapping, cache invalidation on edit
  • Git status display in panels for git and non-git directories
  • CI panel: expand/collapse, async loading, log download
  • Persistent state: save on quit, restore on startup
  • Filesystem watcher: auto-refresh on external changes
  • Unsaved changes dialog on all exit paths
  • Mouse click positioning in editor with dynamic line numbers
  • Word navigation with tabs and control characters
  • Manual: test on macOS and Linux terminals
  • Manual: test with large repos (100K+ files)
  • Manual: test CI panel with Azure DevOps and GitHub Actions PRs

🤖 Generated with Claude Code

bluestreak01 and others added 17 commits March 19, 2026 01:01
Expand tabs to spaces, strip control characters, and truncate lines
to visible width in the viewer. Add buffer pre-fill and line padding
to both viewer and hex viewer to prevent stale cell content from
showing through.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Multi-file selection:
- Shift+Up/Down to extend selection, Insert to toggle
- Selected files highlighted in yellow bold
- F5/F6/F8 operate on all selected files as a batch
- Selection preserved across sort, cleared on directory change

Dialogs redesigned to match Far Manager:
- New dedicated Make Folder dialog with "Process multiple names"
  checkbox (semicolon-separated) and nested path support
- All dialogs: outer margin, inner padding, dark teal focused
  input highlight, blinking cursor, Left/Right cursor navigation
  in input fields, Left/Right to swap buttons on button row
- Copy/Move dialog: cursor navigation in destination field
- Delete dialog: Yes/No buttons with keyboard navigation
- Rename dialog: cursor-aware editing of file name
- Theme colors for dialog input focus (dialog_input_fg_focused,
  dialog_input_bg)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Search (Ctrl+F in editor):
- Search dialog with query input, direction toggle (forward/
  backward), case-sensitive checkbox, Find/Cancel buttons
- Forward and backward search with wrap-around
- Match is highlighted via selection after finding
- F3 / Ctrl+N for find-next, Shift+F3 / Ctrl+Shift+N for
  find-previous (reuses last search parameters)
- Last search query persisted across dialog opens

Dialog refactor:
- Extract common rendering helpers into dialog_helpers.rs:
  render_dialog_frame, render_line, render_separator,
  render_checkbox, render_buttons, dialog_styles
- All dialogs (delete, rename, mkdir, copy/move, search) now
  use shared helpers for consistent styling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Performance:
- Stream raw file bytes in 4MB chunks instead of per-line file opens
- First-byte scan in find_bytes_sensitive/insensitive skips non-matching
  positions instead of checking every window
- Forward search seeks directly to cursor byte offset via sparse index
  instead of reading from file start
- Reverse search reads backwards in chunks from cursor, returns on
  first rfind hit — O(distance_to_match) not O(bytes_before_cursor)
- Removed scan_to_end() from search — only scans enough to cover cursor

Correctness:
- Two-pass wrap-around: pass 0 searches from cursor in direction,
  pass 1 wraps. Cursor segment visited in both passes with proper
  skip_to/limit_to bounds
- Reverse search uses selection anchor (match start) not cursor
  (match end) to avoid re-finding the same match
- Line numbers in reverse computed via sparse index lookup
  (line_at_byte_offset) then converted to segment-relative, fixing
  off-by-one that only manifested in multi-chunk reads
- Consistent vline_start + segment-relative return in both forward
  and reverse paths, correct for edited multi-segment files
- match_char_len uses char count not byte length (UTF-8 safe)

Key bindings: F7 opens search, Shift+F7 repeats in stored direction.
Removed FindPrevious action — direction controlled by dialog toggle.

Tests: 45 total, including 5 large-file tests (~6MB, 80K lines) that
force multi-chunk behavior and verify line accuracy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Search cursor placement:
- Cursor now placed at match START (like Far Manager), anchor at
  match END. Shift+arrow selects from the matched character.
- search_selection flag ensures next Shift+arrow starts a fresh
  selection from cursor, not from the stale search anchor.
- Forward skip uses anchor_col (match end), reverse limit uses
  cursor_col (match start) to avoid re-finding the same match.

Dynamic line number width:
- Line number column width computed from total line count instead
  of hardcoded 7. Fixes cursor/selection misalignment on files
  with 100K+ lines where line numbers exceed 5 digits.
- Affects cursor positioning, visible_cols, and scroll calculations.

Tests: 64 total including selection-after-search tests, CRLF tests,
and large-file multi-chunk search tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Ctrl+Up/Down: go to top/bottom of file (works on all platforms)
- Alt+PageUp/PageDown: same (Fn+Opt+Up/Down on Mac keyboards)
- Ctrl+Home/End: unchanged (Linux keyboards with Home/End keys)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mouse support:
- Left click positions cursor at clicked location
- Handles tab expansion, control chars, scroll offset
- Click on line number gutter ignored
- Mouse scroll moves cursor 3 lines per tick

Word navigation:
- Ctrl+Left/Right on Linux, Option+Left/Right on Mac
- Skips alphanumeric word boundaries and punctuation
- Wraps across lines at line start/end

Also: dynamic line_num_width stored on EditorState for accurate
click-to-cursor coordinate conversion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Unsaved changes dialog:
- Shown when pressing Esc in editor with modifications
- Three buttons: Save, Don't Save, Cancel
- Consistent style with other dialogs (keyboard navigation)

Mouse support in editor:
- Left click positions cursor at clicked location
- Handles tab expansion, control chars, scroll offset
- Mouse scroll moves 3 lines per tick

Word navigation:
- Ctrl+Left/Right on Linux, Option+Left/Right (Alt+b/f) on Mac
- Jumps by alphanumeric word boundaries

Mac key bindings:
- Alt+PageUp/PageDown (Fn+Opt+Up/Down) for top/bottom of file
- Ctrl+Up/Down also works for top/bottom

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Syntax highlighting:
- Tree-sitter integration for Rust, Java, Python, JavaScript/TS,
  JSON, Go, C, Bash, TOML
- Hybrid approach: files <10MB get full parse cached on open
  (always accurate); files >=10MB use 200-line context window
- Cached spans looked up via binary search (O(log n) per line)
- Tab expansion maps all expanded spaces to same original char
  color (fixes tab color bleeding bug)
- display_to_orig mapping built in single O(n) pass
- Syntax colors defined in theme system (15 syn_* fields)
- Cache invalidated on first edit (falls back to context window)

Event handler cleanup:
- Atomic stop flag + thread join before terminal restore
- Fixes random escape sequence garbage printed on exit
- Drop impl as safety net

Also: removed Clone from AppMode (never cloned), fixed duplicate
doc comment, fixed flaky test temp file names (atomic counter).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Editor cursor style (block, bar, underline, blinking variants)
now defined in the theme system. Default set to BlinkingBar.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Git status in panels:
- File status markers: ● modified, + added, - deleted, → renamed,
  ? untracked, ! conflict — colored per status
- Branch display with ⎇ glyph in panel border title
- Ahead/behind remote: ↑N ↓M with rev-list fallback when no
  tracking branch configured
- Repo-wide status summary counts in title

Architecture:
- Shared GitCache across panels (same repo queried once)
- 2-second TTL, invalidated on file operations
- Single git call (status --branch --porcelain -unormal)
- stderr suppressed to prevent TUI corruption
- Path canonicalization for macOS symlink handling
- Priority-based status aggregation for directories

UI improvements:
- Home dir shortened to ~ in panel title
- Path truncated from left with ... when too long
- Active panel title in white bold, inactive dimmed
- Header bar simplified to margin (no duplicate path)
- Panel border title is now the primary info display

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
State saved to ~/.config/middle-manager/state.json on quit:
- Last search query, direction, case sensitivity
- Panel paths (restored on next launch)
- Sort field and direction per panel

Fix: sort_ascending defaults to true (not false) so first launch
sorts ascending. Manual Default impl + serde default_true for
backwards compatibility with older state files.

Removed unused copy dialog fields from state struct.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR status display:
- Shows PR #number with CI check status in panel title
  (✓ pass, ✗ fail, ○ pending)
- F11 opens PR in browser
- Uses gh CLI for GitHub API access

Async architecture:
- gh CLI spawned in background thread, never blocks UI
- Results polled on 250ms tick, panel updates when ready
- PR info preserved across git status refreshes (no flicker)
- PR re-queried only on branch change, not every TTL refresh

Correctness:
- Uses gh pr view (not pr list --head) to find PR for current
  branch specifically, avoiding false matches from forks
- Skips default branches (master/main/develop) — no wasted
  network calls
- Graceful fallback: gh not installed or not authed → no PR shown

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Filesystem watcher:
- notify crate with FSEvents (macOS) / inotify (Linux)
- Zero syscalls when idle, instant detection of external changes
- Watches both panel directories, re-watches on navigation
- Replaced kqueue backend (leaked fds) with FSEvents default

Bug fixes:
- F10/Ctrl+Q in editor now shows unsaved changes dialog
  (previously bypassed it, risking data loss)
- Cursor shape restored to terminal default on exit and panic
  (BlinkingBar was leaking into the user's shell)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CI Panel (F2):
- Tree view: checks shown collapsed, Enter expands to show steps
  inline with indentation, Enter again collapses
- Enter on a step downloads log and opens in editor
- Failed checks sorted to top, status markers (✓ ✗ ○ –)
- Expand/collapse arrows (▶ ▼) on checks

Azure DevOps support:
- Timeline API for fetching job steps
- Direct per-step log download via curl
- Auto-detects Azure URLs from GitHub check rollup

GitHub Actions support:
- Zip log download via gh CLI, extracts largest job log
- Falls back gracefully when step-level logs unavailable

Async operations:
- Check fetching with braille spinner (⠋⠙⠹⠸...)
- Log download with progress display (file size polling)
- Editor opens automatically when download completes

Per-panel CI:
- Each panel can have its own CI panel independently
- Tab cycles: left → left_CI → right → right_CI → left
- CI panel stays pinned to the panel it was opened from
- Focus system: active panel has bright border, inactive dimmed
- CI highlight only shown when focused

UX:
- Logs saved to current directory with sanitized filename
- Footer changes contextually (normal vs CI mode)
- F2 label in footer, consistent key/label styling
- Empty StatusContext entries filtered out
- Error messages shown in panel content, not duplicated

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Quit dialog:
- F10 always shows Quit/Cancel confirmation dialog
- Tab/Left/Right switches between buttons, Enter confirms, Esc cancels
- Works from any panel/mode
- Esc in normal mode no longer exits

CI panel improvements:
- PR number shown in title (CI PR #6800)
- Auto-closes when navigating away from the repo/branch
- Left/Right tree navigation (expand/collapse/jump to parent)
- Mouse click to focus and select items in CI tree
- Mouse click on file panels works when CI is focused
- Prevents closing during active download
- Async step loading with spinner text ("⠙ loading...")
- GitHub Actions: per-job log API (no more full-run zip download)

Cleanup:
- Removed all dead code (ci_provider, find_step_log, download_run_logs, etc)
- Removed unused total_bytes field
- stdout flush on exit (fixes zsh arrow key corruption)
- Footer shows F2CI label

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comprehensive documentation of all features added in the vi_try2
branch: multi-file selection, git integration, CI panel, syntax
highlighting, editor search, persistent state, filesystem watcher,
Mac key bindings, and all dialog improvements.

Updated key bindings tables, architecture section, and roadmap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bluestreak01 bluestreak01 changed the title feat: editor search + git + GitHub integration Add editor search, syntax highlighting, git/CI integration, and UX overhaul Mar 22, 2026
Undo/redo (Ctrl+Z / Ctrl+Shift+Z):
- Operation-based, not snapshot-based: stores minimal deltas
  (Insert/Delete/SplitLine/JoinLine/DeleteLine/ClearLine)
- ~20 bytes per entry vs ~200KB for snapshot approach
- Inverse operations: Insert↔Delete, SplitLine↔JoinLine
- Consecutive typing grouped into single undo entry
- Groups break on whitespace for word-level undo
- Redo stack cleared on new edits
- 10,000 entry cap on undo stack
- Cursor position restored on undo/redo

All editing operations covered:
- Insert character, delete backward/forward
- Line joins (backspace at line start, delete at line end)
- Insert newline (Enter), delete line (Ctrl+K/Ctrl+Y)

20 tests including edge cases:
- Empty stack undo/redo (no panic)
- Single-line file delete/undo
- Full undo-all → redo-all cycles
- Cursor position preservation
- Whitespace group breaking
- No-op operations don't push to stack

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bluestreak01 bluestreak01 changed the title Add editor search, syntax highlighting, git/CI integration, and UX overhaul Add editor search, syntax highlighting, git/CI integration, undo/redo, and UX overhaul Mar 22, 2026
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bluestreak01 bluestreak01 merged commit 6dda06e into master Mar 22, 2026
@bluestreak01 bluestreak01 deleted the vi_try2 branch March 23, 2026 07:35
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