specs/009-open-file-at-comment/plan.md
Ref: Size: 5.1 KiB
# Implementation Plan: Open File at Inline Comment Location
**Branch**: `009-open-file-at-comment` | **Date**: 2026-03-21 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from `/specs/009-open-file-at-comment/spec.md`
## Summary
Add an 'e' keybinding in the TUI patch diff view that opens the file referenced by the nearest inline comment at the exact line number in the user's `$EDITOR`. The implementation lives almost entirely in `src/tui.rs`, reading inline comment data from existing `InlineComment` structs in `src/state.rs` and using `std::process::Command` to launch the editor.
## Technical Context
**Language/Version**: Rust 2021 edition
**Primary Dependencies**: ratatui 0.30, crossterm 0.29, git2 0.19
**Storage**: N/A (no new persistence; reads existing InlineComment data from git refs)
**Testing**: cargo test, cargo clippy
**Target Platform**: Linux/macOS terminal
**Project Type**: CLI / TUI application
**Performance Goals**: Editor launch in under 1 second
**Constraints**: Must suspend/resume TUI cleanly; must handle missing $EDITOR gracefully
**Scale/Scope**: Single keybinding addition; ~50-100 lines of new code
## Constitution Check
No violations. This is a small, self-contained feature addition that does not introduce new dependencies, new data models, or architectural changes.
## Project Structure
### Documentation (this feature)
```text
specs/009-open-file-at-comment/
├── spec.md
├── plan.md
└── checklists/
└── requirements.md
```
### Source Code (repository root)
```text
src/
├── tui.rs # Primary changes: keybinding handler, editor launch, comment-at-scroll lookup
├── state.rs # No changes needed (InlineComment struct already has file + line)
├── event.rs # No changes needed (PatchInlineComment action already defined)
└── ...
tests/
└── ... # New unit tests for comment-at-scroll logic and editor command building
```
**Structure Decision**: All changes are in the existing single-project structure. No new files or modules needed.
## Implementation Details
### Phase 1: Track inline comment positions in rendered diff
The `colorize_diff` function in `src/tui.rs` already renders inline comments into the diff output. During rendering, build a side-channel data structure that maps rendered line numbers (in the `Text` output) to `(file, line)` pairs from the `InlineComment`. This could be a `Vec<(usize, String, u32)>` storing `(rendered_line_index, file_path, source_line)`.
Return this mapping alongside the `Text` from `colorize_diff` (or store it on the `App` struct).
### Phase 2: Find nearest comment from scroll position
Given the current `app.scroll` value and the comment-position mapping from Phase 1, find the inline comment whose rendered line is closest to and at or above the scroll position. This is a simple reverse linear scan of the mapping.
Helper function signature:
```rust
fn find_comment_at_scroll(
comment_positions: &[(usize, String, u32)],
scroll: u16,
) -> Option<(String, u32)> // (file_path, line_number)
```
### Phase 3: Editor resolution and launch
Resolve the editor command:
1. Check `$VISUAL`, then `$EDITOR`, then fall back to showing a status message.
2. Split the editor string on whitespace to handle editors like `code --wait`.
3. Build and execute `std::process::Command` with `+<line>` and `<file>` arguments.
Before spawning:
- Check that the file exists on disk; if not, set a status message and return.
- Suspend the TUI: call `crossterm::terminal::disable_raw_mode()`, `crossterm::execute!(stdout, LeaveAlternateScreen)`.
After the editor exits:
- Restore the TUI: call `crossterm::terminal::enable_raw_mode()`, `crossterm::execute!(stdout, EnterAlternateScreen)`.
- Force a full redraw.
### Phase 4: Keybinding integration
In the main event loop in `src/tui.rs` (around line 258), add a handler for `KeyCode::Char('e')`:
- Guard: only active when `app.tab == Tab::Patches` and `app.mode == ViewMode::Diff`.
- Call `find_comment_at_scroll` to get file and line.
- If found, launch editor. If not found, no-op (or show "No comment at cursor").
### Phase 5: Status message display
Add an optional `status_message: Option<String>` field to `App` (if not already present). Render it in the footer area of the TUI. Clear it on the next keypress or after a timeout.
## Key Design Decisions
1. **Comment-at-scroll vs. cursor-based selection**: Using scroll position rather than a dedicated cursor keeps the implementation simple and avoids introducing a new navigation mode. The tradeoff is slightly less precision, but inline comments are visually distinct (magenta) so the user can easily scroll to one.
2. **$VISUAL before $EDITOR**: Following Unix convention where `$VISUAL` is for full-screen editors and `$EDITOR` is for line-based editors. Since we are opening at a specific line in a file, either works, but `$VISUAL` takes precedence per convention.
3. **No new dependencies**: Uses only `std::process::Command` and existing crossterm APIs for terminal suspend/resume. No new crates needed.
## Complexity Tracking
No constitution violations. This is a small, focused feature.