Navigate the focused main view by file and hunk

Add file and change-block ("hunk") navigation to the focused main view,
mirroring the staging view's hunk keys: `<left>`/`<right>` jump to the
previous/next hunk and `n`/`N` to the next/previous file. A "hunk" here is
lazygit's notion — a run of consecutive added/deleted lines separated by
context, not a git `@@` section — matching what the staging view jumps
between.

This is a consumer of the diff-line primitive in its forward direction:
resolve each rendered row's patch identity, then scan for the next/previous
change block (by the line type) or file boundary (by the path changing). The
file scan lands on the top of the neighbouring file even when a restructuring
pager leaves the header rows untagged, by backing up over them from the
file's first identifiable row — which is impossible without the per-line
metadata once the pager stops emitting a parseable unified diff. The
boundary arithmetic is pulled out into pure functions and unit-tested.

The anchor is the selected line if a selection is showing, else the top
visible line. With a selection we move it to the target and scroll it into
view, like the staging view; with none we stay in scroll mode, bringing the
target to the top without creating a selection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-06-10 15:13:06 +02:00
parent 3b6568cf9f
commit a967fc3099
14 changed files with 412 additions and 7 deletions

View file

@ -225,6 +225,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | Switch view | Switch to other view (staged/unstaged changes). |
| `` <esc> `` | Exit back to side panel | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | Go to previous hunk | |
| `` <right>, l `` | Go to next hunk | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | Search the current view by text | |
## Main panel (patch building)
@ -331,6 +335,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | Switch view | Switch to other view (staged/unstaged changes). |
| `` <esc> `` | Exit back to side panel | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | Go to previous hunk | |
| `` <right>, l `` | Go to next hunk | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | Search the current view by text | |
## Stash

View file

@ -194,6 +194,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 |
| `` <esc> `` | サイドパネルに戻る | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | 前のハンクに移動 | |
| `` <right>, l `` | 次のハンクに移動 | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | 現在のビューをテキストで検索 | |
## タグ
@ -308,6 +312,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 |
| `` <esc> `` | サイドパネルに戻る | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | 前のハンクに移動 | |
| `` <right>, l `` | 次のハンクに移動 | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | 現在のビューをテキストで検索 | |
## メニュー

View file

@ -86,6 +86,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | 패널 전환 | Switch to other view (staged/unstaged changes). |
| `` <esc> `` | Exit back to side panel | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | 이전 hunk를 선택 | |
| `` <right>, l `` | 다음 hunk를 선택 | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | 검색 시작 | |
## Stash
@ -165,6 +169,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | 패널 전환 | Switch to other view (staged/unstaged changes). |
| `` <esc> `` | Exit back to side panel | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | 이전 hunk를 선택 | |
| `` <right>, l `` | 다음 hunk를 선택 | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | 검색 시작 | |
## 메인 패널 (Patch Building)

View file

@ -233,6 +233,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). |
| `` <esc> `` | Exit back to side panel | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | Selecteer de vorige hunk | |
| `` <right>, l `` | Selecteer de volgende hunk | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | Start met zoeken | |
## Patch bouwen
@ -309,6 +313,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). |
| `` <esc> `` | Exit back to side panel | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | Selecteer de vorige hunk | |
| `` <right>, l `` | Selecteer de volgende hunk | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | Start met zoeken | |
## Staging

View file

@ -101,6 +101,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | Przełącz widok | Przełącz na inny widok (zatwierdzone/niezatwierdzone zmiany). |
| `` <esc> `` | Exit back to side panel | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | Idź do poprzedniego fragmentu | |
| `` <right>, l `` | Idź do następnego fragmentu | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | Szukaj w bieżącym widoku po tekście | |
## Drzewa pracy
@ -204,6 +208,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | Przełącz widok | Przełącz na inny widok (zatwierdzone/niezatwierdzone zmiany). |
| `` <esc> `` | Exit back to side panel | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | Idź do poprzedniego fragmentu | |
| `` <right>, l `` | Idź do następnego fragmentu | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | Szukaj w bieżącym widoku po tekście | |
## Panel główny (scalanie)

View file

