m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-14 06:13:47 -05:00

Border around the input section (prompt + info)

Close #4154
This commit is contained in:
Junegunn Choi
2025-01-02 16:24:46 +09:00
parent fd513f8af8
commit ee3916be17
10 changed files with 744 additions and 373 deletions

View File

@@ -164,6 +164,7 @@ type eachLine struct {
}
type itemLine struct {
valid bool
firstLine int
numLines int
cy int
@@ -179,11 +180,15 @@ type itemLine struct {
}
func (t *Terminal) markEmptyLine(line int) {
t.prevLines[line] = itemLine{firstLine: line, empty: true}
if t.window != t.inputWindow {
t.prevLines[line] = itemLine{valid: true, firstLine: line, empty: true}
}
}
func (t *Terminal) markOtherLine(line int) {
t.prevLines[line] = itemLine{firstLine: line, other: true}
if t.window != t.inputWindow {
t.prevLines[line] = itemLine{valid: true, firstLine: line, other: true}
}
}
type fitpad struct {
@@ -241,6 +246,9 @@ type Terminal struct {
previewLabel labelPrinter
previewLabelLen int
previewLabelOpts labelOpts
inputLabel labelPrinter
inputLabelLen int
inputLabelOpts labelOpts
pointer string
pointerLen int
pointerEmpty string
@@ -298,6 +306,7 @@ type Terminal struct {
listenUnsafe bool
borderShape tui.BorderShape
listBorderShape tui.BorderShape
inputBorderShape tui.BorderShape
listLabel labelPrinter
listLabelLen int
listLabelOpts labelOpts
@@ -306,6 +315,8 @@ type Terminal struct {
paused bool
border tui.Window
window tui.Window
inputWindow tui.Window
inputBorder tui.Window
wborder tui.Window
pborder tui.Window
pwindow tui.Window
@@ -394,6 +405,7 @@ const (
reqReinit
reqFullRedraw
reqResize
reqRedrawInputLabel
reqRedrawListLabel
reqRedrawBorderLabel
reqRedrawPreviewLabel
@@ -436,6 +448,7 @@ const (
actCancel
actChangeBorderLabel
actChangeListLabel
actChangeInputLabel
actChangeHeader
actChangeMulti
actChangePreviewLabel
@@ -497,6 +510,7 @@ const (
actTransform
actTransformBorderLabel
actTransformListLabel
actTransformInputLabel
actTransformHeader
actTransformPreviewLabel
actTransformPrompt
@@ -832,6 +846,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
listenUnsafe: opts.Unsafe,
borderShape: opts.BorderShape,
listBorderShape: opts.ListBorderShape,
inputBorderShape: opts.InputBorderShape,
borderWidth: 1,
listLabel: nil,
listLabelOpts: opts.ListLabel,
@@ -839,6 +854,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
borderLabelOpts: opts.BorderLabel,
previewLabel: nil,
previewLabelOpts: opts.PreviewLabel,
inputLabel: nil,
inputLabelOpts: opts.InputLabel,
cleanExit: opts.ClearOnExit,
executor: executor,
paused: opts.Phony,
@@ -902,7 +919,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
t.listLabel, t.listLabelLen = t.ansiLabelPrinter(opts.ListLabel.label, &tui.ColListLabel, false)
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(opts.BorderLabel.label, &tui.ColBorderLabel, false)
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(opts.PreviewLabel.label, &tui.ColPreviewLabel, false)
if opts.Separator == nil || len(*opts.Separator) > 0 {
t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(opts.InputLabel.label, &tui.ColInputLabel, false)
// Disable separator by default if input border is set
if opts.Separator == nil && !t.inputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0 {
bar := "─"
if opts.Separator != nil {
bar = *opts.Separator
@@ -1120,6 +1140,17 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
return printFn, length
}
// Temporarily switch 'window' so that we can use the existing windows with
// a different window
func (t *Terminal) withInputWindow(f func()) {
prevWindow := t.window
if t.inputWindow != nil {
t.window = t.inputWindow
}
f()
t.window = prevWindow
}
func (t *Terminal) parsePrompt(prompt string) (func(), int) {
var state *ansiState
prompt = firstLine(prompt)
@@ -1145,11 +1176,13 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
}
}
output := func() {
line := t.promptLine()
wrap := t.wrap
t.wrap = false
t.printHighlighted(
Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, line, line, true, nil, nil)
t.withInputWindow(func() {
line := t.promptLine()
t.printHighlighted(
Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, line, line, true, nil, nil)
})
t.wrap = wrap
}
_, promptLen := t.processTabs([]rune(trimmed), 0)
@@ -1566,6 +1599,12 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
if t.wborder != nil {
t.wborder = nil
}
if t.inputWindow != nil {
t.inputWindow = nil
}
if t.inputBorder != nil {
t.inputBorder = nil
}
if t.pborder != nil {
t.pborder = nil
}
@@ -1605,6 +1644,27 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
width -= paddingInt[1] + paddingInt[3]
height -= paddingInt[0] + paddingInt[2]
// Adjust position and size of the list window if input border is set
inputWindowHeight := 0
inputBorderHeight := 0
shift := 0
shrink := 0
hasInputWindow := t.inputBorderShape.Visible()
if hasInputWindow {
inputWindowHeight = 2
if t.noSeparatorLine() {
inputWindowHeight--
}
inputBorderHeight = borderLines(t.inputBorderShape) + inputWindowHeight
if t.layout == layoutReverse {
shift = inputBorderHeight
shrink = inputBorderHeight
} else {
shift = 0
shrink = inputBorderHeight
}
}
hasListBorder := t.listBorderShape.Visible()
innerWidth := width
innerHeight := height
@@ -1612,7 +1672,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
innerBorderFn := func(top int, left int, width int, height int) {
if hasListBorder {
t.wborder = t.tui.NewWindow(
top, left, width, height, tui.WindowList, tui.MakeBorderStyle(t.listBorderShape, t.unicode), false)
top+shift, left, width, height-shrink, tui.WindowList, tui.MakeBorderStyle(t.listBorderShape, t.unicode), false)
}
}
if hasListBorder {
@@ -1697,12 +1757,12 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
if previewOpts.position == posUp {
innerBorderFn(marginInt[0]+pheight, marginInt[3], width, height-pheight)
t.window = t.tui.NewWindow(
innerMarginInt[0]+pheight, innerMarginInt[3], innerWidth, innerHeight-pheight, tui.WindowList, noBorder, true)
innerMarginInt[0]+pheight+shift, innerMarginInt[3], innerWidth, innerHeight-pheight-shrink, tui.WindowList, noBorder, true)
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
} else {
innerBorderFn(marginInt[0], marginInt[3], width, height-pheight)
t.window = t.tui.NewWindow(
innerMarginInt[0], innerMarginInt[3], innerWidth, innerHeight-pheight, tui.WindowList, noBorder, true)
innerMarginInt[0]+shift, innerMarginInt[3], innerWidth, innerHeight-pheight-shrink, tui.WindowList, noBorder, true)
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
}
case posLeft, posRight:
@@ -1741,7 +1801,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
m = 1
}
t.window = t.tui.NewWindow(
innerMarginInt[0], innerMarginInt[3]+pwidth+m, innerWidth-pwidth-m, innerHeight, tui.WindowList, noBorder, true)
innerMarginInt[0]+shift, innerMarginInt[3]+pwidth+m, innerWidth-pwidth-m, innerHeight-shrink, tui.WindowList, noBorder, true)
// Clear characters on the margin
// fzf --bind 'space:preview(seq 100)' --preview-window left,1
@@ -1763,7 +1823,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
}
innerBorderFn(marginInt[0], marginInt[3], width-pwidth, height)
t.window = t.tui.NewWindow(
innerMarginInt[0], innerMarginInt[3], innerWidth-pwidth, innerHeight, tui.WindowList, noBorder, true)
innerMarginInt[0]+shift, innerMarginInt[3], innerWidth-pwidth, innerHeight-shrink, tui.WindowList, noBorder, true)
x := marginInt[3] + width - pwidth
createPreviewWindow(marginInt[0], x, pwidth, height)
}
@@ -1801,16 +1861,56 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
}
innerBorderFn(marginInt[0], marginInt[3], width, height)
t.window = t.tui.NewWindow(
innerMarginInt[0],
innerMarginInt[0]+shift,
innerMarginInt[3],
innerWidth,
innerHeight, tui.WindowList, noBorder, true)
innerHeight-shrink, tui.WindowList, noBorder, true)
}
// Set up input border
if hasInputWindow {
w := t.wborder
if t.wborder == nil {
w = t.window
}
if t.layout == layoutReverse {
t.inputBorder = t.tui.NewWindow(
w.Top()-shrink,
w.Left(),
w.Width(),
util.Min(shrink, screenHeight), tui.WindowInput, tui.MakeBorderStyle(t.inputBorderShape, t.unicode), true)
} else {
t.inputBorder = t.tui.NewWindow(
w.Top()+w.Height(),
w.Left(),
w.Width(),
util.Min(shrink, screenHeight), tui.WindowInput, tui.MakeBorderStyle(t.inputBorderShape, t.unicode), true)
}
top := t.inputBorder.Top()
left := t.inputBorder.Left()
if t.inputBorderShape.HasTop() {
top++
}
if t.inputBorderShape.HasLeft() {
left += t.borderWidth + 1
}
width := t.inputBorder.Width() - borderColumns(t.inputBorderShape, t.borderWidth)
if t.inputBorderShape.HasRight() {
width++
}
t.inputWindow = t.tui.NewWindow(
top,
left,
width,
t.inputBorder.Height()-borderLines(t.inputBorderShape),
tui.WindowInput, noBorder, true)
}
// Print border label
t.printLabel(t.wborder, t.listLabel, t.listLabelOpts, t.listLabelLen, t.listBorderShape, false)
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, false)
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.border, false)
t.printLabel(t.inputBorder, t.inputLabel, t.inputLabelOpts, t.inputLabelLen, t.inputBorderShape, false)
}
func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape, redrawBorder bool) {
@@ -1874,7 +1974,11 @@ func (t *Terminal) truncateQuery() {
}
func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
maxWidth := util.Max(1, t.window.Width()-t.promptLen-1)
w := t.window
if t.inputWindow != nil {
w = t.inputWindow
}
maxWidth := util.Max(1, w.Width()-t.promptLen-1)
_, overflow := t.trimLeft(t.input[:t.cx], maxWidth)
minOffset := int(overflow)
@@ -1889,6 +1993,9 @@ func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
}
func (t *Terminal) promptLine() int {
if t.inputWindow != nil {
return 0
}
if t.headerFirst {
max := t.window.Height() - 1
if max <= 0 { // Extremely short terminal
@@ -1903,10 +2010,25 @@ func (t *Terminal) promptLine() int {
}
func (t *Terminal) placeCursor() {
if t.inputWindow != nil {
y := t.inputWindow.Height() - 1
if t.layout == layoutReverse {
y = 0
}
t.inputWindow.Move(y, t.promptLen+t.queryLen[0])
return
}
t.move(t.promptLine(), t.promptLen+t.queryLen[0], false)
}
func (t *Terminal) printPrompt() {
w := t.window
if t.inputWindow != nil {
w = t.inputWindow
}
if w.Height() == 0 {
return
}
t.prompt()
before, after := t.updatePromptOffset()
@@ -1914,8 +2036,8 @@ func (t *Terminal) printPrompt() {
if t.paused {
color = tui.ColDisabled
}
t.window.CPrint(color, string(before))
t.window.CPrint(color, string(after))
w.CPrint(color, string(before))
w.CPrint(color, string(after))
}
func (t *Terminal) trimMessage(message string, maxWidth int) string {
@@ -1927,6 +2049,12 @@ func (t *Terminal) trimMessage(message string, maxWidth int) string {
}
func (t *Terminal) printInfo() {
t.withInputWindow(func() {
t.printInfoImpl()
})
}
func (t *Terminal) printInfoImpl() {
if t.window.Width() <= 1 {
return
}
@@ -2124,7 +2252,7 @@ func (t *Terminal) printHeader() {
return
}
max := t.window.Height()
if t.headerFirst {
if t.inputWindow == nil && t.headerFirst {
max--
if !t.noSeparatorLine() {
max--
@@ -2144,7 +2272,7 @@ func (t *Terminal) printHeader() {
if needReverse && idx < len(t.header0) {
line = len(t.header0) - idx - 1
}
if !t.headerFirst {
if t.inputWindow == nil && !t.headerFirst {
line++
if !t.noSeparatorLine() {
line++
@@ -2189,10 +2317,7 @@ func (t *Terminal) printList() {
count := t.merger.Length() - t.offset
// Start line
startLine := 2 + t.visibleHeaderLines()
if t.noSeparatorLine() {
startLine--
}
startLine := t.promptLines() + t.visibleHeaderLines()
maxy += startLine
barRange := [2]int{startLine + barStart, startLine + barStart + barLength}
@@ -2233,10 +2358,10 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
// Avoid unnecessary redraw
numLines, _ := t.numItemLines(item, maxLine-line+1)
newLine := itemLine{firstLine: line, numLines: numLines, cy: index + t.offset, current: current, selected: selected, label: label,
newLine := itemLine{valid: true, firstLine: line, numLines: numLines, cy: index + t.offset, current: current, selected: selected, label: label,
result: result, queryLen: len(t.input), width: 0, hasBar: line >= barRange[0] && line < barRange[1]}
prevLine := t.prevLines[line]
forceRedraw := prevLine.other || prevLine.firstLine != newLine.firstLine
forceRedraw := !prevLine.valid || prevLine.other || prevLine.firstLine != newLine.firstLine
printBar := func(lineNum int, forceRedraw bool) bool {
return t.printBar(lineNum, forceRedraw, barRange)
}
@@ -3962,6 +4087,8 @@ func (t *Terminal) Loop() error {
if t.hasPreviewer() {
t.previewBox.Set(reqPreviewReady, nil)
}
case reqRedrawInputLabel:
t.printLabel(t.inputBorder, t.inputLabel, t.inputLabelOpts, t.inputLabelLen, t.inputBorderShape, true)
case reqRedrawListLabel:
t.printLabel(t.wborder, t.listLabel, t.listLabelOpts, t.listLabelLen, t.listBorderShape, true)
case reqRedrawBorderLabel:
@@ -4345,6 +4472,12 @@ func (t *Terminal) Loop() error {
} else {
req(reqHeader)
}
case actChangeInputLabel:
t.inputLabelOpts.label = a.a
if t.inputBorder != nil {
t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(a.a, &tui.ColInputLabel, false)
req(reqRedrawInputLabel)
}
case actChangeListLabel:
t.listLabelOpts.label = a.a
if t.wborder != nil {
@@ -4368,6 +4501,13 @@ func (t *Terminal) Loop() error {
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil {
return doActions(actions)
}
case actTransformInputLabel:
label := t.executeCommand(a.a, false, true, true, true, "")
t.inputLabelOpts.label = label
if t.inputBorder != nil {
t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(label, &tui.ColInputLabel, false)
req(reqRedrawInputLabel)
}
case actTransformListLabel:
label := t.executeCommand(a.a, false, true, true, true, "")
t.listLabelOpts.label = label
@@ -4927,6 +5067,21 @@ func (t *Terminal) Loop() error {
break
}
// Inside the input window
if t.inputWindow != nil && t.inputWindow.Enclose(my, mx) {
mx -= t.inputWindow.Left()
my -= t.inputWindow.Top()
y := t.inputWindow.Height() - 1
if t.layout == layoutReverse {
y = 0
}
mxCons := util.Constrain(mx-t.promptLen, 0, len(t.input))
if my == y && mxCons >= 0 {
t.cx = mxCons + t.xoffset
}
break
}
// Ignored
if !t.window.Enclose(my, mx) && !barDragging {
break
@@ -4935,10 +5090,7 @@ func (t *Terminal) Loop() error {
// Translate coordinates
mx -= t.window.Left()
my -= t.window.Top()
min := 2 + t.visibleHeaderLines()
if t.noSeparatorLine() {
min--
}
min := t.promptLines() + t.visibleHeaderLines()
h := t.window.Height()
switch t.layout {
case layoutDefault:
@@ -4990,7 +5142,7 @@ func (t *Terminal) Loop() error {
if me.Down {
mxCons := util.Constrain(mx-t.promptLen, 0, len(t.input))
if my == t.promptLine() && mxCons >= 0 {
if t.inputWindow == nil && my == t.promptLine() && mxCons >= 0 {
// Prompt
t.cx = mxCons + t.xoffset
} else if my >= min {
@@ -5011,7 +5163,7 @@ func (t *Terminal) Loop() error {
// Header
numLines := t.visibleHeaderLines()
lineOffset := 0
if !t.headerFirst {
if t.inputWindow == nil && !t.headerFirst {
// offset for info line
if t.noSeparatorLine() {
lineOffset = 1
@@ -5339,11 +5491,20 @@ func (t *Terminal) vset(o int) bool {
return t.cy == o
}
func (t *Terminal) maxItems() int {
max := t.window.Height() - 2 - t.visibleHeaderLines()
if t.noSeparatorLine() {
max++
// Number of prompt lines in the list window
func (t *Terminal) promptLines() int {
if t.inputWindow != nil {
return 0
}
if t.noSeparatorLine() {
return 1
}
return 2
}
// Number of item lines in the list window
func (t *Terminal) maxItems() int {
max := t.window.Height() - t.visibleHeaderLines() - t.promptLines()
return util.Max(max, 0)
}