Standardise display of range selection across views

We're not fully standardising here: different contexts can store their range state however
they like. What we are standardising on is that now the view is always responsible for
highlighting the selected lines, meaning the context/controller needs to tell the view
where the range start is.

Two convenient benefits from this change:
1) we no longer need bespoke code in integration tests for asserting on selected lines because
we can just ask the view
2) line selection in staging/patch-building/merge-conflicts views now look the same as in
list views i.e. the highlight applies to the whole line (including trailing space)

I also noticed a bug with merge conflicts not rendering the selection on focus though I suspect
it wasn't a bug with any real consequences when the view wasn't displaying the selection.

I'm going to scrap the selectedRangeBgColor config and just let it use the single line
background color. Hopefully nobody cares, but there's really no need for an extra config.
This commit is contained in:
Jesse Duffield 2024-01-14 00:18:05 +11:00
parent c0c3aac02e
commit f3eb180f75
28 changed files with 255 additions and 287 deletions

View file

@ -57,8 +57,6 @@ gui:
- blue - blue
selectedLineBgColor: selectedLineBgColor:
- blue # set to `default` to have no background colour - blue # set to `default` to have no background colour
selectedRangeBgColor:
- blue
cherryPickedCommitBgColor: cherryPickedCommitBgColor:
- cyan - cyan
cherryPickedCommitFgColor: cherryPickedCommitFgColor:
@ -390,15 +388,13 @@ The available attributes are:
## Highlighting the selected line ## Highlighting the selected line
If you don't like the default behaviour of highlighting the selected line with a blue background, you can use the `selectedLineBgColor` and `selectedRangeBgColor` keys to customise the behaviour. If you just want to embolden the selected line (this was the original default), you can do the following: If you don't like the default behaviour of highlighting the selected line with a blue background, you can use the `selectedLineBgColor` key to customise the behaviour. If you just want to embolden the selected line (this was the original default), you can do the following:
```yaml ```yaml
gui: gui:
theme: theme:
selectedLineBgColor: selectedLineBgColor:
- default - default
selectedRangeBgColor:
- default
``` ```
You can also use the reverse attribute like so: You can also use the reverse attribute like so:
@ -408,8 +404,6 @@ gui:
theme: theme:
selectedLineBgColor: selectedLineBgColor:
- reverse - reverse
selectedRangeBgColor:
- reverse
``` ```
## Custom Author Color ## Custom Author Color

View file

@ -37,6 +37,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>.</kbd>: Next page <kbd>.</kbd>: Next page
<kbd>&lt;</kbd>: Scroll to top <kbd>&lt;</kbd>: Scroll to top
<kbd>&gt;</kbd>: Scroll to bottom <kbd>&gt;</kbd>: Scroll to bottom
<kbd>v</kbd>: Toggle range select
<kbd>&lt;s-down&gt;</kbd>: Range select down
<kbd>&lt;s-up&gt;</kbd>: Range select up
<kbd>/</kbd>: Search the current view by text <kbd>/</kbd>: Search the current view by text
<kbd>H</kbd>: Scroll left <kbd>H</kbd>: Scroll left
<kbd>L</kbd>: Scroll right <kbd>L</kbd>: Scroll right
@ -196,8 +199,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<pre> <pre>
<kbd>&lt;left&gt;</kbd>: Select previous hunk <kbd>&lt;left&gt;</kbd>: Select previous hunk
<kbd>&lt;right&gt;</kbd>: Select next hunk <kbd>&lt;right&gt;</kbd>: Select next hunk
<kbd>v</kbd>: Toggle drag select <kbd>v</kbd>: Toggle range select
<kbd>V</kbd>: Toggle drag select
<kbd>a</kbd>: Toggle select hunk <kbd>a</kbd>: Toggle select hunk
<kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard <kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard
<kbd>o</kbd>: Open file <kbd>o</kbd>: Open file
@ -212,8 +214,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<pre> <pre>
<kbd>&lt;left&gt;</kbd>: Select previous hunk <kbd>&lt;left&gt;</kbd>: Select previous hunk
<kbd>&lt;right&gt;</kbd>: Select next hunk <kbd>&lt;right&gt;</kbd>: Select next hunk
<kbd>v</kbd>: Toggle drag select <kbd>v</kbd>: Toggle range select
<kbd>V</kbd>: Toggle drag select
<kbd>a</kbd>: Toggle select hunk <kbd>a</kbd>: Toggle select hunk
<kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard <kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard
<kbd>o</kbd>: Open file <kbd>o</kbd>: Open file

View file

@ -37,6 +37,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>.</kbd>: 次のページ <kbd>.</kbd>: 次のページ
<kbd>&lt;</kbd>: 最上部までスクロール <kbd>&lt;</kbd>: 最上部までスクロール
<kbd>&gt;</kbd>: 最下部までスクロール <kbd>&gt;</kbd>: 最下部までスクロール
<kbd>v</kbd>: 範囲選択を切り替え
<kbd>&lt;s-down&gt;</kbd>: Range select down
<kbd>&lt;s-up&gt;</kbd>: Range select up
<kbd>/</kbd>: 検索を開始 <kbd>/</kbd>: 検索を開始
<kbd>H</kbd>: 左スクロール <kbd>H</kbd>: 左スクロール
<kbd>L</kbd>: 右スクロール <kbd>L</kbd>: 右スクロール
@ -270,7 +273,6 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;left&gt;</kbd>: 前のhunkを選択 <kbd>&lt;left&gt;</kbd>: 前のhunkを選択
<kbd>&lt;right&gt;</kbd>: 次のhunkを選択 <kbd>&lt;right&gt;</kbd>: 次のhunkを選択
<kbd>v</kbd>: 範囲選択を切り替え <kbd>v</kbd>: 範囲選択を切り替え
<kbd>V</kbd>: 範囲選択を切り替え
<kbd>a</kbd>: Hunk選択を切り替え <kbd>a</kbd>: Hunk選択を切り替え
<kbd>&lt;c-o&gt;</kbd>: 選択されたテキストをクリップボードにコピー <kbd>&lt;c-o&gt;</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>o</kbd>: ファイルを開く <kbd>o</kbd>: ファイルを開く
@ -286,7 +288,6 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;left&gt;</kbd>: 前のhunkを選択 <kbd>&lt;left&gt;</kbd>: 前のhunkを選択
<kbd>&lt;right&gt;</kbd>: 次のhunkを選択 <kbd>&lt;right&gt;</kbd>: 次のhunkを選択
<kbd>v</kbd>: 範囲選択を切り替え <kbd>v</kbd>: 範囲選択を切り替え
<kbd>V</kbd>: 範囲選択を切り替え
<kbd>a</kbd>: Hunk選択を切り替え <kbd>a</kbd>: Hunk選択を切り替え
<kbd>&lt;c-o&gt;</kbd>: 選択されたテキストをクリップボードにコピー <kbd>&lt;c-o&gt;</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>o</kbd>: ファイルを開く <kbd>o</kbd>: ファイルを開く

View file

