mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-14 02:57:52 +08:00
144 lines
4.6 KiB
Go
144 lines
4.6 KiB
Go
package cli
|
|
|
|
import "src.elv.sh/pkg/wcwidth"
|
|
|
|
// The number of lines the listing mode keeps between the current selected item
|
|
// and the top and bottom edges of the window, unless the available height is
|
|
// too small or if the selected item is near the top or bottom of the list.
|
|
var respectDistance = 2
|
|
|
|
// Determines the index of the first item to show in vertical mode.
|
|
//
|
|
// This function does not return the full window, but just the first item to
|
|
// show, and how many initial lines to crop. The window determined by this
|
|
// algorithm has the following properties:
|
|
//
|
|
// * It always includes the selected item.
|
|
//
|
|
// * The combined height of all the entries in the window is equal to
|
|
// min(height, combined height of all entries).
|
|
//
|
|
// * There are at least respectDistance rows above the first row of the selected
|
|
// item, as well as that many rows below the last row of the selected item,
|
|
// unless the height is too small.
|
|
//
|
|
// * Among all values satisfying the above conditions, the value of first is
|
|
// the one closest to lastFirst.
|
|
func getVerticalWindow(state ListBoxState, height int) (first, crop int) {
|
|
items, selected, lastFirst := state.Items, state.Selected, state.First
|
|
n := items.Len()
|
|
if selected < 0 {
|
|
selected = 0
|
|
} else if selected >= n {
|
|
selected = n - 1
|
|
}
|
|
selectedHeight := items.Show(selected).CountLines()
|
|
|
|
if height <= selectedHeight {
|
|
// The height is not big enough (or just big enough) to fit the selected
|
|
// item. Fit as much as the selected item as we can.
|
|
return selected, 0
|
|
}
|
|
|
|
// Determine the minimum amount of space required for the downward direction.
|
|
budget := height - selectedHeight
|
|
var needDown int
|
|
if budget >= 2*respectDistance {
|
|
// If we can afford maintaining the respect distance on both sides, then
|
|
// the minimum amount of space required is the respect distance.
|
|
needDown = respectDistance
|
|
} else {
|
|
// Otherwise we split the available space by half. The downward (no pun
|
|
// intended) rounding here is an arbitrary choice.
|
|
needDown = budget / 2
|
|
}
|
|
// Calculate how much of the budget the downward direction can use. This is
|
|
// used to 1) potentially shrink needDown 2) decide how much to expand
|
|
// upward later.
|
|
useDown := 0
|
|
for i := selected + 1; i < n; i++ {
|
|
useDown += items.Show(i).CountLines()
|
|
if useDown >= budget {
|
|
break
|
|
}
|
|
}
|
|
if needDown > useDown {
|
|
// We reached the last item without using all of needDown. That means we
|
|
// don't need so much in the downward direction.
|
|
needDown = useDown
|
|
}
|
|
|
|
// The maximum amount of space we can use in the upward direction is the
|
|
// entire budget minus the minimum amount of space we need in the downward
|
|
// direction.
|
|
budgetUp := budget - needDown
|
|
|
|
useUp := 0
|
|
// Extend upwards until any of the following becomes true:
|
|
//
|
|
// * We have exhausted budgetUp;
|
|
//
|
|
// * We have reached item 0;
|
|
//
|
|
// * We have reached or passed lastFirst, satisfied the upward respect
|
|
// distance, and will be able to use up the entire budget when expanding
|
|
// downwards later.
|
|
for i := selected - 1; i >= 0; i-- {
|
|
useUp += items.Show(i).CountLines()
|
|
if useUp >= budgetUp {
|
|
return i, useUp - budgetUp
|
|
}
|
|
if i <= lastFirst && useUp >= respectDistance && useUp+useDown >= budget {
|
|
return i, 0
|
|
}
|
|
}
|
|
return 0, 0
|
|
}
|
|
|
|
// Determines the window to show in horizontal It returns the first item
|
|
// to show and the amount of height required.
|
|
func getHorizontalWindow(state ListBoxState, padding, width, height int) (int, int) {
|
|
items := state.Items
|
|
n := items.Len()
|
|
// Lower bound of number of items that can fit in a row.
|
|
perRow := (width + listBoxColGap) / (maxWidth(items, padding, 0, n) + listBoxColGap)
|
|
if perRow == 0 {
|
|
// We trim items that are too wide, so there is at least one item per row.
|
|
perRow = 1
|
|
}
|
|
if height*perRow >= n {
|
|
// All items can fit.
|
|
return 0, (n + perRow - 1) / perRow
|
|
}
|
|
// Reduce the amount of available height by one because the last row will be
|
|
// reserved for the scrollbar.
|
|
height--
|
|
selected, lastFirst := state.Selected, state.First
|
|
// Start with the column containing the selected item, move left until
|
|
// either the width is exhausted, or lastFirst has been reached.
|
|
first := selected / height * height
|
|
usedWidth := maxWidth(items, padding, first, first+height)
|
|
for ; first > lastFirst; first -= height {
|
|
usedWidth += maxWidth(items, padding, first-height, first) + listBoxColGap
|
|
if usedWidth > width {
|
|
break
|
|
}
|
|
}
|
|
return first, height
|
|
}
|
|
|
|
func maxWidth(items Items, padding, low, high int) int {
|
|
n := items.Len()
|
|
width := 0
|
|
for i := low; i < high && i < n; i++ {
|
|
w := 0
|
|
for _, seg := range items.Show(i) {
|
|
w += wcwidth.Of(seg.Text)
|
|
}
|
|
if width < w {
|
|
width = w
|
|
}
|
|
}
|
|
return width + 2*padding
|
|
}
|