From f3eb180f75496637719895902abf76af10b8425f Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 14 Jan 2024 00:18:05 +1100 Subject: [PATCH] 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. --- docs/Config.md | 8 +- docs/keybindings/Keybindings_en.md | 9 +- docs/keybindings/Keybindings_ja.md | 5 +- docs/keybindings/Keybindings_ko.md | 5 +- docs/keybindings/Keybindings_nl.md | 5 +- docs/keybindings/Keybindings_pl.md | 9 +- docs/keybindings/Keybindings_ru.md | 5 +- docs/keybindings/Keybindings_zh-CN.md | 5 +- docs/keybindings/Keybindings_zh-TW.md | 5 +- pkg/commands/patch/format.go | 24 +-- pkg/commands/patch/patch_builder.go | 4 +- pkg/config/user_config.go | 4 - pkg/gui/context/merge_conflicts_context.go | 27 +++- pkg/gui/context/patch_explorer_context.go | 15 +- pkg/gui/controllers/files_controller.go | 2 +- .../helpers/merge_conflicts_helper.go | 10 +- .../controllers/merge_conflicts_controller.go | 14 +- pkg/gui/layout.go | 10 -- pkg/gui/mergeconflicts/rendering.go | 10 +- pkg/gui/patch_exploring/state.go | 4 - pkg/gui/views.go | 6 +- pkg/integration/components/view_driver.go | 88 +++-------- pkg/integration/components/views.go | 95 +----------- pkg/integration/tests/test_list.go | 1 + pkg/integration/tests/ui/range_select.go | 142 ++++++++++++++++++ pkg/theme/theme.go | 4 - schema/config.json | 24 +-- test/default_test_config/config.yml | 2 - 28 files changed, 255 insertions(+), 287 deletions(-) create mode 100644 pkg/integration/tests/ui/range_select.go diff --git a/docs/Config.md b/docs/Config.md index 8b8b62c11..966fce0f4 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -57,8 +57,6 @@ gui: - blue selectedLineBgColor: - blue # set to `default` to have no background colour - selectedRangeBgColor: - - blue cherryPickedCommitBgColor: - cyan cherryPickedCommitFgColor: @@ -390,15 +388,13 @@ The available attributes are: ## 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 gui: theme: selectedLineBgColor: - default - selectedRangeBgColor: - - default ``` You can also use the reverse attribute like so: @@ -408,8 +404,6 @@ gui: theme: selectedLineBgColor: - reverse - selectedRangeBgColor: - - reverse ``` ## Custom Author Color diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md index 9ffd28746..b96d22ec6 100644 --- a/docs/keybindings/Keybindings_en.md +++ b/docs/keybindings/Keybindings_en.md @@ -37,6 +37,9 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ .: Next page <: Scroll to top >: Scroll to bottom + v: Toggle range select + <s-down>: Range select down + <s-up>: Range select up /: Search the current view by text H: Scroll left L: Scroll right @@ -196,8 +199,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   <left>: Select previous hunk
   <right>: Select next hunk
-  v: Toggle drag select
-  V: Toggle drag select
+  v: Toggle range select
   a: Toggle select hunk
   <c-o>: Copy the selected text to the clipboard
   o: Open file
@@ -212,8 +214,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
 
   <left>: Select previous hunk
   <right>: Select next hunk
-  v: Toggle drag select
-  V: Toggle drag select
+  v: Toggle range select
   a: Toggle select hunk
   <c-o>: Copy the selected text to the clipboard
   o: Open file
diff --git a/docs/keybindings/Keybindings_ja.md b/docs/keybindings/Keybindings_ja.md
index f2f26db5a..4365e3685 100644
--- a/docs/keybindings/Keybindings_ja.md
+++ b/docs/keybindings/Keybindings_ja.md
@@ -37,6 +37,9 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   .: 次のページ
   <: 最上部までスクロール
   >: 最下部までスクロール
+  v: 範囲選択を切り替え
+  <s-down>: Range select down
+  <s-up>: Range select up
   /: 検索を開始
   H: 左スクロール
   L: 右スクロール
@@ -270,7 +273,6 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   <left>: 前のhunkを選択
   <right>: 次のhunkを選択
   v: 範囲選択を切り替え
-  V: 範囲選択を切り替え
   a: Hunk選択を切り替え
   <c-o>: 選択されたテキストをクリップボードにコピー
   o: ファイルを開く
@@ -286,7 +288,6 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   <left>: 前のhunkを選択
   <right>: 次のhunkを選択
   v: 範囲選択を切り替え