@ -37,6 +37,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>.</kbd>: 다음 페이지 <kbd>.</kbd>: 다음 페이지
<kbd>&lt;</kbd>: 맨 위로 스크롤 <kbd>&lt;</kbd>: 맨 위로 스크롤
<kbd>&gt;</kbd>: 맨 아래로 스크롤 <kbd>&gt;</kbd>: 맨 아래로 스크롤
<kbd>v</kbd>: 드래그 선택 전환
<kbd>&lt;s-down&gt;</kbd>: Range select down
<kbd>&lt;s-up&gt;</kbd>: Range select up
<kbd>/</kbd>: 검색 시작 <kbd>/</kbd>: 검색 시작
<kbd>H</kbd>: 우 스크롤 <kbd>H</kbd>: 우 스크롤
<kbd>L</kbd>: 좌 스크롤 <kbd>L</kbd>: 좌 스크롤
@ -141,7 +144,6 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;left&gt;</kbd>: 이전 hunk를 선택 <kbd>&lt;left&gt;</kbd>: 이전 hunk를 선택
<kbd>&lt;right&gt;</kbd>: 다음 hunk를 선택 <kbd>&lt;right&gt;</kbd>: 다음 hunk를 선택
<kbd>v</kbd>: 드래그 선택 전환 <kbd>v</kbd>: 드래그 선택 전환
<kbd>V</kbd>: 드래그 선택 전환
<kbd>a</kbd>: Toggle select hunk <kbd>a</kbd>: Toggle select hunk
<kbd>&lt;c-o&gt;</kbd>: 선택한 텍스트를 클립보드에 복사 <kbd>&lt;c-o&gt;</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>o</kbd>: 파일 닫기 <kbd>o</kbd>: 파일 닫기
@ -157,7 +159,6 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;left&gt;</kbd>: 이전 hunk를 선택 <kbd>&lt;left&gt;</kbd>: 이전 hunk를 선택
<kbd>&lt;right&gt;</kbd>: 다음 hunk를 선택 <kbd>&lt;right&gt;</kbd>: 다음 hunk를 선택
<kbd>v</kbd>: 드래그 선택 전환 <kbd>v</kbd>: 드래그 선택 전환
<kbd>V</kbd>: 드래그 선택 전환
<kbd>a</kbd>: Toggle select hunk <kbd>a</kbd>: Toggle select hunk
<kbd>&lt;c-o&gt;</kbd>: 선택한 텍스트를 클립보드에 복사 <kbd>&lt;c-o&gt;</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>o</kbd>: 파일 닫기 <kbd>o</kbd>: 파일 닫기

View file

@ -37,6 +37,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>.</kbd>: Volgende pagina <kbd>.</kbd>: Volgende pagina
<kbd>&lt;</kbd>: Scroll naar boven <kbd>&lt;</kbd>: Scroll naar boven
<kbd>&gt;</kbd>: Scroll naar beneden <kbd>&gt;</kbd>: Scroll naar beneden
<kbd>v</kbd>: Toggle drag selecteer
<kbd>&lt;s-down&gt;</kbd>: Range select down
<kbd>&lt;s-up&gt;</kbd>: Range select up
<kbd>/</kbd>: Start met zoeken <kbd>/</kbd>: Start met zoeken
<kbd>H</kbd>: Scroll left <kbd>H</kbd>: Scroll left
<kbd>L</kbd>: Scroll right <kbd>L</kbd>: Scroll right
@ -205,7 +208,6 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;left&gt;</kbd>: Selecteer de vorige hunk <kbd>&lt;left&gt;</kbd>: Selecteer de vorige hunk
<kbd>&lt;right&gt;</kbd>: Selecteer de volgende hunk <kbd>&lt;right&gt;</kbd>: Selecteer de volgende hunk
<kbd>v</kbd>: Toggle drag selecteer <kbd>v</kbd>: Toggle drag selecteer
<kbd>V</kbd>: Toggle drag selecteer
<kbd>a</kbd>: Toggle selecteer hunk <kbd>a</kbd>: Toggle selecteer hunk
<kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard <kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard
<kbd>o</kbd>: Open bestand <kbd>o</kbd>: Open bestand
@ -266,7 +268,6 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;left&gt;</kbd>: Selecteer de vorige hunk <kbd>&lt;left&gt;</kbd>: Selecteer de vorige hunk
<kbd>&lt;right&gt;</kbd>: Selecteer de volgende hunk <kbd>&lt;right&gt;</kbd>: Selecteer de volgende hunk
<kbd>v</kbd>: Toggle drag selecteer <kbd>v</kbd>: Toggle drag selecteer
<kbd>V</kbd>: Toggle drag selecteer
<kbd>a</kbd>: Toggle selecteer hunk <kbd>a</kbd>: Toggle selecteer hunk
<kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard <kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard
<kbd>o</kbd>: Open bestand <kbd>o</kbd>: Open bestand

View file

@ -37,6 +37,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>.</kbd>: Next page <kbd>.</kbd>: Next page
<kbd>&lt;</kbd>: Scroll to top <kbd>&lt;</kbd>: Scroll to top
<kbd>&gt;</kbd>: Scroll to bottom <kbd>&gt;</kbd>: Scroll to bottom
<kbd>v</kbd>: Toggle range select
<kbd>&lt;s-down&gt;</kbd>: Range select down
<kbd>&lt;s-up&gt;</kbd>: Range select up
<kbd>/</kbd>: Search the current view by text <kbd>/</kbd>: Search the current view by text
<kbd>H</kbd>: Scroll left <kbd>H</kbd>: Scroll left
<kbd>L</kbd>: Scroll right <kbd>L</kbd>: Scroll right
@ -127,8 +130,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<pre> <pre>
<kbd>&lt;left&gt;</kbd>: Poprzedni kawałek <kbd>&lt;left&gt;</kbd>: Poprzedni kawałek
<kbd>&lt;right&gt;</kbd>: Następny kawałek <kbd>&lt;right&gt;</kbd>: Następny kawałek
<kbd>v</kbd>: Toggle drag select <kbd>v</kbd>: Toggle range select
<kbd>V</kbd>: Toggle drag select
<kbd>a</kbd>: Toggle select hunk <kbd>a</kbd>: Toggle select hunk
<kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard <kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard
<kbd>o</kbd>: Otwórz plik <kbd>o</kbd>: Otwórz plik
@ -197,8 +199,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<pre> <pre>
<kbd>&lt;left&gt;</kbd>: Poprzedni kawałek <kbd>&lt;left&gt;</kbd>: Poprzedni kawałek
<kbd>&lt;right&gt;</kbd>: Następny kawałek <kbd>&lt;right&gt;</kbd>: Następny kawałek
<kbd>v</kbd>: Toggle drag select <kbd>v</kbd>: Toggle range select
<kbd>V</kbd>: Toggle drag select
<kbd>a</kbd>: Toggle select hunk <kbd>a</kbd>: Toggle select hunk
<kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard <kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard
<kbd>o</kbd>: Otwórz plik <kbd>o</kbd>: Otwórz plik

View file