@ -237,6 +237,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | Mudar de visão | Alternar para outra visão (staged/não processadas alterações). |
| `` <esc> `` | Exit back to side panel | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | Ir para o local anterior | |
| `` <right>, l `` | Ir para o próximo trecho | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | Pesquisar na visualização atual por texto | |
## Painel Principal (preparação)
@ -340,6 +344,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | Mudar de visão | Alternar para outra visão (staged/não processadas alterações). |
| `` <esc> `` | Exit back to side panel | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | Ir para o local anterior | |
| `` <right>, l `` | Ir para o próximo trecho | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | Pesquisar na visualização atual por texto | |
## Stash

View file

@ -76,6 +76,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | Переключиться на другую панель (проиндексированные/непроиндексированные изменения) | Switch to other view (staged/unstaged changes). |
| `` <esc> `` | Exit back to side panel | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | Выбрать предыдущую часть | |
| `` <right>, l `` | Выбрать следующую часть | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | Найти | |
## Главная панель (Индексирование)
@ -109,6 +113,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | Переключиться на другую панель (проиндексированные/непроиндексированные изменения) | Switch to other view (staged/unstaged changes). |
| `` <esc> `` | Exit back to side panel | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | Выбрать предыдущую часть | |
| `` <right>, l `` | Выбрать следующую часть | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | Найти | |
## Главная панель (Слияние)

View file

@ -288,6 +288,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | 切换到其他面板 | 切换到其他视图(已暂存/未暂存的变更) |
| `` <esc> `` | 退出回到侧边面板 | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | 选择上一个区块 | |
| `` <right>, l `` | 选择下一个区块 | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | 开始搜索 | |
## 正在合并
@ -337,6 +341,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | 切换到其他面板 | 切换到其他视图(已暂存/未暂存的变更) |
| `` <esc> `` | 退出回到侧边面板 | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | 选择上一个区块 | |
| `` <right>, l `` | 选择下一个区块 | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | 开始搜索 | |
## 状态

View file

@ -84,6 +84,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). |
| `` <esc> `` | Exit back to side panel | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | 選擇上一段 | |
| `` <right>, l `` | 選擇下一段 | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | 搜尋 | |
## 主面板(合併)
@ -366,6 +370,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). |
| `` <esc> `` | Exit back to side panel | |
| `` <space> `` | Show/hide selection | |
| `` <left>, h `` | 選擇上一段 | |
| `` <right>, l `` | 選擇下一段 | |
| `` N `` | Go to previous file | |
| `` n `` | Go to next file | |
| `` / `` | 搜尋 | |
## 狀態

View file