-  V: 範囲選択を切り替え
   a: Hunk選択を切り替え
   <c-o>: 選択されたテキストをクリップボードにコピー
   o: ファイルを開く
diff --git a/docs/keybindings/Keybindings_ko.md b/docs/keybindings/Keybindings_ko.md
index 36c045ba1..7d60e1b07 100644
--- a/docs/keybindings/Keybindings_ko.md
+++ b/docs/keybindings/Keybindings_ko.md
@@ -37,6 +37,9 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   .: 다음 페이지
   <: 맨 위로 스크롤 
   >: 맨 아래로 스크롤 
+  v: 드래그 선택 전환
+  <s-down>: Range select down
+  <s-up>: Range select up
   /: 검색 시작
   H: 우 스크롤
   L: 좌 스크롤
@@ -141,7 +144,6 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   <left>: 이전 hunk를 선택
   <right>: 다음 hunk를 선택
   v: 드래그 선택 전환
-  V: 드래그 선택 전환
   a: Toggle select hunk
   <c-o>: 선택한 텍스트를 클립보드에 복사
   o: 파일 닫기
@@ -157,7 +159,6 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   <left>: 이전 hunk를 선택
   <right>: 다음 hunk를 선택
   v: 드래그 선택 전환
-  V: 드래그 선택 전환
   a: Toggle select hunk
   <c-o>: 선택한 텍스트를 클립보드에 복사
   o: 파일 닫기
diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md
index bd78ff694..1a1d432d5 100644
--- a/docs/keybindings/Keybindings_nl.md
+++ b/docs/keybindings/Keybindings_nl.md
@@ -37,6 +37,9 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   .: Volgende pagina
   <: Scroll naar boven
   >: Scroll naar beneden
+  v: Toggle drag selecteer
+  <s-down>: Range select down
+  <s-up>: Range select up
   /: Start met zoeken
   H: Scroll left
   L: Scroll right
@@ -205,7 +208,6 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   <left>: Selecteer de vorige hunk
   <right>: Selecteer de volgende hunk
   v: Toggle drag selecteer
-  V: Toggle drag selecteer
   a: Toggle selecteer hunk
   <c-o>: Copy the selected text to the clipboard
   o: Open bestand
@@ -266,7 +268,6 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   <left>: Selecteer de vorige hunk
   <right>: Selecteer de volgende hunk
   v: Toggle drag selecteer
-  V: Toggle drag selecteer
   a: Toggle selecteer hunk
   <c-o>: Copy the selected text to the clipboard
   o: Open bestand
diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md
index 048dd490e..3290d6e38 100644
--- a/docs/keybindings/Keybindings_pl.md
+++ b/docs/keybindings/Keybindings_pl.md
@@ -37,6 +37,9 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   .: Next page
   <: Scroll to top
   >: Scroll to bottom
+  v: Toggle range select
+  <s-down>: Range select down
+  <s-up>: Range select up
   /: Search the current view by text
   H: Scroll left
   L: Scroll right
@@ -127,8 +130,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
 
   <left>: Poprzedni kawałek
   <right>: Następny kawałek
-  v: Toggle drag select
-  V: Toggle drag select
+  v: Toggle range select
   a: Toggle select hunk
   <c-o>: Copy the selected text to the clipboard
   o: Otwórz plik
@@ -197,8 +199,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
 
   <left>: Poprzedni kawałek
   <right>: Następny kawałek
-  v: Toggle drag select
-  V: Toggle drag select
+  v: Toggle range select
   a: Toggle select hunk
   <c-o>: Copy the selected text to the clipboard
   o: Otwórz plik
diff --git a/docs/keybindings/Keybindings_ru.md b/docs/keybindings/Keybindings_ru.md
index ba7af912f..76cec5c5f 100644
--- a/docs/keybindings/Keybindings_ru.md
+++ b/docs/keybindings/Keybindings_ru.md
@@ -37,6 +37,9 @@ _Связки клавиш_
   .: Следующая страница
   <: Пролистать наверх
   >: Прокрутить вниз
+  v: Переключить выборку перетаскивания
+  <s-down>: Range select down
+  <s-up>: Range select up
   /: Найти
   H: Прокрутить влево
   L: Прокрутить вправо
@@ -61,7 +64,6 @@ _Связки клавиш_
   <left>: Выбрать предыдущую часть
   <right>: Выбрать следующую часть
   v: Переключить выборку перетаскивания
-  V: Переключить выборку перетаскивания
   a: Переключить выборку частей
   <c-o>: Скопировать выделенный текст в буфер обмена
   o: Открыть файл
