summaryrefslogtreecommitdiffstats
path: root/pkg/gui/context
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2024-01-07 19:44:19 +1100
committerJesse Duffield <jessedduffield@gmail.com>2024-01-19 10:47:21 +1100
commit24a4302c528e3a11b30855c006669de1adf9e1d4 (patch)
tree4b8053168746c2978c2b47968bae1576d3718bcf /pkg/gui/context
parente887a2eb3c0ece1d2be78b869ff936a0703a3940 (diff)
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
Diffstat (limited to 'pkg/gui/context')
-rw-r--r--pkg/gui/context/list_context_trait.go10
-rw-r--r--pkg/gui/context/traits/list_cursor.go103
-rw-r--r--pkg/gui/context/view_trait.go8
3 files changed, 110 insertions, 11 deletions
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.
+// 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)
}
-// 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, 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 not out of bounds
-func (self *ListCursor) RefreshSelectedIdx() {
- self.SetSelectedLineIdx(self.selectedIdx)
+// 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)