@ -0,0 +1,165 @@
package helpers
import (
"github.com/jesseduffield/lazygit/pkg/gocui"
)
// AdjacentChangeBlock returns the view line to move to for next/previous change-block
// navigation in view's displayed diff, starting from anchorViewLine. A "change block"
// is lazygit's notion of a hunk — a run of consecutive added/deleted lines separated
// by context, of which there may be several within one git @@ hunk — matching what
// the staging view's hunk navigation jumps between. forward=true targets the start of
// the next block; forward=false the start of the previous one (from mid-block this
// skips to the previous block, mirroring State.SelectPreviousHunk). ok is false when
// there's no further block, so the caller leaves the view where it is.
func (self *StagingHelper) AdjacentChangeBlock(view *gocui.View, anchorViewLine int, forward bool) (int, bool) {
contents := view.DiffLineContents()
anchor, ok := view.BufferLineForViewLine(anchorViewLine)
if !ok {
return 0, false
}
isChange := make([]bool, len(contents))
for i := range contents {
if info, ok := self.diffLineInfoFromContents(contents, i); ok {
isChange[i] = info.IsChange()
}
}
target, ok := changeBlockStart(isChange, anchor, forward)
if !ok {
return 0, false
}
return view.ViewLineForBufferLine(target)
}
// AdjacentFile returns the view line to move to for next/previous file navigation in
// view's (possibly multi-file) displayed diff, starting from anchorViewLine: the
// first row belonging to the next/previous file, found where the per-row metadata's
// file changes. ok is false at the first/last file.
func (self *StagingHelper) AdjacentFile(view *gocui.View, anchorViewLine int, forward bool) (int, bool) {
contents := view.DiffLineContents()
anchor, ok := view.BufferLineForViewLine(anchorViewLine)
if !ok {
return 0, false
}
paths := make([]string, len(contents))
for i := range contents {
if info, ok := self.diffLineInfoFromContents(contents, i); ok {
paths[i] = info.Path
}
}
target, ok := fileStart(paths, anchor, forward)
if !ok {
return 0, false
}
return view.ViewLineForBufferLine(target)
}
// changeBlockStart finds, in a diff whose lines are flagged by isChange, the first
// line of the change block adjacent to `from` in the given direction. It is the pure
// index arithmetic behind AdjacentChangeBlock, mirroring the staging view's
// State.SelectNextHunk / SelectPreviousHunk line by line.
func changeBlockStart(isChange []bool, from int, forward bool) (int, bool) {
if forward {
i := from
for i < len(isChange) && isChange[i] { // leave the current block
i++
}
for i < len(isChange) && !isChange[i] { // skip the separating context
i++
}
if i < len(isChange) {
return i, true
}
return 0, false
}
i := from
for i >= 0 && isChange[i] { // leave the current block
i--
}
for i >= 0 && !isChange[i] { // skip context, landing on the previous block's last line
i--
}
if i < 0 {
return 0, false
}
for i > 0 && isChange[i-1] { // walk back to that block's first line
i--
}
return i, true
}
// fileStart finds, in a diff whose lines carry the file path they belong to (empty
// for a row no backend could place, e.g. a restructuring pager's file headers), the
// top row of the file adjacent to `from` in the given direction. It is the pure
// index arithmetic behind AdjacentFile. A file is identified by its path, so we find
// where the path changes and then back up over the neighbouring file's unplaced
// header rows, landing on its first row — the `diff --git`/`@@` header when the
// buffer is parseable, or whatever the pager renders above the file's first tagged
// line otherwise.
func fileStart(paths []string, from int, forward bool) (int, bool) {
anchorPath, ok := anchorFilePath(paths, from)
if !ok {
return 0, false
}
if forward {
for i := from; i < len(paths); i++ {
if paths[i] != "" && paths[i] != anchorPath {
return backUpOverHeader(paths, i), true
}
}
return 0, false
}
// Walk back past the current file (its rows and any unplaced rows) to the
// previous file's last located row, then back over that whole file to its top.
i := from
for i >= 0 && (paths[i] == "" || paths[i] == anchorPath) {
i--
}
if i < 0 {
return 0, false
}
prevPath := paths[i]
for i > 0 && (paths[i-1] == "" || paths[i-1] == prevPath) {
i--
}
return i, true
}
// backUpOverHeader moves from a file's first located row up over the unplaced header
// rows directly above it, to the file's top. It stops at the previous file's last
// located row, so it never crosses into it.
func backUpOverHeader(paths []string, firstLocated int) int {
i := firstLocated
for i > 0 && paths[i-1] == "" {
i--
}
return i
}
// anchorFilePath returns the path of the file the anchor sits in: the first row at or
// below it that carries a path — the file whose content is at or below the top of the
// view — falling back to the nearest above when there's nothing below. Scanning down
// first matters because the anchor is often an untagged file-header row whose nearest
// tagged row is the *previous* file's content just above it; taking that would make
// next-file navigation jump back into the file just left (so a second `n` wouldn't
// advance). ok is false when no row carries a path.
func anchorFilePath(paths []string, from int) (string, bool) {
for i := from; i < len(paths); i++ {
if paths[i] != "" {
return paths[i], true
}
}
for i := from - 1; i >= 0; i-- {
if paths[i] != "" {
return paths[i], true
}
}
return "", false
}

View file

