summaryrefslogtreecommitdiff
path: root/src/terminal.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/terminal.go')
-rw-r--r--src/terminal.go225
1 files changed, 192 insertions, 33 deletions
diff --git a/src/terminal.go b/src/terminal.go
index 1f0a6e41..96b06259 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -274,9 +274,11 @@ type Terminal struct {
footerLabelLen int
footerLabelOpts labelOpts
gutterReverse bool
+ gutterRawReverse bool
pointer string
pointerLen int
pointerEmpty string
+ pointerEmptyRaw string
marker string
markerLen int
markerEmpty string
@@ -297,6 +299,7 @@ type Terminal struct {
subWordNext string
cx int
cy int
+ lastMatchingIndex int32
offset int
xoffset int
yanked []rune
@@ -383,6 +386,9 @@ type Terminal struct {
printer func(string)
printsep string
merger *Merger
+ passMerger *Merger
+ resultMerger *Merger
+ matchMap map[int32]Result
selected map[int32]selectedItem
version int64
revision revision
@@ -429,6 +435,7 @@ type Terminal struct {
clickFooterColumn int
proxyScript string
numLinesCache map[int32]numLinesCacheValue
+ raw bool
}
type numLinesCacheValue struct {
@@ -569,13 +576,16 @@ const (
actToggleWrap
actToggleMultiLine
actToggleHscroll
+ actToggleRaw
actTrackCurrent
actToggleInput
actHideInput
actShowInput
actUntrackCurrent
actDown
+ actDownMatch
actUp
+ actUpMatch
actPageUp
actPageDown
actPosition
@@ -796,8 +806,10 @@ func defaultKeymap() map[tui.Event][]*action {
add(tui.CtrlK, actUp)
add(tui.CtrlL, actClearScreen)
add(tui.Enter, actAccept)
- add(tui.CtrlN, actDown)
- add(tui.CtrlP, actUp)
+ add(tui.CtrlN, actDownMatch)
+ add(tui.CtrlP, actUpMatch)
+ add(tui.AltDown, actDownMatch)
+ add(tui.AltUp, actUpMatch)
add(tui.CtrlU, actUnixLineDiscard)
add(tui.CtrlW, actUnixWordRubout)
add(tui.CtrlY, actYank)
@@ -953,6 +965,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
}
keymapCopy := maps.Clone(opts.Keymap)
+ em := EmptyMerger(revision{})
t := Terminal{
initDelay: delay,
infoCommand: opts.InfoCommand,
@@ -980,6 +993,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
subWordNext: subWordNext,
cx: len(input),
cy: 0,
+ lastMatchingIndex: minItem.Index(),
offset: 0,
xoffset: 0,
yanked: []rune{},
@@ -1039,6 +1053,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
nth: opts.Nth,
nthCurrent: opts.Nth,
tabstop: opts.Tabstop,
+ raw: opts.Raw,
hasStartActions: false,
hasResultActions: false,
hasFocusActions: false,
@@ -1052,7 +1067,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
printer: opts.Printer,
printsep: opts.PrintSep,
proxyScript: opts.ProxyScript,
- merger: EmptyMerger(revision{}),
+ merger: em,
+ passMerger: em,
+ resultMerger: em,
+ matchMap: make(map[int32]Result),
selected: make(map[int32]selectedItem),
runningCmds: util.NewConcurrentSet[*runningCmd](),
reqBox: util.NewEventBox(),
@@ -1093,7 +1111,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
tui.InitTheme(opts.Theme, renderer.DefaultTheme(), opts.Black, opts.InputBorderShape.Visible(), opts.HeaderBorderShape.Visible())
// Gutter character
- var gutterChar string
+ var gutterChar, gutterRawChar string
if opts.Gutter != nil {
gutterChar = *opts.Gutter
} else if t.unicode && !t.theme.Gutter.Color.IsDefault() {
@@ -1103,12 +1121,24 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
t.gutterReverse = true
}
+ if opts.GutterRaw != nil {
+ gutterRawChar = *opts.GutterRaw
+ } else if t.unicode && !t.theme.Gutter.Color.IsDefault() {
+ // TODO: Doesn't look too good. Maybe use a different color instead, or both?
+ gutterRawChar = "▖"
+ } else {
+ gutterRawChar = ":"
+ t.gutterRawReverse = false
+ }
+
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
// Pre-calculated empty pointer and marker signs
if t.pointerLen == 0 {
t.pointerEmpty = ""
+ t.pointerEmptyRaw = ""
} else {
t.pointerEmpty = gutterChar + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
+ t.pointerEmptyRaw = gutterRawChar + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
}
t.markerEmpty = strings.Repeat(" ", t.markerLen)
@@ -1282,7 +1312,7 @@ func (t *Terminal) environImpl(forPreview bool) []string {
}
env = append(env, "FZF_INPUT_STATE="+inputState)
env = append(env, fmt.Sprintf("FZF_TOTAL_COUNT=%d", t.count))
- env = append(env, fmt.Sprintf("FZF_MATCH_COUNT=%d", t.merger.Length()))
+ env = append(env, fmt.Sprintf("FZF_MATCH_COUNT=%d", t.resultMerger.Length()))
env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected)))
env = append(env, fmt.Sprintf("FZF_LINES=%d", t.areaLines))
env = append(env, fmt.Sprintf("FZF_COLUMNS=%d", t.areaColumns))
@@ -1693,7 +1723,8 @@ func (t *Terminal) UpdateProgress(progress float32) {
}
// UpdateList updates Merger to display the list
-func (t *Terminal) UpdateList(merger *Merger) {
+func (t *Terminal) UpdateList(result MatchResult) {
+ merger := result.merger
t.mutex.Lock()
prevIndex := minItem.Index()
newRevision := merger.Revision()
@@ -1706,6 +1737,15 @@ func (t *Terminal) UpdateList(merger *Merger) {
}
t.progress = 100
t.merger = merger
+ t.resultMerger = merger
+ t.passMerger = result.passMerger
+ if t.raw {
+ t.merger = result.passMerger
+ t.matchMap = t.resultMerger.ToMap()
+ } else {
+ t.merger = result.merger
+ t.matchMap = make(map[int32]Result)
+ }
if t.revision != newRevision {
if !t.revision.compatible(newRevision) {
// Reloaded: clear selection
@@ -1754,7 +1794,7 @@ func (t *Terminal) UpdateList(merger *Merger) {
}
needActivation := false
if !t.reading {
- switch t.merger.Length() {
+ switch t.resultMerger.Length() {
case 0:
zero := tui.Zero.AsEvent()
if _, prs := t.keymap[zero]; prs {
@@ -2801,7 +2841,7 @@ func (t *Terminal) printInfoImpl() {
return
}
- found := t.merger.Length()
+ found := t.resultMerger.Length()
total := util.Max(found, t.count)
output := fmt.Sprintf("%d/%d", found, total)
if t.toggleSort {
@@ -3122,12 +3162,16 @@ func (t *Terminal) gutter(current bool) {
var color tui.ColorPair
if current {
color = tui.ColCurrentCursorEmpty
- } else if t.gutterReverse {
+ } else if !t.raw && t.gutterReverse || t.raw && t.gutterRawReverse {
color = tui.ColCursorEmpty
} else {
color = tui.ColCursorEmptyChar
}
- t.window.CPrint(color, t.pointerEmpty)
+ gutter := t.pointerEmpty
+ if t.raw {
+ gutter = t.pointerEmptyRaw
+ }
+ t.window.CPrint(color, gutter)
}
func (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) {
@@ -3162,7 +3206,11 @@ func (t *Terminal) printList() {
for line, itemCount := startLine, 0; line <= maxy; line, itemCount = line+1, itemCount+1 {
if itemCount < count {
item := t.merger.Get(itemCount + t.offset)
- line = t.printItem(item, line, maxy, itemCount, itemCount == t.cy-t.offset, barRange)
+ current := itemCount == t.cy-t.offset
+ if current && (!t.raw || t.isItemMatch(item.item)) {
+ t.lastMatchingIndex = item.Index()
+ }
+ line = t.printItem(item, line, maxy, itemCount, current, barRange)
} else if !t.prevLines[line].empty {
t.renderEmptyLine(line, barRange)
}
@@ -3184,6 +3232,14 @@ func (t *Terminal) printBar(lineNum int, forceRedraw bool, barRange [2]int) bool
func (t *Terminal) printItem(result Result, line int, maxLine int, index int, current bool, barRange [2]int) int {
item := result.item
+ matched := true
+ var matchResult Result
+ if t.raw {
+ if matchResult, matched = t.matchMap[item.Index()]; matched {
+ result = matchResult
+ }
+ }
+
_, selected := t.selected[item.Index()]
label := ""
extraWidth := 0
@@ -3310,7 +3366,13 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
}
return indentSize
}
- finalLineNum = t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true, line, maxLine, forceRedraw, preTask, postTask)
+ base := tui.ColCurrent
+ match := tui.ColCurrentMatch
+ if !matched {
+ base = base.WithFg(t.theme.Hidden)
+ match = match.WithFg(t.theme.Hidden)
+ }
+ finalLineNum = t.printHighlighted(result, base, match, true, true, line, maxLine, forceRedraw, preTask, postTask)
} else {
preTask := func(marker markerClass) int {
w := t.window.Width() - t.pointerLen
@@ -3344,6 +3406,10 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
base = base.WithBg(altBg)
match = match.WithBg(altBg)
}
+ if !matched {
+ base = base.WithFg(t.theme.Hidden)
+ match = match.WithFg(t.theme.Hidden)
+ }
finalLineNum = t.printHighlighted(result, base, match, false, true, line, maxLine, forceRedraw, preTask, postTask)
}
for i := 0; i < t.gap && finalLineNum < maxLine; i++ {
@@ -3396,8 +3462,8 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
item := result.item
matchOffsets := []Offset{}
var pos *[]int
- if match && t.merger.pattern != nil {
- _, matchOffsets, pos = t.merger.pattern.MatchItem(item, true, t.slab)
+ if match && t.resultMerger.pattern != nil {
+ _, matchOffsets, pos = t.resultMerger.pattern.MatchItem(item, true, t.slab)
}
charOffsets := matchOffsets
if pos != nil {
@@ -3429,7 +3495,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
}
if !wholeCovered && t.nthAttr > 0 {
var tokens []Token
- if item.transformed != nil && item.transformed.revision == t.merger.revision {
+ if item.transformed != nil && item.transformed.revision == t.resultMerger.revision {
tokens = item.transformed.tokens
} else {
tokens = Transform(Tokenize(item.text.ToString(), t.delimiter), t.nthCurrent)
@@ -4677,6 +4743,33 @@ func (t *Terminal) currentItem() *Item {
return nil
}
+func (t *Terminal) isCurrentItemMatch() bool {
+ cnt := t.merger.Length()
+ if t.cy >= 0 && cnt > 0 && cnt > t.cy {
+ if !t.raw {
+ return true
+ }
+ item := t.merger.Get(t.cy).item
+ return t.isItemMatch(item)
+ }
+ return false
+}
+
+func (t *Terminal) isItemMatch(item *Item) bool {
+ _, matched := t.matchMap[item.Index()]
+ return matched
+}
+
+func (t *Terminal) filterSelected() {
+ filtered := make(map[int32]selectedItem)
+ for k, v := range t.selected {
+ if t.isItemMatch(v.item) {
+ filtered[k] = v
+ }
+ }
+ t.selected = filtered
+}
+
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, [3][]*Item) {
current := t.currentItem()
slot, plus, asterisk, forceUpdate := hasPreviewFlags(template)
@@ -4721,6 +4814,10 @@ func (t *Terminal) selectItem(item *Item) bool {
if len(t.selected) >= t.multi {
return false
}
+ // TODO: Should we allow selecting non-matching items?
+ // if t.raw && !t.isItemMatch(item) {
+ // return false
+ // }
if _, found := t.selected[item.Index()]; found {
return true
}
@@ -5917,8 +6014,9 @@ func (t *Terminal) Loop() error {
}
case actSelectAll:
if t.multi > 0 {
- for i := 0; i < t.merger.Length(); i++ {
- if !t.selectItem(t.merger.Get(i).item) {
+ // Limit the scope only to the matching items
+ for i := 0; i < t.resultMerger.Length(); i++ {
+ if !t.selectItem(t.resultMerger.Get(i).item) {
break
}
}
@@ -5926,8 +6024,10 @@ func (t *Terminal) Loop() error {
}
case actDeselectAll:
if t.multi > 0 {
- for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ {
- t.deselectItem(t.merger.Get(i).item)
+ // Also limit the scope only to the matching items, while this may
+ // not be straightforward in raw mode.
+ for i := 0; i < t.resultMerger.Length() && len(t.selected) > 0; i++ {
+ t.deselectItem(t.resultMerger.Get(i).item)
}
req(reqList, reqInfo)
}
@@ -5955,17 +6055,17 @@ func (t *Terminal) Loop() error {
case actToggleAll:
if t.multi > 0 {
prevIndexes := make(map[int]struct{})
- for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ {
- item := t.merger.Get(i).item
+ for i := 0; i < t.resultMerger.Length() && len(t.selected) > 0; i++ {
+ item := t.resultMerger.Get(i).item
if _, found := t.selected[item.Index()]; found {
prevIndexes[i] = struct{}{}
t.deselectItem(item)
}
}
- for i := 0; i < t.merger.Length(); i++ {
+ for i := 0; i < t.resultMerger.Length(); i++ {
if _, found := prevIndexes[i]; !found {
- item := t.merger.Get(i).item
+ item := t.resultMerger.Get(i).item
if !t.selectItem(item) {
break
}
@@ -5993,19 +6093,77 @@ func (t *Terminal) Loop() error {
t.vmove(1, true)
req(reqList)
}
- case actDown:
- t.vmove(-1, true)
+ case actDown, actDownMatch:
+ if t.raw && a.t == actDownMatch {
+ if t.resultMerger.Length() > 0 {
+ prevCy := t.cy
+ for t.vmove(-1, true) && !t.isCurrentItemMatch() {
+ }
+ if !t.isCurrentItemMatch() {
+ t.vset(prevCy)
+ }
+ }
+ } else {
+ t.vmove(-1, true)
+ }
req(reqList)
- case actUp:
- t.vmove(1, true)
+ case actUp, actUpMatch:
+ if t.raw && a.t == actUpMatch {
+ if t.resultMerger.Length() > 0 {
+ prevCy := t.cy
+ for t.vmove(1, true) && !t.isCurrentItemMatch() {
+ }
+ if !t.isCurrentItemMatch() {
+ t.vset(prevCy)
+ }
+ }
+ } else {
+ t.vmove(1, true)
+ }
+ req(reqList)
+ case actToggleRaw:
+ t.raw = !t.raw
+ prevPos := t.cy - t.offset
+ prevIndex := t.currentIndex()
+ if t.raw {
+ // Build matchMap if not available
+ if len(t.matchMap) == 0 {
+ t.matchMap = t.resultMerger.ToMap()
+ }
+ t.merger = t.passMerger
+ } else {
+ t.merger = t.resultMerger
+
+ // Need to remove non-matching items from the selection
+ if t.multi > 0 && len(t.selected) > 0 {
+ t.filterSelected()
+ req(reqInfo)
+ }
+ }
+
+ // Try to retain position
+ if prevIndex != minItem.Index() {
+ i := t.merger.FindIndex(prevIndex)
+ if i >= 0 {
+ t.cy = i
+ } else {
+ t.cy = t.merger.FindIndex(t.lastMatchingIndex)
+ }
+ t.offset = t.cy - prevPos
+ }
+
+ // List needs to be rerendered
+ t.forceRerenderList()
req(reqList)
case actAccept:
req(reqClose)
case actAcceptNonEmpty:
+ // TODO: Allow accepting unmatched items in raw mode?
if len(t.selected) > 0 || t.merger.Length() > 0 || !t.reading && t.count == 0 {
req(reqClose)
}
case actAcceptOrPrintQuery:
+ // TODO: Allow accepting unmatched items in raw mode?
if len(t.selected) > 0 || t.merger.Length() > 0 {
req(reqClose)
} else {
@@ -6841,7 +6999,7 @@ func (t *Terminal) Loop() error {
reload := changed || newCommand != nil
var reloadRequest *searchRequest
if reload {
- reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, command: newCommand, environ: t.environ(), changed: changed, denylist: denylist, revision: t.merger.Revision()}
+ reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, command: newCommand, environ: t.environ(), changed: changed, denylist: denylist, revision: t.resultMerger.Revision()}
}
// Dispatch queued background requests
@@ -6961,7 +7119,8 @@ func (t *Terminal) constrain() {
}
}
-func (t *Terminal) vmove(o int, allowCycle bool) {
+// Returns true if the cursor position is successfully updated
+func (t *Terminal) vmove(o int, allowCycle bool) bool {
if t.layout != layoutDefault {
o *= -1
}
@@ -6978,7 +7137,7 @@ func (t *Terminal) vmove(o int, allowCycle bool) {
}
}
}
- t.vset(dest)
+ return t.vset(dest)
}
func (t *Terminal) vset(o int) bool {
@@ -7045,9 +7204,9 @@ func (t *Terminal) dumpStatus(params getParams) string {
selected[i] = t.dumpItem(selectedItems[i+params.offset].item)
}
- matches := make([]StatusItem, util.Max(0, util.Min(params.limit, t.merger.Length()-params.offset)))
+ matches := make([]StatusItem, util.Max(0, util.Min(params.limit, t.resultMerger.Length()-params.offset)))
for i := range matches {
- matches[i] = t.dumpItem(t.merger.Get(i + params.offset).item)
+ matches[i] = t.dumpItem(t.resultMerger.Get(i + params.offset).item)
}
var current *StatusItem
@@ -7064,7 +7223,7 @@ func (t *Terminal) dumpStatus(params getParams) string {
Position: t.cy,
Sort: t.sort,
TotalCount: t.count,
- MatchCount: t.merger.Length(),
+ MatchCount: t.resultMerger.Length(),
Current: current,
Matches: matches,
Selected: selected,