@ -37,6 +37,9 @@ _Связки клавиш_
<kbd>.</kbd>: Следующая страница <kbd>.</kbd>: Следующая страница
<kbd>&lt;</kbd>: Пролистать наверх <kbd>&lt;</kbd>: Пролистать наверх
<kbd>&gt;</kbd>: Прокрутить вниз <kbd>&gt;</kbd>: Прокрутить вниз
<kbd>v</kbd>: Переключить выборку перетаскивания
<kbd>&lt;s-down&gt;</kbd>: Range select down
<kbd>&lt;s-up&gt;</kbd>: Range select up
<kbd>/</kbd>: Найти <kbd>/</kbd>: Найти
<kbd>H</kbd>: Прокрутить влево <kbd>H</kbd>: Прокрутить влево
<kbd>L</kbd>: Прокрутить вправо <kbd>L</kbd>: Прокрутить вправо
@ -61,7 +64,6 @@ _Связки клавиш_
<kbd>&lt;left&gt;</kbd>: Выбрать предыдущую часть <kbd>&lt;left&gt;</kbd>: Выбрать предыдущую часть
<kbd>&lt;right&gt;</kbd>: Выбрать следующую часть <kbd>&lt;right&gt;</kbd>: Выбрать следующую часть
<kbd>v</kbd>: Переключить выборку перетаскивания <kbd>v</kbd>: Переключить выборку перетаскивания
<kbd>V</kbd>: Переключить выборку перетаскивания
<kbd>a</kbd>: Переключить выборку частей <kbd>a</kbd>: Переключить выборку частей
<kbd>&lt;c-o&gt;</kbd>: Скопировать выделенный текст в буфер обмена <kbd>&lt;c-o&gt;</kbd>: Скопировать выделенный текст в буфер обмена
<kbd>o</kbd>: Открыть файл <kbd>o</kbd>: Открыть файл
@ -106,7 +108,6 @@ _Связки клавиш_
<kbd>&lt;left&gt;</kbd>: Выбрать предыдущую часть <kbd>&lt;left&gt;</kbd>: Выбрать предыдущую часть
<kbd>&lt;right&gt;</kbd>: Выбрать следующую часть <kbd>&lt;right&gt;</kbd>: Выбрать следующую часть
<kbd>v</kbd>: Переключить выборку перетаскивания <kbd>v</kbd>: Переключить выборку перетаскивания
<kbd>V</kbd>: Переключить выборку перетаскивания
<kbd>a</kbd>: Переключить выборку частей <kbd>a</kbd>: Переключить выборку частей
<kbd>&lt;c-o&gt;</kbd>: Скопировать выделенный текст в буфер обмена <kbd>&lt;c-o&gt;</kbd>: Скопировать выделенный текст в буфер обмена
<kbd>o</kbd>: Открыть файл <kbd>o</kbd>: Открыть файл

View file

@ -37,6 +37,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>.</kbd>: 下一页 <kbd>.</kbd>: 下一页
<kbd>&lt;</kbd>: 滚动到顶部 <kbd>&lt;</kbd>: 滚动到顶部
<kbd>&gt;</kbd>: 滚动到底部 <kbd>&gt;</kbd>: 滚动到底部
<kbd>v</kbd>: 切换拖动选择
<kbd>&lt;s-down&gt;</kbd>: Range select down
<kbd>&lt;s-up&gt;</kbd>: Range select up
<kbd>/</kbd>: 开始搜索 <kbd>/</kbd>: 开始搜索
<kbd>H</kbd>: 向左滚动 <kbd>H</kbd>: 向左滚动
<kbd>L</kbd>: 向右滚动 <kbd>L</kbd>: 向右滚动
@ -229,7 +232,6 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;left&gt;</kbd>: 选择上一个区块 <kbd>&lt;left&gt;</kbd>: 选择上一个区块
<kbd>&lt;right&gt;</kbd>: 选择下一个区块 <kbd>&lt;right&gt;</kbd>: 选择下一个区块
<kbd>v</kbd>: 切换拖动选择 <kbd>v</kbd>: 切换拖动选择
<kbd>V</kbd>: 切换拖动选择
<kbd>a</kbd>: 切换选择区块 <kbd>a</kbd>: 切换选择区块
<kbd>&lt;c-o&gt;</kbd>: 将选中文本复制到剪贴板 <kbd>&lt;c-o&gt;</kbd>: 将选中文本复制到剪贴板
<kbd>o</kbd>: 打开文件 <kbd>o</kbd>: 打开文件
@ -274,7 +276,6 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;left&gt;</kbd>: 选择上一个区块 <kbd>&lt;left&gt;</kbd>: 选择上一个区块
<kbd>&lt;right&gt;</kbd>: 选择下一个区块 <kbd>&lt;right&gt;</kbd>: 选择下一个区块
<kbd>v</kbd>: 切换拖动选择 <kbd>v</kbd>: 切换拖动选择
<kbd>V</kbd>: 切换拖动选择
<kbd>a</kbd>: 切换选择区块 <kbd>a</kbd>: 切换选择区块
<kbd>&lt;c-o&gt;</kbd>: 将选中文本复制到剪贴板 <kbd>&lt;c-o&gt;</kbd>: 将选中文本复制到剪贴板
<kbd>o</kbd>: 打开文件 <kbd>o</kbd>: 打开文件

View file

