From 24a4302c528e3a11b30855c006669de1adf9e1d4 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 7 Jan 2024 19:44:19 +1100 Subject: [PATCH] Add range selection ability on list contexts This adds range select ability in two ways: 1) Sticky: like what we already have with the staging view i.e. press v then use arrow keys 2) Non-sticky: where you just use shift+up/down to expand the range The state machine works like this: (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 --- docs/Config.md | 5 +- docs/README.md | 3 +- docs/Range_Select.md | 14 ++ go.mod | 6 +- go.sum | 10 +- pkg/config/user_config.go | 22 +-- pkg/gui/context/list_context_trait.go | 10 +- pkg/gui/context/traits/list_cursor.go | 103 ++++++++++-- pkg/gui/context/view_trait.go | 8 + pkg/gui/controllers/list_controller.go | 46 +++++- .../controllers/patch_explorer_controller.go | 9 +- pkg/gui/filetree/file_tree_view_model.go | 2 +- pkg/gui/keybindings/keybindings.go | 122 +++++++------- pkg/gui/types/context.go | 10 +- pkg/i18n/chinese.go | 2 +- pkg/i18n/dutch.go | 2 +- pkg/i18n/english.go | 8 +- pkg/i18n/japanese.go | 2 +- pkg/i18n/korean.go | 2 +- pkg/i18n/russian.go | 2 +- pkg/i18n/traditional_chinese.go | 2 +- .../tests/commit/stage_range_of_lines.go | 2 +- pkg/integration/tests/demo/stage_lines.go | 2 +- .../patch_building/specific_selection.go | 2 +- pkg/integration/tests/staging/stage_ranges.go | 6 +- pkg/utils/utils.go | 7 + schema/config.json | 10 +- .../jesseduffield/gocui/keybinding.go | 48 +++--- .../jesseduffield/gocui/tcell_driver.go | 8 + vendor/github.com/jesseduffield/gocui/view.go | 150 ++++++++++++++---- vendor/golang.org/x/sys/unix/mkerrors.sh | 37 +++-- vendor/golang.org/x/sys/unix/zerrors_linux.go | 54 +++++++ .../x/sys/unix/zsyscall_openbsd_386.go | 2 - .../x/sys/unix/zsyscall_openbsd_amd64.go | 2 - .../x/sys/unix/zsyscall_openbsd_arm.go | 2 - .../x/sys/unix/zsyscall_openbsd_arm64.go | 2 - .../x/sys/unix/zsyscall_openbsd_mips64.go | 2 - .../x/sys/unix/zsyscall_openbsd_ppc64.go | 2 - .../x/sys/unix/zsyscall_openbsd_riscv64.go | 2 - .../x/sys/windows/syscall_windows.go | 1 + .../x/sys/windows/zsyscall_windows.go | 9 ++ vendor/modules.txt | 6 +- 42 files changed, 533 insertions(+), 213 deletions(-) create mode 100644 docs/Range_Select.md diff --git a/docs/Config.md b/docs/Config.md index 8b37c6aaa..8b8b62c11 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -201,6 +201,9 @@ keybinding: toggleWhitespaceInDiffView: '' increaseContextInDiffView: '}' decreaseContextInDiffView: '{' + toggleRangeSelect: 'v' + rangeSelectUp: '' + rangeSelectDown: '' status: checkForUpdate: 'u' recentRepos: '' @@ -263,8 +266,6 @@ keybinding: commitFiles: checkoutCommitFile: 'c' main: - toggleDragSelect: 'v' - toggleDragSelect-alt: 'V' toggleSelectHunk: 'a' pickBothHunks: 'b' submodules: diff --git a/docs/README.md b/docs/README.md index 604c8a07a..d840637a0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,8 +3,9 @@ * [Configuration](./Config.md). * [Custom Commands](./Custom_Command_Keybindings.md) * [Custom Pagers](./Custom_Pagers.md) +* [Dev docs](./dev) * [Keybindings](./keybindings) * [Undo/Redo](./Undoing.md) +* [Range Select](./Range_Select.md) * [Searching/Filtering](./Searching.md) * [Stacked Branches](./Stacked_Branches.md) -* [Dev docs](./dev) diff --git a/docs/Range_Select.md b/docs/Range_Select.md new file mode 100644 index 000000000..e46c26897 --- /dev/null +++ b/docs/Range_Select.md @@ -0,0 +1,14 @@ +# Range Select + +Some actions can be performed on a range of contiguous items. For example: +* staging multiple files at once +* squashing multiple commits at once +* copying (for cherry-pick) multiple commits at once + +There are two ways to select a range of items: +1. Sticky range select: Press 'v' to toggle range select, then expand the selection using the up/down arrow key. To reset the selection, press 'v' again. +2. Non-sticky range select: Press shift+up or shift+down to expand the selection. To reset the selection, press up/down without shift. + +The sticky option will be more familiar to vim users, and the second option will feel more natural to users who aren't used to doing things in a modal way. + +In order to perform an action on a range of items, simply press the normal key for that action. If the action only works on individual items, it will raise an error. This is a new feature and the plan is to incrementally support range select for more and more actions. If there is an action you would like to support range select which currently does not, please raise an issue in the repo. diff --git a/go.mod b/go.mod index 0d709c370..182b42088 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/integrii/flaggy v1.4.0 github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d - github.com/jesseduffield/gocui v0.3.1-0.20240103192639-2874168c14db + github.com/jesseduffield/gocui v0.3.1-0.20240118234343-2d41754af383 github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e @@ -74,8 +74,8 @@ require ( github.com/xanzy/ssh-agent v0.2.1 // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 1762cf605..46d48d48e 100644 --- a/go.sum +++ b/go.sum @@ -187,8 +187,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o= -github.com/jesseduffield/gocui v0.3.1-0.20240103192639-2874168c14db h1:ihJdYk85/XQLGiG3b6m8P2z+RUohRMtPmX74YR9IT8s= -github.com/jesseduffield/gocui v0.3.1-0.20240103192639-2874168c14db/go.mod h1:9zkyjnUmdL3+sUknJrQy/3HweUu8mVln/3J2wRF/l8M= +github.com/jesseduffield/gocui v0.3.1-0.20240118234343-2d41754af383 h1:twcgVo+K7UTXwrsNtlCvTi8AyCp7CuBX//+j4wWkivQ= +github.com/jesseduffield/gocui v0.3.1-0.20240118234343-2d41754af383/go.mod h1:9zkyjnUmdL3+sUknJrQy/3HweUu8mVln/3J2wRF/l8M= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo= github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY= @@ -469,13 +469,15 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index 602dc54bf..d7bea4f03 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -304,6 +304,9 @@ type KeybindingUniversalConfig struct { ScrollRight string `yaml:"scrollRight"` GotoTop string `yaml:"gotoTop"` GotoBottom string `yaml:"gotoBottom"` + ToggleRangeSelect string `yaml:"toggleRangeSelect"` + RangeSelectDown string `yaml:"rangeSelectDown"` + RangeSelectUp string `yaml:"rangeSelectUp"` PrevBlock string `yaml:"prevBlock"` NextBlock string `yaml:"nextBlock"` PrevBlockAlt string `yaml:"prevBlock-alt"` @@ -441,11 +444,9 @@ type KeybindingCommitFilesConfig struct { } type KeybindingMainConfig struct { - ToggleDragSelect string `yaml:"toggleDragSelect"` - ToggleDragSelectAlt string `yaml:"toggleDragSelect-alt"` - ToggleSelectHunk string `yaml:"toggleSelectHunk"` - PickBothHunks string `yaml:"pickBothHunks"` - EditSelectHunk string `yaml:"editSelectHunk"` + ToggleSelectHunk string `yaml:"toggleSelectHunk"` + PickBothHunks string `yaml:"pickBothHunks"` + EditSelectHunk string `yaml:"editSelectHunk"` } type KeybindingSubmodulesConfig struct { @@ -704,6 +705,9 @@ func GetDefaultConfig() *UserConfig { ScrollRight: "L", GotoTop: "<", GotoBottom: ">", + ToggleRangeSelect: "v", + RangeSelectDown: "", + RangeSelectUp: "", PrevBlock: "", NextBlock: "", PrevBlockAlt: "h", @@ -833,11 +837,9 @@ func GetDefaultConfig() *UserConfig { CheckoutCommitFile: "c", }, Main: KeybindingMainConfig{ - ToggleDragSelect: "v", - ToggleDragSelectAlt: "V", - ToggleSelectHunk: "a", - PickBothHunks: "b", - EditSelectHunk: "E", + ToggleSelectHunk: "a", + PickBothHunks: "b", + EditSelectHunk: "E", }, Submodules: KeybindingSubmodulesConfig{ Init: "i", diff --git a/pkg/gui/context/list_context_trait.go b/pkg/gui/context/list_context_trait.go index ca3b3254f..c4f0e2549 100644 --- a/pkg/gui/context/list_context_trait.go +++ b/pkg/gui/context/list_context_trait.go @@ -32,6 +32,14 @@ func (self *ListContextTrait) FocusLine() { self.GetViewTrait().FocusPoint( self.ModelIndexToViewIndex(self.list.GetSelectedLineIdx())) + selectRangeIndex, isSelectingRange := self.list.GetRangeStartIdx() + if isSelectingRange { + selectRangeIndex = self.ModelIndexToViewIndex(selectRangeIndex) + self.GetViewTrait().SetRangeSelectStart(selectRangeIndex) + } else { + self.GetViewTrait().CancelRangeSelect() + } + // If FocusPoint() caused the view to scroll (because the selected line // was out of view before), we need to rerender the view port again. // This can happen when pressing , or . to scroll by pages, or < or > to @@ -84,7 +92,7 @@ func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) error // OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view func (self *ListContextTrait) HandleRender() error { - self.list.RefreshSelectedIdx() + self.list.ClampSelection() content := self.renderLines(-1, -1) self.GetViewTrait().SetContent(content) self.c.Render() diff --git a/pkg/gui/context/traits/list_cursor.go b/pkg/gui/context/traits/list_cursor.go index 9e86d5139..647f2a36b 100644 --- a/pkg/gui/context/traits/list_cursor.go +++ b/pkg/gui/context/traits/list_cursor.go @@ -9,13 +9,34 @@ type HasLength interface { Len() int } +type RangeSelectMode int + +const ( + // None means we are not selecting a range + RangeSelectModeNone RangeSelectMode = iota + // Sticky range select is started by pressing 'v', then the range is expanded + // when you move up or down. It is cancelled by pressing 'v' again. + RangeSelectModeSticky + // Nonsticky range select is started by pressing shift+arrow and cancelled + // when pressing up/down without shift, or by pressing 'v' + RangeSelectModeNonSticky +) + type ListCursor struct { - selectedIdx int - list HasLength + selectedIdx int + rangeSelectMode RangeSelectMode + // value is ignored when rangeSelectMode is RangeSelectModeNone + rangeStartIdx int + list HasLength } func NewListCursor(list HasLength) *ListCursor { - return &ListCursor{selectedIdx: 0, list: list} + return &ListCursor{ + selectedIdx: 0, + rangeStartIdx: 0, + rangeSelectMode: RangeSelectModeNone, + list: list, + } } var _ types.IListCursor = (*ListCursor)(nil) @@ -25,24 +46,86 @@ func (self *ListCursor) GetSelectedLineIdx() int { } func (self *ListCursor) SetSelectedLineIdx(value int) { + self.selectedIdx = self.clampValue(value) +} + +func (self *ListCursor) clampValue(value int) int { clampedValue := -1 if self.list.Len() > 0 { clampedValue = utils.Clamp(value, 0, self.list.Len()-1) } - self.selectedIdx = clampedValue + return clampedValue } -// moves the cursor up or down by the given amount -func (self *ListCursor) MoveSelectedLine(delta int) { - self.SetSelectedLineIdx(self.selectedIdx + delta) +// Moves the cursor up or down by the given amount. +// If we are in non-sticky range select mode, this will cancel the range select +func (self *ListCursor) MoveSelectedLine(change int) { + if self.rangeSelectMode == RangeSelectModeNonSticky { + self.CancelRangeSelect() + } + + self.SetSelectedLineIdx(self.selectedIdx + change) } -// to be called when the model might have shrunk so that our selection is not not out of bounds -func (self *ListCursor) RefreshSelectedIdx() { - self.SetSelectedLineIdx(self.selectedIdx) +// Moves the cursor up or down by the given amount, and also moves the range start +// index by the same amount +func (self *ListCursor) MoveSelection(delta int) { + self.selectedIdx = self.clampValue(self.selectedIdx + delta) + if self.IsSelectingRange() { + self.rangeStartIdx = self.clampValue(self.rangeStartIdx + delta) + } +} + +// To be called when the model might have shrunk so that our selection is not out of bounds +func (self *ListCursor) ClampSelection() { + self.selectedIdx = self.clampValue(self.selectedIdx) + self.rangeStartIdx = self.clampValue(self.rangeStartIdx) } func (self *ListCursor) Len() int { return self.list.Len() } + +func (self *ListCursor) GetRangeStartIdx() (int, bool) { + if self.IsSelectingRange() { + return self.rangeStartIdx, true + } + + return 0, false +} + +func (self *ListCursor) CancelRangeSelect() { + self.rangeSelectMode = RangeSelectModeNone +} + +func (self *ListCursor) IsSelectingRange() bool { + return self.rangeSelectMode != RangeSelectModeNone +} + +func (self *ListCursor) GetSelectionRange() (int, int) { + if self.IsSelectingRange() { + return utils.MinMax(self.selectedIdx, self.rangeStartIdx) + } + + return self.selectedIdx, self.selectedIdx +} + +func (self *ListCursor) ToggleStickyRange() { + if self.IsSelectingRange() { + self.CancelRangeSelect() + } else { + self.rangeStartIdx = self.selectedIdx + self.rangeSelectMode = RangeSelectModeSticky + } +} + +func (self *ListCursor) ExpandNonStickyRange(change int) { + if !self.IsSelectingRange() { + self.rangeStartIdx = self.selectedIdx + } + + self.rangeSelectMode = RangeSelectModeNonSticky + + self.SetSelectedLineIdx(self.selectedIdx + change) +} diff --git a/pkg/gui/context/view_trait.go b/pkg/gui/context/view_trait.go index bf8a49e43..1179a8b14 100644 --- a/pkg/gui/context/view_trait.go +++ b/pkg/gui/context/view_trait.go @@ -21,6 +21,14 @@ func (self *ViewTrait) FocusPoint(yIdx int) { self.view.FocusPoint(self.view.OriginX(), yIdx) } +func (self *ViewTrait) SetRangeSelectStart(yIdx int) { + self.view.SetRangeSelectStart(yIdx) +} + +func (self *ViewTrait) CancelRangeSelect() { + self.view.CancelRangeSelect() +} + func (self *ViewTrait) SetViewPortContent(content string) { _, y := self.view.Origin() self.view.OverwriteLines(y, content) diff --git a/pkg/gui/controllers/list_controller.go b/pkg/gui/controllers/list_controller.go index 025561993..1f3c743bc 100644 --- a/pkg/gui/controllers/list_controller.go +++ b/pkg/gui/controllers/list_controller.go @@ -71,9 +71,25 @@ func (self *ListController) scrollHorizontal(scrollFunc func()) error { } func (self *ListController) handleLineChange(change int) error { - before := self.context.GetList().GetSelectedLineIdx() - self.context.GetList().MoveSelectedLine(change) - after := self.context.GetList().GetSelectedLineIdx() + return self.handleLineChangeAux( + self.context.GetList().MoveSelectedLine, change, + ) +} + +func (self *ListController) HandleRangeSelectChange(change int) error { + return self.handleLineChangeAux( + self.context.GetList().ExpandNonStickyRange, change, + ) +} + +func (self *ListController) handleLineChangeAux(f func(int), change int) error { + list := self.context.GetList() + + rangeBefore := list.IsSelectingRange() + before := list.GetSelectedLineIdx() + f(change) + rangeAfter := list.IsSelectingRange() + after := list.GetSelectedLineIdx() if err := self.pushContextIfNotFocused(); err != nil { return err @@ -81,7 +97,8 @@ func (self *ListController) handleLineChange(change int) error { // doing this check so that if we're holding the up key at the start of the list // we're not constantly re-rendering the main view. - if before != after { + cursorMoved := before != after + if cursorMoved { if change == -1 { checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig, self.context.ModelIndexToViewIndex(before), self.context.ModelIndexToViewIndex(after)) @@ -89,7 +106,9 @@ func (self *ListController) handleLineChange(change int) error { checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig, self.context.ModelIndexToViewIndex(before), self.context.ModelIndexToViewIndex(after)) } + } + if cursorMoved || rangeBefore != rangeAfter { return self.context.HandleFocus(types.OnFocusOpts{}) } @@ -112,6 +131,22 @@ func (self *ListController) HandleGotoBottom() error { return self.handleLineChange(self.context.GetList().Len()) } +func (self *ListController) HandleToggleRangeSelect() error { + list := self.context.GetList() + + list.ToggleStickyRange() + + return self.context.HandleFocus(types.OnFocusOpts{}) +} + +func (self *ListController) HandleRangeSelectDown() error { + return self.HandleRangeSelectChange(1) +} + +func (self *ListController) HandleRangeSelectUp() error { + return self.HandleRangeSelectChange(-1) +} + func (self *ListController) HandleClick(opts gocui.ViewMouseBindingOpts) error { prevSelectedLineIdx := self.context.GetList().GetSelectedLineIdx() newSelectedLineIdx := self.context.ViewIndexToModelIndex(opts.Y) @@ -159,6 +194,9 @@ func (self *ListController) GetKeybindings(opts types.KeybindingsOpts) []*types. {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollLeft), Handler: self.HandleScrollLeft}, {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollRight), Handler: self.HandleScrollRight}, {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoBottom), Handler: self.HandleGotoBottom, Description: self.c.Tr.GotoBottom}, + {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ToggleRangeSelect), Handler: self.HandleToggleRangeSelect, Description: self.c.Tr.ToggleRangeSelect}, + {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.RangeSelectDown), Handler: self.HandleRangeSelectDown, Description: self.c.Tr.RangeSelectDown}, + {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.RangeSelectUp), Handler: self.HandleRangeSelectUp, Description: self.c.Tr.RangeSelectUp}, } } diff --git a/pkg/gui/controllers/patch_explorer_controller.go b/pkg/gui/controllers/patch_explorer_controller.go index 6f193cf2d..2083c2943 100644 --- a/pkg/gui/controllers/patch_explorer_controller.go +++ b/pkg/gui/controllers/patch_explorer_controller.go @@ -75,14 +75,9 @@ func (self *PatchExplorerController) GetKeybindings(opts types.KeybindingsOpts) Handler: self.withRenderAndFocus(self.HandleNextHunk), }, { - Key: opts.GetKey(opts.Config.Main.ToggleDragSelect), + Key: opts.GetKey(opts.Config.Universal.ToggleRangeSelect), Handler: self.withRenderAndFocus(self.HandleToggleSelectRange), - Description: self.c.Tr.ToggleDragSelect, - }, - { - Key: opts.GetKey(opts.Config.Main.ToggleDragSelectAlt), - Handler: self.withRenderAndFocus(self.HandleToggleSelectRange), - Description: self.c.Tr.ToggleDragSelect, + Description: self.c.Tr.ToggleRangeSelect, }, { Key: opts.GetKey(opts.Config.Main.ToggleSelectHunk), diff --git a/pkg/gui/filetree/file_tree_view_model.go b/pkg/gui/filetree/file_tree_view_model.go index 547b62b91..f19f74fbc 100644 --- a/pkg/gui/filetree/file_tree_view_model.go +++ b/pkg/gui/filetree/file_tree_view_model.go @@ -85,7 +85,7 @@ func (self *FileTreeViewModel) SetTree() { } } - self.RefreshSelectedIdx() + self.ClampSelection() } // Let's try to find our file again and move the cursor to that. diff --git a/pkg/gui/keybindings/keybindings.go b/pkg/gui/keybindings/keybindings.go index fd8c694cf..da917b82e 100644 --- a/pkg/gui/keybindings/keybindings.go +++ b/pkg/gui/keybindings/keybindings.go @@ -13,66 +13,68 @@ import ( ) var labelByKey = map[gocui.Key]string{ - gocui.KeyF1: "", - gocui.KeyF2: "", - gocui.KeyF3: "", - gocui.KeyF4: "", - gocui.KeyF5: "", - gocui.KeyF6: "", - gocui.KeyF7: "", - gocui.KeyF8: "", - gocui.KeyF9: "", - gocui.KeyF10: "", - gocui.KeyF11: "", - gocui.KeyF12: "", - gocui.KeyInsert: "", - gocui.KeyDelete: "", - gocui.KeyHome: "", - gocui.KeyEnd: "", - gocui.KeyPgup: "", - gocui.KeyPgdn: "", - gocui.KeyArrowUp: "", - gocui.KeyArrowDown: "", - gocui.KeyArrowLeft: "", - gocui.KeyArrowRight: "", - gocui.KeyTab: "", // - gocui.KeyBacktab: "", - gocui.KeyEnter: "", // - gocui.KeyAltEnter: "", - gocui.KeyEsc: "", // , - gocui.KeyBackspace: "", // - gocui.KeyCtrlSpace: "", // , - gocui.KeyCtrlSlash: "", // - gocui.KeySpace: "", - gocui.KeyCtrlA: "", - gocui.KeyCtrlB: "", - gocui.KeyCtrlC: "", - gocui.KeyCtrlD: "", - gocui.KeyCtrlE: "", - gocui.KeyCtrlF: "", - gocui.KeyCtrlG: "", - gocui.KeyCtrlJ: "", - gocui.KeyCtrlK: "", - gocui.KeyCtrlL: "", - gocui.KeyCtrlN: "", - gocui.KeyCtrlO: "", - gocui.KeyCtrlP: "", - gocui.KeyCtrlQ: "", - gocui.KeyCtrlR: "", - gocui.KeyCtrlS: "", - gocui.KeyCtrlT: "", - gocui.KeyCtrlU: "", - gocui.KeyCtrlV: "", - gocui.KeyCtrlW: "", - gocui.KeyCtrlX: "", - gocui.KeyCtrlY: "", - gocui.KeyCtrlZ: "", - gocui.KeyCtrl4: "", // - gocui.KeyCtrl5: "", // - gocui.KeyCtrl6: "", - gocui.KeyCtrl8: "", - gocui.MouseWheelUp: "mouse wheel up", - gocui.MouseWheelDown: "mouse wheel down", + gocui.KeyF1: "", + gocui.KeyF2: "", + gocui.KeyF3: "", + gocui.KeyF4: "", + gocui.KeyF5: "", + gocui.KeyF6: "", + gocui.KeyF7: "", + gocui.KeyF8: "", + gocui.KeyF9: "", + gocui.KeyF10: "", + gocui.KeyF11: "", + gocui.KeyF12: "", + gocui.KeyInsert: "", + gocui.KeyDelete: "", + gocui.KeyHome: "", + gocui.KeyEnd: "", + gocui.KeyPgup: "", + gocui.KeyPgdn: "", + gocui.KeyArrowUp: "", + gocui.KeyShiftArrowUp: "", + gocui.KeyArrowDown: "", + gocui.KeyShiftArrowDown: "", + gocui.KeyArrowLeft: "", + gocui.KeyArrowRight: "", + gocui.KeyTab: "", // + gocui.KeyBacktab: "", + gocui.KeyEnter: "", // + gocui.KeyAltEnter: "", + gocui.KeyEsc: "", // , + gocui.KeyBackspace: "", // + gocui.KeyCtrlSpace: "", // , + gocui.KeyCtrlSlash: "", // + gocui.KeySpace: "", + gocui.KeyCtrlA: "", + gocui.KeyCtrlB: "", + gocui.KeyCtrlC: "", + gocui.KeyCtrlD: "", + gocui.KeyCtrlE: "", + gocui.KeyCtrlF: "", + gocui.KeyCtrlG: "", + gocui.KeyCtrlJ: "", + gocui.KeyCtrlK: "", + gocui.KeyCtrlL: "", + gocui.KeyCtrlN: "", + gocui.KeyCtrlO: "", + gocui.KeyCtrlP: "", + gocui.KeyCtrlQ: "", + gocui.KeyCtrlR: "", + gocui.KeyCtrlS: "", + gocui.KeyCtrlT: "", + gocui.KeyCtrlU: "", + gocui.KeyCtrlV: "", + gocui.KeyCtrlW: "", + gocui.KeyCtrlX: "", + gocui.KeyCtrlY: "", + gocui.KeyCtrlZ: "", + gocui.KeyCtrl4: "", // + gocui.KeyCtrl5: "", // + gocui.KeyCtrl6: "", + gocui.KeyCtrl8: "", + gocui.MouseWheelUp: "mouse wheel up", + gocui.MouseWheelDown: "mouse wheel down", } var keyByLabel = lo.Invert(labelByKey) diff --git a/pkg/gui/types/context.go b/pkg/gui/types/context.go index 82f03c70f..b20d70beb 100644 --- a/pkg/gui/types/context.go +++ b/pkg/gui/types/context.go @@ -163,6 +163,8 @@ type IPatchExplorerContext interface { type IViewTrait interface { FocusPoint(yIdx int) + SetRangeSelectStart(yIdx int) + CancelRangeSelect() SetViewPortContent(content string) SetContent(content string) SetFooter(value string) @@ -223,7 +225,13 @@ type IListCursor interface { GetSelectedLineIdx() int SetSelectedLineIdx(value int) MoveSelectedLine(delta int) - RefreshSelectedIdx() + ClampSelection() + CancelRangeSelect() + GetRangeStartIdx() (int, bool) + GetSelectionRange() (int, int) + IsSelectingRange() bool + ToggleStickyRange() + ExpandNonStickyRange(int) } type IListPanelState interface { diff --git a/pkg/i18n/chinese.go b/pkg/i18n/chinese.go index 234d470db..0482f4046 100644 --- a/pkg/i18n/chinese.go +++ b/pkg/i18n/chinese.go @@ -163,7 +163,7 @@ func chineseTranslationSet() TranslationSet { FileStagingRequirements: `只能暂存跟踪文件的单独行`, StageSelection: `切换行暂存状态`, DiscardSelection: `取消变更 (git reset)`, - ToggleDragSelect: `切换拖动选择`, + ToggleRangeSelect: `切换拖动选择`, ToggleSelectHunk: `切换选择区块`, ToggleSelectionForPatch: `添加/移除 行到补丁`, ToggleStagingPanel: `切换到其他面板`, diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 7abfaa437..79207490b 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -128,7 +128,7 @@ func dutchTranslationSet() TranslationSet { FileStagingRequirements: `Kan alleen individuele lijnen stagen van getrackte bestanden met onstaged veranderingen`, StageSelection: `Toggle lijnen staged / unstaged`, DiscardSelection: `Verwijdert change (git reset)`, - ToggleDragSelect: `Toggle drag selecteer`, + ToggleRangeSelect: `Toggle drag selecteer`, ToggleSelectHunk: `Toggle selecteer hunk`, ToggleSelectionForPatch: `Voeg toe/verwijder lijn(en) in patch`, ToggleStagingPanel: `Ga naar een ander paneel`, diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 8886217c3..ad859dc1d 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -200,7 +200,6 @@ type TranslationSet struct { FileStagingRequirements string StageSelection string DiscardSelection string - ToggleDragSelect string ToggleSelectHunk string ToggleSelectionForPatch string EditHunk string @@ -651,6 +650,9 @@ type TranslationSet struct { QuickStartInteractiveRebase string QuickStartInteractiveRebaseTooltip string CannotQuickStartInteractiveRebase string + ToggleRangeSelect string + RangeSelectUp string + RangeSelectDown string Actions Actions Bisect Bisect Log Log @@ -1033,7 +1035,7 @@ func EnglishTranslationSet() TranslationSet { FileStagingRequirements: `Can only stage individual lines for tracked files`, StageSelection: `Toggle line staged / unstaged`, DiscardSelection: `Discard change (git reset)`, - ToggleDragSelect: `Toggle drag select`, + ToggleRangeSelect: `Toggle range select`, ToggleSelectHunk: `Toggle select hunk`, ToggleSelectionForPatch: `Add/Remove line(s) to patch`, EditHunk: `Edit hunk`, @@ -1483,6 +1485,8 @@ func EnglishTranslationSet() TranslationSet { QuickStartInteractiveRebase: "Start interactive rebase", QuickStartInteractiveRebaseTooltip: "Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.\nIf you would instead like to start an interactive rebase from the selected commit, press `{{.editKey}}`.", CannotQuickStartInteractiveRebase: "Cannot start interactive rebase: the HEAD commit is a merge commit or is present on the main branch, so there is no appropriate base commit to start the rebase from. You can start an interactive rebase from a specific commit by selecting the commit and pressing `{{.editKey}}`.", + RangeSelectUp: "Range select up", + RangeSelectDown: "Range select down", Actions: Actions{ // TODO: combine this with the original keybinding descriptions (those are all in lowercase atm) CheckoutCommit: "Checkout commit", diff --git a/pkg/i18n/japanese.go b/pkg/i18n/japanese.go index 089c95ed6..660746384 100644 --- a/pkg/i18n/japanese.go +++ b/pkg/i18n/japanese.go @@ -162,7 +162,7 @@ func japaneseTranslationSet() TranslationSet { // FileStagingRequirements: `Can only stage individual lines for tracked files`, StageSelection: `選択行をステージ/アンステージ`, DiscardSelection: `変更を削除 (git reset)`, - ToggleDragSelect: `範囲選択を切り替え`, + ToggleRangeSelect: `範囲選択を切り替え`, ToggleSelectHunk: `Hunk選択を切り替え`, ToggleSelectionForPatch: `行をパッチに追加/削除`, ToggleStagingPanel: `パネルを切り替え`, diff --git a/pkg/i18n/korean.go b/pkg/i18n/korean.go index d6b7793a0..ad23090e7 100644 --- a/pkg/i18n/korean.go +++ b/pkg/i18n/korean.go @@ -164,7 +164,7 @@ func koreanTranslationSet() TranslationSet { FileStagingRequirements: `추적된 파일에 대해 개별 라인만 stage할 수 있습니다.`, StageSelection: `선택한 행을 staged / unstaged`, DiscardSelection: `변경을 삭제 (git reset)`, - ToggleDragSelect: `드래그 선택 전환`, + ToggleRangeSelect: `드래그 선택 전환`, ToggleSelectHunk: `Toggle select hunk`, ToggleSelectionForPatch: `Line(s)을 패치에 추가/삭제`, ToggleStagingPanel: `패널 전환`, diff --git a/pkg/i18n/russian.go b/pkg/i18n/russian.go index 0e63841b9..387e34974 100644 --- a/pkg/i18n/russian.go +++ b/pkg/i18n/russian.go @@ -194,7 +194,7 @@ func RussianTranslationSet() TranslationSet { FileStagingRequirements: `Можно проиндексировать только отдельные строки для отслеживаемых файлов`, StageSelection: `Переключить строку в проиндексированные / непроиндексированные`, DiscardSelection: `Отменить изменение (git reset)`, - ToggleDragSelect: `Переключить выборку перетаскивания`, + ToggleRangeSelect: `Переключить выборку перетаскивания`, ToggleSelectHunk: `Переключить выборку частей`, ToggleSelectionForPatch: `Добавить/удалить строку(и) для патча`, EditHunk: `Изменить эту часть`, diff --git a/pkg/i18n/traditional_chinese.go b/pkg/i18n/traditional_chinese.go index 703c6667c..12207edcf 100644 --- a/pkg/i18n/traditional_chinese.go +++ b/pkg/i18n/traditional_chinese.go @@ -227,7 +227,7 @@ func traditionalChineseTranslationSet() TranslationSet { FileStagingRequirements: `只能選擇跟踪檔案中的單個行`, StageSelection: `切換現有行的狀態 (已預存/未預存)`, DiscardSelection: `刪除變更 (git reset)`, - ToggleDragSelect: `切換拖曳選擇`, + ToggleRangeSelect: `切換拖曳選擇`, ToggleSelectHunk: `切換選擇程式碼塊`, ToggleSelectionForPatch: `向 (或從) 補丁中添加/刪除行`, EditHunk: `編輯程式碼塊`, diff --git a/pkg/integration/tests/commit/stage_range_of_lines.go b/pkg/integration/tests/commit/stage_range_of_lines.go index 1beef60a2..c43706de9 100644 --- a/pkg/integration/tests/commit/stage_range_of_lines.go +++ b/pkg/integration/tests/commit/stage_range_of_lines.go @@ -25,7 +25,7 @@ var StageRangeOfLines = NewIntegrationTest(NewIntegrationTestArgs{ Contains("-1st\n-2nd\n+1st changed\n+2nd changed\n 3rd\n 4th\n-5th\n+5th changed\n 6th"), ). SelectedLine(Equals("-1st")). - Press(keys.Main.ToggleDragSelect). + Press(keys.Universal.ToggleRangeSelect). SelectNextItem(). SelectNextItem(). SelectNextItem(). diff --git a/pkg/integration/tests/demo/stage_lines.go b/pkg/integration/tests/demo/stage_lines.go index 4e8db4fd4..4614db29e 100644 --- a/pkg/integration/tests/demo/stage_lines.go +++ b/pkg/integration/tests/demo/stage_lines.go @@ -60,7 +60,7 @@ var StageLines = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().Staging(). IsFocused(). - Press(keys.Main.ToggleDragSelect). + Press(keys.Universal.ToggleRangeSelect). PressFast(keys.Universal.NextItem). PressFast(keys.Universal.NextItem). Wait(500). diff --git a/pkg/integration/tests/patch_building/specific_selection.go b/pkg/integration/tests/patch_building/specific_selection.go index cfb7e64f1..b9c9da8a6 100644 --- a/pkg/integration/tests/patch_building/specific_selection.go +++ b/pkg/integration/tests/patch_building/specific_selection.go @@ -107,7 +107,7 @@ var SpecificSelection = NewIntegrationTest(NewIntegrationTestArgs{ ). PressPrimaryAction(). NavigateToLine(Contains("+2c")). - Press(keys.Main.ToggleDragSelect). + Press(keys.Universal.ToggleRangeSelect). NavigateToLine(Contains("+2e")). PressPrimaryAction(). NavigateToLine(Contains("+2g")). diff --git a/pkg/integration/tests/staging/stage_ranges.go b/pkg/integration/tests/staging/stage_ranges.go index db5d7148f..cd2157e0c 100644 --- a/pkg/integration/tests/staging/stage_ranges.go +++ b/pkg/integration/tests/staging/stage_ranges.go @@ -29,7 +29,7 @@ var StageRanges = NewIntegrationTest(NewIntegrationTestArgs{ SelectedLines( Contains("+three"), ). - Press(keys.Main.ToggleDragSelect). + Press(keys.Universal.ToggleRangeSelect). NavigateToLine(Contains("+five")). SelectedLines( Contains("+three"), @@ -60,7 +60,7 @@ var StageRanges = NewIntegrationTest(NewIntegrationTestArgs{ SelectedLines( Contains("+three"), ). - Press(keys.Main.ToggleDragSelect). + Press(keys.Universal.ToggleRangeSelect). NavigateToLine(Contains("+five")). SelectedLines( Contains("+three"), @@ -88,7 +88,7 @@ var StageRanges = NewIntegrationTest(NewIntegrationTestArgs{ SelectedLines( Contains("+four"), ). - Press(keys.Main.ToggleDragSelect). + Press(keys.Universal.ToggleRangeSelect). SelectNextItem(). SelectedLines( Contains("+four"), diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index f40cd4fd3..ae876568f 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -50,6 +50,13 @@ func Max(x, y int) int { return y } +func MinMax(x int, y int) (int, int) { + if x < y { + return x, y + } + return y, x +} + func Clamp(x int, min int, max int) int { if x < min { return min diff --git a/schema/config.json b/schema/config.json index 3de7df17d..1239872ee 100644 --- a/schema/config.json +++ b/schema/config.json @@ -1178,14 +1178,6 @@ }, "main": { "properties": { - "toggleDragSelect": { - "type": "string", - "default": "v" - }, - "toggleDragSelect-alt": { - "type": "string", - "default": "V" - }, "toggleSelectHunk": { "type": "string", "default": "a" @@ -1523,4 +1515,4 @@ }, "additionalProperties": false, "type": "object" -} \ No newline at end of file +} diff --git a/vendor/github.com/jesseduffield/gocui/keybinding.go b/vendor/github.com/jesseduffield/gocui/keybinding.go index bee180aea..e2b931d7e 100644 --- a/vendor/github.com/jesseduffield/gocui/keybinding.go +++ b/vendor/github.com/jesseduffield/gocui/keybinding.go @@ -143,7 +143,9 @@ var translate = map[string]Key{ "Pgup": KeyPgup, "Pgdn": KeyPgdn, "ArrowUp": KeyArrowUp, + "ShiftArrowUp": KeyShiftArrowUp, "ArrowDown": KeyArrowDown, + "ShiftArrowDown": KeyShiftArrowDown, "ArrowLeft": KeyArrowLeft, "ArrowRight": KeyArrowRight, "CtrlTilde": KeyCtrlTilde, @@ -203,28 +205,30 @@ var translate = map[string]Key{ // Special keys. const ( - KeyF1 Key = Key(tcell.KeyF1) - KeyF2 = Key(tcell.KeyF2) - KeyF3 = Key(tcell.KeyF3) - KeyF4 = Key(tcell.KeyF4) - KeyF5 = Key(tcell.KeyF5) - KeyF6 = Key(tcell.KeyF6) - KeyF7 = Key(tcell.KeyF7) - KeyF8 = Key(tcell.KeyF8) - KeyF9 = Key(tcell.KeyF9) - KeyF10 = Key(tcell.KeyF10) - KeyF11 = Key(tcell.KeyF11) - KeyF12 = Key(tcell.KeyF12) - KeyInsert = Key(tcell.KeyInsert) - KeyDelete = Key(tcell.KeyDelete) - KeyHome = Key(tcell.KeyHome) - KeyEnd = Key(tcell.KeyEnd) - KeyPgdn = Key(tcell.KeyPgDn) - KeyPgup = Key(tcell.KeyPgUp) - KeyArrowUp = Key(tcell.KeyUp) - KeyArrowDown = Key(tcell.KeyDown) - KeyArrowLeft = Key(tcell.KeyLeft) - KeyArrowRight = Key(tcell.KeyRight) + KeyF1 Key = Key(tcell.KeyF1) + KeyF2 = Key(tcell.KeyF2) + KeyF3 = Key(tcell.KeyF3) + KeyF4 = Key(tcell.KeyF4) + KeyF5 = Key(tcell.KeyF5) + KeyF6 = Key(tcell.KeyF6) + KeyF7 = Key(tcell.KeyF7) + KeyF8 = Key(tcell.KeyF8) + KeyF9 = Key(tcell.KeyF9) + KeyF10 = Key(tcell.KeyF10) + KeyF11 = Key(tcell.KeyF11) + KeyF12 = Key(tcell.KeyF12) + KeyInsert = Key(tcell.KeyInsert) + KeyDelete = Key(tcell.KeyDelete) + KeyHome = Key(tcell.KeyHome) + KeyEnd = Key(tcell.KeyEnd) + KeyPgdn = Key(tcell.KeyPgDn) + KeyPgup = Key(tcell.KeyPgUp) + KeyArrowUp = Key(tcell.KeyUp) + KeyShiftArrowUp = Key(tcell.KeyF62) + KeyArrowDown = Key(tcell.KeyDown) + KeyShiftArrowDown = Key(tcell.KeyF63) + KeyArrowLeft = Key(tcell.KeyLeft) + KeyArrowRight = Key(tcell.KeyRight) ) // Keys combinations. diff --git a/vendor/github.com/jesseduffield/gocui/tcell_driver.go b/vendor/github.com/jesseduffield/gocui/tcell_driver.go index a3153d4c7..96f24390f 100644 --- a/vendor/github.com/jesseduffield/gocui/tcell_driver.go +++ b/vendor/github.com/jesseduffield/gocui/tcell_driver.go @@ -300,6 +300,14 @@ func (g *Gui) pollEvent() GocuiEvent { mod = 0 ch = rune(0) k = tcell.KeyCtrlSpace + } else if mod == tcell.ModShift && k == tcell.KeyUp { + mod = 0 + ch = rune(0) + k = tcell.KeyF62 + } else if mod == tcell.ModShift && k == tcell.KeyDown { + mod = 0 + ch = rune(0) + k = tcell.KeyF63 } else if mod == tcell.ModCtrl || mod == tcell.ModShift { // remove Ctrl or Shift if specified // - shift - will be translated to the final code of rune diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index 51c968b14..6bfc4c487 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -41,6 +41,14 @@ type View struct { wx, wy int // Write() offsets lines [][]cell // All the data outMode OutputMode + // The y position of the first line of a range selection. + // This is not relative to the view's origin: it is relative to the first line + // of the view's content, so you can scroll the view and this value will remain + // the same, unlike the view's cy value. + // A value of -1 means that there is no range selection. + // This value can be greater than the selected line index, in the event that + // a user starts a range select and then moves the cursor up. + rangeSelectStartY int // readBuffer is used for storing unread bytes readBuffer []byte @@ -284,6 +292,14 @@ func (v *View) FocusPoint(cx int, cy int) { v.cy = cy - v.oy } +func (v *View) SetRangeSelectStart(rangeSelectStartY int) { + v.rangeSelectStartY = rangeSelectStartY +} + +func (v *View) CancelRangeSelect() { + v.rangeSelectStartY = -1 +} + func calculateNewOrigin(selectedLine int, oldOrigin int, lineCount int, viewHeight int) int { if viewHeight > lineCount { return 0 @@ -349,19 +365,20 @@ func (l lineType) String() string { // newView returns a new View object. func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View { v := &View{ - name: name, - x0: x0, - y0: y0, - x1: x1, - y1: y1, - Visible: true, - Frame: true, - Editor: DefaultEditor, - tainted: true, - outMode: mode, - ei: newEscapeInterpreter(mode), - searcher: &searcher{}, - TextArea: &TextArea{}, + name: name, + x0: x0, + y0: y0, + x1: x1, + y1: y1, + Visible: true, + Frame: true, + Editor: DefaultEditor, + tainted: true, + outMode: mode, + ei: newEscapeInterpreter(mode), + searcher: &searcher{}, + TextArea: &TextArea{}, + rangeSelectStartY: -1, } v.FgColor, v.BgColor = ColorDefault, ColorDefault @@ -428,11 +445,17 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error { if x < 0 || x >= maxX || y < 0 || y >= maxY { return ErrInvalidPoint } - var ( - ry, rcy int - err error - ) - if v.Highlight { + + if v.Mask != 0 { + fgColor = v.FgColor + bgColor = v.BgColor + ch = v.Mask + } else if v.Highlight { + var ( + ry, rcy int + err error + ) + _, ry, err = v.realPosition(x, y) if err != nil { return err @@ -442,20 +465,28 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error { if err == nil { rcy = rrcy } - } - if v.Mask != 0 { - fgColor = v.FgColor - bgColor = v.BgColor - ch = v.Mask - } else if v.Highlight && ry == rcy { - // this ensures we use the bright variant of a colour upon highlight - fgColorComponent := fgColor & ^AttrAll - if fgColorComponent >= AttrIsValidColor && fgColorComponent < AttrIsValidColor+8 { - fgColor += 8 + rangeSelectStart := rcy + rangeSelectEnd := rcy + if v.rangeSelectStartY != -1 { + _, realRangeSelectStart, err := v.realPosition(0, v.rangeSelectStartY-v.oy) + if err != nil { + return err + } + + rangeSelectStart = min(realRangeSelectStart, rcy) + rangeSelectEnd = max(realRangeSelectStart, rcy) + } + + if ry >= rangeSelectStart && ry <= rangeSelectEnd { + // this ensures we use the bright variant of a colour upon highlight + fgColorComponent := fgColor & ^AttrAll + if fgColorComponent >= AttrIsValidColor && fgColorComponent < AttrIsValidColor+8 { + fgColor += 8 + } + fgColor = fgColor | AttrBold + bgColor = bgColor | v.SelBgColor } - fgColor = fgColor | AttrBold - bgColor = bgColor | v.SelBgColor } // Don't display NUL characters @@ -468,6 +499,20 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error { return nil } +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} + // SetCursor sets the cursor position of the view at the given point, // relative to the view. It checks if the position is valid. func (v *View) SetCursor(x, y int) error { @@ -1388,7 +1433,31 @@ func (v *View) SelectedLine() string { if len(v.lines) == 0 { return "" } - line := v.lines[v.SelectedLineIdx()] + + return v.lineContentAtIdx(v.SelectedLineIdx()) +} + +// expected to only be used in tests +func (v *View) SelectedLines() []string { + v.writeMutex.Lock() + defer v.writeMutex.Unlock() + + if len(v.lines) == 0 { + return nil + } + + startIdx, endIdx := v.SelectedLineRange() + + lines := make([]string, 0, endIdx-startIdx+1) + for i := startIdx; i <= endIdx; i++ { + lines = append(lines, v.lineContentAtIdx(i)) + } + + return lines +} + +func (v *View) lineContentAtIdx(idx int) string { + line := v.lines[idx] str := lineType(line).String() return strings.Replace(str, "\x00", "", -1) } @@ -1399,6 +1468,25 @@ func (v *View) SelectedPoint() (int, int) { return cx + ox, cy + oy } +func (v *View) SelectedLineRange() (int, int) { + _, cy := v.Cursor() + _, oy := v.Origin() + + start := cy + oy + + if v.rangeSelectStartY == -1 { + return start, start + } + + end := v.rangeSelectStartY + + if start > end { + return end, start + } else { + return start, end + } +} + func (v *View) RenderTextArea() { v.Clear() fmt.Fprint(v, v.TextArea.GetContent()) diff --git a/vendor/golang.org/x/sys/unix/mkerrors.sh b/vendor/golang.org/x/sys/unix/mkerrors.sh index 6202638ba..c6492020e 100644 --- a/vendor/golang.org/x/sys/unix/mkerrors.sh +++ b/vendor/golang.org/x/sys/unix/mkerrors.sh @@ -248,6 +248,7 @@ struct ltchars { #include #include #include +#include #include #include #include @@ -283,10 +284,6 @@ struct ltchars { #include #endif -#ifndef MSG_FASTOPEN -#define MSG_FASTOPEN 0x20000000 -#endif - #ifndef PTRACE_GETREGS #define PTRACE_GETREGS 0xc #endif @@ -295,14 +292,6 @@ struct ltchars { #define PTRACE_SETREGS 0xd #endif -#ifndef SOL_NETLINK -#define SOL_NETLINK 270 -#endif - -#ifndef SOL_SMC -#define SOL_SMC 286 -#endif - #ifdef SOL_BLUETOOTH // SPARC includes this in /usr/include/sparc64-linux-gnu/bits/socket.h // but it is already in bluetooth_linux.go @@ -319,10 +308,23 @@ struct ltchars { #undef TIPC_WAIT_FOREVER #define TIPC_WAIT_FOREVER 0xffffffff -// Copied from linux/l2tp.h -// Including linux/l2tp.h here causes conflicts between linux/in.h -// and netinet/in.h included via net/route.h above. -#define IPPROTO_L2TP 115 +// Copied from linux/netfilter/nf_nat.h +// Including linux/netfilter/nf_nat.h here causes conflicts between linux/in.h +// and netinet/in.h. +#define NF_NAT_RANGE_MAP_IPS (1 << 0) +#define NF_NAT_RANGE_PROTO_SPECIFIED (1 << 1) +#define NF_NAT_RANGE_PROTO_RANDOM (1 << 2) +#define NF_NAT_RANGE_PERSISTENT (1 << 3) +#define NF_NAT_RANGE_PROTO_RANDOM_FULLY (1 << 4) +#define NF_NAT_RANGE_PROTO_OFFSET (1 << 5) +#define NF_NAT_RANGE_NETMAP (1 << 6) +#define NF_NAT_RANGE_PROTO_RANDOM_ALL \ + (NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PROTO_RANDOM_FULLY) +#define NF_NAT_RANGE_MASK \ + (NF_NAT_RANGE_MAP_IPS | NF_NAT_RANGE_PROTO_SPECIFIED | \ + NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PERSISTENT | \ + NF_NAT_RANGE_PROTO_RANDOM_FULLY | NF_NAT_RANGE_PROTO_OFFSET | \ + NF_NAT_RANGE_NETMAP) // Copied from linux/hid.h. // Keep in sync with the size of the referenced fields. @@ -603,6 +605,9 @@ ccflags="$@" $2 ~ /^FSOPT_/ || $2 ~ /^WDIO[CFS]_/ || $2 ~ /^NFN/ || + $2 !~ /^NFT_META_IIFTYPE/ && + $2 ~ /^NFT_/ || + $2 ~ /^NF_NAT_/ || $2 ~ /^XDP_/ || $2 ~ /^RWF_/ || $2 ~ /^(HDIO|WIN|SMART)_/ || diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go index c73cfe2f1..a5d3ff8df 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -2127,6 +2127,60 @@ const ( NFNL_SUBSYS_QUEUE = 0x3 NFNL_SUBSYS_ULOG = 0x4 NFS_SUPER_MAGIC = 0x6969 + NFT_CHAIN_FLAGS = 0x7 + NFT_CHAIN_MAXNAMELEN = 0x100 + NFT_CT_MAX = 0x17 + NFT_DATA_RESERVED_MASK = 0xffffff00 + NFT_DATA_VALUE_MAXLEN = 0x40 + NFT_EXTHDR_OP_MAX = 0x4 + NFT_FIB_RESULT_MAX = 0x3 + NFT_INNER_MASK = 0xf + NFT_LOGLEVEL_MAX = 0x8 + NFT_NAME_MAXLEN = 0x100 + NFT_NG_MAX = 0x1 + NFT_OBJECT_CONNLIMIT = 0x5 + NFT_OBJECT_COUNTER = 0x1 + NFT_OBJECT_CT_EXPECT = 0x9 + NFT_OBJECT_CT_HELPER = 0x3 + NFT_OBJECT_CT_TIMEOUT = 0x7 + NFT_OBJECT_LIMIT = 0x4 + NFT_OBJECT_MAX = 0xa + NFT_OBJECT_QUOTA = 0x2 + NFT_OBJECT_SECMARK = 0x8 + NFT_OBJECT_SYNPROXY = 0xa + NFT_OBJECT_TUNNEL = 0x6 + NFT_OBJECT_UNSPEC = 0x0 + NFT_OBJ_MAXNAMELEN = 0x100 + NFT_OSF_MAXGENRELEN = 0x10 + NFT_QUEUE_FLAG_BYPASS = 0x1 + NFT_QUEUE_FLAG_CPU_FANOUT = 0x2 + NFT_QUEUE_FLAG_MASK = 0x3 + NFT_REG32_COUNT = 0x10 + NFT_REG32_SIZE = 0x4 + NFT_REG_MAX = 0x4 + NFT_REG_SIZE = 0x10 + NFT_REJECT_ICMPX_MAX = 0x3 + NFT_RT_MAX = 0x4 + NFT_SECMARK_CTX_MAXLEN = 0x100 + NFT_SET_MAXNAMELEN = 0x100 + NFT_SOCKET_MAX = 0x3 + NFT_TABLE_F_MASK = 0x3 + NFT_TABLE_MAXNAMELEN = 0x100 + NFT_TRACETYPE_MAX = 0x3 + NFT_TUNNEL_F_MASK = 0x7 + NFT_TUNNEL_MAX = 0x1 + NFT_TUNNEL_MODE_MAX = 0x2 + NFT_USERDATA_MAXLEN = 0x100 + NFT_XFRM_KEY_MAX = 0x6 + NF_NAT_RANGE_MAP_IPS = 0x1 + NF_NAT_RANGE_MASK = 0x7f + NF_NAT_RANGE_NETMAP = 0x40 + NF_NAT_RANGE_PERSISTENT = 0x8 + NF_NAT_RANGE_PROTO_OFFSET = 0x20 + NF_NAT_RANGE_PROTO_RANDOM = 0x4 + NF_NAT_RANGE_PROTO_RANDOM_ALL = 0x14 + NF_NAT_RANGE_PROTO_RANDOM_FULLY = 0x10 + NF_NAT_RANGE_PROTO_SPECIFIED = 0x2 NILFS_SUPER_MAGIC = 0x3434 NL0 = 0x0 NL1 = 0x100 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go index a1d061597..9dc42410b 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go @@ -2297,5 +2297,3 @@ func unveil(path *byte, flags *byte) (err error) { var libc_unveil_trampoline_addr uintptr //go:cgo_import_dynamic libc_unveil unveil "libc.so" - - diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go index 5b2a74097..0d3a0751c 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go @@ -2297,5 +2297,3 @@ func unveil(path *byte, flags *byte) (err error) { var libc_unveil_trampoline_addr uintptr //go:cgo_import_dynamic libc_unveil unveil "libc.so" - - diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go index f6eda1344..c39f7776d 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go @@ -2297,5 +2297,3 @@ func unveil(path *byte, flags *byte) (err error) { var libc_unveil_trampoline_addr uintptr //go:cgo_import_dynamic libc_unveil unveil "libc.so" - - diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go index 55df20ae9..57571d072 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go @@ -2297,5 +2297,3 @@ func unveil(path *byte, flags *byte) (err error) { var libc_unveil_trampoline_addr uintptr //go:cgo_import_dynamic libc_unveil unveil "libc.so" - - diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go index 8c1155cbc..e62963e67 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go @@ -2297,5 +2297,3 @@ func unveil(path *byte, flags *byte) (err error) { var libc_unveil_trampoline_addr uintptr //go:cgo_import_dynamic libc_unveil unveil "libc.so" - - diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go index 7cc80c58d..00831354c 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go @@ -2297,5 +2297,3 @@ func unveil(path *byte, flags *byte) (err error) { var libc_unveil_trampoline_addr uintptr //go:cgo_import_dynamic libc_unveil unveil "libc.so" - - diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go index 0688737f4..79029ed58 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go @@ -2297,5 +2297,3 @@ func unveil(path *byte, flags *byte) (err error) { var libc_unveil_trampoline_addr uintptr //go:cgo_import_dynamic libc_unveil unveil "libc.so" - - diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go index 47dc57967..ffb8708cc 100644 --- a/vendor/golang.org/x/sys/windows/syscall_windows.go +++ b/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -194,6 +194,7 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys GetComputerName(buf *uint16, n *uint32) (err error) = GetComputerNameW //sys GetComputerNameEx(nametype uint32, buf *uint16, n *uint32) (err error) = GetComputerNameExW //sys SetEndOfFile(handle Handle) (err error) +//sys SetFileValidData(handle Handle, validDataLength int64) (err error) //sys GetSystemTimeAsFileTime(time *Filetime) //sys GetSystemTimePreciseAsFileTime(time *Filetime) //sys GetTimeZoneInformation(tzi *Timezoneinformation) (rc uint32, err error) [failretval==0xffffffff] diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go index 146a1f019..e8791c82c 100644 --- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go +++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go @@ -342,6 +342,7 @@ var ( procSetDefaultDllDirectories = modkernel32.NewProc("SetDefaultDllDirectories") procSetDllDirectoryW = modkernel32.NewProc("SetDllDirectoryW") procSetEndOfFile = modkernel32.NewProc("SetEndOfFile") + procSetFileValidData = modkernel32.NewProc("SetFileValidData") procSetEnvironmentVariableW = modkernel32.NewProc("SetEnvironmentVariableW") procSetErrorMode = modkernel32.NewProc("SetErrorMode") procSetEvent = modkernel32.NewProc("SetEvent") @@ -2988,6 +2989,14 @@ func SetEndOfFile(handle Handle) (err error) { return } +func SetFileValidData(handle Handle, validDataLength int64) (err error) { + r1, _, e1 := syscall.Syscall(procSetFileValidData.Addr(), 2, uintptr(handle), uintptr(validDataLength), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func SetEnvironmentVariable(name *uint16, value *uint16) (err error) { r1, _, e1 := syscall.Syscall(procSetEnvironmentVariableW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), 0) if r1 == 0 { diff --git a/vendor/modules.txt b/vendor/modules.txt index 1121027e0..071b257ad 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -173,7 +173,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem github.com/jesseduffield/go-git/v5/utils/merkletrie/index github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame github.com/jesseduffield/go-git/v5/utils/merkletrie/noder -# github.com/jesseduffield/gocui v0.3.1-0.20240103192639-2874168c14db +# github.com/jesseduffield/gocui v0.3.1-0.20240118234343-2d41754af383 ## explicit; go 1.12 github.com/jesseduffield/gocui # github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 @@ -314,13 +314,13 @@ golang.org/x/exp/slices golang.org/x/net/context golang.org/x/net/internal/socks golang.org/x/net/proxy -# golang.org/x/sys v0.15.0 +# golang.org/x/sys v0.16.0 ## explicit; go 1.18 golang.org/x/sys/cpu golang.org/x/sys/plan9 golang.org/x/sys/unix golang.org/x/sys/windows -# golang.org/x/term v0.15.0 +# golang.org/x/term v0.16.0 ## explicit; go 1.18 golang.org/x/term # golang.org/x/text v0.14.0