How tuicr Implements Gap Expansion for Viewing Large Diffs

tuicr collapses unchanged lines between diff hunks into virtual gaps and automatically expands minimal context when users jump to hidden lines, using a three-layer architecture of gap bookkeeping, expansion planning, and source-agnostic context fetching.

tuicr is a terminal-based code review tool designed to handle massive diffs efficiently. When viewing changes, the tuicr gap expansion system hides unchanged context between hunks while maintaining the ability to instantly reveal specific lines when you navigate to them, regardless of whether the source is a local Git repository or a remote pull request.

Gap Bookkeeping and Gap Identification

Every diff file in tuicr is represented as a DiffFile containing a list of hunks. The unchanged lines between these hunks—or before the first hunk and after the last—are tracked as gaps. Each gap receives a unique GapId and is categorized as either a standard inter-hunk gap or an EOF gap.

The gap_size function in src/app/gaps.rs (line 4) returns the number of hidden lines for any given gap:

pub(crate) fn gap_size(&self, gap_id: &GapId) -> Option<u32> { … }

This function distinguishes EOF gaps from normal gaps and delegates the actual calculation to calculate_gap in the Git backend at src/vcs/git/context.rs (lines 84‑99). The bookkeeping layer tracks metadata including the gap's position relative to hunks and whether it represents the end of the file.

Locating the Target Gap

When a user jumps to a specific line number, tuicr must first determine which gap contains that line. The find_gap_containing_lineno function (lines 35‑86 in src/app/gaps.rs) iterates through all hunks to compute the line range of each gap on the requested side (LineSide::New or LineSide::Old).

pub(in crate::app) fn find_gap_containing_lineno(
    &self, file_idx: usize, target_lineno: u32, side: LineSide,
) -> Option<GapId> { … }

The function calculates gap boundaries by comparing consecutive hunks and checking if the target line number falls within the unchanged region. It also handles the special case of the EOF gap separately (lines 64‑85), ensuring that navigation to any line in the file resolves to the correct expandable region.

Planning the Expansion Strategy

Once tuicr identifies the containing gap, expand_plan_to_reach (lines 98‑159 in src/app/gaps.rs) determines exactly how to expand the gap to reveal the target line. This planning phase considers several factors:

  • Gap type detection: It first checks if the gap is an EOF gap (lines 107‑124), which requires different boundary handling.
  • Side translation: For old-side targets, it calculates the equivalent new-side line number using the constant offset new - old that persists across unchanged context (lines 138‑142).
  • Direction selection: Based on the cursor position relative to the gap (cursor_below_gap), it chooses ExpandDirection::Up when the cursor is below the gap, otherwise Down (lines 44‑58).
  • Line limit calculation: It computes the exact number of lines needed to reach the target, preventing over-fetching.
pub(in crate::app) fn expand_plan_to_reach(
    &self, gap_id: &GapId, target_lineno: u32, side: LineSide,
) -> (ExpandDirection, Option<usize>) { … }

Fetching Context from Any Source

The actual expansion is executed by expand_gap (lines 26‑119 in src/app/gaps.rs), which abstracts the content source through the ContextProvider trait defined in src/forge/context.rs (lines 20‑44). This trait enables tuicr to fetch lines from both local VCS and remote forges using a unified interface:

pub trait ContextProvider {
    fn fetch_context_lines(
        &self,
        old_path: Option<&PathBuf>,
        new_path: Option<&PathBuf>,
        file_status: FileStatus,
        start_line: u32,
        end_line: u32,
    ) -> Result<Vec<DiffLine>>;

    fn file_line_count(
        &self,
        old_path: Option<&PathBuf>,
        new_path: Option<&PathBuf>,
        file_status: FileStatus,
    ) -> Result<u32>;
}

Two implementations handle different data sources:

  • VcsContextProvider (lines 46‑91): Forwards requests to the local VcsBackend, optionally reading from a specific commit via ref_commit.
  • ForgeContextProvider (lines 93‑117): Constructs ForgeFileLinesRequest objects to fetch content from remote PR base or head commits.

The provider selection logic in context_provider (lines 322‑360) automatically chooses the appropriate backend based on whether you're reviewing a local change or a remote PR.

For Git repositories, the concrete implementation in src/vcs/git/context.rs (lines 10‑19) handles working tree files, specific commits, or HEAD blobs for deleted files, then slices the requested range using slice_context_lines.