@@ -106,7 +108,6 @@ _Связки клавиш_
   <left>: Выбрать предыдущую часть
   <right>: Выбрать следующую часть
   v: Переключить выборку перетаскивания
-  V: Переключить выборку перетаскивания
   a: Переключить выборку частей
   <c-o>: Скопировать выделенный текст в буфер обмена
   o: Открыть файл
diff --git a/docs/keybindings/Keybindings_zh-CN.md b/docs/keybindings/Keybindings_zh-CN.md
index eaa7c725f..333cfada5 100644
--- a/docs/keybindings/Keybindings_zh-CN.md
+++ b/docs/keybindings/Keybindings_zh-CN.md
@@ -37,6 +37,9 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   .: 下一页
   <: 滚动到顶部
   >: 滚动到底部
+  v: 切换拖动选择
+  <s-down>: Range select down
+  <s-up>: Range select up
   /: 开始搜索
   H: 向左滚动
   L: 向右滚动
@@ -229,7 +232,6 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   <left>: 选择上一个区块
   <right>: 选择下一个区块
   v: 切换拖动选择
-  V: 切换拖动选择
   a: 切换选择区块
   <c-o>: 将选中文本复制到剪贴板
   o: 打开文件
@@ -274,7 +276,6 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   <left>: 选择上一个区块
   <right>: 选择下一个区块
   v: 切换拖动选择
-  V: 切换拖动选择
   a: 切换选择区块
   <c-o>: 将选中文本复制到剪贴板
   o: 打开文件
diff --git a/docs/keybindings/Keybindings_zh-TW.md b/docs/keybindings/Keybindings_zh-TW.md
index 9f623678b..81a2c4248 100644
--- a/docs/keybindings/Keybindings_zh-TW.md
+++ b/docs/keybindings/Keybindings_zh-TW.md
@@ -37,6 +37,9 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_
   .: 下一頁
   <: 捲動到頂部
   >: 捲動到底部
+  v: 切換拖曳選擇
+  <s-down>: Range select down
+  <s-up>: Range select up
   /: 開始搜尋
   H: 向左捲動
   L: 向右捲動
@@ -102,7 +105,6 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_
   <left>: 選擇上一段
   <right>: 選擇下一段
   v: 切換拖曳選擇
-  V: 切換拖曳選擇
   a: 切換選擇程式碼塊
   <c-o>: 複製所選文本至剪貼簿
   o: 開啟檔案
@@ -124,7 +126,6 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_
   <left>: 選擇上一段
   <right>: 選擇下一段
   v: 切換拖曳選擇
-  V: 切換拖曳選擇
   a: 切換選擇程式碼塊
   <c-o>: 複製所選文本至剪貼簿
   o: 開啟檔案
diff --git a/pkg/commands/patch/format.go b/pkg/commands/patch/format.go
index d04b6bec1..c61c8ef05 100644
--- a/pkg/commands/patch/format.go
+++ b/pkg/commands/patch/format.go
@@ -14,11 +14,6 @@ type patchPresenter struct {
 	// if true, all following fields are ignored
 	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)
 	incLineIndices *set.Set[int]
 }
@@ -44,11 +39,6 @@ func formatRangePlain(patch *Patch, startIdx int, endIdx int) string {
 }
 
 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)
 	IncLineIndices *set.Set[int]
 }