@ -0,0 +1,99 @@
package helpers
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestChangeBlockStart(t *testing.T) {
// A diff with three change blocks separated by context:
// 0 file header 1 hunk header 2 context
// 3 + 4 + (block A)
// 5 context
// 6 - (block B)
// 7 context
// 8 + (block C)
isChange := []bool{false, false, false, true, true, false, true, false, true}
scenarios := []struct {
name string
from int
forward bool
expected int
found bool
}{
{"forward from a header lands on the first block", 0, true, 3, true},
{"forward from separating context lands on the next block", 5, true, 6, true},
{"forward from the start of a block skips to the next", 3, true, 6, true},
{"forward from inside a block skips the rest of it", 4, true, 6, true},
{"forward from the last block finds nothing", 8, true, 0, false},
{"backward from a later block lands on the previous one's start", 8, false, 6, true},
{"backward from a block start lands on the previous block's start", 6, false, 3, true},
{"backward from inside the first block finds nothing", 4, false, 0, false},
{"backward from the first block's start finds nothing", 3, false, 0, false},
{"backward from context lands on the preceding block's start", 7, false, 6, true},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
got, found := changeBlockStart(isChange, s.from, s.forward)
assert.Equal(t, s.found, found)
if s.found {
assert.Equal(t, s.expected, got)
}
})
}
}
func TestFileStart(t *testing.T) {
// A parseable two-file diff: every row carries its file's path (the headers
// included), as the buffer parser reports.
parseable := []string{"a", "a", "a", "a", "b", "b", "b", "b"}
// The same diff as a restructuring pager emits it: only content lines carry the
// path; the file/hunk header rows above each file are untagged (empty).
tagged := []string{"", "", "a", "a", "", "", "b", "b"}
// Three such files, to exercise navigating from one file's untagged header to the
// next: the row just above b's header is a's content, so the anchor file must be
// found by scanning down (b), not up (a) — otherwise next-file would jump back
// into b and a second `n` couldn't advance.
taggedThree := []string{"", "", "a", "a", "", "", "b", "b", "", "", "c", "c"}
scenarios := []struct {
name string
paths []string
from int
forward bool
expected int
found bool
}{
{"parseable: next file lands on its header", parseable, 1, true, 4, true},
{"parseable: next from the last file finds nothing", parseable, 6, true, 0, false},
{"parseable: previous file lands on its header", parseable, 5, false, 0, true},
{"parseable: previous from the first file finds nothing", parseable, 1, false, 0, false},
// With only content tagged, both directions still land on the file's top
// (the untagged header rows), so navigation feels the same.
{"tagged: next file lands on its header, not its first content", tagged, 2, true, 4, true},
{"tagged: next from an untagged header still advances", tagged, 0, true, 4, true},
{"tagged: previous file lands on its header", tagged, 7, false, 0, true},
{"tagged: previous from the first file finds nothing", tagged, 2, false, 0, false},
// From b's untagged header (row 4), the anchor file is b (below), so next goes
// to c and previous goes to a — neither sticks on b.
{"tagged: next from a middle file's header advances past it", taggedThree, 4, true, 8, true},
{"tagged: previous from a middle file's header lands on the prior file", taggedThree, 4, false, 0, true},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
got, found := fileStart(s.paths, s.from, s.forward)
assert.Equal(t, s.found, found)
if s.found {
assert.Equal(t, s.expected, got)
}
})
}
}

View file

@ -7,6 +7,7 @@ import (
"fmt"
"path/filepath"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
@ -94,6 +95,26 @@ func (self *MainViewController) GetKeybindings(opts types.KeybindingsOpts) []*ty
Description: openPullRequestDescription,
Tooltip: openPullRequestTooltip,
},
{
Keys: opts.GetKeys(opts.Config.Main.PrevHunk),
Handler: self.prevChangeBlock,
Description: self.c.Tr.PrevHunk,
},
{
Keys: opts.GetKeys(opts.Config.Main.NextHunk),
Handler: self.nextChangeBlock,
Description: self.c.Tr.NextHunk,
},
{
Keys: opts.GetKeys(config.Keybinding{"N"}),
Handler: self.prevFile,
Description: self.c.Tr.PrevFile,
},
{
Keys: opts.GetKeys(config.Keybinding{"n"}),
Handler: self.nextFile,
Description: self.c.Tr.NextFile,
},
{
// overriding this because we want to read all of the task's output before we start searching
Keys: opts.GetKeys(opts.Config.Universal.StartSearch),
@ -161,7 +182,7 @@ func (self *MainViewController) toggleSelection() error {
return nil
}
// Start the selection in the middle of the visible area.
showSelectionAtLine(v, v.OriginY()+v.InnerHeight()/2)
showSelectionAtLine(v, v.OriginY()+v.InnerHeight()/2, false)
return nil
}
@ -184,12 +205,56 @@ func (self *MainViewController) enterForLine(lineIdx int) error {
}
// showSelectionAtLine turns on the focused main view's selection and moves it to
// the given view line, clamped to the content.
func showSelectionAtLine(view *gocui.View, lineIdx int) {
// the given view line, clamped to the content. scrollIntoView scrolls the line into
// view if it's off-screen (used when navigating to it); a click leaves it false, the
// clicked line being visible already.
func showSelectionAtLine(view *gocui.View, lineIdx int, scrollIntoView bool) {
view.Highlight = true
view.HighlightInactive = false
lineIdx = lo.Clamp(lineIdx, 0, view.ViewLinesHeight()-1)
view.FocusPoint(0, lineIdx, false)
view.FocusPoint(0, lineIdx, scrollIntoView)
}
// navigate jumps the focused main view by file or change block (hunk), using find to
// locate the target row from the current anchor. The anchor is the selected line if a
// selection is showing, otherwise the top visible line. With a selection showing we
// move it to the target and scroll it into view, like the staging view; with none we
// stay in scroll mode, bringing the target to the top without selecting anything.
func (self *MainViewController) navigate(find func(*gocui.View, int, bool) (int, bool), forward bool) error {
v := self.context.GetView()
showSelection := v.Highlight
anchor := v.OriginY()
if showSelection {
anchor = v.SelectedLineIdx()
}
target, ok := find(v, anchor, forward)
if !ok {
return nil
}
if showSelection {
showSelectionAtLine(v, target, true)
} else {
v.SetOrigin(0, target)
}
return nil
}
func (self *MainViewController) nextChangeBlock() error {
return self.navigate(self.c.Helpers().Staging.AdjacentChangeBlock, true)
}
func (self *MainViewController) prevChangeBlock() error {
return self.navigate(self.c.Helpers().Staging.AdjacentChangeBlock, false)
}
func (self *MainViewController) nextFile() error {
return self.navigate(self.c.Helpers().Staging.AdjacentFile, true)
}
func (self *MainViewController) prevFile() error {
return self.navigate(self.c.Helpers().Staging.AdjacentFile, false)
}
// focusedMainViewContextForViewName maps a focused main view's view name (as
@ -333,7 +398,7 @@ func githubPullRequestLineURL(prURL string, commitSha string, relativePath strin
func (self *MainViewController) onClickInAlreadyFocusedView(opts gocui.ViewMouseBindingOpts) error {
// A click points at a line, so it sets the selection there; a double-click
// additionally dives into staging/patch-building for that line.
showSelectionAtLine(self.context.GetView(), opts.Y)
showSelectionAtLine(self.context.GetView(), opts.Y, false)
if opts.IsDoubleClick {
return self.enterForLine(opts.Y)
}
@ -342,7 +407,7 @@ func (self *MainViewController) onClickInAlreadyFocusedView(opts gocui.ViewMouse
func (self *MainViewController) onClickInOtherViewOfMainViewPair(opts gocui.ViewMouseBindingOpts) error {
self.c.Context().Push(self.context, types.OnFocusOpts{})
showSelectionAtLine(self.context.GetView(), opts.Y)
showSelectionAtLine(self.context.GetView(), opts.Y, false)
if opts.IsDoubleClick {
return self.enterForLine(opts.Y)
}

View file

@ -81,7 +81,7 @@ func (self *SwitchToFocusedMainViewController) focusMainView(mainViewContext typ
}
self.c.Context().Push(mainViewContext, types.OnFocusOpts{})
if clickedLineIdx >= 0 {
showSelectionAtLine(mainViewContext.GetView(), clickedLineIdx)
showSelectionAtLine(mainViewContext.GetView(), clickedLineIdx, false)
}
return nil
}

View file

@ -396,6 +396,8 @@ type TranslationSet struct {
AskQuestion string
PrevHunk string
NextHunk string
PrevFile string
NextFile string
PrevConflict string
NextConflict string
SelectPrevHunk string
@ -1561,6 +1563,8 @@ func EnglishTranslationSet() *TranslationSet {
AskQuestion: "Ask Question",
PrevHunk: "Go to previous hunk",
NextHunk: "Go to next hunk",
PrevFile: "Go to previous file",
NextFile: "Go to next file",
PrevConflict: "Previous conflict",
NextConflict: "Next conflict",
SelectPrevHunk: "Previous hunk",