Skip to content

Expose line number information to custom command templates #4677

Open

Description

@cappyzawa

Is your feature request related to a problem? Please describe.

When creating custom commands that need to operate on specific lines in diffs, it would be valuable to have access to the current line number information. While lazygit already tracks line numbers internally (via pkg/gui/patch_exploring/state.go), this information is not currently exposed to custom command templates.

This would enable more precise integrations with external tools like linters, analysis tools, or other utilities that benefit from knowing the exact line context.

Describe the solution you'd like

I'd like to have access to line number information in custom command templates, such as:

customCommands:
  - key: 'A'
    command: 'my-tool --file={{.SelectedFile.Name}} --line={{.CurrentLineNumber}}'
    context: 'files'
    description: 'Run tool on current line'

Specifically, I'd like template variables like:

  • {{.CurrentLineNumber}} - The actual line number in the original file
  • {{.SelectedLineInDiff}} - The position in the diff view (optional)

Describe alternatives you've considered

  1. Parsing git diff output externally - This duplicates lazygit's existing line tracking logic and is less reliable
  2. Using file-level information only - This works but loses precision for line-specific operations
  3. External tools to analyze cursor position - More complex to implement and maintain

I noticed that lazygit already has excellent infrastructure for this in the patch exploring system (CurrentLineNumber() method), so exposing this to custom commands would leverage existing functionality.

Additional context

  • Current lazygit version: 0.52.0
  • Use case example: I'm building an MCP (Model Context Protocol) bridge that allows AI assistants to interact with lazygit. When implementing this, I realized that having access to line numbers from custom commands would make the integration much more precise and efficient.
  • The line tracking functionality already exists in pkg/gui/patch_exploring/state.go
  • This would enhance lazygit's integration capabilities while maintaining its core simplicity

The line numbers would only be populated when in applicable contexts (diff views, staging) and would be nil otherwise, maintaining backwards compatibility.

I can work on a PR if this feature request is accepted.

Activity

  1. KennethHoff commented on Aug 6, 2026

    @KennethHoff

    This comment extends #4677's original ask beyond a single-line cursor position to also expose range-selection information to custom command templates. Everything below is traced from source only, pinned to commit bd76666 on master (current release v0.64.0) — I haven't run these code paths live, only read them.

    The original issue requests {{.CurrentLineNumber}} for diff-browsing contexts like staging, which is valuable for automating line-level operations. Alongside that, the staging and patch-building panels also support visual-mode range selection (v, extendable with shift+arrow keys, or a plain mouse drag), and exposing that selection's bounds would unlock range-based commands — exactly as {{.SelectedCommitRange}} already does for ranged commit selection (PR #4204).

    Tracing the source, the necessary machinery already exists in the codebase, simply not wired into the custom command template system. The SessionState struct (pkg/gui/services/custom_commands/session_state_loader.go lines 195–213) holds the fields that become {{.SelectedX}} template variables and includes SelectedCommitRange *CommitRange (line 200), but nothing equivalent for diffs. SessionStateLoader.call() (starting line 215, body running to its return at lines 240–258) populates SessionState entirely by reading list-based contexts — Files, Branches, Commits, etc. — and never touches the diff-browsing contexts: Staging, StagingSecondary, or CustomPatchBuilder.

    Those three contexts are all constructed via NewPatchExplorerContext(...) (pkg/gui/context/setup.go lines 44–74), which surfaces selection state through PatchExplorerContext.GetState() *patch_exploring.State (pkg/gui/context/patch_explorer_context.go). The State type (pkg/gui/patch_exploring/state.go) already exposes both halves of this ask: CurrentLineNumber() (lines 389–391) returns the single-cursor file line number, and SelectedViewRange() / SelectedPatchRange() (lines 353, 370) return the bounds of the current selection as (start, end) indices — these work across all select modes (line, hunk, and range), not just an active range. File line numbers are mapped from patch indices via Patch.LineNumberOfLine(idx int) (pkg/commands/patch/patch.go line 92).

    One way to close this gap: add {{.CurrentLineNumber}} (int) to satisfy the original ask, and {{.SelectedLineRange}} with .From/.To fields (both file line numbers, as ints) mirroring SelectedCommitRange's existing .To/.From shape from PR #4204. When no range is active, .From and .To would both equal CurrentLineNumber, matching how a single selected commit already degenerates for SelectedCommitRange. Populate these only in the Staging, StagingSecondary, and CustomPatchBuilder contexts, and leave them unset elsewhere — as the original issue already proposed for its narrower scope. The file path for these lines is already available via the existing {{.SelectedFile.Name}}, {{.SelectedPath}}, or {{.SelectedCommitFilePath}} fields, so no new field is needed there.

    This project doesn't accept pull requests (per CONTRIBUTING.md), so this stays a discussion topic for your design rather than a code contribution.


    Drafted with AI Assistance and reviewed by the reporter before posting.

  2. nat-e commented on Sep 1, 2026

    @nat-e

    I'm using Lazygit to review LLM changes before opening PRs and would find this super useful so that I could add comments directly through lg via a custom command

    customCommands:
      - key: 'n'
        context: 'files, staging'
        description: 'Add review note'
        prompts:
          - type: 'input'
            title: 'Review comment'
            key: 'Comment'
        command: >
          jq -nc --arg f {{.SelectedFile.Name | quote}}
          --arg c {{.Form.Comment | quote}}
          '{file:$f, comment:$c, ts:(now|todate)}'
          >> .git/review-notes.jsonl
        loadingText: 'Saving note'
        output: none
  3. jozefRudy commented on Oct 31, 2026

    @jozefRudy

    Extending this to the commit-files diff view, which the staging/patch contexts above don't cover.

    There, editAtLine resolves the diff line against the worktree copy of the file (EditFileOfDiffAtLine → existingPathInWorktreeOfDiff), so external tools cannot open the file as it exists in the selected commit. A custom command could, if it knew the selected line.

    Minimal shape, using what already exists:

    • add SelectedLine (1-based view line), SelectedLineContent, and SelectedFileLine (only where a mapping exists) to SessionState in pkg/gui/services/custom_commands/session_state_loader.go, populated in call() from the focused context's view (ViewTrait.SelectedLineIdx() / SelectedLine())
    • reuse DiffHelper.AdjustLineNumber(path, lineIndex+1, viewName) for the file line — the same call editAtLine already makes
    • document that in a diff the view line includes hunk headers and +/- markers, which is why both values are useful

    SelectedLineContent is what makes arbitrary revisions work without diff math: a custom command can locate that text in git show <rev>:<path>, so hunk offsets don't matter and lazygit needs no knowledge of old files, revisions, or diff direction. It also lets line-scoped commands work outside diffs, where a file line is not tracked at all.

    Concrete use case: opening a file at the selected commit and its parent at the same place, side by side in an external editor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions