a73x

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.