summaryrefslogtreecommitdiffstats
path: root/pkg/gui/context/list_context_trait.go
blob: aca33cbd061b2fcabead6b5bbc379b8e833b9016 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package context

import (
	"fmt"

	"github.com/jesseduffield/lazygit/pkg/gui/types"
)

type ListContextTrait struct {
	types.Context
	ListRenderer

	c *ContextCommon
	// Some contexts, like the commit context, will highlight the path from the selected commit
	// to its parents, because it's ambiguous otherwise. For these, we need to refresh the viewport
	// so that we show the highlighted path.
	// TODO: now that we allow scrolling, we should be smarter about what gets refreshed:
	// we should find out exactly which lines are now part of the path and refresh those.
	// We should also keep track of the previous path and refresh those lines too.
	refreshViewportOnChange bool
	// If this is true, we only render the visible lines of the list. Useful for lists that can
	// get very long, because it can save a lot of memory
	renderOnlyVisibleLines bool
}

func (self *ListContextTrait) IsListContext() {}

func (self *ListContextTrait) FocusLine() {
	// Doing this at the end of the layout function because we need the view to be
	// resized before we focus the line, otherwise if we're in accordion mode
	// the view could be squashed and won't how to adjust the cursor/origin.
	// Also, refreshing the viewport needs to happen after the view has been resized.
	self.c.AfterLayout(func() error {
		oldOrigin, _ := self.GetViewTrait().ViewPortYBounds()

		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 self.refreshViewportOnChange {
			self.refreshViewport()
		} else if self.renderOnlyVisibleLines {
			newOrigin, _ := self.GetViewTrait().ViewPortYBounds()
			if oldOrigin != newOrigin {
				return self.HandleRender()
			}
		}
		return nil
	})

	self.setFooter()
}

func (self *ListContextTrait) refreshViewport() {
	startIdx, length := self.GetViewTrait().ViewPortYBounds()
	content := self.renderLines(startIdx, startIdx+length)
	self.GetViewTrait().SetViewPortContent(content)
}

func (self *ListContextTrait) setFooter() {
	self.GetViewTrait().SetFooter(formatListFooter(self.list.GetSelectedLineIdx(), self.list.Len()))
}

func formatListFooter(selectedLineIdx int, length int) string {
	return fmt.Sprintf("%d of %d", selectedLineIdx+1, length)
}

func (self *ListContextTrait) HandleFocus(opts types.OnFocusOpts) error {
	self.FocusLine()

	self.GetViewTrait().SetHighlight(self.list.Len() > 0)

	return self.Context.HandleFocus(opts)
}

func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) error {
	self.GetViewTrait().SetOriginX(0)

	if self.refreshViewportOnChange {
		self.refreshViewport()
	}

	return self.Context.HandleFocusLost(opts)
}

// 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.ClampSelection()
	if self.renderOnlyVisibleLines {
		// Rendering only the visible area can save a lot of cell memory for
		// those views that support it.
		totalLength := self.list.Len()
		if self.getNonModelItems != nil {
			totalLength += len(self.getNonModelItems())
		}
		self.GetViewTrait().SetContentLineCount(totalLength)
		startIdx, length := self.GetViewTrait().ViewPortYBounds()
		content := self.renderLines(startIdx, startIdx+length)
		self.GetViewTrait().SetViewPortContentAndClearEverythingElse(content)
	} else {
		content := self.renderLines(-1, -1)
		self.GetViewTrait().SetContent(content)
	}
	self.c.Render()
	self.setFooter()

	return nil
}

func (self *ListContextTrait) OnSearchSelect(selectedLineIdx int) error {
	self.GetList().SetSelection(self.ViewIndexToModelIndex(selectedLineIdx))
	return self.HandleFocus(types.OnFocusOpts{})
}

func (self *ListContextTrait) IsItemVisible(item types.HasUrn) bool {
	startIdx, length := self.GetViewTrait().ViewPortYBounds()
	selectionStart := self.ViewIndexToModelIndex(startIdx)
	selectionEnd := self.ViewIndexToModelIndex(startIdx + length)
	for i := selectionStart; i < selectionEnd; i++ {
		iterItem := self.GetList().GetItem(i)
		if iterItem != nil && iterItem.URN() == item.URN() {
			return true
		}
	}
	return false
}

// By default, list contexts supporta range select
func (self *ListContextTrait) RangeSelectEnabled() bool {
	return true
}