Executing the Gap Expansion

The expand_gap function performs the actual UI update after fetching context lines. It first populates the file line-count cache (line 34), then retrieves absolute boundaries via gap_boundaries (lines 75‑108). It calculates the delta between new-side and old-side line numbers (lines 57‑69) to ensure proper alignment when displaying lines.

After fetching via the ContextProvider, the function inserts lines according to the expansion direction:

  • Down: Prepends fetched lines to expanded_top (lines 92‑97).
  • Up: Prepends to expanded_bottom (lines 99‑107).
  • Both: Fetches all remaining context (lines 109‑115).

Finally, rebuild_annotations (line 118) regenerates the rendering data, causing the UI to instantly display the newly revealed context without reloading the entire diff.

Collapsing and Resetting Gaps

Users can collapse previously expanded gaps to restore the compact view. The collapse_gap function (lines 62‑66) removes stored expansions for a specific GapId:

pub fn collapse_gap(&mut self, gap_id: GapId) { … }

When the diff is reloaded—such as when refreshing from the remote forge—clear_expanded_gaps (lines 69‑73) wipes all gap expansion data, ensuring the view returns to the initial collapsed state.

Practical Usage Examples

Programmatically expanding a gap to reveal a specific line:

// Navigate to line 120 in the first file on the new side
let file_idx = 0;
let target_line = 120;
let side = LineSide::New;

if let Some(gap_id) = app.find_gap_containing_lineno(file_idx, target_line, side) {
    let (direction, limit) = app.expand_plan_to_reach(&gap_id, target_line, side);
    app.expand_gap(gap_id, direction, limit).expect("expansion failed");
}

Fetching context directly via the provider interface:

let provider = app.context_provider();
let lines = provider
    .fetch_context_lines(
        Some(&old_path),
        Some(&new_path),
        FileStatus::Modified,
        200,  // start line
        210,  // end line
    )
    .expect("fetch failed");

Collapsing a gap programmatically:

app.collapse_gap(gap_id);

Summary

  • tuicr represents unchanged lines between hunks as gaps tracked by GapId in src/app/gaps.rs, distinguishing between standard gaps and EOF gaps.
  • Gap location is determined by find_gap_containing_lineno, which maps line numbers to specific gaps based on LineSide (New or Old).
  • Expansion planning via expand_plan_to_reach calculates the optimal direction (Up/Down) and exact line count needed to reveal a target line without over-fetching.
  • The ContextProvider trait abstracts content fetching across VcsContextProvider (local Git) and ForgeContextProvider (remote APIs), enabling seamless gap expansion regardless of data source.
  • Execution occurs in expand_gap, which fetches boundaries, calculates line number deltas, inserts lines into expanded_top or expanded_bottom, and calls rebuild_annotations to update the UI.
  • State management functions collapse_gap and clear_expanded_gaps allow users to restore compact views or reset state during diff reloads.

Frequently Asked Questions

How does tuicr determine which direction to expand a gap?

tuicr inspects the cursor position relative to the target gap using the cursor_below_gap logic in expand_plan_to_reach (lines 44‑58). If the cursor is already positioned below the gap, it expands Up toward the cursor; otherwise, it expands Down. This minimizes the amount of content fetched while ensuring the target line becomes visible in the most intuitive direction.

Can tuicr expand gaps when reviewing remote PRs without cloning the repository?

Yes. When reviewing remote PRs, tuicr uses ForgeContextProvider (defined in src/forge/context.rs lines 93‑117) to fetch context lines via the forge's API. This provider constructs ForgeFileLinesRequest objects that specify the PR's base and head SHAs, allowing gap expansion to work entirely through remote API calls without local repository access.

What happens to expanded gaps when the diff is reloaded?

When the diff reloads—such as when refreshing to get the latest PR updates—the clear_expanded_gaps function (lines 69‑73 in src/app/gaps.rs) wipes all expansion state. This returns the view to the initial collapsed state, ensuring that stale context is not displayed after the underlying diff changes.

How does tuicr handle line number mapping between old and new file versions?

During expansion, tuicr calculates a delta between new-side and old-side line numbers using the constant offset that exists across unchanged context gaps (lines 57‑69 in expand_gap). When fetching lines via ContextProvider, it applies this delta to adjust old_lineno on each returned DiffLine (lines 81‑84), ensuring that line numbers remain synchronized regardless of which side (old or new) initiated the expansion.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →