mirror of
				https://github.com/jesseduffield/lazygit.git
				synced 2025-10-30 23:57:43 +02:00 
			
		
		
		
	Set groundwork for better disabled reasons with range select
Something dumb that we're currently doing is expecting list items to define an ID method which returns a string. We use that when copying items to clipboard with ctrl+o and when getting a ref name for diffing. This commit gets us a little deeper into that hole by explicitly requiring list items to implement that method so that we can easily use the new helper functions in list_controller_trait.go. In future we need to just remove the whole ID thing entirely but I'm too lazy to do that right now.
This commit is contained in:
		| @@ -59,15 +59,6 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext { | ||||
| 	return self | ||||
| } | ||||
|  | ||||
| func (self *BranchesContext) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.ID() | ||||
| } | ||||
|  | ||||
| func (self *BranchesContext) GetSelectedRef() types.Ref { | ||||
| 	branch := self.GetSelected() | ||||
| 	if branch == nil { | ||||
|   | ||||
| @@ -72,15 +72,6 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext { | ||||
| 	return ctx | ||||
| } | ||||
|  | ||||
| func (self *CommitFilesContext) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.ID() | ||||
| } | ||||
|  | ||||
| func (self *CommitFilesContext) GetDiffTerminals() []string { | ||||
| 	return []string{self.GetRef().RefName()} | ||||
| } | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| package context | ||||
|  | ||||
| type FilteredListViewModel[T any] struct { | ||||
| type FilteredListViewModel[T HasID] struct { | ||||
| 	*FilteredList[T] | ||||
| 	*ListViewModel[T] | ||||
| 	*SearchHistory | ||||
| } | ||||
|  | ||||
| func NewFilteredListViewModel[T any](getList func() []T, getFilterFields func(T) []string) *FilteredListViewModel[T] { | ||||
| func NewFilteredListViewModel[T HasID](getList func() []T, getFilterFields func(T) []string) *FilteredListViewModel[T] { | ||||
| 	filteredList := NewFilteredList(getList, getFilterFields) | ||||
|  | ||||
| 	self := &FilteredListViewModel[T]{ | ||||
|   | ||||
| @@ -9,10 +9,17 @@ import ( | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| // wrapping string in my own type to give it an ID method which is required for list items | ||||
| type mystring string | ||||
|  | ||||
| func (self mystring) ID() string { | ||||
| 	return string(self) | ||||
| } | ||||
|  | ||||
| func TestListRenderer_renderLines(t *testing.T) { | ||||
| 	scenarios := []struct { | ||||
| 		name            string | ||||
| 		modelStrings    []string | ||||
| 		modelStrings    []mystring | ||||
| 		nonModelIndices []int | ||||
| 		startIdx        int | ||||
| 		endIdx          int | ||||
| @@ -20,7 +27,7 @@ func TestListRenderer_renderLines(t *testing.T) { | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:         "Render whole list", | ||||
| 			modelStrings: []string{"a", "b", "c"}, | ||||
| 			modelStrings: []mystring{"a", "b", "c"}, | ||||
| 			startIdx:     0, | ||||
| 			endIdx:       3, | ||||
| 			expectedOutput: ` | ||||
| @@ -30,7 +37,7 @@ func TestListRenderer_renderLines(t *testing.T) { | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "Partial list, beginning", | ||||
| 			modelStrings: []string{"a", "b", "c"}, | ||||
| 			modelStrings: []mystring{"a", "b", "c"}, | ||||
| 			startIdx:     0, | ||||
| 			endIdx:       2, | ||||
| 			expectedOutput: ` | ||||
| @@ -39,7 +46,7 @@ func TestListRenderer_renderLines(t *testing.T) { | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "Partial list, end", | ||||
| 			modelStrings: []string{"a", "b", "c"}, | ||||
| 			modelStrings: []mystring{"a", "b", "c"}, | ||||
| 			startIdx:     1, | ||||
| 			endIdx:       3, | ||||
| 			expectedOutput: ` | ||||
| @@ -48,7 +55,7 @@ func TestListRenderer_renderLines(t *testing.T) { | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "Pass an endIdx greater than the model length", | ||||
| 			modelStrings: []string{"a", "b", "c"}, | ||||
| 			modelStrings: []mystring{"a", "b", "c"}, | ||||
| 			startIdx:     2, | ||||
| 			endIdx:       5, | ||||
| 			expectedOutput: ` | ||||
| @@ -56,7 +63,7 @@ func TestListRenderer_renderLines(t *testing.T) { | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:            "Whole list with section headers", | ||||
| 			modelStrings:    []string{"a", "b", "c"}, | ||||
| 			modelStrings:    []mystring{"a", "b", "c"}, | ||||
| 			nonModelIndices: []int{1, 3}, | ||||
| 			startIdx:        0, | ||||
| 			endIdx:          5, | ||||
| @@ -69,7 +76,7 @@ func TestListRenderer_renderLines(t *testing.T) { | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:            "Multiple consecutive headers", | ||||
| 			modelStrings:    []string{"a", "b", "c"}, | ||||
| 			modelStrings:    []mystring{"a", "b", "c"}, | ||||
| 			nonModelIndices: []int{0, 0, 2, 2, 2}, | ||||
| 			startIdx:        0, | ||||
| 			endIdx:          8, | ||||
| @@ -85,7 +92,7 @@ func TestListRenderer_renderLines(t *testing.T) { | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:            "Partial list with headers, beginning", | ||||
| 			modelStrings:    []string{"a", "b", "c"}, | ||||
| 			modelStrings:    []mystring{"a", "b", "c"}, | ||||
| 			nonModelIndices: []int{1, 3}, | ||||
| 			startIdx:        0, | ||||
| 			endIdx:          3, | ||||
| @@ -96,7 +103,7 @@ func TestListRenderer_renderLines(t *testing.T) { | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:            "Partial list with headers, end (beyond end index)", | ||||
| 			modelStrings:    []string{"a", "b", "c"}, | ||||
| 			modelStrings:    []mystring{"a", "b", "c"}, | ||||
| 			nonModelIndices: []int{1, 3}, | ||||
| 			startIdx:        2, | ||||
| 			endIdx:          7, | ||||
| @@ -108,7 +115,7 @@ func TestListRenderer_renderLines(t *testing.T) { | ||||
| 	} | ||||
| 	for _, s := range scenarios { | ||||
| 		t.Run(s.name, func(t *testing.T) { | ||||
| 			viewModel := NewListViewModel[string](func() []string { return s.modelStrings }) | ||||
| 			viewModel := NewListViewModel[mystring](func() []mystring { return s.modelStrings }) | ||||
| 			var getNonModelItems func() []*NonModelItem | ||||
| 			if s.nonModelIndices != nil { | ||||
| 				getNonModelItems = func() []*NonModelItem { | ||||
| @@ -124,7 +131,7 @@ func TestListRenderer_renderLines(t *testing.T) { | ||||
| 				list: viewModel, | ||||
| 				getDisplayStrings: func(startIdx int, endIdx int) [][]string { | ||||
| 					return lo.Map(s.modelStrings[startIdx:endIdx], | ||||
| 						func(s string, _ int) []string { return []string{s} }) | ||||
| 						func(s mystring, _ int) []string { return []string{string(s)} }) | ||||
| 				}, | ||||
| 				getNonModelItems: getNonModelItems, | ||||
| 			} | ||||
| @@ -138,6 +145,12 @@ func TestListRenderer_renderLines(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type myint int | ||||
|  | ||||
| func (self myint) ID() string { | ||||
| 	return fmt.Sprint(int(self)) | ||||
| } | ||||
|  | ||||
| func TestListRenderer_ModelIndexToViewIndex_and_back(t *testing.T) { | ||||
| 	scenarios := []struct { | ||||
| 		name            string | ||||
| @@ -222,8 +235,8 @@ func TestListRenderer_ModelIndexToViewIndex_and_back(t *testing.T) { | ||||
| 			assert.Equal(t, len(s.modelIndices), len(s.expectedViewIndices)) | ||||
| 			assert.Equal(t, len(s.viewIndices), len(s.expectedModelIndices)) | ||||
|  | ||||
| 			modelInts := lo.Range(s.numModelItems) | ||||
| 			viewModel := NewListViewModel[int](func() []int { return modelInts }) | ||||
| 			modelInts := lo.Map(lo.Range(s.numModelItems), func(i int, _ int) myint { return myint(i) }) | ||||
| 			viewModel := NewListViewModel[myint](func() []myint { return modelInts }) | ||||
| 			var getNonModelItems func() []*NonModelItem | ||||
| 			if s.nonModelIndices != nil { | ||||
| 				getNonModelItems = func() []*NonModelItem { | ||||
| @@ -236,7 +249,7 @@ func TestListRenderer_ModelIndexToViewIndex_and_back(t *testing.T) { | ||||
| 				list: viewModel, | ||||
| 				getDisplayStrings: func(startIdx int, endIdx int) [][]string { | ||||
| 					return lo.Map(modelInts[startIdx:endIdx], | ||||
| 						func(i int, _ int) []string { return []string{fmt.Sprint(i)} }) | ||||
| 						func(i myint, _ int) []string { return []string{fmt.Sprint(i)} }) | ||||
| 				}, | ||||
| 				getNonModelItems: getNonModelItems, | ||||
| 			} | ||||
|   | ||||
| @@ -3,14 +3,19 @@ package context | ||||
| import ( | ||||
| 	"github.com/jesseduffield/lazygit/pkg/gui/context/traits" | ||||
| 	"github.com/jesseduffield/lazygit/pkg/gui/types" | ||||
| 	"github.com/samber/lo" | ||||
| ) | ||||
|  | ||||
| type ListViewModel[T any] struct { | ||||
| type HasID interface { | ||||
| 	ID() string | ||||
| } | ||||
|  | ||||
| type ListViewModel[T HasID] struct { | ||||
| 	*traits.ListCursor | ||||
| 	getModel func() []T | ||||
| } | ||||
|  | ||||
| func NewListViewModel[T any](getModel func() []T) *ListViewModel[T] { | ||||
| func NewListViewModel[T HasID](getModel func() []T) *ListViewModel[T] { | ||||
| 	self := &ListViewModel[T]{ | ||||
| 		getModel: getModel, | ||||
| 	} | ||||
| @@ -32,6 +37,34 @@ func (self *ListViewModel[T]) GetSelected() T { | ||||
| 	return self.getModel()[self.GetSelectedLineIdx()] | ||||
| } | ||||
|  | ||||
| func (self *ListViewModel[T]) GetSelectedItemId() string { | ||||
| 	if self.Len() == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return self.GetSelected().ID() | ||||
| } | ||||
|  | ||||
| func (self *ListViewModel[T]) GetSelectedItems() ([]T, int, int) { | ||||
| 	if self.Len() == 0 { | ||||
| 		return nil, -1, -1 | ||||
| 	} | ||||
|  | ||||
| 	startIdx, endIdx := self.GetSelectionRange() | ||||
|  | ||||
| 	return self.getModel()[startIdx : endIdx+1], startIdx, endIdx | ||||
| } | ||||
|  | ||||
| func (self *ListViewModel[T]) GetSelectedItemIds() ([]string, int, int) { | ||||
| 	selectedItems, startIdx, endIdx := self.GetSelectedItems() | ||||
|  | ||||
| 	ids := lo.Map(selectedItems, func(item T, _ int) string { | ||||
| 		return item.ID() | ||||
| 	}) | ||||
|  | ||||
| 	return ids, startIdx, endIdx | ||||
| } | ||||
|  | ||||
| func (self *ListViewModel[T]) GetItems() []T { | ||||
| 	return self.getModel() | ||||
| } | ||||
|   | ||||
| @@ -92,15 +92,6 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext { | ||||
| 	return ctx | ||||
| } | ||||
|  | ||||
| func (self *LocalCommitsContext) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.ID() | ||||
| } | ||||
|  | ||||
| type LocalCommitsViewModel struct { | ||||
| 	*ListViewModel[*models.Commit] | ||||
|  | ||||
|   | ||||
| @@ -45,16 +45,6 @@ func NewMenuContext( | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // TODO: remove this thing. | ||||
| func (self *MenuContext) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.Label | ||||
| } | ||||
|  | ||||
| type MenuViewModel struct { | ||||
| 	c               *ContextCommon | ||||
| 	menuItems       []*types.MenuItem | ||||
|   | ||||
| @@ -59,15 +59,6 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *ReflogCommitsContext) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.ID() | ||||
| } | ||||
|  | ||||
| func (self *ReflogCommitsContext) CanRebase() bool { | ||||
| 	return false | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import ( | ||||
| 	"github.com/jesseduffield/lazygit/pkg/commands/models" | ||||
| 	"github.com/jesseduffield/lazygit/pkg/gui/presentation" | ||||
| 	"github.com/jesseduffield/lazygit/pkg/gui/types" | ||||
| 	"github.com/samber/lo" | ||||
| ) | ||||
|  | ||||
| type RemoteBranchesContext struct { | ||||
| @@ -53,15 +54,6 @@ func NewRemoteBranchesContext( | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *RemoteBranchesContext) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.ID() | ||||
| } | ||||
|  | ||||
| func (self *RemoteBranchesContext) GetSelectedRef() types.Ref { | ||||
| 	remoteBranch := self.GetSelected() | ||||
| 	if remoteBranch == nil { | ||||
| @@ -70,6 +62,16 @@ func (self *RemoteBranchesContext) GetSelectedRef() types.Ref { | ||||
| 	return remoteBranch | ||||
| } | ||||
|  | ||||
| func (self *RemoteBranchesContext) GetSelectedRefs() ([]types.Ref, int, int) { | ||||
| 	items, startIdx, endIdx := self.GetSelectedItems() | ||||
|  | ||||
| 	refs := lo.Map(items, func(item *models.RemoteBranch, _ int) types.Ref { | ||||
| 		return item | ||||
| 	}) | ||||
|  | ||||
| 	return refs, startIdx, endIdx | ||||
| } | ||||
|  | ||||
| func (self *RemoteBranchesContext) GetDiffTerminals() []string { | ||||
| 	itemId := self.GetSelectedItemId() | ||||
|  | ||||
|   | ||||
| @@ -47,15 +47,6 @@ func NewRemotesContext(c *ContextCommon) *RemotesContext { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *RemotesContext) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.ID() | ||||
| } | ||||
|  | ||||
| func (self *RemotesContext) GetDiffTerminals() []string { | ||||
| 	itemId := self.GetSelectedItemId() | ||||
|  | ||||
|   | ||||
| @@ -49,15 +49,6 @@ func NewStashContext( | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *StashContext) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.ID() | ||||
| } | ||||
|  | ||||
| func (self *StashContext) CanRebase() bool { | ||||
| 	return false | ||||
| } | ||||
|   | ||||
| @@ -175,15 +175,6 @@ func (self *SubCommitsViewModel) GetShowBranchHeads() bool { | ||||
| 	return self.showBranchHeads | ||||
| } | ||||
|  | ||||
| func (self *SubCommitsContext) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.ID() | ||||
| } | ||||
|  | ||||
| func (self *SubCommitsContext) CanRebase() bool { | ||||
| 	return false | ||||
| } | ||||
|   | ||||
| @@ -43,12 +43,3 @@ func NewSubmodulesContext(c *ContextCommon) *SubmodulesContext { | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *SubmodulesContext) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.ID() | ||||
| } | ||||
|   | ||||
| @@ -63,15 +63,6 @@ func NewSuggestionsContext( | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *SuggestionsContext) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.Value | ||||
| } | ||||
|  | ||||
| func (self *SuggestionsContext) SetSuggestions(suggestions []*types.Suggestion) { | ||||
| 	self.State.Suggestions = suggestions | ||||
| 	self.SetSelection(0) | ||||
|   | ||||
| @@ -52,15 +52,6 @@ func NewTagsContext( | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *TagsContext) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.ID() | ||||
| } | ||||
|  | ||||
| func (self *TagsContext) GetSelectedRef() types.Ref { | ||||
| 	tag := self.GetSelected() | ||||
| 	if tag == nil { | ||||
|   | ||||
| @@ -58,12 +58,3 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext { | ||||
|  | ||||
| 	return ctx | ||||
| } | ||||
|  | ||||
| func (self *WorkingTreeContext) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.ID() | ||||
| } | ||||
|   | ||||
| @@ -46,12 +46,3 @@ func NewWorktreesContext(c *ContextCommon) *WorktreesContext { | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *WorktreesContext) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.ID() | ||||
| } | ||||
|   | ||||
| @@ -16,6 +16,7 @@ type ContainsCommits interface { | ||||
| 	types.Context | ||||
| 	types.IListContext | ||||
| 	GetSelected() *models.Commit | ||||
| 	GetSelectedItems() ([]*models.Commit, int, int) | ||||
| 	GetCommits() []*models.Commit | ||||
| 	GetSelectedLineIdx() int | ||||
| } | ||||
| @@ -36,6 +37,7 @@ func NewBasicCommitsController(c *ControllerCommon, context ContainsCommits) *Ba | ||||
| 			c, | ||||
| 			context, | ||||
| 			context.GetSelected, | ||||
| 			context.GetSelectedItems, | ||||
| 		), | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -30,6 +30,7 @@ func NewBisectController( | ||||
| 			c, | ||||
| 			c.Contexts().LocalCommits, | ||||
| 			c.Contexts().LocalCommits.GetSelected, | ||||
| 			c.Contexts().LocalCommits.GetSelectedItems, | ||||
| 		), | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -33,6 +33,7 @@ func NewBranchesController( | ||||
| 			c, | ||||
| 			c.Contexts().Branches, | ||||
| 			c.Contexts().Branches.GetSelected, | ||||
| 			c.Contexts().Branches.GetSelectedItems, | ||||
| 		), | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -28,6 +28,7 @@ func NewCommitFilesController( | ||||
| 			c, | ||||
| 			c.Contexts().CommitFiles, | ||||
| 			c.Contexts().CommitFiles.GetSelected, | ||||
| 			c.Contexts().CommitFiles.GetSelectedItems, | ||||
| 		), | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -28,6 +28,7 @@ func NewFilesController( | ||||
| 			c, | ||||
| 			c.Contexts().Files, | ||||
| 			c.Contexts().Files.GetSelected, | ||||
| 			c.Contexts().Files.GetSelectedItems, | ||||
| 		), | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -28,6 +28,7 @@ func NewFilesRemoveController( | ||||
| 			c, | ||||
| 			c.Contexts().Files, | ||||
| 			c.Contexts().Files.GetSelected, | ||||
| 			c.Contexts().Files.GetSelectedItems, | ||||
| 		), | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -25,6 +25,7 @@ func NewGitFlowController( | ||||
| 			c, | ||||
| 			c.Contexts().Branches, | ||||
| 			c.Contexts().Branches.GetSelected, | ||||
| 			c.Contexts().Branches.GetSelectedItems, | ||||
| 		), | ||||
| 		c: c, | ||||
| 	} | ||||
|   | ||||
| @@ -6,20 +6,23 @@ import "github.com/jesseduffield/lazygit/pkg/gui/types" | ||||
| // ensuring a single item is selected, etc. | ||||
|  | ||||
| type ListControllerTrait[T comparable] struct { | ||||
| 	c           *ControllerCommon | ||||
| 	context     types.IListContext | ||||
| 	getSelected func() T | ||||
| 	c                *ControllerCommon | ||||
| 	context          types.IListContext | ||||
| 	getSelectedItem  func() T | ||||
| 	getSelectedItems func() ([]T, int, int) | ||||
| } | ||||
|  | ||||
| func NewListControllerTrait[T comparable]( | ||||
| 	c *ControllerCommon, | ||||
| 	context types.IListContext, | ||||
| 	getSelected func() T, | ||||
| 	getSelectedItems func() ([]T, int, int), | ||||
| ) *ListControllerTrait[T] { | ||||
| 	return &ListControllerTrait[T]{ | ||||
| 		c:           c, | ||||
| 		context:     context, | ||||
| 		getSelected: getSelected, | ||||
| 		c:                c, | ||||
| 		context:          context, | ||||
| 		getSelectedItem:  getSelected, | ||||
| 		getSelectedItems: getSelectedItems, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -47,7 +50,7 @@ func (self *ListControllerTrait[T]) singleItemSelected(callbacks ...func(T) *typ | ||||
| 		} | ||||
|  | ||||
| 		var zeroValue T | ||||
| 		item := self.getSelected() | ||||
| 		item := self.getSelectedItem() | ||||
| 		if item == zeroValue { | ||||
| 			return &types.DisabledReason{Text: self.c.Tr.NoItemSelected} | ||||
| 		} | ||||
| @@ -62,11 +65,46 @@ func (self *ListControllerTrait[T]) singleItemSelected(callbacks ...func(T) *typ | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Ensures that at least one item is selected. | ||||
| func (self *ListControllerTrait[T]) itemRangeSelected(callbacks ...func([]T, int, int) *types.DisabledReason) func() *types.DisabledReason { | ||||
| 	return func() *types.DisabledReason { | ||||
| 		items, startIdx, endIdx := self.getSelectedItems() | ||||
| 		if len(items) == 0 { | ||||
| 			return &types.DisabledReason{Text: self.c.Tr.NoItemSelected} | ||||
| 		} | ||||
|  | ||||
| 		for _, callback := range callbacks { | ||||
| 			if reason := callback(items, startIdx, endIdx); reason != nil { | ||||
| 				return reason | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *ListControllerTrait[T]) itemsSelected(callbacks ...func([]T) *types.DisabledReason) func() *types.DisabledReason { //nolint:unused | ||||
| 	return func() *types.DisabledReason { | ||||
| 		items, _, _ := self.getSelectedItems() | ||||
| 		if len(items) == 0 { | ||||
| 			return &types.DisabledReason{Text: self.c.Tr.NoItemSelected} | ||||
| 		} | ||||
|  | ||||
| 		for _, callback := range callbacks { | ||||
| 			if reason := callback(items); reason != nil { | ||||
| 				return reason | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Passes the selected item to the callback. Used for handler functions. | ||||
| func (self *ListControllerTrait[T]) withItem(callback func(T) error) func() error { | ||||
| 	return func() error { | ||||
| 		var zeroValue T | ||||
| 		commit := self.getSelected() | ||||
| 		commit := self.getSelectedItem() | ||||
| 		if commit == zeroValue { | ||||
| 			return self.c.ErrorMsg(self.c.Tr.NoItemSelected) | ||||
| 		} | ||||
| @@ -75,12 +113,35 @@ func (self *ListControllerTrait[T]) withItem(callback func(T) error) func() erro | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *ListControllerTrait[T]) withItems(callback func([]T) error) func() error { | ||||
| 	return func() error { | ||||
| 		items, _, _ := self.getSelectedItems() | ||||
| 		if len(items) == 0 { | ||||
| 			return self.c.ErrorMsg(self.c.Tr.NoItemSelected) | ||||
| 		} | ||||
|  | ||||
| 		return callback(items) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // like withItems but also passes the start and end index of the selection | ||||
| func (self *ListControllerTrait[T]) withItemsRange(callback func([]T, int, int) error) func() error { | ||||
| 	return func() error { | ||||
| 		items, startIdx, endIdx := self.getSelectedItems() | ||||
| 		if len(items) == 0 { | ||||
| 			return self.c.ErrorMsg(self.c.Tr.NoItemSelected) | ||||
| 		} | ||||
|  | ||||
| 		return callback(items, startIdx, endIdx) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Like withItem, but doesn't show an error message if no item is selected. | ||||
| // Use this for click actions (it's a no-op to click empty space) | ||||
| func (self *ListControllerTrait[T]) withItemGraceful(callback func(T) error) func() error { | ||||
| 	return func() error { | ||||
| 		var zeroValue T | ||||
| 		commit := self.getSelected() | ||||
| 		commit := self.getSelectedItem() | ||||
| 		if commit == zeroValue { | ||||
| 			return nil | ||||
| 		} | ||||
|   | ||||
| @@ -22,6 +22,7 @@ func NewMenuController( | ||||
| 			c, | ||||
| 			c.Contexts().Menu, | ||||
| 			c.Contexts().Menu.GetSelected, | ||||
| 			c.Contexts().Menu.GetSelectedItems, | ||||
| 		), | ||||
| 		c: c, | ||||
| 	} | ||||
|   | ||||
| @@ -23,6 +23,7 @@ func NewReflogCommitsController( | ||||
| 			c, | ||||
| 			c.Contexts().ReflogCommits, | ||||
| 			c.Contexts().ReflogCommits.GetSelected, | ||||
| 			c.Contexts().ReflogCommits.GetSelectedItems, | ||||
| 		), | ||||
| 		c: c, | ||||
| 	} | ||||
|   | ||||
| @@ -26,6 +26,7 @@ func NewRemoteBranchesController( | ||||
| 			c, | ||||
| 			c.Contexts().RemoteBranches, | ||||
| 			c.Contexts().RemoteBranches.GetSelected, | ||||
| 			c.Contexts().RemoteBranches.GetSelectedItems, | ||||
| 		), | ||||
| 		c: c, | ||||
| 	} | ||||
|   | ||||
| @@ -32,6 +32,7 @@ func NewRemotesController( | ||||
| 			c, | ||||
| 			c.Contexts().Remotes, | ||||
| 			c.Contexts().Remotes.GetSelected, | ||||
| 			c.Contexts().Remotes.GetSelectedItems, | ||||
| 		), | ||||
| 		c:                 c, | ||||
| 		setRemoteBranches: setRemoteBranches, | ||||
|   | ||||
| @@ -24,6 +24,7 @@ func NewStashController( | ||||
| 			c, | ||||
| 			c.Contexts().Stash, | ||||
| 			c.Contexts().Stash.GetSelected, | ||||
| 			c.Contexts().Stash.GetSelectedItems, | ||||
| 		), | ||||
| 		c: c, | ||||
| 	} | ||||
|   | ||||
| @@ -24,6 +24,7 @@ func NewSubCommitsController( | ||||
| 			c, | ||||
| 			c.Contexts().SubCommits, | ||||
| 			c.Contexts().SubCommits.GetSelected, | ||||
| 			c.Contexts().SubCommits.GetSelectedItems, | ||||
| 		), | ||||
| 		c: c, | ||||
| 	} | ||||
|   | ||||
| @@ -29,6 +29,7 @@ func NewSubmodulesController( | ||||
| 			c, | ||||
| 			c.Contexts().Submodules, | ||||
| 			c.Contexts().Submodules.GetSelected, | ||||
| 			c.Contexts().Submodules.GetSelectedItems, | ||||
| 		), | ||||
| 		c: c, | ||||
| 	} | ||||
|   | ||||
| @@ -22,6 +22,7 @@ func NewSuggestionsController( | ||||
| 			c, | ||||
| 			c.Contexts().Suggestions, | ||||
| 			c.Contexts().Suggestions.GetSelected, | ||||
| 			c.Contexts().Suggestions.GetSelectedItems, | ||||
| 		), | ||||
| 		c: c, | ||||
| 	} | ||||
|   | ||||
| @@ -36,6 +36,9 @@ func NewSwitchToDiffFilesController( | ||||
| 			c, | ||||
| 			context, | ||||
| 			context.GetSelectedRef, | ||||
| 			func() ([]types.Ref, int, int) { | ||||
| 				panic("Not implemented") | ||||
| 			}, | ||||
| 		), | ||||
| 		c:                c, | ||||
| 		context:          context, | ||||
|   | ||||
| @@ -32,6 +32,9 @@ func NewSwitchToSubCommitsController( | ||||
| 			c, | ||||
| 			context, | ||||
| 			context.GetSelectedRef, | ||||
| 			func() ([]types.Ref, int, int) { | ||||
| 				panic("Not implemented") | ||||
| 			}, | ||||
| 		), | ||||
| 		c:       c, | ||||
| 		context: context, | ||||
|   | ||||
| @@ -25,6 +25,7 @@ func NewTagsController( | ||||
| 			c, | ||||
| 			c.Contexts().Tags, | ||||
| 			c.Contexts().Tags.GetSelected, | ||||
| 			c.Contexts().Tags.GetSelectedItems, | ||||
| 		), | ||||
| 		c: c, | ||||
| 	} | ||||
|   | ||||
| @@ -26,6 +26,7 @@ func NewWorktreeOptionsController(c *ControllerCommon, context CanViewWorktreeOp | ||||
| 			c, | ||||
| 			context, | ||||
| 			context.GetSelectedItemId, | ||||
| 			context.GetSelectedItemIds, | ||||
| 		), | ||||
| 		c:       c, | ||||
| 		context: context, | ||||
|   | ||||
| @@ -28,6 +28,7 @@ func NewWorktreesController( | ||||
| 			c, | ||||
| 			c.Contexts().Worktrees, | ||||
| 			c.Contexts().Worktrees.GetSelected, | ||||
| 			c.Contexts().Worktrees.GetSelectedItems, | ||||
| 		), | ||||
| 		c: c, | ||||
| 	} | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import ( | ||||
| 	"github.com/jesseduffield/lazygit/pkg/commands/models" | ||||
| 	"github.com/jesseduffield/lazygit/pkg/gui/context/traits" | ||||
| 	"github.com/jesseduffield/lazygit/pkg/gui/types" | ||||
| 	"github.com/samber/lo" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| @@ -69,6 +70,29 @@ func (self *CommitFileTreeViewModel) GetSelected() *CommitFileNode { | ||||
| 	return self.Get(self.GetSelectedLineIdx()) | ||||
| } | ||||
|  | ||||
| func (self *CommitFileTreeViewModel) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.ID() | ||||
| } | ||||
|  | ||||
| func (self *CommitFileTreeViewModel) GetSelectedItems() ([]*CommitFileNode, int, int) { | ||||
| 	panic("Not implemented") | ||||
| } | ||||
|  | ||||
| func (self *CommitFileTreeViewModel) GetSelectedItemIds() ([]string, int, int) { | ||||
| 	selectedItems, startIdx, endIdx := self.GetSelectedItems() | ||||
|  | ||||
| 	ids := lo.Map(selectedItems, func(item *CommitFileNode, _ int) string { | ||||
| 		return item.ID() | ||||
| 	}) | ||||
|  | ||||
| 	return ids, startIdx, endIdx | ||||
| } | ||||
|  | ||||
| func (self *CommitFileTreeViewModel) GetSelectedFile() *models.CommitFile { | ||||
| 	node := self.GetSelected() | ||||
| 	if node == nil { | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import ( | ||||
| 	"github.com/jesseduffield/lazygit/pkg/gui/context/traits" | ||||
| 	"github.com/jesseduffield/lazygit/pkg/gui/types" | ||||
| 	"github.com/jesseduffield/lazygit/pkg/utils" | ||||
| 	"github.com/samber/lo" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| @@ -43,6 +44,36 @@ func (self *FileTreeViewModel) GetSelected() *FileNode { | ||||
| 	return self.Get(self.GetSelectedLineIdx()) | ||||
| } | ||||
|  | ||||
| func (self *FileTreeViewModel) GetSelectedItemId() string { | ||||
| 	item := self.GetSelected() | ||||
| 	if item == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return item.ID() | ||||
| } | ||||
|  | ||||
| func (self *FileTreeViewModel) GetSelectedItems() ([]*FileNode, int, int) { | ||||
| 	startIdx, endIdx := self.GetSelectionRange() | ||||
|  | ||||
| 	nodes := []*FileNode{} | ||||
| 	for i := startIdx; i <= endIdx; i++ { | ||||
| 		nodes = append(nodes, self.Get(i)) | ||||
| 	} | ||||
|  | ||||
| 	return nodes, startIdx, endIdx | ||||
| } | ||||
|  | ||||
| func (self *FileTreeViewModel) GetSelectedItemIds() ([]string, int, int) { | ||||
| 	selectedItems, startIdx, endIdx := self.GetSelectedItems() | ||||
|  | ||||
| 	ids := lo.Map(selectedItems, func(item *FileNode, _ int) string { | ||||
| 		return item.ID() | ||||
| 	}) | ||||
|  | ||||
| 	return ids, startIdx, endIdx | ||||
| } | ||||
|  | ||||
| func (self *FileTreeViewModel) GetSelectedFile() *models.File { | ||||
| 	node := self.GetSelected() | ||||
| 	if node == nil { | ||||
|   | ||||
| @@ -242,6 +242,12 @@ type MenuItem struct { | ||||
| 	Section *MenuSection | ||||
| } | ||||
|  | ||||
| // Defining this for the sake of conforming to the HasID interface, which is used | ||||
| // in list contexts. | ||||
| func (self *MenuItem) ID() string { | ||||
| 	return self.Label | ||||
| } | ||||
|  | ||||
| type Model struct { | ||||
| 	CommitFiles  []*models.CommitFile | ||||
| 	Files        []*models.File | ||||
|   | ||||
| @@ -136,6 +136,7 @@ type IListContext interface { | ||||
| 	Context | ||||
|  | ||||
| 	GetSelectedItemId() string | ||||
| 	GetSelectedItemIds() ([]string, int, int) | ||||
| 	IsItemVisible(item HasUrn) bool | ||||
|  | ||||
| 	GetList() IList | ||||
|   | ||||
| @@ -6,3 +6,8 @@ type Suggestion struct { | ||||
| 	// label is what is actually displayed so it can e.g. contain color | ||||
| 	Label string | ||||
| } | ||||
|  | ||||
| // Conforming to the HasID interface, which is needed for list contexts | ||||
| func (self *Suggestion) ID() string { | ||||
| 	return self.Value | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user