@ -37,6 +37,9 @@ _說明`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B`B`表示 Shift+B_
<kbd>.</kbd>: 下一頁 <kbd>.</kbd>: 下一頁
<kbd>&lt;</kbd>: 捲動到頂部 <kbd>&lt;</kbd>: 捲動到頂部
<kbd>&gt;</kbd>: 捲動到底部 <kbd>&gt;</kbd>: 捲動到底部
<kbd>v</kbd>: 切換拖曳選擇
<kbd>&lt;s-down&gt;</kbd>: Range select down
<kbd>&lt;s-up&gt;</kbd>: Range select up
<kbd>/</kbd>: 開始搜尋 <kbd>/</kbd>: 開始搜尋
<kbd>H</kbd>: 向左捲動 <kbd>H</kbd>: 向左捲動
<kbd>L</kbd>: 向右捲動 <kbd>L</kbd>: 向右捲動
@ -102,7 +105,6 @@ _說明`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B`B`表示 Shift+B_
<kbd>&lt;left&gt;</kbd>: 選擇上一段 <kbd>&lt;left&gt;</kbd>: 選擇上一段
<kbd>&lt;right&gt;</kbd>: 選擇下一段 <kbd>&lt;right&gt;</kbd>: 選擇下一段
<kbd>v</kbd>: 切換拖曳選擇 <kbd>v</kbd>: 切換拖曳選擇
<kbd>V</kbd>: 切換拖曳選擇
<kbd>a</kbd>: 切換選擇程式碼塊 <kbd>a</kbd>: 切換選擇程式碼塊
<kbd>&lt;c-o&gt;</kbd>: 複製所選文本至剪貼簿 <kbd>&lt;c-o&gt;</kbd>: 複製所選文本至剪貼簿
<kbd>o</kbd>: 開啟檔案 <kbd>o</kbd>: 開啟檔案
@ -124,7 +126,6 @@ _說明`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B`B`表示 Shift+B_
<kbd>&lt;left&gt;</kbd>: 選擇上一段 <kbd>&lt;left&gt;</kbd>: 選擇上一段
<kbd>&lt;right&gt;</kbd>: 選擇下一段 <kbd>&lt;right&gt;</kbd>: 選擇下一段
<kbd>v</kbd>: 切換拖曳選擇 <kbd>v</kbd>: 切換拖曳選擇
<kbd>V</kbd>: 切換拖曳選擇
<kbd>a</kbd>: 切換選擇程式碼塊 <kbd>a</kbd>: 切換選擇程式碼塊
<kbd>&lt;c-o&gt;</kbd>: 複製所選文本至剪貼簿 <kbd>&lt;c-o&gt;</kbd>: 複製所選文本至剪貼簿
<kbd>o</kbd>: 開啟檔案 <kbd>o</kbd>: 開啟檔案

View file

@ -14,11 +14,6 @@ type patchPresenter struct {
// if true, all following fields are ignored // if true, all following fields are ignored
plain bool plain bool
isFocused bool
// first line index for selected cursor range
firstLineIndex int
// last line index for selected cursor range
lastLineIndex int
// line indices for tagged lines (e.g. lines added to a custom patch) // line indices for tagged lines (e.g. lines added to a custom patch)
incLineIndices *set.Set[int] incLineIndices *set.Set[int]
} }
@ -44,11 +39,6 @@ func formatRangePlain(patch *Patch, startIdx int, endIdx int) string {
} }
type FormatViewOpts struct { type FormatViewOpts struct {
IsFocused bool
// first line index for selected cursor range
FirstLineIndex int
// last line index for selected cursor range
LastLineIndex int
// line indices for tagged lines (e.g. lines added to a custom patch) // line indices for tagged lines (e.g. lines added to a custom patch)
IncLineIndices *set.Set[int] IncLineIndices *set.Set[int]
} }
@ -63,9 +53,6 @@ func formatView(patch *Patch, opts FormatViewOpts) string {
presenter := &patchPresenter{ presenter := &patchPresenter{
patch: patch, patch: patch,
plain: false, plain: false,
isFocused: opts.IsFocused,
firstLineIndex: opts.FirstLineIndex,
lastLineIndex: opts.LastLineIndex,
incLineIndices: includedLineIndices, incLineIndices: includedLineIndices,
} }
return presenter.format() return presenter.format()
@ -112,7 +99,6 @@ func (self *patchPresenter) format() string {
self.formatLineAux( self.formatLineAux(
hunk.headerContext, hunk.headerContext,
theme.DefaultTextColor, theme.DefaultTextColor,
lineIdx,
false, false,
), ),
) )
@ -139,23 +125,17 @@ func (self *patchPresenter) patchLineStyle(patchLine *PatchLine) style.TextStyle
func (self *patchPresenter) formatLine(str string, textStyle style.TextStyle, index int) string { func (self *patchPresenter) formatLine(str string, textStyle style.TextStyle, index int) string {
included := self.incLineIndices.Includes(index) included := self.incLineIndices.Includes(index)
return self.formatLineAux(str, textStyle, index, included) return self.formatLineAux(str, textStyle, included)
} }
// 'selected' means you've got it highlighted with your cursor // 'selected' means you've got it highlighted with your cursor
// 'included' means the line has been included in the patch (only applicable when // 'included' means the line has been included in the patch (only applicable when
// building a patch) // building a patch)
func (self *patchPresenter) formatLineAux(str string, textStyle style.TextStyle, index int, included bool) string { func (self *patchPresenter) formatLineAux(str string, textStyle style.TextStyle, included bool) string {
if self.plain { if self.plain {
return str return str
} }
selected := self.isFocused && index >= self.firstLineIndex && index <= self.lastLineIndex
if selected {
textStyle = textStyle.MergeStyle(theme.SelectedRangeBgColor)
}
firstCharStyle := textStyle firstCharStyle := textStyle
if included { if included {
firstCharStyle = firstCharStyle.MergeStyle(style.BgGreen) firstCharStyle = firstCharStyle.MergeStyle(style.BgGreen)

View file

@ -197,9 +197,7 @@ func (p *PatchBuilder) RenderPatchForFile(filename string, plain bool, reverse b
if plain { if plain {
return patch.FormatPlain() return patch.FormatPlain()
} else { } else {
return patch.FormatView(FormatViewOpts{ return patch.FormatView(FormatViewOpts{})
IsFocused: false,
})
} }
} }

View file

@ -154,9 +154,6 @@ type ThemeConfig struct {
// Background color of selected line. // Background color of selected line.
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line
SelectedLineBgColor []string `yaml:"selectedLineBgColor" jsonschema:"minItems=1,uniqueItems=true"` SelectedLineBgColor []string `yaml:"selectedLineBgColor" jsonschema:"minItems=1,uniqueItems=true"`
// Background color of selected range
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line
SelectedRangeBgColor []string `yaml:"selectedRangeBgColor" jsonschema:"minItems=1,uniqueItems=true"`
// Foreground color of copied commit // Foreground color of copied commit
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor" jsonschema:"minItems=1,uniqueItems=true"` CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor" jsonschema:"minItems=1,uniqueItems=true"`
// Background color of copied commit // Background color of copied commit
@ -622,7 +619,6 @@ func GetDefaultConfig() *UserConfig {
InactiveBorderColor: []string{"default"}, InactiveBorderColor: []string{"default"},
OptionsTextColor: []string{"blue"}, OptionsTextColor: []string{"blue"},
SelectedLineBgColor: []string{"blue"}, SelectedLineBgColor: []string{"blue"},
SelectedRangeBgColor: []string{"blue"},
CherryPickedCommitBgColor: []string{"cyan"}, CherryPickedCommitBgColor: []string{"cyan"},
CherryPickedCommitFgColor: []string{"blue"}, CherryPickedCommitFgColor: []string{"blue"},
MarkedBaseCommitBgColor: []string{"yellow"}, MarkedBaseCommitBgColor: []string{"yellow"},

View file

@ -68,8 +68,8 @@ func (self *MergeConflictsContext) IsUserScrolling() bool {
return self.viewModel.userVerticalScrolling return self.viewModel.userVerticalScrolling
} }
func (self *MergeConflictsContext) RenderAndFocus(isFocused bool) error { func (self *MergeConflictsContext) RenderAndFocus() error {
self.setContent(isFocused) self.setContent()
self.FocusSelection() self.FocusSelection()
self.c.Render() self.c.Render()
@ -77,30 +77,41 @@ func (self *MergeConflictsContext) RenderAndFocus(isFocused bool) error {
return nil return nil
} }
func (self *MergeConflictsContext) Render(isFocused bool) error { func (self *MergeConflictsContext) Render() error {
self.setContent(isFocused) self.setContent()
self.c.Render() self.c.Render()
return nil return nil
} }
func (self *MergeConflictsContext) GetContentToRender(isFocused bool) string { func (self *MergeConflictsContext) GetContentToRender() string {
if self.GetState() == nil { if self.GetState() == nil {
return "" return ""
} }
return mergeconflicts.ColoredConflictFile(self.GetState(), isFocused) return mergeconflicts.ColoredConflictFile(self.GetState())
} }
func (self *MergeConflictsContext) setContent(isFocused bool) { func (self *MergeConflictsContext) setContent() {
self.GetView().SetContent(self.GetContentToRender(isFocused)) self.GetView().SetContent(self.GetContentToRender())
} }
func (self *MergeConflictsContext) FocusSelection() { func (self *MergeConflictsContext) FocusSelection() {
if !self.IsUserScrolling() { if !self.IsUserScrolling() {
_ = self.GetView().SetOriginY(self.GetOriginY()) _ = self.GetView().SetOriginY(self.GetOriginY())
} }
self.SetSelectedLineRange()
}
func (self *MergeConflictsContext) SetSelectedLineRange() {
startIdx, endIdx := self.GetState().GetSelectedRange()
view := self.GetView()
originY := view.OriginY()
// As far as the view is concerned, we are always selecting a range
view.SetRangeSelectStart(startIdx)
view.SetCursorY(endIdx - originY)
} }
func (self *MergeConflictsContext) GetOriginY() int { func (self *MergeConflictsContext) GetOriginY() int {

View file

@ -115,17 +115,10 @@ func (self *PatchExplorerContext) FocusSelection() {
_ = view.SetOriginY(newOriginY) _ = view.SetOriginY(newOriginY)
view.SetCursorY(state.GetSelectedLineIdx() - newOriginY) startIdx, endIdx := state.SelectedRange()
// As far as the view is concerned, we are always selecting a range
// At present this is just bookkeeping: the reason for setting this would be view.SetRangeSelectStart(startIdx)
// so that gocui knows which lines to highlight, but we're currently handling view.SetCursorY(endIdx - newOriginY)
// highlighting ourselves.
rangeStartLineIdx, isSelectingRange := state.RangeStartLineIdx()
if isSelectingRange {
view.SetRangeSelectStart(rangeStartLineIdx)
} else {
view.CancelRangeSelect()
}
} }
func (self *PatchExplorerContext) GetContentToRender(isFocused bool) string { func (self *PatchExplorerContext) GetContentToRender(isFocused bool) string {

View file

@ -205,7 +205,7 @@ func (self *FilesController) GetOnRenderToMain() func() error {
} }
if hasConflicts { if hasConflicts {
return self.c.Helpers().MergeConflicts.Render(false) return self.c.Helpers().MergeConflicts.Render()
} }
} }

View file

@ -69,14 +69,14 @@ func (self *MergeConflictsHelper) EscapeMerge() error {
return nil return nil
} }
func (self *MergeConflictsHelper) SetConflictsAndRender(path string, isFocused bool) (bool, error) { func (self *MergeConflictsHelper) SetConflictsAndRender(path string) (bool, error) {
hasConflicts, err := self.setMergeStateWithoutLock(path) hasConflicts, err := self.setMergeStateWithoutLock(path)
if err != nil { if err != nil {
return false, err return false, err
} }
if hasConflicts { if hasConflicts {
return true, self.context().Render(isFocused) return true, self.context().Render()
} }
return false, nil return false, nil
@ -100,8 +100,8 @@ func (self *MergeConflictsHelper) context() *context.MergeConflictsContext {
return self.c.Contexts().MergeConflicts return self.c.Contexts().MergeConflicts
} }
func (self *MergeConflictsHelper) Render(isFocused bool) error { func (self *MergeConflictsHelper) Render() error {
content := self.context().GetContentToRender(isFocused) content := self.context().GetContentToRender()
var task types.UpdateTask var task types.UpdateTask
if self.context().IsUserScrolling() { if self.context().IsUserScrolling() {
@ -127,7 +127,7 @@ func (self *MergeConflictsHelper) RefreshMergeState() error {
return nil return nil
} }
hasConflicts, err := self.SetConflictsAndRender(self.c.Contexts().MergeConflicts.GetState().GetPath(), true) hasConflicts, err := self.SetConflictsAndRender(self.c.Contexts().MergeConflicts.GetState().GetPath())
if err != nil { if err != nil {
return self.c.Error(err) return self.c.Error(err)
} }

View file

@ -145,7 +145,13 @@ func (self *MergeConflictsController) GetOnFocus() func(types.OnFocusOpts) error
return func(types.OnFocusOpts) error { return func(types.OnFocusOpts) error {
self.c.Views().MergeConflicts.Wrap = false self.c.Views().MergeConflicts.Wrap = false
return self.c.Helpers().MergeConflicts.Render(true) if err := self.c.Helpers().MergeConflicts.Render(); err != nil {
return err
}
self.context().SetSelectedLineRange()
return nil
} }
} }
@ -313,17 +319,13 @@ func (self *MergeConflictsController) onLastConflictResolved() error {
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}}) return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
} }
func (self *MergeConflictsController) isFocused() bool {
return self.c.CurrentContext().GetKey() == self.context().GetKey()
}
func (self *MergeConflictsController) withRenderAndFocus(f func() error) func() error { func (self *MergeConflictsController) withRenderAndFocus(f func() error) func() error {
return self.withLock(func() error { return self.withLock(func() error {
if err := f(); err != nil { if err := f(); err != nil {
return err return err
} }
return self.context().RenderAndFocus(self.isFocused()) return self.context().RenderAndFocus()
}) })
} }

