/
githubmirror
/
lazygit
Обзор
Документация
Войти
/
githubmirror
/
lazygit
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
pkg/gui/context/list_renderer.go
160 строк
5 KB
Stefan Haller
Change list section headers to a slightly more elegant design
31 июл 2026, 09:03
31 июл 2026, 09:03
31cbabe
Код
Авторство
О чём код?
package context import ( "fmt" "strings" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" "github.com/samber/lo" "golang.org/x/exp/slices" ) func formatListSectionHeader(label string) string { return fmt.Sprintf("─── %s", label) } type NonModelItem struct { // Where in the model this should be inserted Index int // Content to render Content string // The column from which to render the item Column int } type ListRenderer struct { list types.IList // Function to get the display strings for each model item in the given // range. startIdx and endIdx are model indices. For each model item, return // an array of strings, one for each column; the list renderer will take // care of aligning the columns appropriately. getDisplayStrings func(startIdx int, endIdx int) [][]string // Alignment for each column. If nil, the default is left alignment getColumnAlignments func() []utils.Alignment // Function to insert non-model items (e.g. section headers). If nil, no // such items are inserted getNonModelItems func() []*NonModelItem // The remaining fields are private and shouldn't be initialized by clients columnPositions []int } func (self *ListRenderer) GetList() types.IList { return self.list } func (self *ListRenderer) getNonModelItemList() []*NonModelItem { if self.getNonModelItems == nil { return nil } return self.getNonModelItems() } func (self *ListRenderer) ModelIndexToViewIndex(modelIndex int) int { return modelIndexToViewIndex(self.list.Len(), self.getNonModelItemList(), modelIndex) } func (self *ListRenderer) ViewIndexToModelIndex(viewIndex int) int { return viewIndexToModelIndex(self.list.Len(), self.getNonModelItemList(), viewIndex) } // modelToViewIndexConverter returns a model-to-view index conversion that // reuses a single snapshot of the non-model items. Callers that convert many // indices in a row (e.g. search, which converts every commit) should use this // rather than calling ModelIndexToViewIndex per index, which would rebuild the // non-model items each time. func (self *ListRenderer) modelToViewIndexConverter() func(modelIndex int) int { listLength := self.list.Len() nonModelItems := self.getNonModelItemList() return func(modelIndex int) int { return modelIndexToViewIndex(listLength, nonModelItems, modelIndex) } } // The view shows the model items with the non-model items (e.g. section // headers) inserted at their model indices. The two conversions below are // computed directly from the current list length and non-model items, so they // don't depend on the list having been rendered, and they can never be stale // with respect to a model that changed since the last render (which used to // cause both wrong results and index-out-of-range panics). // // The non-model items are assumed to be ordered by their Index, which is how // all producers build them; the i-th one therefore ends up at view index // Index+i. func modelIndexToViewIndex(listLength int, nonModelItems []*NonModelItem, modelIndex int) int { modelIndex = lo.Clamp(modelIndex, 0, listLength) // Each non-model item inserted at or before this model item pushes it down // by one row in the view. viewIndex := modelIndex for _, item := range nonModelItems { if item.Index <= modelIndex { viewIndex++ } } return viewIndex } func viewIndexToModelIndex(listLength int, nonModelItems []*NonModelItem, viewIndex int) int { viewIndex = lo.Clamp(viewIndex, 0, listLength+len(nonModelItems)) // Subtract the non-model items that appear before this view index. modelIndex := viewIndex for i, item := range nonModelItems { if item.Index+i < viewIndex { modelIndex-- } } return modelIndex } func (self *ListRenderer) ColumnPositions() []int { return self.columnPositions } // startIdx and endIdx are view indices, not model indices. If you want to // render the whole list, pass -1 for both. func (self *ListRenderer) renderLines(startIdx int, endIdx int) string { var columnAlignments []utils.Alignment if self.getColumnAlignments != nil { columnAlignments = self.getColumnAlignments() } nonModelItems := self.getNonModelItemList() startModelIdx := 0 if startIdx == -1 { startIdx = 0 } else { startModelIdx = viewIndexToModelIndex(self.list.Len(), nonModelItems, startIdx) } endModelIdx := self.list.Len() if endIdx == -1 { endIdx = endModelIdx + len(nonModelItems) } else { endModelIdx = viewIndexToModelIndex(self.list.Len(), nonModelItems, endIdx) } lines, columnPositions := utils.RenderDisplayStrings( self.getDisplayStrings(startModelIdx, endModelIdx), columnAlignments) self.columnPositions = columnPositions lines = self.insertNonModelItems(nonModelItems, endIdx, startIdx, lines, columnPositions) return strings.Join(lines, "\n") } func (self *ListRenderer) insertNonModelItems( nonModelItems []*NonModelItem, endIdx int, startIdx int, lines []string, columnPositions []int, ) []string { offset := 0 for _, item := range nonModelItems { if item.Index+offset >= endIdx { break } if item.Index+offset >= startIdx { padding := "" if columnPositions != nil { padding = strings.Repeat(" ", columnPositions[item.Column]) } lines = slices.Insert(lines, item.Index+offset-startIdx, padding+item.Content) } offset++ } return lines }