@@ -63,9 +53,6 @@ func formatView(patch *Patch, opts FormatViewOpts) string {
 	presenter := &patchPresenter{
 		patch:          patch,
 		plain:          false,
-		isFocused:      opts.IsFocused,
-		firstLineIndex: opts.FirstLineIndex,
-		lastLineIndex:  opts.LastLineIndex,
 		incLineIndices: includedLineIndices,
 	}
 	return presenter.format()
@@ -112,7 +99,6 @@ func (self *patchPresenter) format() string {
 				self.formatLineAux(
 					hunk.headerContext,
 					theme.DefaultTextColor,
-					lineIdx,
 					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 {
 	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
 // 'included' means the line has been included in the patch (only applicable when
 // 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 {
 		return str
 	}
 
-	selected := self.isFocused && index >= self.firstLineIndex && index <= self.lastLineIndex
-
-	if selected {
-		textStyle = textStyle.MergeStyle(theme.SelectedRangeBgColor)
-	}
-
 	firstCharStyle := textStyle
 	if included {
 		firstCharStyle = firstCharStyle.MergeStyle(style.BgGreen)
diff --git a/pkg/commands/patch/patch_builder.go b/pkg/commands/patch/patch_builder.go
index 2f350a40b..88f1becc5 100644
--- a/pkg/commands/patch/patch_builder.go
+++ b/pkg/commands/patch/patch_builder.go
@@ -197,9 +197,7 @@ func (p *PatchBuilder) RenderPatchForFile(filename string, plain bool, reverse b
 	if plain {
 		return patch.FormatPlain()
 	} else {
-		return patch.FormatView(FormatViewOpts{
-			IsFocused: false,
-		})
+		return patch.FormatView(FormatViewOpts{})
 	}
 }
 
diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go
index d7bea4f03..a966905d0 100644
--- a/pkg/config/user_config.go
+++ b/pkg/config/user_config.go
@@ -154,9 +154,6 @@ type ThemeConfig struct {
 	// Background color of 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"`
-	// 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
 	CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor" jsonschema:"minItems=1,uniqueItems=true"`
 	// Background color of copied commit
@@ -622,7 +619,6 @@ func GetDefaultConfig() *UserConfig {
 				InactiveBorderColor:        []string{"default"},
 				OptionsTextColor:           []string{"blue"},
 				SelectedLineBgColor:        []string{"blue"},
-				SelectedRangeBgColor:       []string{"blue"},
 				CherryPickedCommitBgColor:  []string{"cyan"},
 				CherryPickedCommitFgColor:  []string{"blue"},
 				MarkedBaseCommitBgColor:    []string{"yellow"},
diff --git a/pkg/gui/context/merge_conflicts_context.go b/pkg/gui/context/merge_conflicts_context.go
index 60aac6e3a..1cfe3c50a 100644
--- a/pkg/gui/context/merge_conflicts_context.go
+++ b/pkg/gui/context/merge_conflicts_context.go
@@ -68,8 +68,8 @@ func (self *MergeConflictsContext) IsUserScrolling() bool {
 	return self.viewModel.userVerticalScrolling
 }
 
-func (self *MergeConflictsContext) RenderAndFocus(isFocused bool) error {
-	self.setContent(isFocused)
+func (self *MergeConflictsContext) RenderAndFocus() error {
+	self.setContent()
 	self.FocusSelection()
 
 	self.c.Render()
@@ -77,30 +77,41 @@ func (self *MergeConflictsContext) RenderAndFocus(isFocused bool) error {
 	return nil
 }
 
-func (self *MergeConflictsContext) Render(isFocused bool) error {
-	self.setContent(isFocused)
+func (self *MergeConflictsContext) Render() error {
+	self.setContent()
 
 	self.c.Render()
 
 	return nil
 }
 
-func (self *MergeConflictsContext) GetContentToRender(isFocused bool) string {
+func (self *MergeConflictsContext) GetContentToRender() string {
 	if self.GetState() == nil {
 		return ""
 	}
 
-	return mergeconflicts.ColoredConflictFile(self.GetState(), isFocused)
+	return mergeconflicts.ColoredConflictFile(self.GetState())
 }
 
-func (self *MergeConflictsContext) setContent(isFocused bool) {
-	self.GetView().SetContent(self.GetContentToRender(isFocused))
+func (self *MergeConflictsContext) setContent() {
+	self.GetView().SetContent(self.GetContentToRender())
 }
 
 func (self *MergeConflictsContext) FocusSelection() {
 	if !self.IsUserScrolling() {
 		_ = 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 {
diff --git a/pkg/gui/context/patch_explorer_context.go b/pkg/gui/context/patch_explorer_context.go
index 6f086e7f4..34f70e2c7 100644
--- a/pkg/gui/context/patch_explorer_context.go
+++ b/pkg/gui/context/patch_explorer_context.go
@@ -115,17 +115,10 @@ func (self *PatchExplorerContext) FocusSelection() {
 
 	_ = view.SetOriginY(newOriginY)
 
-	view.SetCursorY(state.GetSelectedLineIdx() - newOriginY)
-
-	// At present this is just bookkeeping: the reason for setting this would be
-	// so that gocui knows which lines to highlight, but we're currently handling
-	// highlighting ourselves.
-	rangeStartLineIdx, isSelectingRange := state.RangeStartLineIdx()
-	if isSelectingRange {
-		view.SetRangeSelectStart(rangeStartLineIdx)
-	} else {
-		view.CancelRangeSelect()
-	}
+	startIdx, endIdx := state.SelectedRange()
+	// As far as the view is concerned, we are always selecting a range
+	view.SetRangeSelectStart(startIdx)
+	view.SetCursorY(endIdx - newOriginY)
 }
 
 func (self *PatchExplorerContext) GetContentToRender(isFocused bool) string {
diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go
index 7776da5b3..7fb21dda8 100644
--- a/pkg/gui/controllers/files_controller.go
+++ b/pkg/gui/controllers/files_controller.go
@@ -205,7 +205,7 @@ func (self *FilesController) GetOnRenderToMain() func() error {
 				}
 
 				if hasConflicts {
-					return self.c.Helpers().MergeConflicts.Render(false)
+					return self.c.Helpers().MergeConflicts.Render()
 				}
 			}
 
diff --git a/pkg/gui/controllers/helpers/merge_conflicts_helper.go b/pkg/gui/controllers/helpers/merge_conflicts_helper.go
index e6a56bfae..31f7bd5e3 100644
--- a/pkg/gui/controllers/helpers/merge_conflicts_helper.go
+++ b/pkg/gui/controllers/helpers/merge_conflicts_helper.go
@@ -69,14 +69,14 @@ func (self *MergeConflictsHelper) EscapeMerge() error {
 	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)
 	if err != nil {
 		return false, err
 	}
 
 	if hasConflicts {
-		return true, self.context().Render(isFocused)
+		return true, self.context().Render()
 	}
 
 	return false, nil
@@ -100,8 +100,8 @@ func (self *MergeConflictsHelper) context() *context.MergeConflictsContext {
 	return self.c.Contexts().MergeConflicts
 }
 
-func (self *MergeConflictsHelper) Render(isFocused bool) error {
-	content := self.context().GetContentToRender(isFocused)
+func (self *MergeConflictsHelper) Render() error {
+	content := self.context().GetContentToRender()
 
 	var task types.UpdateTask
 	if self.context().IsUserScrolling() {
@@ -127,7 +127,7 @@ func (self *MergeConflictsHelper) RefreshMergeState() error {
 		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 {
 		return self.c.Error(err)
 	}
diff --git a/pkg/gui/controllers/merge_conflicts_controller.go b/pkg/gui/controllers/merge_conflicts_controller.go
index 86f49489c..450cd1816 100644
--- a/pkg/gui/controllers/merge_conflicts_controller.go
+++ b/pkg/gui/controllers/merge_conflicts_controller.go
@@ -145,7 +145,13 @@ func (self *MergeConflictsController) GetOnFocus() func(types.OnFocusOpts) error
 	return func(types.OnFocusOpts) error {
 		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}})
 }
 
-func (self *MergeConflictsController) isFocused() bool {
-	return self.c.CurrentContext().GetKey() == self.context().GetKey()
-}
-
 func (self *MergeConflictsController) withRenderAndFocus(f func() error) func() error {
 	return self.withLock(func() error {
 		if err := f(); err != nil {
 			return err
 		}
 
-		return self.context().RenderAndFocus(self.isFocused())
+		return self.context().RenderAndFocus()
 	})
 }
 
diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go
index 7b43f8aaa..02c74b023 100644
--- a/pkg/gui/layout.go
+++ b/pkg/gui/layout.go
@@ -4,7 +4,6 @@ import (
 	"github.com/jesseduffield/gocui"
 	"github.com/jesseduffield/lazygit/pkg/gui/context"
 	"github.com/jesseduffield/lazygit/pkg/gui/types"
-	"github.com/jesseduffield/lazygit/pkg/theme"
 	"github.com/samber/lo"
 	"golang.org/x/exp/slices"
 )
@@ -143,15 +142,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
 		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()
 	if mainViewWidth != gui.PrevLayout.MainWidth || mainViewHeight != gui.PrevLayout.MainHeight {
 		gui.PrevLayout.MainWidth = mainViewWidth
diff --git a/pkg/gui/mergeconflicts/rendering.go b/pkg/gui/mergeconflicts/rendering.go
index 54fc4e836..e57754e4b 100644
--- a/pkg/gui/mergeconflicts/rendering.go
+++ b/pkg/gui/mergeconflicts/rendering.go
@@ -8,7 +8,7 @@ import (
 	"github.com/jesseduffield/lazygit/pkg/utils"
 )
 
-func ColoredConflictFile(state *State, hasFocus bool) string {
+func ColoredConflictFile(state *State) string {
 	content := state.GetContent()
 	if len(state.conflicts) == 0 {
 		return content
@@ -21,9 +21,6 @@ func ColoredConflictFile(state *State, hasFocus bool) string {
 			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 {
 			conflict, remainingConflicts = shiftConflict(remainingConflicts)
 		}
@@ -35,8 +32,3 @@ func ColoredConflictFile(state *State, hasFocus bool) string {
 func shiftConflict(conflicts []*mergeConflict) (*mergeConflict, []*mergeConflict) {
 	return conflicts[0], conflicts[1:]
 }
-
-func shouldHighlightLine(index int, conflict *mergeConflict, selection Selection) bool {
-	selectionStart, selectionEnd := selection.bounds(conflict)
-	return index >= selectionStart && index <= selectionEnd
-}
diff --git a/pkg/gui/patch_exploring/state.go b/pkg/gui/patch_exploring/state.go
index d54b7ec7c..ccd30d03f 100644
--- a/pkg/gui/patch_exploring/state.go
+++ b/pkg/gui/patch_exploring/state.go
@@ -240,12 +240,8 @@ func (s *State) AdjustSelectedLineIdx(change int) {
 }
 
 func (s *State) RenderForLineIndices(isFocused bool, includedLineIndices []int) string {
-	firstLineIdx, lastLineIdx := s.SelectedRange()
 	includedLineIndicesSet := set.NewFromSlice(includedLineIndices)
 	return s.patch.FormatView(patch.FormatViewOpts{
-		IsFocused:      isFocused,
-		FirstLineIndex: firstLineIdx,
-		LastLineIndex:  lastLineIdx,
 		IncLineIndices: includedLineIndicesSet,
 	})
 }
diff --git a/pkg/gui/views.go b/pkg/gui/views.go
index 1c9748486..be279c9d4 100644
--- a/pkg/gui/views.go
+++ b/pkg/gui/views.go
@@ -91,6 +91,7 @@ func (gui *Gui) createAllViews() error {
 		}
 		(*mapping.viewPtr).FrameRunes = frameRunes
 		(*mapping.viewPtr).FgColor = theme.GocuiDefaultTextColor
+		(*mapping.viewPtr).SelBgColor = theme.GocuiSelectedLineBgColor
 	}
 
 	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.Highlight = false
 	gui.Views.Staging.Wrap = true
 
 	gui.Views.StagingSecondary.Title = gui.c.Tr.StagedChanges
-	gui.Views.StagingSecondary.Highlight = false
 	gui.Views.StagingSecondary.Wrap = true
 
 	gui.Views.PatchBuilding.Title = gui.Tr.Patch
-	gui.Views.PatchBuilding.Highlight = false
 	gui.Views.PatchBuilding.Wrap = true
 
 	gui.Views.PatchBuildingSecondary.Title = gui.Tr.CustomPatch
-	gui.Views.PatchBuildingSecondary.Highlight = false
 	gui.Views.PatchBuildingSecondary.Wrap = true
 
 	gui.Views.MergeConflicts.Title = gui.c.Tr.MergeConflictsTitle
-	gui.Views.MergeConflicts.Highlight = false
 	gui.Views.MergeConflicts.Wrap = false
 
 	gui.Views.Limit.Title = gui.c.Tr.NotEnoughSpace
diff --git a/pkg/integration/components/view_driver.go b/pkg/integration/components/view_driver.go
index b5e985155..437e647be 100644
--- a/pkg/integration/components/view_driver.go
+++ b/pkg/integration/components/view_driver.go
@@ -10,45 +10,24 @@ import (
 
 type ViewDriver struct {
 	// context is prepended to any error messages e.g. 'context: "current view"'
-	context              string
-	getView              func() *gocui.View
-	t                    *TestDriver
-	getSelectedLinesFn   func() ([]string, error)
-	getSelectedRangeFn   func() (int, int, error)
-	getSelectedLineIdxFn func() (int, error)
+	context string
+	getView func() *gocui.View
+	t       *TestDriver
 }
 
-func (self *ViewDriver) getSelectedLines() ([]string, error) {
-	if self.getSelectedLinesFn == nil {
-		view := self.t.gui.View(self.getView().Name())
-
-		return []string{view.SelectedLine()}, nil
-	}
-
-	return self.getSelectedLinesFn()
+func (self *ViewDriver) getSelectedLines() []string {
+	view := self.t.gui.View(self.getView().Name())
+	return view.SelectedLines()
 }
 
-func (self *ViewDriver) getSelectedRange() (int, int, error) {
-	if self.getSelectedRangeFn == nil {
-		view := self.t.gui.View(self.getView().Name())
-		idx := view.SelectedLineIdx()
-
-		return idx, idx, nil
-	}
-
-	return self.getSelectedRangeFn()
+func (self *ViewDriver) getSelectedRange() (int, int) {
+	view := self.t.gui.View(self.getView().Name())
+	return view.SelectedLineRange()
 }
 
-// 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())
-
-		return view.SelectedLineIdx(), nil
-	}
-
-	return self.getSelectedLineIdxFn()
+func (self *ViewDriver) getSelectedLineIdx() int {
+	view := self.t.gui.View(self.getView().Name())
+	return view.SelectedLineIdx()
 }
 
 // asserts that the view has the expected title
@@ -105,7 +84,7 @@ func (self *ViewDriver) ContainsLines(matchers ...*TextMatcher) *ViewDriver {
 		content := self.getView().Buffer()
 		lines := strings.Split(content, "\n")
 
-		startIdx, endIdx, err := self.getSelectedRange()
+		startIdx, endIdx := self.getSelectedRange()
 
 		for i := 0; i < len(lines)-len(matchers)+1; i++ {
 			matches := true
@@ -118,10 +97,6 @@ func (self *ViewDriver) ContainsLines(matchers ...*TextMatcher) *ViewDriver {
 					break
 				}
 				if checkIsSelected {
-					if err != nil {
-						matches = false
-						break
-					}
 					if lineIdx < startIdx || lineIdx > endIdx {
 						matches = false
 						break
@@ -181,10 +156,7 @@ func (self *ViewDriver) SelectedLines(matchers ...*TextMatcher) *ViewDriver {
 	self.validateEnoughLines(matchers)
 
 	self.t.assertWithRetries(func() (bool, string) {
-		selectedLines, err := self.getSelectedLines()
-		if err != nil {
-			return false, err.Error()
-		}
+		selectedLines := self.getSelectedLines()
 
 		selectedContent := strings.Join(selectedLines, "\n")
 		expectedContent := expectedContentFromMatchers(matchers)
@@ -251,19 +223,13 @@ func (self *ViewDriver) assertLines(offset int, matchers ...*TextMatcher) *ViewD
 
 		if checkIsSelected {
 			self.t.assertWithRetries(func() (bool, string) {
-				startIdx, endIdx, err := self.getSelectedRange()
-				if err != nil {
-					return false, err.Error()
-				}
+				startIdx, endIdx := self.getSelectedRange()
 
 				if lineIdx < startIdx || lineIdx > endIdx {
 					if startIdx == endIdx {
 						return false, fmt.Sprintf("Unexpected selected line index in view '%s'. Expected %d, got %d", view.Name(), lineIdx, startIdx)
 					} else {
-						lines, err := self.getSelectedLines()
-						if err != nil {
-							return false, err.Error()
-						}
+						lines := self.getSelectedLines()
 						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
 }
 
-// asserts on the selected line of the view. If your view has multiple lines selected,
-// but also has a concept of a cursor position, this will assert on the line that
-// the cursor is on. Otherwise it will assert on the first line of the selection.
+// asserts on the selected line of the view. If you are selecting a range,
+// you should use the SelectedLines method instead.
 func (self *ViewDriver) SelectedLine(matcher *TextMatcher) *ViewDriver {
 	self.t.assertWithRetries(func() (bool, string) {
-		selectedLineIdx, err := self.getSelectedLineIdx()
-		if err != nil {
-			return false, err.Error()
-		}
+		selectedLineIdx := self.getSelectedLineIdx()
 
 		viewLines := self.getView().BufferLines()
 
@@ -480,11 +442,7 @@ func (self *ViewDriver) NavigateToLine(matcher *TextMatcher) *ViewDriver {
 		}
 	})
 
-	selectedLineIdx, err := self.getSelectedLineIdx()
-	if err != nil {
-		self.t.fail(err.Error())
-		return self
-	}
+	selectedLineIdx := self.getSelectedLineIdx()
 	if selectedLineIdx == matchIndex {
 		return self.SelectedLine(matcher)
 	}
@@ -507,11 +465,7 @@ func (self *ViewDriver) NavigateToLine(matcher *TextMatcher) *ViewDriver {
 
 	for i := 0; i < maxNumKeyPresses; i++ {
 		keyPress()
-		idx, err := self.getSelectedLineIdx()
-		if err != nil {
-			self.t.fail(err.Error())
-			return self
-		}
+		idx := self.getSelectedLineIdx()
 		if ok, _ := matcher.test(lines[idx]); ok {
 			return self
 		}
diff --git a/pkg/integration/components/views.go b/pkg/integration/components/views.go
index c7655da74..edb2b85b6 100644
--- a/pkg/integration/components/views.go
+++ b/pkg/integration/components/views.go
@@ -2,11 +2,8 @@ package components
 
 import (
 	"fmt"
-	"strings"
 
-	"github.com/go-errors/errors"
 	"github.com/jesseduffield/gocui"
-	"github.com/jesseduffield/lazygit/pkg/gui/context"
 )
 
 type Views struct {
@@ -30,95 +27,19 @@ func (self *Views) Secondary() *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{
-		context:              fmt.Sprintf("%s view", viewName),
-		getView:              func() *gocui.View { return self.t.gui.View(viewName) },
-		getSelectedLinesFn:   getSelectedLinesFn,
-		getSelectedRangeFn:   getSelectedLineRangeFn,
-		getSelectedLineIdxFn: getSelectedLineIdxFn,
-		t:                    self.t,
+		context: fmt.Sprintf("%s view", viewName),
+		getView: func() *gocui.View { return self.t.gui.View(viewName) },
+		t:       self.t,
 	}
 }
 
-func (self *Views) MergeConflicts() *ViewDriver {
-	viewName := "mergeConflicts"
-	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")
+func (self *Views) patchExplorerViewByName(viewName string) *ViewDriver {
+	return self.regularView(viewName)
+}
 
-			return selectedContent, nil
-		},
-		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) MergeConflicts() *ViewDriver {
+	return self.regularView("mergeConflicts")
 }
 
 func (self *Views) Commits() *ViewDriver {
diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go
index a0e58d6fb..5b8e21e4b 100644
--- a/pkg/integration/tests/test_list.go
+++ b/pkg/integration/tests/test_list.go
@@ -260,6 +260,7 @@ var tests = []*components.IntegrationTest{
 	ui.DoublePopup,
 	ui.EmptyMenu,
 	ui.OpenLinkFailure,
+	ui.RangeSelect,
 	ui.SwitchTabFromMenu,
 	undo.UndoCheckoutAndDrop,
 	undo.UndoDrop,
diff --git a/pkg/integration/tests/ui/range_select.go b/pkg/integration/tests/ui/range_select.go
new file mode 100644
index 000000000..84441701c
--- /dev/null
+++ b/pkg/integration/tests/ui/range_select.go
@@ -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())
+	},
+})
diff --git a/pkg/theme/theme.go b/pkg/theme/theme.go
index 20832f6d3..78be46fb6 100644
--- a/pkg/theme/theme.go
+++ b/pkg/theme/theme.go
@@ -30,9 +30,6 @@ var (
 	// SelectedLineBgColor is the background color for the selected line
 	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
 	CherryPickedCommitTextStyle = style.New()
 
@@ -52,7 +49,6 @@ func UpdateTheme(themeConfig config.ThemeConfig) {
 	InactiveBorderColor = GetGocuiStyle(themeConfig.InactiveBorderColor)
 	SearchingActiveBorderColor = GetGocuiStyle(themeConfig.SearchingActiveBorderColor)
 	SelectedLineBgColor = GetTextStyle(themeConfig.SelectedLineBgColor, true)
-	SelectedRangeBgColor = GetTextStyle(themeConfig.SelectedRangeBgColor, true)
 
 	cherryPickedCommitBgTextStyle := GetTextStyle(themeConfig.CherryPickedCommitBgColor, true)
 	cherryPickedCommitFgTextStyle := GetTextStyle(themeConfig.CherryPickedCommitFgColor, false)
diff --git a/schema/config.json b/schema/config.json
index 1239872ee..6d322ef3d 100644
--- a/schema/config.json
+++ b/schema/config.json
@@ -176,18 +176,6 @@
                 "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": {
               "items": {
                 "type": "string"
@@ -667,6 +655,18 @@
               "type": "string",
               "default": "\u003e"
             },
+            "toggleRangeSelect": {
+              "type": "string",
+              "default": "v"
+            },
+            "rangeSelectDown": {
+              "type": "string",
+              "default": "\u003cs-down\u003e"
+            },
+            "rangeSelectUp": {
+              "type": "string",
+              "default": "\u003cs-up\u003e"
+            },
             "prevBlock": {
               "type": "string",
               "default": "\u003cleft\u003e"
diff --git a/test/default_test_config/config.yml b/test/default_test_config/config.yml
index 4f481f0bc..5a822ae77 100644
--- a/test/default_test_config/config.yml
+++ b/test/default_test_config/config.yml
@@ -10,8 +10,6 @@ gui:
     - bold
     inactiveBorderColor:
     - black
-    SelectedRangeBgcolor:
-    - reverse
   # Not important in tests but it creates clutter in demos
   showRandomTip: false
   animateExplosion: false # takes too long