View file

@ -4,7 +4,6 @@ import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/samber/lo" "github.com/samber/lo"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
@ -143,15 +142,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
gui.State.ViewsSetup = true gui.State.ViewsSetup = true
} }
for _, listContext := range gui.c.Context().AllList() {
view, err := gui.g.View(listContext.GetViewName())
if err != nil {
continue
}
view.SelBgColor = theme.GocuiSelectedLineBgColor
}
mainViewWidth, mainViewHeight := gui.Views.Main.Size() mainViewWidth, mainViewHeight := gui.Views.Main.Size()
if mainViewWidth != gui.PrevLayout.MainWidth || mainViewHeight != gui.PrevLayout.MainHeight { if mainViewWidth != gui.PrevLayout.MainWidth || mainViewHeight != gui.PrevLayout.MainHeight {
gui.PrevLayout.MainWidth = mainViewWidth gui.PrevLayout.MainWidth = mainViewWidth

View file

@ -8,7 +8,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
func ColoredConflictFile(state *State, hasFocus bool) string { func ColoredConflictFile(state *State) string {
content := state.GetContent() content := state.GetContent()
if len(state.conflicts) == 0 { if len(state.conflicts) == 0 {
return content return content
@ -21,9 +21,6 @@ func ColoredConflictFile(state *State, hasFocus bool) string {
textStyle = style.FgRed textStyle = style.FgRed
} }
if hasFocus && state.conflictIndex < len(state.conflicts) && *state.conflicts[state.conflictIndex] == *conflict && shouldHighlightLine(i, conflict, state.Selection()) {
textStyle = textStyle.MergeStyle(theme.SelectedRangeBgColor).SetBold()
}
if i == conflict.end && len(remainingConflicts) > 0 { if i == conflict.end && len(remainingConflicts) > 0 {
conflict, remainingConflicts = shiftConflict(remainingConflicts) conflict, remainingConflicts = shiftConflict(remainingConflicts)
} }
@ -35,8 +32,3 @@ func ColoredConflictFile(state *State, hasFocus bool) string {
func shiftConflict(conflicts []*mergeConflict) (*mergeConflict, []*mergeConflict) { func shiftConflict(conflicts []*mergeConflict) (*mergeConflict, []*mergeConflict) {
return conflicts[0], conflicts[1:] return conflicts[0], conflicts[1:]
} }
func shouldHighlightLine(index int, conflict *mergeConflict, selection Selection) bool {
selectionStart, selectionEnd := selection.bounds(conflict)
return index >= selectionStart && index <= selectionEnd
}

View file

@ -240,12 +240,8 @@ func (s *State) AdjustSelectedLineIdx(change int) {
} }
func (s *State) RenderForLineIndices(isFocused bool, includedLineIndices []int) string { func (s *State) RenderForLineIndices(isFocused bool, includedLineIndices []int) string {
firstLineIdx, lastLineIdx := s.SelectedRange()
includedLineIndicesSet := set.NewFromSlice(includedLineIndices) includedLineIndicesSet := set.NewFromSlice(includedLineIndices)
return s.patch.FormatView(patch.FormatViewOpts{ return s.patch.FormatView(patch.FormatViewOpts{
IsFocused: isFocused,
FirstLineIndex: firstLineIdx,
LastLineIndex: lastLineIdx,
IncLineIndices: includedLineIndicesSet, IncLineIndices: includedLineIndicesSet,
}) })
} }

View file

@ -91,6 +91,7 @@ func (gui *Gui) createAllViews() error {
} }
(*mapping.viewPtr).FrameRunes = frameRunes (*mapping.viewPtr).FrameRunes = frameRunes
(*mapping.viewPtr).FgColor = theme.GocuiDefaultTextColor (*mapping.viewPtr).FgColor = theme.GocuiDefaultTextColor
(*mapping.viewPtr).SelBgColor = theme.GocuiSelectedLineBgColor
} }
gui.Views.Options.FgColor = theme.OptionsColor gui.Views.Options.FgColor = theme.OptionsColor
@ -134,23 +135,18 @@ func (gui *Gui) createAllViews() error {
} }
gui.Views.Staging.Title = gui.c.Tr.UnstagedChanges gui.Views.Staging.Title = gui.c.Tr.UnstagedChanges
gui.Views.Staging.Highlight = false
gui.Views.Staging.Wrap = true gui.Views.Staging.Wrap = true
gui.Views.StagingSecondary.Title = gui.c.Tr.StagedChanges gui.Views.StagingSecondary.Title = gui.c.Tr.StagedChanges
gui.Views.StagingSecondary.Highlight = false
gui.Views.StagingSecondary.Wrap = true gui.Views.StagingSecondary.Wrap = true
gui.Views.PatchBuilding.Title = gui.Tr.Patch gui.Views.PatchBuilding.Title = gui.Tr.Patch
gui.Views.PatchBuilding.Highlight = false
gui.Views.PatchBuilding.Wrap = true gui.Views.PatchBuilding.Wrap = true
gui.Views.PatchBuildingSecondary.Title = gui.Tr.CustomPatch gui.Views.PatchBuildingSecondary.Title = gui.Tr.CustomPatch
gui.Views.PatchBuildingSecondary.Highlight = false
gui.Views.PatchBuildingSecondary.Wrap = true gui.Views.PatchBuildingSecondary.Wrap = true
gui.Views.MergeConflicts.Title = gui.c.Tr.MergeConflictsTitle gui.Views.MergeConflicts.Title = gui.c.Tr.MergeConflictsTitle
gui.Views.MergeConflicts.Highlight = false
gui.Views.MergeConflicts.Wrap = false gui.Views.MergeConflicts.Wrap = false
gui.Views.Limit.Title = gui.c.Tr.NotEnoughSpace gui.Views.Limit.Title = gui.c.Tr.NotEnoughSpace

View file

@ -13,42 +13,21 @@ type ViewDriver struct {
context string context string
getView func() *gocui.View getView func() *gocui.View
t *TestDriver t *TestDriver
getSelectedLinesFn func() ([]string, error)
getSelectedRangeFn func() (int, int, error)
getSelectedLineIdxFn func() (int, error)
} }
func (self *ViewDriver) getSelectedLines() ([]string, error) { func (self *ViewDriver) getSelectedLines() []string {
if self.getSelectedLinesFn == nil {
view := self.t.gui.View(self.getView().Name()) view := self.t.gui.View(self.getView().Name())
return view.SelectedLines()
return []string{view.SelectedLine()}, nil
} }
return self.getSelectedLinesFn() func (self *ViewDriver) getSelectedRange() (int, int) {
}
func (self *ViewDriver) getSelectedRange() (int, int, error) {
if self.getSelectedRangeFn == nil {
view := self.t.gui.View(self.getView().Name()) view := self.t.gui.View(self.getView().Name())
idx := view.SelectedLineIdx() return view.SelectedLineRange()
return idx, idx, nil
} }
return self.getSelectedRangeFn() func (self *ViewDriver) getSelectedLineIdx() int {
}
// even if you have a selected range, there may still be a line within that range
// which the cursor points at. This function returns that line index.
func (self *ViewDriver) getSelectedLineIdx() (int, error) {
if self.getSelectedLineIdxFn == nil {
view := self.t.gui.View(self.getView().Name()) view := self.t.gui.View(self.getView().Name())
return view.SelectedLineIdx()
return view.SelectedLineIdx(), nil
}
return self.getSelectedLineIdxFn()
} }
// asserts that the view has the expected title // asserts that the view has the expected title
@ -105,7 +84,7 @@ func (self *ViewDriver) ContainsLines(matchers ...*TextMatcher) *ViewDriver {
content := self.getView().Buffer() content := self.getView().Buffer()
lines := strings.Split(content, "\n") lines := strings.Split(content, "\n")
startIdx, endIdx, err := self.getSelectedRange() startIdx, endIdx := self.getSelectedRange()
for i := 0; i < len(lines)-len(matchers)+1; i++ { for i := 0; i < len(lines)-len(matchers)+1; i++ {
matches := true matches := true
@ -118,10 +97,6 @@ func (self *ViewDriver) ContainsLines(matchers ...*TextMatcher) *ViewDriver {
break break
} }
if checkIsSelected { if checkIsSelected {
if err != nil {
matches = false
break
}
if lineIdx < startIdx || lineIdx > endIdx { if lineIdx < startIdx || lineIdx > endIdx {
matches = false matches = false
break break
@ -181,10 +156,7 @@ func (self *ViewDriver) SelectedLines(matchers ...*TextMatcher) *ViewDriver {
self.validateEnoughLines(matchers) self.validateEnoughLines(matchers)
self.t.assertWithRetries(func() (bool, string) { self.t.assertWithRetries(func() (bool, string) {
selectedLines, err := self.getSelectedLines() selectedLines := self.getSelectedLines()
if err != nil {
return false, err.Error()
}
selectedContent := strings.Join(selectedLines, "\n") selectedContent := strings.Join(selectedLines, "\n")
expectedContent := expectedContentFromMatchers(matchers) expectedContent := expectedContentFromMatchers(matchers)
@ -251,19 +223,13 @@ func (self *ViewDriver) assertLines(offset int, matchers ...*TextMatcher) *ViewD
if checkIsSelected { if checkIsSelected {
self.t.assertWithRetries(func() (bool, string) { self.t.assertWithRetries(func() (bool, string) {
startIdx, endIdx, err := self.getSelectedRange() startIdx, endIdx := self.getSelectedRange()
if err != nil {
return false, err.Error()
}
if lineIdx < startIdx || lineIdx > endIdx { if lineIdx < startIdx || lineIdx > endIdx {
if startIdx == endIdx { if startIdx == endIdx {
return false, fmt.Sprintf("Unexpected selected line index in view '%s'. Expected %d, got %d", view.Name(), lineIdx, startIdx) return false, fmt.Sprintf("Unexpected selected line index in view '%s'. Expected %d, got %d", view.Name(), lineIdx, startIdx)
} else { } else {
lines, err := self.getSelectedLines() lines := self.getSelectedLines()
if err != nil {
return false, err.Error()
}
return false, fmt.Sprintf("Unexpected selected line index in view '%s'. Expected line %d to be in range %d to %d. Selected lines:\n---\n%s\n---\n\nExpected line: '%s'", view.Name(), lineIdx, startIdx, endIdx, strings.Join(lines, "\n"), matcher.name()) return false, fmt.Sprintf("Unexpected selected line index in view '%s'. Expected line %d to be in range %d to %d. Selected lines:\n---\n%s\n---\n\nExpected line: '%s'", view.Name(), lineIdx, startIdx, endIdx, strings.Join(lines, "\n"), matcher.name())
} }
} }
@ -286,15 +252,11 @@ func (self *ViewDriver) Content(matcher *TextMatcher) *ViewDriver {
return self return self
} }
// asserts on the selected line of the view. If your view has multiple lines selected, // asserts on the selected line of the view. If you are selecting a range,
// but also has a concept of a cursor position, this will assert on the line that // you should use the SelectedLines method instead.
// the cursor is on. Otherwise it will assert on the first line of the selection.
func (self *ViewDriver) SelectedLine(matcher *TextMatcher) *ViewDriver { func (self *ViewDriver) SelectedLine(matcher *TextMatcher) *ViewDriver {
self.t.assertWithRetries(func() (bool, string) { self.t.assertWithRetries(func() (bool, string) {
selectedLineIdx, err := self.getSelectedLineIdx() selectedLineIdx := self.getSelectedLineIdx()
if err != nil {
return false, err.Error()
}
viewLines := self.getView().BufferLines() viewLines := self.getView().BufferLines()
@ -480,11 +442,7 @@ func (self *ViewDriver) NavigateToLine(matcher *TextMatcher) *ViewDriver {
} }
}) })
selectedLineIdx, err := self.getSelectedLineIdx() selectedLineIdx := self.getSelectedLineIdx()
if err != nil {
self.t.fail(err.Error())
return self
}
if selectedLineIdx == matchIndex { if selectedLineIdx == matchIndex {
return self.SelectedLine(matcher) return self.SelectedLine(matcher)
} }
@ -507,11 +465,7 @@ func (self *ViewDriver) NavigateToLine(matcher *TextMatcher) *ViewDriver {
for i := 0; i < maxNumKeyPresses; i++ { for i := 0; i < maxNumKeyPresses; i++ {
keyPress() keyPress()
idx, err := self.getSelectedLineIdx() idx := self.getSelectedLineIdx()
if err != nil {
self.t.fail(err.Error())
return self
}
if ok, _ := matcher.test(lines[idx]); ok { if ok, _ := matcher.test(lines[idx]); ok {
return self return self
} }

View file

@ -2,11 +2,8 @@ package components
import ( import (
"fmt" "fmt"
"strings"
"github.com/go-errors/errors"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
) )
type Views struct { type Views struct {
@ -30,95 +27,19 @@ func (self *Views) Secondary() *ViewDriver {
} }
func (self *Views) regularView(viewName string) *ViewDriver { func (self *Views) regularView(viewName string) *ViewDriver {
return self.newStaticViewDriver(viewName, nil, nil, nil)
}
func (self *Views) patchExplorerViewByName(viewName string) *ViewDriver {
return self.newStaticViewDriver(
viewName,
func() ([]string, error) {
ctx := self.t.gui.ContextForView(viewName).(*context.PatchExplorerContext)
state := ctx.GetState()
if state == nil {
return nil, errors.New("Expected patch explorer to be activated")
}
selectedContent := state.PlainRenderSelected()
// the above method returns a string with a trailing newline so we need to remove that before splitting
selectedLines := strings.Split(strings.TrimSuffix(selectedContent, "\n"), "\n")
return selectedLines, nil
},
func() (int, int, error) {
ctx := self.t.gui.ContextForView(viewName).(*context.PatchExplorerContext)
state := ctx.GetState()
if state == nil {
return 0, 0, errors.New("Expected patch explorer to be activated")
}
startIdx, endIdx := state.SelectedRange()
return startIdx, endIdx, nil
},
func() (int, error) {
ctx := self.t.gui.ContextForView(viewName).(*context.PatchExplorerContext)
state := ctx.GetState()
if state == nil {
return 0, errors.New("Expected patch explorer to be activated")
}
return state.GetSelectedLineIdx(), nil
},
)
}
// 'static' because it'll always refer to the same view, as opposed to the 'main' view which could actually be
// one of several views, or the 'current' view which depends on focus.
func (self *Views) newStaticViewDriver(
viewName string,
getSelectedLinesFn func() ([]string, error),
getSelectedLineRangeFn func() (int, int, error),
getSelectedLineIdxFn func() (int, error),
) *ViewDriver {
return &ViewDriver{ return &ViewDriver{
context: fmt.Sprintf("%s view", viewName), context: fmt.Sprintf("%s view", viewName),
getView: func() *gocui.View { return self.t.gui.View(viewName) }, getView: func() *gocui.View { return self.t.gui.View(viewName) },
getSelectedLinesFn: getSelectedLinesFn,
getSelectedRangeFn: getSelectedLineRangeFn,
getSelectedLineIdxFn: getSelectedLineIdxFn,
t: self.t, t: self.t,
} }
} }
func (self *Views) MergeConflicts() *ViewDriver { func (self *Views) patchExplorerViewByName(viewName string) *ViewDriver {
viewName := "mergeConflicts" return self.regularView(viewName)
return self.newStaticViewDriver(
viewName,
func() ([]string, error) {
ctx := self.t.gui.ContextForView(viewName).(*context.MergeConflictsContext)
state := ctx.GetState()
if state == nil {
return nil, errors.New("Expected patch explorer to be activated")
} }
selectedContent := strings.Split(state.PlainRenderSelected(), "\n")
return selectedContent, nil func (self *Views) MergeConflicts() *ViewDriver {
}, return self.regularView("mergeConflicts")
func() (int, int, error) {
ctx := self.t.gui.ContextForView(viewName).(*context.MergeConflictsContext)
state := ctx.GetState()
if state == nil {
return 0, 0, errors.New("Expected patch explorer to be activated")
}
startIdx, endIdx := state.GetSelectedRange()
return startIdx, endIdx, nil
},
// there is no concept of a cursor in the merge conflicts panel so we just return the start of the selection
func() (int, error) {
ctx := self.t.gui.ContextForView(viewName).(*context.MergeConflictsContext)
state := ctx.GetState()
if state == nil {
return 0, errors.New("Expected patch explorer to be activated")
}
startIdx, _ := state.GetSelectedRange()
return startIdx, nil
},
)
} }
func (self *Views) Commits() *ViewDriver { func (self *Views) Commits() *ViewDriver {

View file

@ -260,6 +260,7 @@ var tests = []*components.IntegrationTest{
ui.DoublePopup, ui.DoublePopup,
ui.EmptyMenu, ui.EmptyMenu,
ui.OpenLinkFailure, ui.OpenLinkFailure,
ui.RangeSelect,
ui.SwitchTabFromMenu, ui.SwitchTabFromMenu,
undo.UndoCheckoutAndDrop, undo.UndoCheckoutAndDrop,
undo.UndoDrop, undo.UndoDrop,

View file

@ -0,0 +1,142 @@
package ui
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
// Here's the state machine we need to verify:
// (no range, press 'v') -> sticky range
// (no range, press arrow) -> no range
// (no range, press shift+arrow) -> nonsticky range
// (sticky range, press 'v') -> no range
// (sticky range, press arrow) -> sticky range
// (sticky range, press shift+arrow) -> nonsticky range
// (nonsticky range, press 'v') -> no range
// (nonsticky range, press arrow) -> no range
// (nonsticky range, press shift+arrow) -> nonsticky range
// Importantly, if you press 'v' when in a nonsticky range, it clears the range,
// so no matter which mode you're in, 'v' will cancel the range.
// And, if you press shift+up/down when in a sticky range, it switches to a non-
// sticky range, meaning if you then press up/down without shift, it clears
// the range.
var RangeSelect = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Verify range select works as expected in list views and in patch explorer views",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
// We're testing the commits view as our representative list context,
// as well as the staging view, and we're using the exact same code to test
// both to ensure they have the exact same behaviour (they are currently implemented
// separately)
// In both views we're going to have 10 lines starting from 'line 1' going down to
// 'line 10'.
fileContent := ""
total := 10
for i := 1; i <= total; i++ {
remaining := total - i + 1
// Commits are displayed in reverse order so to we need to create them in reverse to have them appear as 'line 1', 'line 2' etc.
shell.EmptyCommit(fmt.Sprintf("line %d", remaining))
fileContent = fmt.Sprintf("%sline %d\n", fileContent, i)
}
shell.CreateFile("file1", fileContent)
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
assertRangeSelectBehaviour := func(v *ViewDriver) {
v.
SelectedLines(
Contains("line 1"),
).
// (no range, press 'v') -> sticky range
Press(keys.Universal.ToggleRangeSelect).
SelectedLines(
Contains("line 1"),
).
// (sticky range, press arrow) -> sticky range
SelectNextItem().
SelectedLines(
Contains("line 1"),
Contains("line 2"),
).
// (sticky range, press 'v') -> no range
Press(keys.Universal.ToggleRangeSelect).
SelectedLines(
Contains("line 2"),
).
// (no range, press arrow) -> no range
SelectPreviousItem().
SelectedLines(
Contains("line 1"),
).
// (no range, press shift+arrow) -> nonsticky range
Press(keys.Universal.RangeSelectDown).
SelectedLines(
Contains("line 1"),
Contains("line 2"),
).
// (nonsticky range, press shift+arrow) -> nonsticky range
Press(keys.Universal.RangeSelectDown).
SelectedLines(
Contains("line 1"),
Contains("line 2"),
Contains("line 3"),
).
Press(keys.Universal.RangeSelectUp).
SelectedLines(
Contains("line 1"),
Contains("line 2"),
).
// (nonsticky range, press arrow) -> no range
SelectNextItem().
SelectedLines(
Contains("line 3"),
).
Press(keys.Universal.ToggleRangeSelect).
SelectedLines(
Contains("line 3"),
).
SelectNextItem().
SelectedLines(
Contains("line 3"),
Contains("line 4"),
).
// (sticky range, press shift+arrow) -> nonsticky range
Press(keys.Universal.RangeSelectDown).
SelectedLines(
Contains("line 3"),
Contains("line 4"),
Contains("line 5"),
).
SelectNextItem().
SelectedLines(
Contains("line 6"),
).
Press(keys.Universal.RangeSelectDown).
SelectedLines(
Contains("line 6"),
Contains("line 7"),
).
// (nonsticky range, press 'v') -> no range
Press(keys.Universal.ToggleRangeSelect).
SelectedLines(
Contains("line 7"),
)
}
assertRangeSelectBehaviour(t.Views().Commits().Focus())
t.Views().Files().
Focus().
SelectedLine(
Contains("file1"),
).
PressEnter()
assertRangeSelectBehaviour(t.Views().Staging().IsFocused())
},
})

View file

@ -30,9 +30,6 @@ var (
// SelectedLineBgColor is the background color for the selected line // SelectedLineBgColor is the background color for the selected line
SelectedLineBgColor = style.New() SelectedLineBgColor = style.New()
// SelectedRangeBgColor is the background color of the selected range of lines
SelectedRangeBgColor = style.New()
// CherryPickedCommitColor is the text style when cherry picking a commit // CherryPickedCommitColor is the text style when cherry picking a commit
CherryPickedCommitTextStyle = style.New() CherryPickedCommitTextStyle = style.New()
@ -52,7 +49,6 @@ func UpdateTheme(themeConfig config.ThemeConfig) {
InactiveBorderColor = GetGocuiStyle(themeConfig.InactiveBorderColor) InactiveBorderColor = GetGocuiStyle(themeConfig.InactiveBorderColor)
SearchingActiveBorderColor = GetGocuiStyle(themeConfig.SearchingActiveBorderColor) SearchingActiveBorderColor = GetGocuiStyle(themeConfig.SearchingActiveBorderColor)
SelectedLineBgColor = GetTextStyle(themeConfig.SelectedLineBgColor, true) SelectedLineBgColor = GetTextStyle(themeConfig.SelectedLineBgColor, true)
SelectedRangeBgColor = GetTextStyle(themeConfig.SelectedRangeBgColor, true)
cherryPickedCommitBgTextStyle := GetTextStyle(themeConfig.CherryPickedCommitBgColor, true) cherryPickedCommitBgTextStyle := GetTextStyle(themeConfig.CherryPickedCommitBgColor, true)
cherryPickedCommitFgTextStyle := GetTextStyle(themeConfig.CherryPickedCommitFgColor, false) cherryPickedCommitFgTextStyle := GetTextStyle(themeConfig.CherryPickedCommitFgColor, false)

View file

@ -176,18 +176,6 @@
"blue" "blue"
] ]
}, },
"selectedRangeBgColor": {
"items": {
"type": "string"
},
"type": "array",
"minItems": 1,
"uniqueItems": true,
"description": "Background color of selected range\nSee https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line",
"default": [
"blue"
]
},
"cherryPickedCommitFgColor": { "cherryPickedCommitFgColor": {
"items": { "items": {
"type": "string" "type": "string"
@ -667,6 +655,18 @@
"type": "string", "type": "string",
"default": "\u003e" "default": "\u003e"
}, },
"toggleRangeSelect": {
"type": "string",
"default": "v"
},
"rangeSelectDown": {
"type": "string",
"default": "\u003cs-down\u003e"
},
"rangeSelectUp": {
"type": "string",
"default": "\u003cs-up\u003e"
},
"prevBlock": { "prevBlock": {
"type": "string", "type": "string",
"default": "\u003cleft\u003e" "default": "\u003cleft\u003e"

View file

@ -10,8 +10,6 @@ gui:
- bold - bold
inactiveBorderColor: inactiveBorderColor:
- black - black
SelectedRangeBgcolor:
- reverse
# Not important in tests but it creates clutter in demos # Not important in tests but it creates clutter in demos
showRandomTip: false showRandomTip: false
animateExplosion: false # takes too long animateExplosion: false # takes too long