2015-01-01 19:49:30 +00:00
|
|
|
package fzf
|
|
|
|
|
|
|
|
import (
|
2017-01-11 13:13:40 +00:00
|
|
|
"bufio"
|
2015-01-01 19:49:30 +00:00
|
|
|
"fmt"
|
2023-04-22 13:01:00 +00:00
|
|
|
"io"
|
2021-12-22 14:22:08 +00:00
|
|
|
"math"
|
2015-01-01 19:49:30 +00:00
|
|
|
"os"
|
2023-02-11 11:21:10 +00:00
|
|
|
"os/exec"
|
2015-01-23 11:30:50 +00:00
|
|
|
"os/signal"
|
2015-01-01 19:49:30 +00:00
|
|
|
"regexp"
|
|
|
|
"sort"
|
2016-11-06 17:15:34 +00:00
|
|
|
"strconv"
|
2015-01-18 07:59:04 +00:00
|
|
|
"strings"
|
2015-01-01 19:49:30 +00:00
|
|
|
"sync"
|
2015-01-23 11:30:50 +00:00
|
|
|
"syscall"
|
2015-01-01 19:49:30 +00:00
|
|
|
"time"
|
2015-01-12 03:56:17 +00:00
|
|
|
|
2021-05-14 02:43:32 +00:00
|
|
|
"github.com/mattn/go-runewidth"
|
|
|
|
"github.com/rivo/uniseg"
|
|
|
|
|
2016-10-24 00:44:56 +00:00
|
|
|
"github.com/junegunn/fzf/src/tui"
|
2015-01-12 03:56:17 +00:00
|
|
|
"github.com/junegunn/fzf/src/util"
|
2015-01-01 19:49:30 +00:00
|
|
|
)
|
|
|
|
|
2016-09-07 00:58:18 +00:00
|
|
|
// import "github.com/pkg/profile"
|
|
|
|
|
Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands
This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.
* [tests] Add testing of placeholder parsing and matching
Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.
* [tests] Add more test cases of replacing placeholders focused on flags
Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.
There is at least one test for each flag, not all combinations are
tested though.
* [refactoring] Split OS-specific function quoteEntry() to corresponding source file
This is minor refactoring, and also the function's test was made
crossplatform.
* [refactoring] Simplify replacePlaceholder function
Should be equivalent to the original, but has simpler structure.
2021-10-15 13:31:59 +00:00
|
|
|
/*
|
2022-08-12 13:11:15 +00:00
|
|
|
Placeholder regex is used to extract placeholders from fzf's template
|
|
|
|
strings. Acts as input validation for parsePlaceholder function.
|
|
|
|
Describes the syntax, but it is fairly lenient.
|
Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands
This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.
* [tests] Add testing of placeholder parsing and matching
Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.
* [tests] Add more test cases of replacing placeholders focused on flags
Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.
There is at least one test for each flag, not all combinations are
tested though.
* [refactoring] Split OS-specific function quoteEntry() to corresponding source file
This is minor refactoring, and also the function's test was made
crossplatform.
* [refactoring] Simplify replacePlaceholder function
Should be equivalent to the original, but has simpler structure.
2021-10-15 13:31:59 +00:00
|
|
|
|
2022-08-12 13:11:15 +00:00
|
|
|
The following pseudo regex has been reverse engineered from the
|
2022-11-18 01:23:04 +00:00
|
|
|
implementation. It is overly strict, but better describes what's possible.
|
2022-08-12 13:11:15 +00:00
|
|
|
As such it is not useful for validation, but rather to generate test
|
|
|
|
cases for example.
|
Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands
This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.
* [tests] Add testing of placeholder parsing and matching
Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.
* [tests] Add more test cases of replacing placeholders focused on flags
Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.
There is at least one test for each flag, not all combinations are
tested though.
* [refactoring] Split OS-specific function quoteEntry() to corresponding source file
This is minor refactoring, and also the function's test was made
crossplatform.
* [refactoring] Simplify replacePlaceholder function
Should be equivalent to the original, but has simpler structure.
2021-10-15 13:31:59 +00:00
|
|
|
|
|
|
|
\\?(?: # escaped type
|
|
|
|
{\+?s?f?RANGE(?:,RANGE)*} # token type
|
|
|
|
|{q} # query type
|
|
|
|
|{\+?n?f?} # item type (notice no mandatory element inside brackets)
|
|
|
|
)
|
|
|
|
RANGE = (?:
|
|
|
|
(?:-?[0-9]+)?\.\.(?:-?[0-9]+)? # ellipsis syntax for token range (x..y)
|
|
|
|
|-?[0-9]+ # shorthand syntax (x..x)
|
|
|
|
)
|
|
|
|
*/
|
2016-10-03 05:16:10 +00:00
|
|
|
var placeholder *regexp.Regexp
|
2020-10-25 10:29:37 +00:00
|
|
|
var whiteSuffix *regexp.Regexp
|
2021-03-12 17:24:37 +00:00
|
|
|
var offsetComponentRegex *regexp.Regexp
|
|
|
|
var offsetTrimCharsRegex *regexp.Regexp
|
2019-10-27 14:50:12 +00:00
|
|
|
var activeTempFiles []string
|
2016-10-03 05:16:10 +00:00
|
|
|
|
2020-10-23 12:37:20 +00:00
|
|
|
const clearCode string = "\x1b[2J"
|
2020-03-11 13:35:24 +00:00
|
|
|
|
2016-10-03 05:16:10 +00:00
|
|
|
func init() {
|
Improvements to code quality and readability (#1737)
* Remove 1 unused field and 3 unused functions
unused elements fount by running
golangci-lint run --disable-all --enable unused
src/result.go:19:2: field `index` is unused (unused)
index int32
^
src/tui/light.go:716:23: func `(*LightWindow).stderr` is unused (unused)
func (w *LightWindow) stderr(str string) {
^
src/terminal.go:1015:6: func `numLinesMax` is unused (unused)
func numLinesMax(str string, max int) int {
^
src/tui/tui.go:167:20: func `ColorPair.is24` is unused (unused)
func (p ColorPair) is24() bool {
^
* Address warnings from "gosimple" linter
src/options.go:389:83: S1003: should use strings.Contains(str, ",,,") instead (gosimple)
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Index(str, ",,,") >= 0 {
^
src/options.go:630:18: S1007: should use raw string (`...`) with regexp.MustCompile to avoid having to escape twice (gosimple)
executeRegexp = regexp.MustCompile(
^
src/terminal.go:29:16: S1007: should use raw string (`...`) with regexp.MustCompile to avoid having to escape twice (gosimple)
placeholder = regexp.MustCompile("\\\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\\+?f?nf?})")
^
src/terminal_test.go:92:10: S1007: should use raw string (`...`) with regexp.MustCompile to avoid having to escape twice (gosimple)
regex = regexp.MustCompile("\\w+")
^
* Address warnings from "staticcheck" linter
src/algo/algo.go:374:2: SA4006: this value of `offset32` is never used (staticcheck)
offset32, T := alloc32(offset32, slab, N)
^
src/algo/algo.go:456:2: SA4006: this value of `offset16` is never used (staticcheck)
offset16, C := alloc16(offset16, slab, width*M)
^
src/tui/tui.go:119:2: SA9004: only the first constant in this group has an explicit type (staticcheck)
colUndefined Color = -2
^
2019-11-05 00:46:51 +00:00
|
|
|
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
|
2020-10-25 10:29:37 +00:00
|
|
|
whiteSuffix = regexp.MustCompile(`\s*$`)
|
2021-03-12 17:24:37 +00:00
|
|
|
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
|
|
|
|
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
|
2019-10-27 14:50:12 +00:00
|
|
|
activeTempFiles = []string{}
|
2016-10-03 05:16:10 +00:00
|
|
|
}
|
|
|
|
|
2016-05-17 17:06:52 +00:00
|
|
|
type jumpMode int
|
|
|
|
|
|
|
|
const (
|
|
|
|
jumpDisabled jumpMode = iota
|
|
|
|
jumpEnabled
|
|
|
|
jumpAcceptEnabled
|
|
|
|
)
|
|
|
|
|
2023-01-12 14:12:26 +00:00
|
|
|
type resumableState int
|
|
|
|
|
|
|
|
const (
|
|
|
|
disabledState resumableState = iota
|
|
|
|
pausedState
|
|
|
|
enabledState
|
|
|
|
)
|
|
|
|
|
|
|
|
func (s resumableState) Enabled() bool {
|
|
|
|
return s == enabledState
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *resumableState) Force(flag bool) {
|
|
|
|
if flag {
|
|
|
|
*s = enabledState
|
|
|
|
} else {
|
|
|
|
*s = disabledState
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *resumableState) Set(flag bool) {
|
|
|
|
if *s == disabledState {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if flag {
|
|
|
|
*s = enabledState
|
|
|
|
} else {
|
|
|
|
*s = pausedState
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-24 17:02:00 +00:00
|
|
|
type previewer struct {
|
2020-10-24 07:55:55 +00:00
|
|
|
version int64
|
2020-10-18 08:03:33 +00:00
|
|
|
lines []string
|
|
|
|
offset int
|
|
|
|
scrollable bool
|
|
|
|
final bool
|
2023-01-12 14:12:26 +00:00
|
|
|
following resumableState
|
2020-10-18 08:03:33 +00:00
|
|
|
spinner string
|
2023-01-06 06:36:12 +00:00
|
|
|
bar []bool
|
2020-10-18 08:03:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type previewed struct {
|
2020-10-24 07:55:55 +00:00
|
|
|
version int64
|
2020-10-18 08:03:33 +00:00
|
|
|
numLines int
|
|
|
|
offset int
|
|
|
|
filled bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type eachLine struct {
|
|
|
|
line string
|
|
|
|
err error
|
2016-09-24 17:02:00 +00:00
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
type itemLine struct {
|
2023-07-25 13:11:15 +00:00
|
|
|
offset int
|
2017-01-07 17:29:31 +00:00
|
|
|
current bool
|
|
|
|
selected bool
|
|
|
|
label string
|
2017-02-23 17:30:11 +00:00
|
|
|
queryLen int
|
2017-01-15 17:26:36 +00:00
|
|
|
width int
|
2023-01-01 05:48:14 +00:00
|
|
|
bar bool
|
2017-01-07 17:29:31 +00:00
|
|
|
result Result
|
2017-01-07 16:30:31 +00:00
|
|
|
}
|
|
|
|
|
2022-09-07 16:01:22 +00:00
|
|
|
type fitpad struct {
|
|
|
|
fit int
|
|
|
|
pad int
|
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
var emptyLine = itemLine{}
|
|
|
|
|
2022-11-10 07:23:33 +00:00
|
|
|
type labelPrinter func(tui.Window, int)
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// Terminal represents terminal input/output
|
2015-01-01 19:49:30 +00:00
|
|
|
type Terminal struct {
|
2021-11-30 14:37:48 +00:00
|
|
|
initDelay time.Duration
|
|
|
|
infoStyle infoStyle
|
2023-01-24 08:40:08 +00:00
|
|
|
infoSep string
|
2022-11-10 07:23:33 +00:00
|
|
|
separator labelPrinter
|
|
|
|
separatorLen int
|
2021-11-30 14:37:48 +00:00
|
|
|
spinner []string
|
|
|
|
prompt func()
|
|
|
|
promptLen int
|
2022-11-10 07:23:33 +00:00
|
|
|
borderLabel labelPrinter
|
2022-10-29 15:12:01 +00:00
|
|
|
borderLabelLen int
|
2022-10-30 15:22:41 +00:00
|
|
|
borderLabelOpts labelOpts
|
2022-11-10 07:23:33 +00:00
|
|
|
previewLabel labelPrinter
|
2022-10-30 11:44:46 +00:00
|
|
|
previewLabelLen int
|
2022-10-30 15:22:41 +00:00
|
|
|
previewLabelOpts labelOpts
|
2021-11-30 14:37:48 +00:00
|
|
|
pointer string
|
|
|
|
pointerLen int
|
|
|
|
pointerEmpty string
|
|
|
|
marker string
|
|
|
|
markerLen int
|
|
|
|
markerEmpty string
|
|
|
|
queryLen [2]int
|
|
|
|
layout layoutType
|
|
|
|
fullscreen bool
|
|
|
|
keepRight bool
|
|
|
|
hscroll bool
|
|
|
|
hscrollOff int
|
|
|
|
scrollOff int
|
|
|
|
wordRubout string
|
|
|
|
wordNext string
|
|
|
|
cx int
|
|
|
|
cy int
|
|
|
|
offset int
|
|
|
|
xoffset int
|
|
|
|
yanked []rune
|
|
|
|
input []rune
|
|
|
|
multi int
|
|
|
|
sort bool
|
|
|
|
toggleSort bool
|
2023-04-22 14:39:35 +00:00
|
|
|
track trackOption
|
2021-11-30 14:37:48 +00:00
|
|
|
delimiter Delimiter
|
|
|
|
expect map[tui.Event]string
|
2021-12-04 02:46:15 +00:00
|
|
|
keymap map[tui.Event][]*action
|
2022-04-04 12:54:22 +00:00
|
|
|
keymapOrg map[tui.Event][]*action
|
2021-11-30 14:37:48 +00:00
|
|
|
pressed string
|
|
|
|
printQuery bool
|
|
|
|
history *History
|
|
|
|
cycle bool
|
2023-07-25 13:11:15 +00:00
|
|
|
headerVisible bool
|
2021-11-30 14:37:48 +00:00
|
|
|
headerFirst bool
|
|
|
|
headerLines int
|
|
|
|
header []string
|
|
|
|
header0 []string
|
2022-03-29 12:35:36 +00:00
|
|
|
ellipsis string
|
2023-01-01 05:48:14 +00:00
|
|
|
scrollbar string
|
2023-05-16 14:53:10 +00:00
|
|
|
previewScrollbar string
|
2021-11-30 14:37:48 +00:00
|
|
|
ansi bool
|
|
|
|
tabstop int
|
|
|
|
margin [4]sizeSpec
|
|
|
|
padding [4]sizeSpec
|
|
|
|
unicode bool
|
2023-03-19 06:42:47 +00:00
|
|
|
listenPort *int
|
2021-11-30 14:37:48 +00:00
|
|
|
borderShape tui.BorderShape
|
|
|
|
cleanExit bool
|
|
|
|
paused bool
|
|
|
|
border tui.Window
|
|
|
|
window tui.Window
|
|
|
|
pborder tui.Window
|
|
|
|
pwindow tui.Window
|
2023-01-15 16:22:02 +00:00
|
|
|
borderWidth int
|
2021-11-30 14:37:48 +00:00
|
|
|
count int
|
|
|
|
progress int
|
2022-12-29 11:01:50 +00:00
|
|
|
hasLoadActions bool
|
|
|
|
triggerLoad bool
|
2021-11-30 14:37:48 +00:00
|
|
|
reading bool
|
|
|
|
running bool
|
|
|
|
failed *string
|
|
|
|
jumping jumpMode
|
|
|
|
jumpLabels string
|
|
|
|
printer func(string)
|
|
|
|
printsep string
|
|
|
|
merger *Merger
|
|
|
|
selected map[int32]selectedItem
|
|
|
|
version int64
|
2023-05-27 06:43:31 +00:00
|
|
|
revision int
|
2021-11-30 14:37:48 +00:00
|
|
|
reqBox *util.EventBox
|
|
|
|
initialPreviewOpts previewOpts
|
|
|
|
previewOpts previewOpts
|
2023-01-07 02:21:52 +00:00
|
|
|
activePreviewOpts *previewOpts
|
2021-11-30 14:37:48 +00:00
|
|
|
previewer previewer
|
|
|
|
previewed previewed
|
|
|
|
previewBox *util.EventBox
|
|
|
|
eventBox *util.EventBox
|
|
|
|
mutex sync.Mutex
|
|
|
|
initFunc func()
|
|
|
|
prevLines []itemLine
|
|
|
|
suppress bool
|
|
|
|
sigstop bool
|
2022-09-07 16:01:22 +00:00
|
|
|
startChan chan fitpad
|
2021-11-30 14:37:48 +00:00
|
|
|
killChan chan int
|
2022-12-25 10:53:53 +00:00
|
|
|
serverChan chan []*action
|
2022-12-29 11:01:50 +00:00
|
|
|
eventChan chan tui.Event
|
2021-11-30 14:37:48 +00:00
|
|
|
slab *util.Slab
|
|
|
|
theme *tui.ColorTheme
|
|
|
|
tui tui.Renderer
|
|
|
|
executing *util.AtomicBool
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-01-17 02:07:04 +00:00
|
|
|
type selectedItem struct {
|
|
|
|
at time.Time
|
2016-10-03 05:16:10 +00:00
|
|
|
item *Item
|
2015-01-17 02:07:04 +00:00
|
|
|
}
|
|
|
|
|
2015-03-22 07:05:54 +00:00
|
|
|
type byTimeOrder []selectedItem
|
2015-01-17 02:07:04 +00:00
|
|
|
|
2015-03-22 07:05:54 +00:00
|
|
|
func (a byTimeOrder) Len() int {
|
2015-01-17 02:07:04 +00:00
|
|
|
return len(a)
|
|
|
|
}
|
|
|
|
|
2015-03-22 07:05:54 +00:00
|
|
|
func (a byTimeOrder) Swap(i, j int) {
|
2015-01-17 02:07:04 +00:00
|
|
|
a[i], a[j] = a[j], a[i]
|
|
|
|
}
|
|
|
|
|
2015-03-22 07:05:54 +00:00
|
|
|
func (a byTimeOrder) Less(i, j int) bool {
|
2015-01-17 02:07:04 +00:00
|
|
|
return a[i].at.Before(a[j].at)
|
|
|
|
}
|
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
const (
|
2015-01-12 03:56:17 +00:00
|
|
|
reqPrompt util.EventType = iota
|
2015-01-11 18:01:24 +00:00
|
|
|
reqInfo
|
2015-07-21 18:21:20 +00:00
|
|
|
reqHeader
|
2015-01-11 18:01:24 +00:00
|
|
|
reqList
|
2016-05-17 17:06:52 +00:00
|
|
|
reqJump
|
2015-01-11 18:01:24 +00:00
|
|
|
reqRefresh
|
2017-04-28 13:58:08 +00:00
|
|
|
reqReinit
|
2021-12-04 02:46:15 +00:00
|
|
|
reqFullRedraw
|
2023-01-21 16:56:29 +00:00
|
|
|
reqRedrawBorderLabel
|
|
|
|
reqRedrawPreviewLabel
|
2015-01-11 18:01:24 +00:00
|
|
|
reqClose
|
2016-05-12 15:51:15 +00:00
|
|
|
reqPrintQuery
|
2016-06-11 10:59:12 +00:00
|
|
|
reqPreviewEnqueue
|
|
|
|
reqPreviewDisplay
|
2016-09-24 17:02:00 +00:00
|
|
|
reqPreviewRefresh
|
2020-10-18 08:03:33 +00:00
|
|
|
reqPreviewDelayed
|
2015-01-11 18:01:24 +00:00
|
|
|
reqQuit
|
2015-01-01 19:49:30 +00:00
|
|
|
)
|
|
|
|
|
2017-01-21 17:32:49 +00:00
|
|
|
type action struct {
|
|
|
|
t actionType
|
|
|
|
a string
|
|
|
|
}
|
|
|
|
|
2015-05-20 12:25:15 +00:00
|
|
|
type actionType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
actIgnore actionType = iota
|
|
|
|
actInvalid
|
|
|
|
actRune
|
|
|
|
actMouse
|
|
|
|
actBeginningOfLine
|
|
|
|
actAbort
|
|
|
|
actAccept
|
2017-12-01 17:27:02 +00:00
|
|
|
actAcceptNonEmpty
|
2015-05-20 12:25:15 +00:00
|
|
|
actBackwardChar
|
|
|
|
actBackwardDeleteChar
|
2020-02-27 17:38:32 +00:00
|
|
|
actBackwardDeleteCharEOF
|
2015-05-20 12:25:15 +00:00
|
|
|
actBackwardWord
|
2015-07-23 12:05:33 +00:00
|
|
|
actCancel
|
2023-01-17 18:43:02 +00:00
|
|
|
actChangeBorderLabel
|
2023-04-22 13:01:00 +00:00
|
|
|
actChangeHeader
|
2023-01-17 18:43:02 +00:00
|
|
|
actChangePreviewLabel
|
2020-12-04 11:34:41 +00:00
|
|
|
actChangePrompt
|
2022-12-17 09:59:16 +00:00
|
|
|
actChangeQuery
|
2015-05-20 12:25:15 +00:00
|
|
|
actClearScreen
|
2019-12-07 05:44:24 +00:00
|
|
|
actClearQuery
|
|
|
|
actClearSelection
|
2021-02-01 15:08:54 +00:00
|
|
|
actClose
|
2015-05-20 12:25:15 +00:00
|
|
|
actDeleteChar
|
2015-08-02 04:06:15 +00:00
|
|
|
actDeleteCharEOF
|
2015-05-20 12:25:15 +00:00
|
|
|
actEndOfLine
|
|
|
|
actForwardChar
|
|
|
|
actForwardWord
|
|
|
|
actKillLine
|
|
|
|
actKillWord
|
|
|
|
actUnixLineDiscard
|
|
|
|
actUnixWordRubout
|
|
|
|
actYank
|
|
|
|
actBackwardKillWord
|
2015-06-09 14:44:54 +00:00
|
|
|
actSelectAll
|
|
|
|
actDeselectAll
|
2015-05-20 16:35:35 +00:00
|
|
|
actToggle
|
2021-01-02 15:00:40 +00:00
|
|
|
actToggleSearch
|
2015-06-09 14:44:54 +00:00
|
|
|
actToggleAll
|
2015-05-20 12:25:15 +00:00
|
|
|
actToggleDown
|
|
|
|
actToggleUp
|
2016-01-13 17:35:43 +00:00
|
|
|
actToggleIn
|
|
|
|
actToggleOut
|
2023-04-22 06:48:51 +00:00
|
|
|
actToggleTrack
|
2023-07-25 13:11:15 +00:00
|
|
|
actToggleHeader
|
2023-04-22 14:39:35 +00:00
|
|
|
actTrack
|
2015-05-20 12:25:15 +00:00
|
|
|
actDown
|
|
|
|
actUp
|
|
|
|
actPageUp
|
|
|
|
actPageDown
|
2022-12-26 16:01:06 +00:00
|
|
|
actPosition
|
2017-01-16 02:58:13 +00:00
|
|
|
actHalfPageUp
|
|
|
|
actHalfPageDown
|
2016-05-17 17:06:52 +00:00
|
|
|
actJump
|
|
|
|
actJumpAccept
|
2016-05-12 15:51:15 +00:00
|
|
|
actPrintQuery
|
2020-06-20 13:04:09 +00:00
|
|
|
actRefreshPreview
|
2017-12-01 04:08:55 +00:00
|
|
|
actReplaceQuery
|
2015-05-20 12:25:15 +00:00
|
|
|
actToggleSort
|
2023-01-31 08:33:53 +00:00
|
|
|
actShowPreview
|
|
|
|
actHidePreview
|
2016-06-11 10:59:12 +00:00
|
|
|
actTogglePreview
|
2017-02-18 14:49:00 +00:00
|
|
|
actTogglePreviewWrap
|
2023-01-21 16:56:29 +00:00
|
|
|
actTransformBorderLabel
|
2023-04-22 13:01:00 +00:00
|
|
|
actTransformHeader
|
2023-01-21 16:56:29 +00:00
|
|
|
actTransformPreviewLabel
|
2022-12-31 00:27:11 +00:00
|
|
|
actTransformPrompt
|
2022-12-27 15:05:31 +00:00
|
|
|
actTransformQuery
|
2020-06-21 13:41:33 +00:00
|
|
|
actPreview
|
2021-11-30 14:37:48 +00:00
|
|
|
actChangePreview
|
|
|
|
actChangePreviewWindow
|
2020-12-31 03:54:58 +00:00
|
|
|
actPreviewTop
|
|
|
|
actPreviewBottom
|
2016-09-24 17:02:00 +00:00
|
|
|
actPreviewUp
|
|
|
|
actPreviewDown
|
2016-09-24 19:12:44 +00:00
|
|
|
actPreviewPageUp
|
|
|
|
actPreviewPageDown
|
2020-10-05 12:58:56 +00:00
|
|
|
actPreviewHalfPageUp
|
|
|
|
actPreviewHalfPageDown
|
2022-12-10 13:42:56 +00:00
|
|
|
actPrevHistory
|
2022-12-10 15:59:34 +00:00
|
|
|
actPrevSelected
|
2022-12-27 10:54:46 +00:00
|
|
|
actPut
|
2015-06-13 15:43:44 +00:00
|
|
|
actNextHistory
|
2022-12-10 15:59:34 +00:00
|
|
|
actNextSelected
|
2015-06-14 03:25:08 +00:00
|
|
|
actExecute
|
2017-01-27 08:46:56 +00:00
|
|
|
actExecuteSilent
|
|
|
|
actExecuteMulti // Deprecated
|
2017-04-28 13:58:08 +00:00
|
|
|
actSigStop
|
2020-12-29 16:51:25 +00:00
|
|
|
actFirst
|
|
|
|
actLast
|
2019-11-10 02:36:22 +00:00
|
|
|
actReload
|
2022-12-29 11:03:51 +00:00
|
|
|
actReloadSync
|
2021-01-02 15:00:40 +00:00
|
|
|
actDisableSearch
|
|
|
|
actEnableSearch
|
2021-02-25 12:14:15 +00:00
|
|
|
actSelect
|
|
|
|
actDeselect
|
2021-05-22 04:13:55 +00:00
|
|
|
actUnbind
|
2022-04-04 12:54:22 +00:00
|
|
|
actRebind
|
2023-02-11 11:21:10 +00:00
|
|
|
actBecome
|
2015-05-20 12:25:15 +00:00
|
|
|
)
|
|
|
|
|
2018-03-30 02:47:46 +00:00
|
|
|
type placeholderFlags struct {
|
|
|
|
plus bool
|
|
|
|
preserveSpace bool
|
2019-02-18 16:12:57 +00:00
|
|
|
number bool
|
2018-06-09 16:40:22 +00:00
|
|
|
query bool
|
2019-10-27 14:50:12 +00:00
|
|
|
file bool
|
2018-03-30 02:47:46 +00:00
|
|
|
}
|
|
|
|
|
2019-11-10 02:36:22 +00:00
|
|
|
type searchRequest struct {
|
|
|
|
sort bool
|
2022-12-29 11:03:51 +00:00
|
|
|
sync bool
|
2019-11-10 02:36:22 +00:00
|
|
|
command *string
|
2023-04-29 12:27:30 +00:00
|
|
|
changed bool
|
2019-11-10 02:36:22 +00:00
|
|
|
}
|
|
|
|
|
2020-06-21 13:41:33 +00:00
|
|
|
type previewRequest struct {
|
2021-11-30 14:37:48 +00:00
|
|
|
template string
|
|
|
|
pwindow tui.Window
|
|
|
|
scrollOffset int
|
|
|
|
list []*Item
|
2020-06-21 13:41:33 +00:00
|
|
|
}
|
|
|
|
|
2020-07-26 15:15:25 +00:00
|
|
|
type previewResult struct {
|
2020-10-24 07:55:55 +00:00
|
|
|
version int64
|
2020-10-18 08:03:33 +00:00
|
|
|
lines []string
|
2020-07-26 15:15:25 +00:00
|
|
|
offset int
|
2020-10-18 08:03:33 +00:00
|
|
|
spinner string
|
2020-07-26 15:15:25 +00:00
|
|
|
}
|
|
|
|
|
2021-12-04 02:46:15 +00:00
|
|
|
func toActions(types ...actionType) []*action {
|
|
|
|
actions := make([]*action, len(types))
|
2017-01-21 17:32:49 +00:00
|
|
|
for idx, t := range types {
|
2021-12-04 02:46:15 +00:00
|
|
|
actions[idx] = &action{t: t, a: ""}
|
2017-01-21 17:32:49 +00:00
|
|
|
}
|
|
|
|
return actions
|
|
|
|
}
|
|
|
|
|
2021-12-04 02:46:15 +00:00
|
|
|
func defaultKeymap() map[tui.Event][]*action {
|
|
|
|
keymap := make(map[tui.Event][]*action)
|
2020-12-29 16:59:18 +00:00
|
|
|
add := func(e tui.EventType, a actionType) {
|
|
|
|
keymap[e.AsEvent()] = toActions(a)
|
|
|
|
}
|
|
|
|
addEvent := func(e tui.Event, a actionType) {
|
|
|
|
keymap[e] = toActions(a)
|
|
|
|
}
|
|
|
|
|
|
|
|
add(tui.Invalid, actInvalid)
|
|
|
|
add(tui.Resize, actClearScreen)
|
|
|
|
add(tui.CtrlA, actBeginningOfLine)
|
|
|
|
add(tui.CtrlB, actBackwardChar)
|
|
|
|
add(tui.CtrlC, actAbort)
|
|
|
|
add(tui.CtrlG, actAbort)
|
|
|
|
add(tui.CtrlQ, actAbort)
|
|
|
|
add(tui.ESC, actAbort)
|
|
|
|
add(tui.CtrlD, actDeleteCharEOF)
|
|
|
|
add(tui.CtrlE, actEndOfLine)
|
|
|
|
add(tui.CtrlF, actForwardChar)
|
|
|
|
add(tui.CtrlH, actBackwardDeleteChar)
|
|
|
|
add(tui.BSpace, actBackwardDeleteChar)
|
|
|
|
add(tui.Tab, actToggleDown)
|
|
|
|
add(tui.BTab, actToggleUp)
|
|
|
|
add(tui.CtrlJ, actDown)
|
|
|
|
add(tui.CtrlK, actUp)
|
|
|
|
add(tui.CtrlL, actClearScreen)
|
|
|
|
add(tui.CtrlM, actAccept)
|
|
|
|
add(tui.CtrlN, actDown)
|
|
|
|
add(tui.CtrlP, actUp)
|
|
|
|
add(tui.CtrlU, actUnixLineDiscard)
|
|
|
|
add(tui.CtrlW, actUnixWordRubout)
|
|
|
|
add(tui.CtrlY, actYank)
|
2017-04-28 13:58:08 +00:00
|
|
|
if !util.IsWindows() {
|
2020-12-29 16:59:18 +00:00
|
|
|
add(tui.CtrlZ, actSigStop)
|
|
|
|
}
|
|
|
|
|
|
|
|
addEvent(tui.AltKey('b'), actBackwardWord)
|
|
|
|
add(tui.SLeft, actBackwardWord)
|
|
|
|
addEvent(tui.AltKey('f'), actForwardWord)
|
|
|
|
add(tui.SRight, actForwardWord)
|
|
|
|
addEvent(tui.AltKey('d'), actKillWord)
|
|
|
|
add(tui.AltBS, actBackwardKillWord)
|
|
|
|
|
|
|
|
add(tui.Up, actUp)
|
|
|
|
add(tui.Down, actDown)
|
|
|
|
add(tui.Left, actBackwardChar)
|
|
|
|
add(tui.Right, actForwardChar)
|
|
|
|
|
|
|
|
add(tui.Home, actBeginningOfLine)
|
|
|
|
add(tui.End, actEndOfLine)
|
|
|
|
add(tui.Del, actDeleteChar)
|
|
|
|
add(tui.PgUp, actPageUp)
|
|
|
|
add(tui.PgDn, actPageDown)
|
|
|
|
|
|
|
|
add(tui.SUp, actPreviewUp)
|
|
|
|
add(tui.SDown, actPreviewDown)
|
|
|
|
|
|
|
|
add(tui.Mouse, actMouse)
|
|
|
|
add(tui.LeftClick, actIgnore)
|
|
|
|
add(tui.RightClick, actToggle)
|
2015-05-20 12:25:15 +00:00
|
|
|
return keymap
|
|
|
|
}
|
|
|
|
|
2017-08-09 14:25:32 +00:00
|
|
|
func trimQuery(query string) []rune {
|
|
|
|
return []rune(strings.Replace(query, "\t", " ", -1))
|
|
|
|
}
|
|
|
|
|
2020-06-21 13:41:33 +00:00
|
|
|
func hasPreviewAction(opts *Options) bool {
|
|
|
|
for _, actions := range opts.Keymap {
|
|
|
|
for _, action := range actions {
|
2021-11-30 14:37:48 +00:00
|
|
|
if action.t == actPreview || action.t == actChangePreview {
|
2020-06-21 13:41:33 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-10-18 08:03:33 +00:00
|
|
|
func makeSpinner(unicode bool) []string {
|
|
|
|
if unicode {
|
|
|
|
return []string{`⠋`, `⠙`, `⠹`, `⠸`, `⠼`, `⠴`, `⠦`, `⠧`, `⠇`, `⠏`}
|
|
|
|
}
|
|
|
|
return []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
|
|
|
}
|
|
|
|
|
2022-09-07 16:01:22 +00:00
|
|
|
func evaluateHeight(opts *Options, termHeight int) int {
|
|
|
|
if opts.Height.percent {
|
|
|
|
return util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)
|
|
|
|
}
|
|
|
|
return int(opts.Height.size)
|
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// NewTerminal returns new Terminal object
|
2015-01-12 03:56:17 +00:00
|
|
|
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
2017-08-09 14:25:32 +00:00
|
|
|
input := trimQuery(opts.Query)
|
2016-01-16 09:07:50 +00:00
|
|
|
var delay time.Duration
|
|
|
|
if opts.Tac {
|
|
|
|
delay = initialDelayTac
|
|
|
|
} else {
|
|
|
|
delay = initialDelay
|
|
|
|
}
|
2016-06-11 10:59:12 +00:00
|
|
|
var previewBox *util.EventBox
|
2022-12-17 15:22:15 +00:00
|
|
|
// We need to start previewer if HTTP server is enabled even when --preview option is not specified
|
2023-03-19 06:42:47 +00:00
|
|
|
if len(opts.Preview.command) > 0 || hasPreviewAction(opts) || opts.ListenPort != nil {
|
2016-06-11 10:59:12 +00:00
|
|
|
previewBox = util.NewEventBox()
|
|
|
|
}
|
2017-01-07 16:30:31 +00:00
|
|
|
var renderer tui.Renderer
|
2022-09-07 16:01:22 +00:00
|
|
|
fullscreen := !opts.Height.auto && (opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100)
|
2017-04-28 13:58:08 +00:00
|
|
|
if fullscreen {
|
2020-10-30 17:53:10 +00:00
|
|
|
if tui.HasFullscreenRenderer() {
|
|
|
|
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
|
|
|
|
} else {
|
2017-03-04 05:09:36 +00:00
|
|
|
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit,
|
|
|
|
true, func(h int) int { return h })
|
|
|
|
}
|
|
|
|
} else {
|
2017-01-07 16:30:31 +00:00
|
|
|
maxHeightFunc := func(termHeight int) int {
|
2022-09-07 16:01:22 +00:00
|
|
|
// Minimum height required to render fzf excluding margin and padding
|
2017-02-04 12:51:22 +00:00
|
|
|
effectiveMinHeight := minHeight
|
2022-09-07 16:01:22 +00:00
|
|
|
if previewBox != nil && opts.Preview.aboveOrBelow() {
|
|
|
|
effectiveMinHeight += 1 + borderLines(opts.Preview.border)
|
2017-01-07 16:30:31 +00:00
|
|
|
}
|
2023-06-10 15:04:24 +00:00
|
|
|
if opts.InfoStyle.noExtraLine() {
|
2020-03-05 11:15:15 +00:00
|
|
|
effectiveMinHeight--
|
2017-02-04 12:51:22 +00:00
|
|
|
}
|
2022-09-07 16:01:22 +00:00
|
|
|
effectiveMinHeight += borderLines(opts.BorderShape)
|
|
|
|
return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight))
|
2017-01-07 16:30:31 +00:00
|
|
|
}
|
2017-03-04 05:09:36 +00:00
|
|
|
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
|
2017-01-07 16:30:31 +00:00
|
|
|
}
|
2020-07-28 03:58:37 +00:00
|
|
|
wordRubout := "[^\\pL\\pN][\\pL\\pN]"
|
|
|
|
wordNext := "[\\pL\\pN][^\\pL\\pN]|(.$)"
|
2017-01-15 10:42:28 +00:00
|
|
|
if opts.FileWord {
|
|
|
|
sep := regexp.QuoteMeta(string(os.PathSeparator))
|
|
|
|
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
|
|
|
|
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
|
|
|
}
|
2022-04-04 12:54:22 +00:00
|
|
|
keymapCopy := make(map[tui.Event][]*action)
|
|
|
|
for key, action := range opts.Keymap {
|
|
|
|
keymapCopy[key] = action
|
|
|
|
}
|
2017-05-26 10:02:49 +00:00
|
|
|
t := Terminal{
|
2021-11-30 14:37:48 +00:00
|
|
|
initDelay: delay,
|
|
|
|
infoStyle: opts.InfoStyle,
|
2023-01-24 08:40:08 +00:00
|
|
|
infoSep: opts.InfoSep,
|
2022-11-10 07:23:33 +00:00
|
|
|
separator: nil,
|
2021-11-30 14:37:48 +00:00
|
|
|
spinner: makeSpinner(opts.Unicode),
|
|
|
|
queryLen: [2]int{0, 0},
|
|
|
|
layout: opts.Layout,
|
|
|
|
fullscreen: fullscreen,
|
|
|
|
keepRight: opts.KeepRight,
|
|
|
|
hscroll: opts.Hscroll,
|
|
|
|
hscrollOff: opts.HscrollOff,
|
|
|
|
scrollOff: opts.ScrollOff,
|
|
|
|
wordRubout: wordRubout,
|
|
|
|
wordNext: wordNext,
|
|
|
|
cx: len(input),
|
|
|
|
cy: 0,
|
|
|
|
offset: 0,
|
|
|
|
xoffset: 0,
|
|
|
|
yanked: []rune{},
|
|
|
|
input: input,
|
|
|
|
multi: opts.Multi,
|
|
|
|
sort: opts.Sort > 0,
|
|
|
|
toggleSort: opts.ToggleSort,
|
2023-03-29 11:36:09 +00:00
|
|
|
track: opts.Track,
|
2021-11-30 14:37:48 +00:00
|
|
|
delimiter: opts.Delimiter,
|
|
|
|
expect: opts.Expect,
|
|
|
|
keymap: opts.Keymap,
|
2022-04-04 12:54:22 +00:00
|
|
|
keymapOrg: keymapCopy,
|
2021-11-30 14:37:48 +00:00
|
|
|
pressed: "",
|
|
|
|
printQuery: opts.PrintQuery,
|
|
|
|
history: opts.History,
|
|
|
|
margin: opts.Margin,
|
|
|
|
padding: opts.Padding,
|
|
|
|
unicode: opts.Unicode,
|
2022-12-17 15:22:15 +00:00
|
|
|
listenPort: opts.ListenPort,
|
2021-11-30 14:37:48 +00:00
|
|
|
borderShape: opts.BorderShape,
|
2023-01-15 16:22:02 +00:00
|
|
|
borderWidth: 1,
|
2022-10-29 15:12:01 +00:00
|
|
|
borderLabel: nil,
|
2022-10-30 15:22:41 +00:00
|
|
|
borderLabelOpts: opts.BorderLabel,
|
2022-10-30 11:44:46 +00:00
|
|
|
previewLabel: nil,
|
2022-10-30 15:22:41 +00:00
|
|
|
previewLabelOpts: opts.PreviewLabel,
|
2021-11-30 14:37:48 +00:00
|
|
|
cleanExit: opts.ClearOnExit,
|
|
|
|
paused: opts.Phony,
|
|
|
|
cycle: opts.Cycle,
|
2023-07-25 13:11:15 +00:00
|
|
|
headerVisible: true,
|
2021-11-30 14:37:48 +00:00
|
|
|
headerFirst: opts.HeaderFirst,
|
|
|
|
headerLines: opts.HeaderLines,
|
2023-04-22 13:01:00 +00:00
|
|
|
header: []string{},
|
2023-07-25 13:20:31 +00:00
|
|
|
header0: opts.Header,
|
2022-03-29 12:35:36 +00:00
|
|
|
ellipsis: opts.Ellipsis,
|
2021-11-30 14:37:48 +00:00
|
|
|
ansi: opts.Ansi,
|
|
|
|
tabstop: opts.Tabstop,
|
2022-12-29 11:01:50 +00:00
|
|
|
hasLoadActions: false,
|
|
|
|
triggerLoad: false,
|
2021-11-30 14:37:48 +00:00
|
|
|
reading: true,
|
|
|
|
running: true,
|
|
|
|
failed: nil,
|
|
|
|
jumping: jumpDisabled,
|
|
|
|
jumpLabels: opts.JumpLabels,
|
|
|
|
printer: opts.Printer,
|
|
|
|
printsep: opts.PrintSep,
|
2023-05-27 06:43:31 +00:00
|
|
|
merger: EmptyMerger(0),
|
2021-11-30 14:37:48 +00:00
|
|
|
selected: make(map[int32]selectedItem),
|
|
|
|
reqBox: util.NewEventBox(),
|
|
|
|
initialPreviewOpts: opts.Preview,
|
|
|
|
previewOpts: opts.Preview,
|
2023-02-01 09:16:58 +00:00
|
|
|
previewer: previewer{0, []string{}, 0, false, true, disabledState, "", []bool{}},
|
2021-11-30 14:37:48 +00:00
|
|
|
previewed: previewed{0, 0, 0, false},
|
|
|
|
previewBox: previewBox,
|
|
|
|
eventBox: eventBox,
|
|
|
|
mutex: sync.Mutex{},
|
|
|
|
suppress: true,
|
|
|
|
sigstop: false,
|
|
|
|
slab: util.MakeSlab(slab16Size, slab32Size),
|
|
|
|
theme: opts.Theme,
|
2022-09-07 16:01:22 +00:00
|
|
|
startChan: make(chan fitpad, 1),
|
2021-11-30 14:37:48 +00:00
|
|
|
killChan: make(chan int),
|
2022-12-28 03:50:59 +00:00
|
|
|
serverChan: make(chan []*action, 10),
|
2022-12-29 11:01:50 +00:00
|
|
|
eventChan: make(chan tui.Event, 1),
|
2021-11-30 14:37:48 +00:00
|
|
|
tui: renderer,
|
|
|
|
initFunc: func() { renderer.Init() },
|
|
|
|
executing: util.NewAtomicBool(false)}
|
2020-07-05 07:16:46 +00:00
|
|
|
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
|
2020-02-17 01:19:03 +00:00
|
|
|
t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0)
|
|
|
|
t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0)
|
|
|
|
// Pre-calculated empty pointer and marker signs
|
|
|
|
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
|
|
|
|
t.markerEmpty = strings.Repeat(" ", t.markerLen)
|
2022-11-10 07:23:33 +00:00
|
|
|
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(opts.BorderLabel.label, &tui.ColBorderLabel, false)
|
2022-12-09 03:05:27 +00:00
|
|
|
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(opts.PreviewLabel.label, &tui.ColPreviewLabel, false)
|
2022-11-10 07:23:33 +00:00
|
|
|
if opts.Separator == nil || len(*opts.Separator) > 0 {
|
|
|
|
bar := "─"
|
|
|
|
if opts.Separator != nil {
|
|
|
|
bar = *opts.Separator
|
|
|
|
} else if !t.unicode {
|
|
|
|
bar = "-"
|
|
|
|
}
|
|
|
|
t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true)
|
|
|
|
}
|
2023-01-15 16:22:02 +00:00
|
|
|
if t.unicode {
|
|
|
|
t.borderWidth = runewidth.RuneWidth('│')
|
|
|
|
}
|
2023-01-01 05:48:14 +00:00
|
|
|
if opts.Scrollbar == nil {
|
2023-01-15 16:22:02 +00:00
|
|
|
if t.unicode && t.borderWidth == 1 {
|
2023-01-01 08:38:34 +00:00
|
|
|
t.scrollbar = "│"
|
2023-01-01 05:48:14 +00:00
|
|
|
} else {
|
|
|
|
t.scrollbar = "|"
|
|
|
|
}
|
2023-05-16 14:53:10 +00:00
|
|
|
t.previewScrollbar = t.scrollbar
|
2023-01-01 05:48:14 +00:00
|
|
|
} else {
|
2023-05-16 14:53:10 +00:00
|
|
|
runes := []rune(*opts.Scrollbar)
|
|
|
|
if len(runes) > 0 {
|
|
|
|
t.scrollbar = string(runes[0])
|
|
|
|
t.previewScrollbar = t.scrollbar
|
|
|
|
if len(runes) > 1 {
|
|
|
|
t.previewScrollbar = string(runes[1])
|
|
|
|
}
|
|
|
|
}
|
2023-01-01 05:48:14 +00:00
|
|
|
}
|
2020-02-17 01:19:03 +00:00
|
|
|
|
2022-12-29 11:01:50 +00:00
|
|
|
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
|
|
|
|
|
2023-03-19 06:42:47 +00:00
|
|
|
if t.listenPort != nil {
|
|
|
|
err, port := startHttpServer(*t.listenPort, t.serverChan)
|
|
|
|
if err != nil {
|
|
|
|
errorExit(err.Error())
|
|
|
|
}
|
|
|
|
t.listenPort = &port
|
2022-12-21 04:02:25 +00:00
|
|
|
}
|
|
|
|
|
2017-05-26 10:02:49 +00:00
|
|
|
return &t
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2023-03-19 06:42:47 +00:00
|
|
|
func (t *Terminal) environ() []string {
|
|
|
|
env := os.Environ()
|
|
|
|
if t.listenPort != nil {
|
|
|
|
env = append(env, fmt.Sprintf("FZF_PORT=%d", *t.listenPort))
|
|
|
|
}
|
|
|
|
return env
|
|
|
|
}
|
|
|
|
|
2022-09-07 16:01:22 +00:00
|
|
|
func borderLines(shape tui.BorderShape) int {
|
|
|
|
switch shape {
|
2023-06-10 05:48:29 +00:00
|
|
|
case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
2022-09-07 16:01:22 +00:00
|
|
|
return 2
|
|
|
|
case tui.BorderTop, tui.BorderBottom:
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2023-07-25 13:11:15 +00:00
|
|
|
func (t *Terminal) visibleHeaderLines() int {
|
|
|
|
if !t.headerVisible {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return len(t.header0) + t.headerLines
|
|
|
|
}
|
|
|
|
|
2022-09-07 16:01:22 +00:00
|
|
|
// Extra number of lines needed to display fzf
|
|
|
|
func (t *Terminal) extraLines() int {
|
2023-07-25 13:11:15 +00:00
|
|
|
extra := t.visibleHeaderLines() + 1
|
2022-09-07 16:01:22 +00:00
|
|
|
if !t.noInfoLine() {
|
|
|
|
extra++
|
|
|
|
}
|
|
|
|
return extra
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) MaxFitAndPad(opts *Options) (int, int) {
|
|
|
|
_, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding()
|
|
|
|
padHeight := marginInt[0] + marginInt[2] + paddingInt[0] + paddingInt[2]
|
|
|
|
fit := screenHeight - padHeight - t.extraLines()
|
|
|
|
return fit, padHeight
|
|
|
|
}
|
|
|
|
|
2022-11-10 07:23:33 +00:00
|
|
|
func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool) (labelPrinter, int) {
|
|
|
|
// Nothing to do
|
|
|
|
if len(str) == 0 {
|
2022-10-30 15:22:41 +00:00
|
|
|
return nil, 0
|
|
|
|
}
|
2022-11-10 07:23:33 +00:00
|
|
|
|
|
|
|
// Extract ANSI color codes
|
2023-01-24 10:33:14 +00:00
|
|
|
str = firstLine(str)
|
2022-11-10 07:23:33 +00:00
|
|
|
text, colors, _ := extractColor(str, nil, nil)
|
2022-10-29 15:12:01 +00:00
|
|
|
runes := []rune(text)
|
2022-11-10 07:23:33 +00:00
|
|
|
|
|
|
|
// Simpler printer for strings without ANSI colors or tab characters
|
2023-06-17 10:10:12 +00:00
|
|
|
if colors == nil && !strings.ContainsRune(str, '\t') {
|
2023-03-25 01:23:05 +00:00
|
|
|
length := util.StringWidth(str)
|
2022-11-10 07:23:33 +00:00
|
|
|
if length == 0 {
|
|
|
|
return nil, 0
|
|
|
|
}
|
|
|
|
printFn := func(window tui.Window, limit int) {
|
|
|
|
if length > limit {
|
|
|
|
trimmedRunes, _ := t.trimRight(runes, limit)
|
|
|
|
window.CPrint(*color, string(trimmedRunes))
|
|
|
|
} else if fill {
|
|
|
|
window.CPrint(*color, util.RepeatToFill(str, length, limit))
|
|
|
|
} else {
|
|
|
|
window.CPrint(*color, str)
|
|
|
|
}
|
|
|
|
}
|
2023-09-01 11:28:56 +00:00
|
|
|
return printFn, length
|
2022-11-10 07:23:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Printer that correctly handles ANSI color codes and tab characters
|
2022-10-29 15:12:01 +00:00
|
|
|
item := &Item{text: util.RunesToChars(runes), colors: colors}
|
2022-11-10 07:23:33 +00:00
|
|
|
length := t.displayWidth(runes)
|
|
|
|
if length == 0 {
|
|
|
|
return nil, 0
|
|
|
|
}
|
2022-10-29 15:12:01 +00:00
|
|
|
result := Result{item: item}
|
|
|
|
var offsets []colorOffset
|
2022-11-10 07:23:33 +00:00
|
|
|
printFn := func(window tui.Window, limit int) {
|
2022-10-29 15:12:01 +00:00
|
|
|
if offsets == nil {
|
|
|
|
// tui.Col* are not initialized until renderer.Init()
|
2022-11-10 07:23:33 +00:00
|
|
|
offsets = result.colorOffsets(nil, t.theme, *color, *color, false)
|
|
|
|
}
|
|
|
|
for limit > 0 {
|
|
|
|
if length > limit {
|
|
|
|
trimmedRunes, _ := t.trimRight(runes, limit)
|
|
|
|
t.printColoredString(window, trimmedRunes, offsets, *color)
|
|
|
|
break
|
|
|
|
} else if fill {
|
|
|
|
t.printColoredString(window, runes, offsets, *color)
|
|
|
|
limit -= length
|
|
|
|
} else {
|
|
|
|
t.printColoredString(window, runes, offsets, *color)
|
|
|
|
break
|
|
|
|
}
|
2022-10-29 15:12:01 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-10 07:23:33 +00:00
|
|
|
return printFn, length
|
2022-10-29 15:12:01 +00:00
|
|
|
}
|
|
|
|
|
2020-07-05 07:16:46 +00:00
|
|
|
func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
|
|
|
var state *ansiState
|
2023-01-24 10:33:14 +00:00
|
|
|
prompt = firstLine(prompt)
|
2020-07-05 07:16:46 +00:00
|
|
|
trimmed, colors, _ := extractColor(prompt, state, nil)
|
|
|
|
item := &Item{text: util.ToChars([]byte(trimmed)), colors: colors}
|
2020-10-25 10:29:37 +00:00
|
|
|
|
|
|
|
// "Prompt> "
|
|
|
|
// ------- // Do not apply ANSI attributes to the trailing whitespaces
|
|
|
|
// // unless the part has a non-default ANSI state
|
|
|
|
loc := whiteSuffix.FindStringIndex(trimmed)
|
|
|
|
if loc != nil {
|
2020-11-24 16:40:30 +00:00
|
|
|
blankState := ansiOffset{[2]int32{int32(loc[0]), int32(loc[1])}, ansiState{-1, -1, tui.AttrClear, -1}}
|
2020-10-25 10:29:37 +00:00
|
|
|
if item.colors != nil {
|
|
|
|
lastColor := (*item.colors)[len(*item.colors)-1]
|
|
|
|
if lastColor.offset[1] < int32(loc[1]) {
|
|
|
|
blankState.offset[0] = lastColor.offset[1]
|
|
|
|
colors := append(*item.colors, blankState)
|
|
|
|
item.colors = &colors
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
colors := []ansiOffset{blankState}
|
|
|
|
item.colors = &colors
|
|
|
|
}
|
|
|
|
}
|
2020-07-05 07:16:46 +00:00
|
|
|
output := func() {
|
|
|
|
t.printHighlighted(
|
2020-10-25 10:29:37 +00:00
|
|
|
Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false)
|
2020-07-05 07:16:46 +00:00
|
|
|
}
|
|
|
|
_, promptLen := t.processTabs([]rune(trimmed), 0)
|
|
|
|
|
|
|
|
return output, promptLen
|
|
|
|
}
|
|
|
|
|
2019-11-14 15:39:29 +00:00
|
|
|
func (t *Terminal) noInfoLine() bool {
|
2023-06-10 15:04:24 +00:00
|
|
|
return t.infoStyle.noExtraLine()
|
2019-11-14 15:39:29 +00:00
|
|
|
}
|
|
|
|
|
2023-01-06 06:36:12 +00:00
|
|
|
func getScrollbar(total int, height int, offset int) (int, int) {
|
|
|
|
if total == 0 || total <= height {
|
2023-01-01 05:48:14 +00:00
|
|
|
return 0, 0
|
|
|
|
}
|
2023-01-06 06:36:12 +00:00
|
|
|
barLength := util.Max(1, height*height/total)
|
2023-01-01 05:48:14 +00:00
|
|
|
var barStart int
|
2023-01-06 06:36:12 +00:00
|
|
|
if total == height {
|
2023-01-01 05:48:14 +00:00
|
|
|
barStart = 0
|
|
|
|
} else {
|
2023-01-06 06:36:12 +00:00
|
|
|
barStart = (height - barLength) * offset / (total - height)
|
2023-01-01 05:48:14 +00:00
|
|
|
}
|
|
|
|
return barLength, barStart
|
|
|
|
}
|
|
|
|
|
2023-01-06 06:36:12 +00:00
|
|
|
func (t *Terminal) getScrollbar() (int, int) {
|
|
|
|
return getScrollbar(t.merger.Length(), t.maxItems(), t.offset)
|
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// Input returns current query string
|
2021-01-02 15:00:40 +00:00
|
|
|
func (t *Terminal) Input() (bool, []rune) {
|
2015-01-01 19:49:30 +00:00
|
|
|
t.mutex.Lock()
|
|
|
|
defer t.mutex.Unlock()
|
2021-01-02 15:00:40 +00:00
|
|
|
return t.paused, copySlice(t.input)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// UpdateCount updates the count information
|
2019-11-10 02:36:22 +00:00
|
|
|
func (t *Terminal) UpdateCount(cnt int, final bool, failedCommand *string) {
|
2015-01-01 19:49:30 +00:00
|
|
|
t.mutex.Lock()
|
|
|
|
t.count = cnt
|
2022-12-29 11:01:50 +00:00
|
|
|
if t.hasLoadActions && t.reading && final {
|
|
|
|
t.triggerLoad = true
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
t.reading = !final
|
2019-11-10 02:36:22 +00:00
|
|
|
t.failed = failedCommand
|
2015-01-01 19:49:30 +00:00
|
|
|
t.mutex.Unlock()
|
2015-01-11 18:01:24 +00:00
|
|
|
t.reqBox.Set(reqInfo, nil)
|
2015-01-07 03:46:45 +00:00
|
|
|
if final {
|
2015-01-11 18:01:24 +00:00
|
|
|
t.reqBox.Set(reqRefresh, nil)
|
2015-01-07 03:46:45 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2023-04-22 13:01:00 +00:00
|
|
|
func (t *Terminal) changeHeader(header string) bool {
|
|
|
|
lines := strings.Split(strings.TrimSuffix(header, "\n"), "\n")
|
|
|
|
needFullRedraw := len(t.header0) != len(lines)
|
|
|
|
t.header0 = lines
|
|
|
|
return needFullRedraw
|
|
|
|
}
|
|
|
|
|
2015-07-21 18:21:20 +00:00
|
|
|
// UpdateHeader updates the header
|
2015-09-15 10:04:53 +00:00
|
|
|
func (t *Terminal) UpdateHeader(header []string) {
|
2015-07-21 18:21:20 +00:00
|
|
|
t.mutex.Lock()
|
2023-04-22 13:01:00 +00:00
|
|
|
t.header = header
|
2015-07-21 18:21:20 +00:00
|
|
|
t.mutex.Unlock()
|
|
|
|
t.reqBox.Set(reqHeader, nil)
|
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// UpdateProgress updates the search progress
|
2015-01-01 19:49:30 +00:00
|
|
|
func (t *Terminal) UpdateProgress(progress float32) {
|
|
|
|
t.mutex.Lock()
|
2015-01-11 12:56:55 +00:00
|
|
|
newProgress := int(progress * 100)
|
|
|
|
changed := t.progress != newProgress
|
|
|
|
t.progress = newProgress
|
2015-01-01 19:49:30 +00:00
|
|
|
t.mutex.Unlock()
|
2015-01-11 12:56:55 +00:00
|
|
|
|
|
|
|
if changed {
|
2015-01-11 18:01:24 +00:00
|
|
|
t.reqBox.Set(reqInfo, nil)
|
2015-01-11 12:56:55 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// UpdateList updates Merger to display the list
|
2023-05-27 06:43:31 +00:00
|
|
|
func (t *Terminal) UpdateList(merger *Merger) {
|
2015-01-01 19:49:30 +00:00
|
|
|
t.mutex.Lock()
|
2023-03-29 11:36:09 +00:00
|
|
|
var prevIndex int32 = -1
|
2023-05-27 06:43:31 +00:00
|
|
|
reset := t.revision != merger.Revision()
|
2023-04-22 14:39:35 +00:00
|
|
|
if !reset && t.track != trackDisabled {
|
2023-04-22 06:06:22 +00:00
|
|
|
if t.merger.Length() > 0 {
|
|
|
|
prevIndex = t.merger.Get(t.cy).item.Index()
|
|
|
|
} else if merger.Length() > 0 {
|
|
|
|
prevIndex = merger.First().item.Index()
|
|
|
|
}
|
2023-03-29 11:36:09 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
t.progress = 100
|
2015-01-09 16:06:08 +00:00
|
|
|
t.merger = merger
|
2019-12-09 12:32:58 +00:00
|
|
|
if reset {
|
|
|
|
t.selected = make(map[int32]selectedItem)
|
2023-05-27 06:43:31 +00:00
|
|
|
t.revision = merger.Revision()
|
2022-03-29 13:25:48 +00:00
|
|
|
t.version++
|
2019-12-09 12:32:58 +00:00
|
|
|
}
|
2023-04-30 16:55:14 +00:00
|
|
|
if t.triggerLoad {
|
2022-12-29 11:01:50 +00:00
|
|
|
t.triggerLoad = false
|
|
|
|
t.eventChan <- tui.Load.AsEvent()
|
|
|
|
}
|
2023-03-29 11:36:09 +00:00
|
|
|
if prevIndex >= 0 {
|
|
|
|
pos := t.cy - t.offset
|
|
|
|
count := t.merger.Length()
|
|
|
|
i := t.merger.FindIndex(prevIndex)
|
|
|
|
if i >= 0 {
|
|
|
|
t.cy = i
|
|
|
|
t.offset = t.cy - pos
|
2023-04-22 14:39:35 +00:00
|
|
|
} else if t.track == trackCurrent {
|
|
|
|
t.track = trackDisabled
|
|
|
|
t.cy = pos
|
|
|
|
t.offset = 0
|
2023-03-29 11:36:09 +00:00
|
|
|
} else if t.cy > count {
|
|
|
|
// Try to keep the vertical position when the list shrinks
|
|
|
|
t.cy = count - util.Min(count, t.maxItems()) + pos
|
|
|
|
}
|
|
|
|
}
|
2023-04-26 06:13:08 +00:00
|
|
|
if !t.reading {
|
|
|
|
switch t.merger.Length() {
|
|
|
|
case 0:
|
|
|
|
zero := tui.Zero.AsEvent()
|
|
|
|
if _, prs := t.keymap[zero]; prs {
|
|
|
|
t.eventChan <- zero
|
|
|
|
}
|
|
|
|
case 1:
|
|
|
|
one := tui.One.AsEvent()
|
|
|
|
if _, prs := t.keymap[one]; prs {
|
|
|
|
t.eventChan <- one
|
|
|
|
}
|
2023-04-01 08:16:02 +00:00
|
|
|
}
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
t.mutex.Unlock()
|
2015-01-11 18:01:24 +00:00
|
|
|
t.reqBox.Set(reqInfo, nil)
|
|
|
|
t.reqBox.Set(reqList, nil)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-09-15 04:21:51 +00:00
|
|
|
func (t *Terminal) output() bool {
|
2015-01-01 19:49:30 +00:00
|
|
|
if t.printQuery {
|
2016-09-17 19:52:47 +00:00
|
|
|
t.printer(string(t.input))
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-03-28 17:59:32 +00:00
|
|
|
if len(t.expect) > 0 {
|
2016-09-17 19:52:47 +00:00
|
|
|
t.printer(t.pressed)
|
2015-03-28 17:59:32 +00:00
|
|
|
}
|
2015-09-15 04:21:51 +00:00
|
|
|
found := len(t.selected) > 0
|
|
|
|
if !found {
|
2017-01-27 07:38:42 +00:00
|
|
|
current := t.currentItem()
|
|
|
|
if current != nil {
|
|
|
|
t.printer(current.AsString(t.ansi))
|
2015-09-15 04:21:51 +00:00
|
|
|
found = true
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
} else {
|
2015-11-08 16:42:01 +00:00
|
|
|
for _, sel := range t.sortSelected() {
|
2016-10-03 05:16:10 +00:00
|
|
|
t.printer(sel.item.AsString(t.ansi))
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
}
|
2015-09-15 04:21:51 +00:00
|
|
|
return found
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-11-08 16:42:01 +00:00
|
|
|
func (t *Terminal) sortSelected() []selectedItem {
|
|
|
|
sels := make([]selectedItem, 0, len(t.selected))
|
|
|
|
for _, sel := range t.selected {
|
|
|
|
sels = append(sels, sel)
|
|
|
|
}
|
|
|
|
sort.Sort(byTimeOrder(sels))
|
|
|
|
return sels
|
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
func (t *Terminal) displayWidth(runes []rune) int {
|
2021-12-22 14:22:08 +00:00
|
|
|
width, _ := util.RunesWidth(runes, 0, t.tabstop, math.MaxInt32)
|
2021-05-14 02:43:32 +00:00
|
|
|
return width
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2016-06-11 10:59:12 +00:00
|
|
|
const (
|
2020-11-03 12:49:21 +00:00
|
|
|
minWidth = 4
|
2022-09-07 16:01:22 +00:00
|
|
|
minHeight = 3
|
2016-06-11 10:59:12 +00:00
|
|
|
)
|
2015-07-26 14:02:04 +00:00
|
|
|
|
2020-10-06 10:37:33 +00:00
|
|
|
func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int) int {
|
|
|
|
max := base - occupied
|
2022-09-07 16:01:22 +00:00
|
|
|
if max < minSize {
|
|
|
|
max = minSize
|
|
|
|
}
|
2016-06-11 10:59:12 +00:00
|
|
|
if size.percent {
|
|
|
|
return util.Constrain(int(float64(base)*0.01*size.size), minSize, max)
|
|
|
|
}
|
2020-10-06 10:37:33 +00:00
|
|
|
return util.Constrain(int(size.size)+pad, minSize, max)
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
|
|
|
|
2022-09-07 16:01:22 +00:00
|
|
|
func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
|
2017-01-07 16:30:31 +00:00
|
|
|
screenWidth := t.tui.MaxX()
|
|
|
|
screenHeight := t.tui.MaxY()
|
2020-11-09 11:34:08 +00:00
|
|
|
marginInt := [4]int{} // TRBL
|
|
|
|
paddingInt := [4]int{} // TRBL
|
|
|
|
sizeSpecToInt := func(index int, spec sizeSpec) int {
|
|
|
|
if spec.percent {
|
2016-06-11 10:59:12 +00:00
|
|
|
var max float64
|
2020-11-09 11:34:08 +00:00
|
|
|
if index%2 == 0 {
|
2016-06-11 10:59:12 +00:00
|
|
|
max = float64(screenHeight)
|
2015-07-26 14:02:04 +00:00
|
|
|
} else {
|
2016-06-11 10:59:12 +00:00
|
|
|
max = float64(screenWidth)
|
2015-07-26 14:02:04 +00:00
|
|
|
}
|
2020-11-09 11:34:08 +00:00
|
|
|
return int(max * spec.size * 0.01)
|
2015-07-26 14:02:04 +00:00
|
|
|
}
|
2020-11-09 11:34:08 +00:00
|
|
|
return int(spec.size)
|
|
|
|
}
|
|
|
|
for idx, sizeSpec := range t.padding {
|
|
|
|
paddingInt[idx] = sizeSpecToInt(idx, sizeSpec)
|
|
|
|
}
|
|
|
|
|
2023-01-15 16:22:02 +00:00
|
|
|
bw := t.borderWidth
|
2020-11-09 11:34:08 +00:00
|
|
|
extraMargin := [4]int{} // TRBL
|
|
|
|
for idx, sizeSpec := range t.margin {
|
2020-03-05 11:15:15 +00:00
|
|
|
switch t.borderShape {
|
|
|
|
case tui.BorderHorizontal:
|
2020-11-09 11:34:08 +00:00
|
|
|
extraMargin[idx] += 1 - idx%2
|
2020-10-26 13:33:41 +00:00
|
|
|
case tui.BorderVertical:
|
2023-01-15 16:22:02 +00:00
|
|
|
extraMargin[idx] += (1 + bw) * (idx % 2)
|
2020-10-26 13:33:41 +00:00
|
|
|
case tui.BorderTop:
|
|
|
|
if idx == 0 {
|
2020-11-09 11:34:08 +00:00
|
|
|
extraMargin[idx]++
|
2020-10-26 13:33:41 +00:00
|
|
|
}
|
|
|
|
case tui.BorderRight:
|
|
|
|
if idx == 1 {
|
2023-01-15 16:22:02 +00:00
|
|
|
extraMargin[idx] += 1 + bw
|
2020-10-26 13:33:41 +00:00
|
|
|
}
|
|
|
|
case tui.BorderBottom:
|
|
|
|
if idx == 2 {
|
2020-11-09 11:34:08 +00:00
|
|
|
extraMargin[idx]++
|
2020-10-26 13:33:41 +00:00
|
|
|
}
|
|
|
|
case tui.BorderLeft:
|
|
|
|
if idx == 3 {
|
2023-01-15 16:22:02 +00:00
|
|
|
extraMargin[idx] += 1 + bw
|
2020-10-26 13:33:41 +00:00
|
|
|
}
|
2023-06-10 05:48:29 +00:00
|
|
|
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
2023-01-15 16:22:02 +00:00
|
|
|
extraMargin[idx] += 1 + bw*(idx%2)
|
2017-02-04 12:51:22 +00:00
|
|
|
}
|
2020-11-09 11:34:08 +00:00
|
|
|
marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
|
2015-07-26 14:02:04 +00:00
|
|
|
}
|
2020-11-09 11:34:08 +00:00
|
|
|
|
2015-07-26 14:02:04 +00:00
|
|
|
adjust := func(idx1 int, idx2 int, max int, min int) {
|
2022-09-07 16:01:22 +00:00
|
|
|
if min > max {
|
|
|
|
min = max
|
|
|
|
}
|
|
|
|
margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
|
|
|
|
if max-margin < min {
|
|
|
|
desired := max - min
|
|
|
|
paddingInt[idx1] = desired * paddingInt[idx1] / margin
|
|
|
|
paddingInt[idx2] = desired * paddingInt[idx2] / margin
|
|
|
|
marginInt[idx1] = util.Max(extraMargin[idx1], desired*marginInt[idx1]/margin)
|
|
|
|
marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin)
|
2015-07-26 14:02:04 +00:00
|
|
|
}
|
|
|
|
}
|
2017-01-23 15:23:16 +00:00
|
|
|
|
2016-06-11 10:59:12 +00:00
|
|
|
minAreaWidth := minWidth
|
|
|
|
minAreaHeight := minHeight
|
2022-09-07 16:01:22 +00:00
|
|
|
if t.noInfoLine() {
|
|
|
|
minAreaHeight -= 1
|
|
|
|
}
|
2023-02-01 09:16:58 +00:00
|
|
|
if t.needPreviewWindow() {
|
2022-09-07 16:01:22 +00:00
|
|
|
minPreviewHeight := 1 + borderLines(t.previewOpts.border)
|
|
|
|
minPreviewWidth := 5
|
2020-12-05 12:16:35 +00:00
|
|
|
switch t.previewOpts.position {
|
2016-06-11 10:59:12 +00:00
|
|
|
case posUp, posDown:
|
2022-09-07 16:01:22 +00:00
|
|
|
minAreaHeight += minPreviewHeight
|
|
|
|
minAreaWidth = util.Max(minPreviewWidth, minAreaWidth)
|
2016-06-11 10:59:12 +00:00
|
|
|
case posLeft, posRight:
|
2022-09-07 16:01:22 +00:00
|
|
|
minAreaWidth += minPreviewWidth
|
|
|
|
minAreaHeight = util.Max(minPreviewHeight, minAreaHeight)
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
adjust(1, 3, screenWidth, minAreaWidth)
|
|
|
|
adjust(0, 2, screenHeight, minAreaHeight)
|
2022-09-07 16:01:22 +00:00
|
|
|
|
|
|
|
return screenWidth, screenHeight, marginInt, paddingInt
|
|
|
|
}
|
|
|
|
|
2023-01-07 02:21:52 +00:00
|
|
|
func (t *Terminal) resizeWindows(forcePreview bool) {
|
2022-09-07 16:01:22 +00:00
|
|
|
screenWidth, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding()
|
|
|
|
width := screenWidth - marginInt[1] - marginInt[3]
|
|
|
|
height := screenHeight - marginInt[0] - marginInt[2]
|
|
|
|
|
|
|
|
t.prevLines = make([]itemLine, screenHeight)
|
2017-02-04 12:51:22 +00:00
|
|
|
if t.border != nil {
|
|
|
|
t.border.Close()
|
|
|
|
}
|
2016-06-11 10:59:12 +00:00
|
|
|
if t.window != nil {
|
|
|
|
t.window.Close()
|
2022-07-20 03:08:54 +00:00
|
|
|
t.window = nil
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
2017-02-04 12:51:22 +00:00
|
|
|
if t.pborder != nil {
|
|
|
|
t.pborder.Close()
|
2022-07-20 03:08:54 +00:00
|
|
|
t.pborder = nil
|
2020-08-23 08:05:45 +00:00
|
|
|
}
|
|
|
|
if t.pwindow != nil {
|
2016-06-11 10:59:12 +00:00
|
|
|
t.pwindow.Close()
|
2022-07-20 03:08:54 +00:00
|
|
|
t.pwindow = nil
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
2020-10-18 08:03:33 +00:00
|
|
|
// Reset preview version so that full redraw occurs
|
|
|
|
t.previewed.version = 0
|
2016-06-11 10:59:12 +00:00
|
|
|
|
2023-01-16 10:34:28 +00:00
|
|
|
bw := t.borderWidth
|
2020-03-05 11:15:15 +00:00
|
|
|
switch t.borderShape {
|
|
|
|
case tui.BorderHorizontal:
|
2017-02-04 12:51:22 +00:00
|
|
|
t.border = t.tui.NewWindow(
|
2020-10-26 13:33:41 +00:00
|
|
|
marginInt[0]-1, marginInt[3], width, height+2,
|
2019-12-12 14:03:17 +00:00
|
|
|
false, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode))
|
2020-10-26 13:33:41 +00:00
|
|
|
case tui.BorderVertical:
|
|
|
|
t.border = t.tui.NewWindow(
|
2023-01-16 10:34:28 +00:00
|
|
|
marginInt[0], marginInt[3]-(1+bw), width+(1+bw)*2, height,
|
2020-10-26 13:33:41 +00:00
|
|
|
false, tui.MakeBorderStyle(tui.BorderVertical, t.unicode))
|
|
|
|
case tui.BorderTop:
|
|
|
|
t.border = t.tui.NewWindow(
|
|
|
|
marginInt[0]-1, marginInt[3], width, height+1,
|
|
|
|
false, tui.MakeBorderStyle(tui.BorderTop, t.unicode))
|
|
|
|
case tui.BorderBottom:
|
|
|
|
t.border = t.tui.NewWindow(
|
|
|
|
marginInt[0], marginInt[3], width, height+1,
|
|
|
|
false, tui.MakeBorderStyle(tui.BorderBottom, t.unicode))
|
|
|
|
case tui.BorderLeft:
|
|
|
|
t.border = t.tui.NewWindow(
|
2023-01-16 10:34:28 +00:00
|
|
|
marginInt[0], marginInt[3]-(1+bw), width+(1+bw), height,
|
2020-10-26 13:33:41 +00:00
|
|
|
false, tui.MakeBorderStyle(tui.BorderLeft, t.unicode))
|
|
|
|
case tui.BorderRight:
|
|
|
|
t.border = t.tui.NewWindow(
|
2023-01-16 10:34:28 +00:00
|
|
|
marginInt[0], marginInt[3], width+(1+bw), height,
|
2020-10-26 13:33:41 +00:00
|
|
|
false, tui.MakeBorderStyle(tui.BorderRight, t.unicode))
|
2023-06-10 05:48:29 +00:00
|
|
|
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
2020-03-05 11:15:15 +00:00
|
|
|
t.border = t.tui.NewWindow(
|
2023-01-16 10:34:28 +00:00
|
|
|
marginInt[0]-1, marginInt[3]-(1+bw), width+(1+bw)*2, height+2,
|
2020-03-05 11:15:15 +00:00
|
|
|
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
|
2017-02-04 12:51:22 +00:00
|
|
|
}
|
2020-11-09 11:34:08 +00:00
|
|
|
|
2022-09-07 16:01:22 +00:00
|
|
|
// Add padding to margin
|
2020-11-09 11:34:08 +00:00
|
|
|
for idx, val := range paddingInt {
|
|
|
|
marginInt[idx] += val
|
|
|
|
}
|
2022-09-07 16:01:22 +00:00
|
|
|
width -= paddingInt[1] + paddingInt[3]
|
|
|
|
height -= paddingInt[0] + paddingInt[2]
|
2020-11-09 11:34:08 +00:00
|
|
|
|
2022-07-20 03:08:54 +00:00
|
|
|
// Set up preview window
|
2019-03-28 17:11:03 +00:00
|
|
|
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
2023-02-01 09:16:58 +00:00
|
|
|
if forcePreview || t.needPreviewWindow() {
|
2023-01-07 02:21:52 +00:00
|
|
|
var resizePreviewWindows func(previewOpts *previewOpts)
|
|
|
|
resizePreviewWindows = func(previewOpts *previewOpts) {
|
|
|
|
t.activePreviewOpts = previewOpts
|
|
|
|
if previewOpts.size.size == 0 {
|
|
|
|
return
|
|
|
|
}
|
2022-07-20 03:08:54 +00:00
|
|
|
hasThreshold := previewOpts.threshold > 0 && previewOpts.alternative != nil
|
|
|
|
createPreviewWindow := func(y int, x int, w int, h int) {
|
|
|
|
pwidth := w
|
|
|
|
pheight := h
|
|
|
|
var previewBorder tui.BorderStyle
|
|
|
|
if previewOpts.border == tui.BorderNone {
|
|
|
|
previewBorder = tui.MakeTransparentBorder()
|
|
|
|
} else {
|
|
|
|
previewBorder = tui.MakeBorderStyle(previewOpts.border, t.unicode)
|
|
|
|
}
|
|
|
|
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
|
|
|
switch previewOpts.border {
|
2023-06-10 05:48:29 +00:00
|
|
|
case tui.BorderSharp, tui.BorderRounded, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
2023-01-15 16:22:02 +00:00
|
|
|
pwidth -= (1 + bw) * 2
|
2022-07-20 03:08:54 +00:00
|
|
|
pheight -= 2
|
2023-01-15 16:22:02 +00:00
|
|
|
x += 1 + bw
|
2022-07-20 03:08:54 +00:00
|
|
|
y += 1
|
|
|
|
case tui.BorderLeft:
|
2023-01-15 16:22:02 +00:00
|
|
|
pwidth -= 1 + bw
|
|
|
|
x += 1 + bw
|
2022-07-20 03:08:54 +00:00
|
|
|
case tui.BorderRight:
|
2023-01-15 16:22:02 +00:00
|
|
|
pwidth -= 1 + bw
|
2022-07-20 03:08:54 +00:00
|
|
|
case tui.BorderTop:
|
|
|
|
pheight -= 1
|
|
|
|
y += 1
|
|
|
|
case tui.BorderBottom:
|
|
|
|
pheight -= 1
|
|
|
|
case tui.BorderHorizontal:
|
|
|
|
pheight -= 2
|
|
|
|
y += 1
|
|
|
|
case tui.BorderVertical:
|
2023-01-15 16:22:02 +00:00
|
|
|
pwidth -= (1 + bw) * 2
|
|
|
|
x += 1 + bw
|
2022-07-20 03:08:54 +00:00
|
|
|
}
|
2023-01-06 06:36:12 +00:00
|
|
|
if len(t.scrollbar) > 0 && !previewOpts.border.HasRight() {
|
|
|
|
// Need a column to show scrollbar
|
|
|
|
pwidth -= 1
|
|
|
|
}
|
2023-05-16 15:06:35 +00:00
|
|
|
pwidth = util.Max(0, pwidth)
|
|
|
|
pheight = util.Max(0, pheight)
|
2022-07-20 03:08:54 +00:00
|
|
|
t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, true, noBorder)
|
|
|
|
}
|
|
|
|
verticalPad := 2
|
|
|
|
minPreviewHeight := 3
|
|
|
|
switch previewOpts.border {
|
|
|
|
case tui.BorderNone, tui.BorderVertical, tui.BorderLeft, tui.BorderRight:
|
|
|
|
verticalPad = 0
|
|
|
|
minPreviewHeight = 1
|
|
|
|
case tui.BorderTop, tui.BorderBottom:
|
|
|
|
verticalPad = 1
|
|
|
|
minPreviewHeight = 2
|
|
|
|
}
|
|
|
|
switch previewOpts.position {
|
|
|
|
case posUp, posDown:
|
|
|
|
pheight := calculateSize(height, previewOpts.size, minHeight, minPreviewHeight, verticalPad)
|
|
|
|
if hasThreshold && pheight < previewOpts.threshold {
|
2023-01-07 02:21:52 +00:00
|
|
|
t.activePreviewOpts = previewOpts.alternative
|
|
|
|
if forcePreview {
|
|
|
|
previewOpts.alternative.hidden = false
|
|
|
|
}
|
2022-07-20 03:29:45 +00:00
|
|
|
if !previewOpts.alternative.hidden {
|
2023-01-07 02:21:52 +00:00
|
|
|
resizePreviewWindows(previewOpts.alternative)
|
2022-07-20 03:29:45 +00:00
|
|
|
}
|
2022-07-20 03:08:54 +00:00
|
|
|
return
|
|
|
|
}
|
2023-01-07 02:21:52 +00:00
|
|
|
if forcePreview {
|
|
|
|
previewOpts.hidden = false
|
|
|
|
}
|
|
|
|
if previewOpts.hidden {
|
|
|
|
return
|
|
|
|
}
|
2023-01-06 06:36:12 +00:00
|
|
|
// Put scrollbar closer to the right border for consistent look
|
|
|
|
if t.borderShape.HasRight() {
|
|
|
|
width++
|
|
|
|
}
|
2022-07-20 03:08:54 +00:00
|
|
|
if previewOpts.position == posUp {
|
|
|
|
t.window = t.tui.NewWindow(
|
|
|
|
marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
|
|
|
|
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
|
|
|
|
} else {
|
|
|
|
t.window = t.tui.NewWindow(
|
|
|
|
marginInt[0], marginInt[3], width, height-pheight, false, noBorder)
|
|
|
|
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
|
|
|
|
}
|
|
|
|
case posLeft, posRight:
|
|
|
|
pwidth := calculateSize(width, previewOpts.size, minWidth, 5, 4)
|
|
|
|
if hasThreshold && pwidth < previewOpts.threshold {
|
2023-01-07 02:21:52 +00:00
|
|
|
t.activePreviewOpts = previewOpts.alternative
|
|
|
|
if forcePreview {
|
|
|
|
previewOpts.alternative.hidden = false
|
|
|
|
}
|
2022-07-20 03:29:45 +00:00
|
|
|
if !previewOpts.alternative.hidden {
|
2023-01-07 02:21:52 +00:00
|
|
|
resizePreviewWindows(previewOpts.alternative)
|
2022-07-20 03:29:45 +00:00
|
|
|
}
|
2022-07-20 03:08:54 +00:00
|
|
|
return
|
|
|
|
}
|
2023-01-07 02:21:52 +00:00
|
|
|
if forcePreview {
|
|
|
|
previewOpts.hidden = false
|
|
|
|
}
|
|
|
|
if previewOpts.hidden {
|
|
|
|
return
|
|
|
|
}
|
2022-07-20 03:08:54 +00:00
|
|
|
if previewOpts.position == posLeft {
|
2023-01-06 06:36:12 +00:00
|
|
|
// Put scrollbar closer to the right border for consistent look
|
|
|
|
if t.borderShape.HasRight() {
|
|
|
|
width++
|
|
|
|
}
|
|
|
|
// Add a 1-column margin between the preview window and the main window
|
2022-07-20 03:08:54 +00:00
|
|
|
t.window = t.tui.NewWindow(
|
2023-01-06 06:36:12 +00:00
|
|
|
marginInt[0], marginInt[3]+pwidth+1, width-pwidth-1, height, false, noBorder)
|
2022-07-20 03:08:54 +00:00
|
|
|
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
|
|
|
|
} else {
|
|
|
|
t.window = t.tui.NewWindow(
|
|
|
|
marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
|
2023-01-06 06:36:12 +00:00
|
|
|
// NOTE: fzf --preview 'cat {}' --preview-window border-left --border
|
|
|
|
x := marginInt[3] + width - pwidth
|
|
|
|
if !previewOpts.border.HasRight() && t.borderShape.HasRight() {
|
|
|
|
pwidth++
|
|
|
|
}
|
|
|
|
createPreviewWindow(marginInt[0], x, pwidth, height)
|
2022-07-20 03:08:54 +00:00
|
|
|
}
|
2019-11-15 02:37:52 +00:00
|
|
|
}
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
2023-01-07 02:21:52 +00:00
|
|
|
resizePreviewWindows(&t.previewOpts)
|
2023-01-30 12:39:18 +00:00
|
|
|
} else {
|
|
|
|
t.activePreviewOpts = &t.previewOpts
|
2022-07-20 03:08:54 +00:00
|
|
|
}
|
2023-01-06 06:36:12 +00:00
|
|
|
|
|
|
|
// Without preview window
|
2022-07-20 03:08:54 +00:00
|
|
|
if t.window == nil {
|
2023-01-06 06:36:12 +00:00
|
|
|
if t.borderShape.HasRight() {
|
|
|
|
// Put scrollbar closer to the right border for consistent look
|
|
|
|
width++
|
|
|
|
}
|
2017-01-07 16:30:31 +00:00
|
|
|
t.window = t.tui.NewWindow(
|
2016-06-11 10:59:12 +00:00
|
|
|
marginInt[0],
|
|
|
|
marginInt[3],
|
|
|
|
width,
|
2019-12-12 14:03:17 +00:00
|
|
|
height, false, noBorder)
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
2022-10-30 11:44:46 +00:00
|
|
|
|
|
|
|
// Print border label
|
2023-01-21 16:56:29 +00:00
|
|
|
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, false)
|
|
|
|
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, false)
|
2022-10-30 11:44:46 +00:00
|
|
|
|
2017-09-09 04:50:07 +00:00
|
|
|
for i := 0; i < t.window.Height(); i++ {
|
|
|
|
t.window.MoveAndClear(i, 0)
|
2017-01-16 03:06:54 +00:00
|
|
|
}
|
2015-07-26 14:02:04 +00:00
|
|
|
}
|
|
|
|
|
2023-01-21 16:56:29 +00:00
|
|
|
func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape, redrawBorder bool) {
|
2023-01-23 07:22:25 +00:00
|
|
|
if window == nil {
|
2023-01-21 16:56:29 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch borderShape {
|
2023-06-10 05:48:29 +00:00
|
|
|
case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
2023-01-21 16:56:29 +00:00
|
|
|
if redrawBorder {
|
|
|
|
window.DrawHBorder()
|
|
|
|
}
|
2023-01-23 07:22:25 +00:00
|
|
|
if render == nil {
|
|
|
|
return
|
|
|
|
}
|
2023-01-21 16:56:29 +00:00
|
|
|
var col int
|
|
|
|
if opts.column == 0 {
|
|
|
|
col = util.Max(0, (window.Width()-length)/2)
|
|
|
|
} else if opts.column < 0 {
|
|
|
|
col = util.Max(0, window.Width()+opts.column+1-length)
|
|
|
|
} else {
|
|
|
|
col = util.Min(opts.column-1, window.Width()-length)
|
|
|
|
}
|
|
|
|
row := 0
|
|
|
|
if borderShape == tui.BorderBottom || opts.bottom {
|
|
|
|
row = window.Height() - 1
|
|
|
|
}
|
|
|
|
window.Move(row, col)
|
|
|
|
render(window, window.Width())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
func (t *Terminal) move(y int, x int, clear bool) {
|
2018-06-09 16:41:50 +00:00
|
|
|
h := t.window.Height()
|
|
|
|
|
|
|
|
switch t.layout {
|
|
|
|
case layoutDefault:
|
|
|
|
y = h - y - 1
|
|
|
|
case layoutReverseList:
|
2023-07-25 13:11:15 +00:00
|
|
|
n := 2 + t.visibleHeaderLines()
|
2019-11-14 15:39:29 +00:00
|
|
|
if t.noInfoLine() {
|
2018-06-09 16:41:50 +00:00
|
|
|
n--
|
|
|
|
}
|
|
|
|
if y < n {
|
|
|
|
y = h - y - 1
|
|
|
|
} else {
|
|
|
|
y -= n
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if clear {
|
2016-06-11 10:59:12 +00:00
|
|
|
t.window.MoveAndClear(y, x)
|
2015-01-01 19:49:30 +00:00
|
|
|
} else {
|
2016-06-11 10:59:12 +00:00
|
|
|
t.window.Move(y, x)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-25 10:07:12 +00:00
|
|
|
func (t *Terminal) truncateQuery() {
|
|
|
|
t.input, _ = t.trimRight(t.input, maxPatternLength)
|
|
|
|
t.cx = util.Constrain(t.cx, 0, len(t.input))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
|
|
|
|
maxWidth := util.Max(1, t.window.Width()-t.promptLen-1)
|
|
|
|
|
|
|
|
_, overflow := t.trimLeft(t.input[:t.cx], maxWidth)
|
|
|
|
minOffset := int(overflow)
|
2023-02-19 07:38:26 +00:00
|
|
|
maxOffset := minOffset + (maxWidth-util.Max(0, maxWidth-t.cx))/2
|
2018-06-25 10:07:12 +00:00
|
|
|
t.xoffset = util.Constrain(t.xoffset, minOffset, maxOffset)
|
|
|
|
before, _ := t.trimLeft(t.input[t.xoffset:t.cx], maxWidth)
|
|
|
|
beforeLen := t.displayWidth(before)
|
|
|
|
after, _ := t.trimRight(t.input[t.cx:], maxWidth-beforeLen)
|
|
|
|
afterLen := t.displayWidth(after)
|
|
|
|
t.queryLen = [2]int{beforeLen, afterLen}
|
|
|
|
return before, after
|
|
|
|
}
|
|
|
|
|
2021-11-03 12:19:22 +00:00
|
|
|
func (t *Terminal) promptLine() int {
|
|
|
|
if t.headerFirst {
|
|
|
|
max := t.window.Height() - 1
|
|
|
|
if !t.noInfoLine() {
|
|
|
|
max--
|
|
|
|
}
|
2023-07-25 13:11:15 +00:00
|
|
|
return util.Min(t.visibleHeaderLines(), max)
|
2021-11-03 12:19:22 +00:00
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
func (t *Terminal) placeCursor() {
|
2021-11-03 12:19:22 +00:00
|
|
|
t.move(t.promptLine(), t.promptLen+t.queryLen[0], false)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) printPrompt() {
|
2021-11-03 12:19:22 +00:00
|
|
|
t.move(t.promptLine(), 0, true)
|
2020-07-05 07:16:46 +00:00
|
|
|
t.prompt()
|
2018-06-25 10:07:12 +00:00
|
|
|
|
|
|
|
before, after := t.updatePromptOffset()
|
2021-01-02 15:00:40 +00:00
|
|
|
color := tui.ColInput
|
|
|
|
if t.paused {
|
|
|
|
color = tui.ColDisabled
|
|
|
|
}
|
|
|
|
t.window.CPrint(color, string(before))
|
|
|
|
t.window.CPrint(color, string(after))
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2020-10-18 08:03:33 +00:00
|
|
|
func (t *Terminal) trimMessage(message string, maxWidth int) string {
|
|
|
|
if len(message) <= maxWidth {
|
|
|
|
return message
|
|
|
|
}
|
|
|
|
runes, _ := t.trimRight([]rune(message), maxWidth-2)
|
|
|
|
return string(runes) + strings.Repeat(".", util.Constrain(maxWidth, 0, 2))
|
|
|
|
}
|
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
func (t *Terminal) printInfo() {
|
2017-02-18 14:17:29 +00:00
|
|
|
pos := 0
|
2021-11-03 12:19:22 +00:00
|
|
|
line := t.promptLine()
|
2023-06-10 14:11:05 +00:00
|
|
|
printSpinner := func() {
|
2019-11-14 15:39:29 +00:00
|
|
|
if t.reading {
|
|
|
|
duration := int64(spinnerDuration)
|
2020-01-15 01:43:09 +00:00
|
|
|
idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(tui.ColSpinner, t.spinner[idx])
|
2022-11-25 14:50:12 +00:00
|
|
|
} else {
|
|
|
|
t.window.Print(" ") // Clear spinner
|
2019-11-14 15:39:29 +00:00
|
|
|
}
|
2023-06-10 14:11:05 +00:00
|
|
|
}
|
|
|
|
switch t.infoStyle {
|
|
|
|
case infoDefault:
|
|
|
|
t.move(line+1, 0, t.separatorLen == 0)
|
|
|
|
printSpinner()
|
2021-11-03 12:19:22 +00:00
|
|
|
t.move(line+1, 2, false)
|
2019-11-14 15:39:29 +00:00
|
|
|
pos = 2
|
2023-06-10 15:04:24 +00:00
|
|
|
case infoRight:
|
|
|
|
t.move(line+1, 0, false)
|
2023-06-10 14:11:05 +00:00
|
|
|
case infoInlineRight:
|
|
|
|
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
|
|
|
t.move(line, pos, true)
|
2019-11-14 15:39:29 +00:00
|
|
|
case infoInline:
|
2018-06-25 10:07:12 +00:00
|
|
|
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
2023-01-24 08:40:08 +00:00
|
|
|
str := t.infoSep
|
|
|
|
maxWidth := t.window.Width() - pos
|
2023-03-25 01:23:05 +00:00
|
|
|
width := util.StringWidth(str)
|
2023-01-24 08:40:08 +00:00
|
|
|
if width > maxWidth {
|
|
|
|
trimmed, _ := t.trimRight([]rune(str), maxWidth)
|
|
|
|
str = string(trimmed)
|
|
|
|
width = maxWidth
|
2017-02-18 14:17:29 +00:00
|
|
|
}
|
2022-11-25 14:50:12 +00:00
|
|
|
t.move(line, pos, t.separatorLen == 0)
|
2015-04-21 14:50:53 +00:00
|
|
|
if t.reading {
|
2023-01-24 08:40:08 +00:00
|
|
|
t.window.CPrint(tui.ColSpinner, str)
|
2015-04-21 14:50:53 +00:00
|
|
|
} else {
|
2023-01-24 08:40:08 +00:00
|
|
|
t.window.CPrint(tui.ColPrompt, str)
|
2015-04-21 14:50:53 +00:00
|
|
|
}
|
2023-01-24 08:40:08 +00:00
|
|
|
pos += width
|
2019-11-14 15:39:29 +00:00
|
|
|
case infoHidden:
|
|
|
|
return
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2019-11-10 02:36:22 +00:00
|
|
|
found := t.merger.Length()
|
|
|
|
total := util.Max(found, t.count)
|
|
|
|
output := fmt.Sprintf("%d/%d", found, total)
|
2015-05-20 12:25:15 +00:00
|
|
|
if t.toggleSort {
|
2015-04-16 05:44:41 +00:00
|
|
|
if t.sort {
|
2017-03-02 17:26:30 +00:00
|
|
|
output += " +S"
|
2015-04-16 05:44:41 +00:00
|
|
|
} else {
|
2017-03-02 17:26:30 +00:00
|
|
|
output += " -S"
|
2015-04-16 05:44:41 +00:00
|
|
|
}
|
|
|
|
}
|
2023-04-22 14:39:35 +00:00
|
|
|
if t.track != trackDisabled {
|
2023-04-22 06:48:51 +00:00
|
|
|
output += " +T"
|
|
|
|
}
|
2020-10-20 11:04:06 +00:00
|
|
|
if t.multi > 0 {
|
2019-11-02 03:55:26 +00:00
|
|
|
if t.multi == maxMulti {
|
|
|
|
output += fmt.Sprintf(" (%d)", len(t.selected))
|
|
|
|
} else {
|
|
|
|
output += fmt.Sprintf(" (%d/%d)", len(t.selected), t.multi)
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
if t.progress > 0 && t.progress < 100 {
|
|
|
|
output += fmt.Sprintf(" (%d%%)", t.progress)
|
|
|
|
}
|
2019-11-10 02:36:22 +00:00
|
|
|
if t.failed != nil && t.count == 0 {
|
|
|
|
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
|
2017-06-30 16:13:15 +00:00
|
|
|
}
|
2023-06-10 15:04:24 +00:00
|
|
|
|
|
|
|
printSeparator := func(fillLength int, pad bool) {
|
|
|
|
// --------_
|
|
|
|
if t.separatorLen > 0 {
|
|
|
|
t.separator(t.window, fillLength)
|
|
|
|
t.window.Print(" ")
|
|
|
|
} else if pad {
|
|
|
|
t.window.Print(strings.Repeat(" ", fillLength+1))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if t.infoStyle == infoRight {
|
|
|
|
maxWidth := t.window.Width()
|
|
|
|
if t.reading {
|
|
|
|
// Need space for spinner and a margin column
|
|
|
|
maxWidth -= 2
|
|
|
|
}
|
|
|
|
output = t.trimMessage(output, maxWidth)
|
|
|
|
fillLength := t.window.Width() - len(output) - 2
|
|
|
|
if t.reading {
|
|
|
|
if fillLength >= 2 {
|
|
|
|
printSeparator(fillLength-2, true)
|
|
|
|
}
|
|
|
|
printSpinner()
|
|
|
|
t.window.Print(" ")
|
|
|
|
} else if fillLength >= 0 {
|
|
|
|
printSeparator(fillLength, true)
|
|
|
|
}
|
|
|
|
t.window.CPrint(tui.ColInfo, output)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-06-10 14:11:05 +00:00
|
|
|
if t.infoStyle == infoInlineRight {
|
|
|
|
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
|
|
|
|
if pos >= t.window.Width() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.move(line, pos, false)
|
|
|
|
printSpinner()
|
|
|
|
t.window.Print(" ")
|
|
|
|
pos += 2
|
|
|
|
}
|
2023-06-10 15:04:24 +00:00
|
|
|
|
2022-10-30 07:28:58 +00:00
|
|
|
maxWidth := t.window.Width() - pos
|
|
|
|
output = t.trimMessage(output, maxWidth)
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(tui.ColInfo, output)
|
2022-11-10 07:23:33 +00:00
|
|
|
fillLength := maxWidth - len(output) - 2
|
2023-06-10 15:04:24 +00:00
|
|
|
if fillLength > 0 {
|
2022-11-10 07:23:33 +00:00
|
|
|
t.window.CPrint(tui.ColSeparator, " ")
|
2023-06-10 15:04:24 +00:00
|
|
|
printSeparator(fillLength, false)
|
2022-10-30 07:28:58 +00:00
|
|
|
}
|
2015-07-26 14:02:04 +00:00
|
|
|
}
|
|
|
|
|
2015-07-21 15:19:37 +00:00
|
|
|
func (t *Terminal) printHeader() {
|
2023-07-25 13:11:15 +00:00
|
|
|
if t.visibleHeaderLines() == 0 {
|
2015-07-21 15:19:37 +00:00
|
|
|
return
|
|
|
|
}
|
2017-01-07 16:30:31 +00:00
|
|
|
max := t.window.Height()
|
2021-11-03 12:19:22 +00:00
|
|
|
if t.headerFirst {
|
|
|
|
max--
|
|
|
|
if !t.noInfoLine() {
|
|
|
|
max--
|
|
|
|
}
|
|
|
|
}
|
2015-07-22 05:19:45 +00:00
|
|
|
var state *ansiState
|
2023-07-25 13:20:31 +00:00
|
|
|
needReverse := false
|
|
|
|
switch t.layout {
|
|
|
|
case layoutDefault, layoutReverseList:
|
|
|
|
needReverse = true
|
|
|
|
}
|
2023-04-22 13:01:00 +00:00
|
|
|
for idx, lineStr := range append(append([]string{}, t.header0...), t.header...) {
|
2021-11-03 12:19:22 +00:00
|
|
|
line := idx
|
2023-07-25 13:20:31 +00:00
|
|
|
if needReverse && idx < len(t.header0) {
|
|
|
|
line = len(t.header0) - idx - 1
|
|
|
|
}
|
2021-11-03 12:19:22 +00:00
|
|
|
if !t.headerFirst {
|
|
|
|
line++
|
|
|
|
if !t.noInfoLine() {
|
|
|
|
line++
|
|
|
|
}
|
2015-07-22 04:45:38 +00:00
|
|
|
}
|
|
|
|
if line >= max {
|
2015-07-22 12:22:59 +00:00
|
|
|
continue
|
2015-07-22 04:45:38 +00:00
|
|
|
}
|
2016-06-14 12:52:47 +00:00
|
|
|
trimmed, colors, newState := extractColor(lineStr, state, nil)
|
2015-07-22 05:19:45 +00:00
|
|
|
state = newState
|
2015-07-21 15:19:37 +00:00
|
|
|
item := &Item{
|
2017-07-21 08:29:14 +00:00
|
|
|
text: util.ToChars([]byte(trimmed)),
|
2016-08-18 17:39:32 +00:00
|
|
|
colors: colors}
|
2015-07-21 15:19:37 +00:00
|
|
|
|
2023-07-25 13:11:15 +00:00
|
|
|
t.move(line, 0, true)
|
|
|
|
t.window.Print(" ")
|
2017-07-17 18:10:49 +00:00
|
|
|
t.printHighlighted(Result{item: item},
|
2020-10-25 10:29:37 +00:00
|
|
|
tui.ColHeader, tui.ColHeader, false, false)
|
2015-07-21 15:19:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
func (t *Terminal) printList() {
|
|
|
|
t.constrain()
|
2023-01-01 05:48:14 +00:00
|
|
|
barLength, barStart := t.getScrollbar()
|
2015-01-01 19:49:30 +00:00
|
|
|
|
2015-04-21 14:50:53 +00:00
|
|
|
maxy := t.maxItems()
|
2015-01-09 16:06:08 +00:00
|
|
|
count := t.merger.Length() - t.offset
|
2017-01-07 16:30:31 +00:00
|
|
|
for j := 0; j < maxy; j++ {
|
|
|
|
i := j
|
2018-06-09 16:41:50 +00:00
|
|
|
if t.layout == layoutDefault {
|
2017-01-07 16:30:31 +00:00
|
|
|
i = maxy - 1 - j
|
|
|
|
}
|
2023-07-25 13:11:15 +00:00
|
|
|
line := i + 2 + t.visibleHeaderLines()
|
2019-11-14 15:39:29 +00:00
|
|
|
if t.noInfoLine() {
|
2015-08-02 04:06:15 +00:00
|
|
|
line--
|
2015-04-21 14:50:53 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
if i < count {
|
2023-01-01 05:48:14 +00:00
|
|
|
t.printItem(t.merger.Get(i+t.offset), line, i, i == t.cy-t.offset, i >= barStart && i < barStart+barLength)
|
2023-07-25 13:11:15 +00:00
|
|
|
} else if t.prevLines[i] != emptyLine || t.prevLines[i].offset != line {
|
2017-01-07 16:30:31 +00:00
|
|
|
t.prevLines[i] = emptyLine
|
|
|
|
t.move(line, 0, true)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-01 05:48:14 +00:00
|
|
|
func (t *Terminal) printItem(result Result, line int, i int, current bool, bar bool) {
|
2016-08-18 17:39:32 +00:00
|
|
|
item := result.item
|
2016-01-13 12:36:44 +00:00
|
|
|
_, selected := t.selected[item.Index()]
|
2020-10-25 10:29:37 +00:00
|
|
|
label := ""
|
2016-05-17 17:06:52 +00:00
|
|
|
if t.jumping != jumpDisabled {
|
|
|
|
if i < len(t.jumpLabels) {
|
|
|
|
// Striped
|
|
|
|
current = i%2 == 0
|
2020-02-17 01:19:03 +00:00
|
|
|
label = t.jumpLabels[i:i+1] + strings.Repeat(" ", t.pointerLen-1)
|
2016-05-17 17:06:52 +00:00
|
|
|
}
|
|
|
|
} else if current {
|
2020-02-17 01:19:03 +00:00
|
|
|
label = t.pointer
|
2016-05-17 17:06:52 +00:00
|
|
|
}
|
2017-01-07 16:30:31 +00:00
|
|
|
|
|
|
|
// Avoid unnecessary redraw
|
2023-07-25 13:11:15 +00:00
|
|
|
newLine := itemLine{offset: line, current: current, selected: selected, label: label,
|
2023-01-01 05:48:14 +00:00
|
|
|
result: result, queryLen: len(t.input), width: 0, bar: bar}
|
2017-01-15 17:26:36 +00:00
|
|
|
prevLine := t.prevLines[i]
|
2023-07-25 13:11:15 +00:00
|
|
|
forceRedraw := prevLine.offset != newLine.offset
|
2023-01-01 12:16:09 +00:00
|
|
|
printBar := func() {
|
2023-07-25 13:11:15 +00:00
|
|
|
if len(t.scrollbar) > 0 && (bar != prevLine.bar || forceRedraw) {
|
2023-01-01 16:10:01 +00:00
|
|
|
t.prevLines[i].bar = bar
|
2023-01-01 12:16:09 +00:00
|
|
|
t.move(line, t.window.Width()-1, true)
|
|
|
|
if bar {
|
|
|
|
t.window.CPrint(tui.ColScrollbar, t.scrollbar)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-25 13:11:15 +00:00
|
|
|
if !forceRedraw &&
|
|
|
|
prevLine.current == newLine.current &&
|
2017-01-15 17:26:36 +00:00
|
|
|
prevLine.selected == newLine.selected &&
|
|
|
|
prevLine.label == newLine.label &&
|
2017-02-23 17:30:11 +00:00
|
|
|
prevLine.queryLen == newLine.queryLen &&
|
2017-01-15 17:26:36 +00:00
|
|
|
prevLine.result == newLine.result {
|
2023-01-01 12:16:09 +00:00
|
|
|
printBar()
|
2017-01-07 16:30:31 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-07-25 13:11:15 +00:00
|
|
|
t.move(line, 0, forceRedraw)
|
2015-01-01 19:49:30 +00:00
|
|
|
if current {
|
2020-10-25 10:29:37 +00:00
|
|
|
if len(label) == 0 {
|
|
|
|
t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty)
|
|
|
|
} else {
|
|
|
|
t.window.CPrint(tui.ColCurrentCursor, label)
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
if selected {
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(tui.ColCurrentSelected, t.marker)
|
2015-01-01 19:49:30 +00:00
|
|
|
} else {
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(tui.ColCurrentSelectedEmpty, t.markerEmpty)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2020-10-25 10:29:37 +00:00
|
|
|
newLine.width = t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true)
|
2015-01-01 19:49:30 +00:00
|
|
|
} else {
|
2020-10-25 10:29:37 +00:00
|
|
|
if len(label) == 0 {
|
|
|
|
t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty)
|
|
|
|
} else {
|
|
|
|
t.window.CPrint(tui.ColCursor, label)
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
if selected {
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(tui.ColSelected, t.marker)
|
2015-01-01 19:49:30 +00:00
|
|
|
} else {
|
2020-02-17 01:19:03 +00:00
|
|
|
t.window.Print(t.markerEmpty)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2020-10-25 10:29:37 +00:00
|
|
|
newLine.width = t.printHighlighted(result, tui.ColNormal, tui.ColMatch, false, true)
|
2017-01-15 17:26:36 +00:00
|
|
|
}
|
2017-09-09 04:50:07 +00:00
|
|
|
fillSpaces := prevLine.width - newLine.width
|
|
|
|
if fillSpaces > 0 {
|
|
|
|
t.window.Print(strings.Repeat(" ", fillSpaces))
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2023-01-01 12:16:09 +00:00
|
|
|
printBar()
|
2017-01-15 17:26:36 +00:00
|
|
|
t.prevLines[i] = newLine
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2021-05-14 02:43:32 +00:00
|
|
|
func (t *Terminal) trimRight(runes []rune, width int) ([]rune, bool) {
|
2015-01-18 07:59:04 +00:00
|
|
|
// We start from the beginning to handle tab characters
|
2021-12-22 14:22:08 +00:00
|
|
|
_, overflowIdx := util.RunesWidth(runes, 0, t.tabstop, width)
|
2021-05-14 02:43:32 +00:00
|
|
|
if overflowIdx >= 0 {
|
|
|
|
return runes[:overflowIdx], true
|
2015-01-18 07:59:04 +00:00
|
|
|
}
|
2021-05-14 02:43:32 +00:00
|
|
|
return runes, false
|
2015-01-18 07:59:04 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
func (t *Terminal) displayWidthWithLimit(runes []rune, prefixWidth int, limit int) int {
|
2021-05-14 02:43:32 +00:00
|
|
|
width, _ := util.RunesWidth(runes, prefixWidth, t.tabstop, limit)
|
|
|
|
return width
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
func (t *Terminal) trimLeft(runes []rune, width int) ([]rune, int32) {
|
2020-12-05 12:59:42 +00:00
|
|
|
width = util.Max(0, width)
|
2020-11-24 10:03:59 +00:00
|
|
|
var trimmed int32
|
|
|
|
// Assume that each rune takes at least one column on screen
|
2021-03-12 01:06:44 +00:00
|
|
|
if len(runes) > width+2 {
|
|
|
|
diff := len(runes) - width - 2
|
2020-11-24 10:03:59 +00:00
|
|
|
trimmed = int32(diff)
|
|
|
|
runes = runes[diff:]
|
2016-09-20 16:23:41 +00:00
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
currentWidth := t.displayWidth(runes)
|
2015-01-01 19:49:30 +00:00
|
|
|
|
|
|
|
for currentWidth > width && len(runes) > 0 {
|
|
|
|
runes = runes[1:]
|
2015-01-11 18:01:24 +00:00
|
|
|
trimmed++
|
2017-01-07 16:30:31 +00:00
|
|
|
currentWidth = t.displayWidthWithLimit(runes, 2, width)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
return runes, trimmed
|
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
func (t *Terminal) overflow(runes []rune, max int) bool {
|
2017-01-15 17:26:36 +00:00
|
|
|
return t.displayWidthWithLimit(runes, 0, max) > max
|
2016-08-14 08:44:11 +00:00
|
|
|
}
|
|
|
|
|
2020-10-25 10:29:37 +00:00
|
|
|
func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool) int {
|
2016-08-18 17:39:32 +00:00
|
|
|
item := result.item
|
2015-01-01 19:49:30 +00:00
|
|
|
|
|
|
|
// Overflow
|
2016-08-13 15:39:44 +00:00
|
|
|
text := make([]rune, item.text.Length())
|
|
|
|
copy(text, item.text.ToRunes())
|
2016-08-19 16:46:54 +00:00
|
|
|
matchOffsets := []Offset{}
|
2016-09-07 00:58:18 +00:00
|
|
|
var pos *[]int
|
2017-01-11 13:47:26 +00:00
|
|
|
if match && t.merger.pattern != nil {
|
2016-09-07 00:58:18 +00:00
|
|
|
_, matchOffsets, pos = t.merger.pattern.MatchItem(item, true, t.slab)
|
|
|
|
}
|
|
|
|
charOffsets := matchOffsets
|
|
|
|
if pos != nil {
|
|
|
|
charOffsets = make([]Offset, len(*pos))
|
|
|
|
for idx, p := range *pos {
|
|
|
|
offset := Offset{int32(p), int32(p + 1)}
|
|
|
|
charOffsets[idx] = offset
|
|
|
|
}
|
|
|
|
sort.Sort(ByOrder(charOffsets))
|
2016-08-19 16:46:54 +00:00
|
|
|
}
|
|
|
|
var maxe int
|
2016-09-07 00:58:18 +00:00
|
|
|
for _, offset := range charOffsets {
|
2016-08-19 16:46:54 +00:00
|
|
|
maxe = util.Max(maxe, int(offset[1]))
|
|
|
|
}
|
2016-09-07 00:58:18 +00:00
|
|
|
|
2020-10-25 10:29:37 +00:00
|
|
|
offsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current)
|
2020-09-24 02:06:20 +00:00
|
|
|
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
|
2022-03-29 12:35:36 +00:00
|
|
|
ellipsis, ellipsisWidth := util.Truncate(t.ellipsis, maxWidth/2)
|
|
|
|
maxe = util.Constrain(maxe+util.Min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(text))
|
2017-01-15 17:26:36 +00:00
|
|
|
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
|
|
|
|
if displayWidth > maxWidth {
|
2022-03-29 12:35:36 +00:00
|
|
|
transformOffsets := func(diff int32, rightTrim bool) {
|
2020-03-23 10:05:06 +00:00
|
|
|
for idx, offset := range offsets {
|
|
|
|
b, e := offset.offset[0], offset.offset[1]
|
2022-03-29 12:35:36 +00:00
|
|
|
el := int32(len(ellipsis))
|
|
|
|
b += el - diff
|
|
|
|
e += el - diff
|
|
|
|
b = util.Max32(b, el)
|
|
|
|
if rightTrim {
|
|
|
|
e = util.Min32(e, int32(maxWidth-ellipsisWidth))
|
|
|
|
}
|
2020-03-23 10:05:06 +00:00
|
|
|
offsets[idx].offset[0] = b
|
|
|
|
offsets[idx].offset[1] = util.Max32(b, e)
|
|
|
|
}
|
|
|
|
}
|
2015-04-16 03:56:01 +00:00
|
|
|
if t.hscroll {
|
2020-03-11 13:35:24 +00:00
|
|
|
if t.keepRight && pos == nil {
|
2022-03-29 12:35:36 +00:00
|
|
|
trimmed, diff := t.trimLeft(text, maxWidth-ellipsisWidth)
|
|
|
|
transformOffsets(diff, false)
|
|
|
|
text = append(ellipsis, trimmed...)
|
|
|
|
} else if !t.overflow(text[:maxe], maxWidth-ellipsisWidth) {
|
2020-03-11 13:35:24 +00:00
|
|
|
// Stri..
|
2022-03-29 12:35:36 +00:00
|
|
|
text, _ = t.trimRight(text, maxWidth-ellipsisWidth)
|
|
|
|
text = append(text, ellipsis...)
|
2015-04-16 03:56:01 +00:00
|
|
|
} else {
|
|
|
|
// Stri..
|
2022-03-29 12:35:36 +00:00
|
|
|
rightTrim := false
|
|
|
|
if t.overflow(text[maxe:], ellipsisWidth) {
|
|
|
|
text = append(text[:maxe], ellipsis...)
|
|
|
|
rightTrim = true
|
2015-04-16 03:56:01 +00:00
|
|
|
}
|
|
|
|
// ..ri..
|
|
|
|
var diff int32
|
2022-03-29 12:35:36 +00:00
|
|
|
text, diff = t.trimLeft(text, maxWidth-ellipsisWidth)
|
2015-04-16 03:56:01 +00:00
|
|
|
|
|
|
|
// Transform offsets
|
2022-03-29 12:35:36 +00:00
|
|
|
transformOffsets(diff, rightTrim)
|
|
|
|
text = append(ellipsis, text...)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-04-16 03:56:01 +00:00
|
|
|
} else {
|
2022-03-29 12:35:36 +00:00
|
|
|
text, _ = t.trimRight(text, maxWidth-ellipsisWidth)
|
|
|
|
text = append(text, ellipsis...)
|
2015-01-01 19:49:30 +00:00
|
|
|
|
2015-03-18 16:59:14 +00:00
|
|
|
for idx, offset := range offsets {
|
2022-03-29 12:35:36 +00:00
|
|
|
offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-len(ellipsis)))
|
2015-04-16 03:56:01 +00:00
|
|
|
offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
}
|
2017-01-24 17:39:49 +00:00
|
|
|
displayWidth = t.displayWidthWithLimit(text, 0, displayWidth)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2022-10-29 15:12:01 +00:00
|
|
|
t.printColoredString(t.window, text, offsets, colBase)
|
|
|
|
return displayWidth
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []colorOffset, colBase tui.ColorPair) {
|
2015-01-11 18:01:24 +00:00
|
|
|
var index int32
|
2015-01-18 07:59:04 +00:00
|
|
|
var substr string
|
|
|
|
var prefixWidth int
|
2015-03-18 16:59:14 +00:00
|
|
|
maxOffset := int32(len(text))
|
2015-01-01 19:49:30 +00:00
|
|
|
for _, offset := range offsets {
|
2015-03-18 16:59:14 +00:00
|
|
|
b := util.Constrain32(offset.offset[0], index, maxOffset)
|
|
|
|
e := util.Constrain32(offset.offset[1], index, maxOffset)
|
2015-01-18 07:59:04 +00:00
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
|
2022-10-29 15:12:01 +00:00
|
|
|
window.CPrint(colBase, substr)
|
2015-01-18 07:59:04 +00:00
|
|
|
|
2015-03-19 03:14:26 +00:00
|
|
|
if b < e {
|
2017-01-07 16:30:31 +00:00
|
|
|
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
|
2022-10-29 15:12:01 +00:00
|
|
|
window.CPrint(offset.color, substr)
|
2015-03-19 03:14:26 +00:00
|
|
|
}
|
2015-01-18 07:59:04 +00:00
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
index = e
|
2015-03-18 16:59:14 +00:00
|
|
|
if index >= maxOffset {
|
|
|
|
break
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-03-18 16:59:14 +00:00
|
|
|
if index < maxOffset {
|
2017-01-07 16:30:31 +00:00
|
|
|
substr, _ = t.processTabs(text[index:], prefixWidth)
|
2022-10-29 15:12:01 +00:00
|
|
|
window.CPrint(colBase, substr)
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-18 08:03:33 +00:00
|
|
|
func (t *Terminal) renderPreviewSpinner() {
|
|
|
|
numLines := len(t.previewer.lines)
|
|
|
|
spin := t.previewer.spinner
|
|
|
|
if len(spin) > 0 || t.previewer.scrollable {
|
|
|
|
maxWidth := t.pwindow.Width()
|
|
|
|
if !t.previewer.scrollable {
|
|
|
|
if maxWidth > 0 {
|
|
|
|
t.pwindow.Move(0, maxWidth-1)
|
2023-06-09 08:13:55 +00:00
|
|
|
t.pwindow.CPrint(tui.ColPreviewSpinner, spin)
|
2020-10-18 08:03:33 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
offsetString := fmt.Sprintf("%d/%d", t.previewer.offset+1, numLines)
|
|
|
|
if len(spin) > 0 {
|
|
|
|
spin += " "
|
|
|
|
maxWidth -= 2
|
|
|
|
}
|
|
|
|
offsetRunes, _ := t.trimRight([]rune(offsetString), maxWidth)
|
|
|
|
pos := maxWidth - t.displayWidth(offsetRunes)
|
|
|
|
t.pwindow.Move(0, pos)
|
|
|
|
if maxWidth > 0 {
|
2023-06-09 08:13:55 +00:00
|
|
|
t.pwindow.CPrint(tui.ColPreviewSpinner, spin)
|
2020-10-25 10:29:37 +00:00
|
|
|
t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), string(offsetRunes))
|
2020-10-18 08:03:33 +00:00
|
|
|
}
|
|
|
|
}
|
2016-09-28 17:53:05 +00:00
|
|
|
}
|
2020-10-18 08:03:33 +00:00
|
|
|
}
|
2017-01-11 13:13:40 +00:00
|
|
|
|
2021-03-12 10:51:28 +00:00
|
|
|
func (t *Terminal) renderPreviewArea(unchanged bool) {
|
2020-10-18 08:03:33 +00:00
|
|
|
if unchanged {
|
2021-03-12 10:51:28 +00:00
|
|
|
t.pwindow.MoveAndClear(0, 0) // Clear scroll offset display
|
2020-10-18 08:03:33 +00:00
|
|
|
} else {
|
|
|
|
t.previewed.filled = false
|
|
|
|
t.pwindow.Erase()
|
|
|
|
}
|
2021-03-12 10:51:28 +00:00
|
|
|
|
|
|
|
height := t.pwindow.Height()
|
|
|
|
header := []string{}
|
|
|
|
body := t.previewer.lines
|
|
|
|
headerLines := t.previewOpts.headerLines
|
|
|
|
// Do not enable preview header lines if it's value is too large
|
|
|
|
if headerLines > 0 && headerLines < util.Min(len(body), height) {
|
|
|
|
header = t.previewer.lines[0:headerLines]
|
|
|
|
body = t.previewer.lines[headerLines:]
|
|
|
|
// Always redraw header
|
|
|
|
t.renderPreviewText(height, header, 0, false)
|
|
|
|
t.pwindow.MoveAndClear(t.pwindow.Y(), 0)
|
|
|
|
}
|
|
|
|
t.renderPreviewText(height, body, -t.previewer.offset+headerLines, unchanged)
|
|
|
|
|
|
|
|
if !unchanged {
|
|
|
|
t.pwindow.FinishFill()
|
|
|
|
}
|
2023-01-06 06:36:12 +00:00
|
|
|
|
|
|
|
if len(t.scrollbar) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
effectiveHeight := height - headerLines
|
|
|
|
barLength, barStart := getScrollbar(len(body), effectiveHeight, util.Min(len(body)-effectiveHeight, t.previewer.offset-headerLines))
|
|
|
|
t.renderPreviewScrollbar(headerLines, barLength, barStart)
|
2021-03-12 10:51:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unchanged bool) {
|
|
|
|
maxWidth := t.pwindow.Width()
|
2017-05-27 17:26:42 +00:00
|
|
|
var ansi *ansiState
|
2023-01-04 16:55:59 +00:00
|
|
|
spinnerRedraw := t.pwindow.Y() == 0
|
2021-03-12 10:51:28 +00:00
|
|
|
for _, line := range lines {
|
2020-11-24 16:40:30 +00:00
|
|
|
var lbg tui.Color = -1
|
|
|
|
if ansi != nil {
|
|
|
|
ansi.lbg = -1
|
|
|
|
}
|
2023-04-30 09:13:31 +00:00
|
|
|
line = strings.TrimRight(line, "\r\n")
|
2019-03-29 09:28:45 +00:00
|
|
|
if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
|
2020-10-18 08:03:33 +00:00
|
|
|
t.previewed.filled = true
|
2022-04-28 02:24:59 +00:00
|
|
|
t.previewer.scrollable = true
|
2017-01-11 13:13:40 +00:00
|
|
|
break
|
2019-03-29 09:28:45 +00:00
|
|
|
} else if lineNo >= 0 {
|
2023-01-04 16:55:59 +00:00
|
|
|
if spinnerRedraw && lineNo > 0 {
|
|
|
|
spinnerRedraw = false
|
|
|
|
y := t.pwindow.Y()
|
|
|
|
x := t.pwindow.X()
|
|
|
|
t.renderPreviewSpinner()
|
|
|
|
t.pwindow.Move(y, x)
|
|
|
|
}
|
2017-01-11 13:13:40 +00:00
|
|
|
var fillRet tui.FillReturn
|
2018-12-22 02:52:18 +00:00
|
|
|
prefixWidth := 0
|
2017-05-27 17:26:42 +00:00
|
|
|
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
|
2017-01-11 13:13:40 +00:00
|
|
|
trimmed := []rune(str)
|
2021-05-14 02:43:32 +00:00
|
|
|
isTrimmed := false
|
2020-12-05 12:16:35 +00:00
|
|
|
if !t.previewOpts.wrap {
|
2021-05-14 02:43:32 +00:00
|
|
|
trimmed, isTrimmed = t.trimRight(trimmed, maxWidth-t.pwindow.X())
|
2017-01-11 13:13:40 +00:00
|
|
|
}
|
2018-12-22 02:52:18 +00:00
|
|
|
str, width := t.processTabs(trimmed, prefixWidth)
|
2023-03-17 04:22:20 +00:00
|
|
|
if width > prefixWidth {
|
|
|
|
prefixWidth = width
|
|
|
|
if t.theme.Colored && ansi != nil && ansi.colored() {
|
|
|
|
lbg = ansi.lbg
|
|
|
|
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
|
|
|
|
} else {
|
|
|
|
fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
|
|
|
|
}
|
2017-01-11 13:13:40 +00:00
|
|
|
}
|
2021-05-14 02:43:32 +00:00
|
|
|
return !isTrimmed &&
|
2021-03-12 10:56:24 +00:00
|
|
|
(fillRet == tui.FillContinue || t.previewOpts.wrap && fillRet == tui.FillNextLine)
|
2017-01-11 13:13:40 +00:00
|
|
|
})
|
2020-10-18 08:03:33 +00:00
|
|
|
t.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width()
|
2019-03-29 08:29:24 +00:00
|
|
|
if fillRet == tui.FillNextLine {
|
2017-01-11 13:13:40 +00:00
|
|
|
continue
|
2019-03-29 08:29:24 +00:00
|
|
|
} else if fillRet == tui.FillSuspend {
|
2020-10-18 08:03:33 +00:00
|
|
|
t.previewed.filled = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if unchanged && lineNo == 0 {
|
2017-01-11 13:13:40 +00:00
|
|
|
break
|
2017-01-07 16:30:31 +00:00
|
|
|
}
|
2020-11-24 16:40:30 +00:00
|
|
|
if lbg >= 0 {
|
|
|
|
t.pwindow.CFill(-1, lbg, tui.AttrRegular,
|
|
|
|
strings.Repeat(" ", t.pwindow.Width()-t.pwindow.X())+"\n")
|
|
|
|
} else {
|
|
|
|
t.pwindow.Fill("\n")
|
|
|
|
}
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
2020-10-18 08:03:33 +00:00
|
|
|
lineNo++
|
2017-01-11 13:13:40 +00:00
|
|
|
}
|
2015-01-18 07:59:04 +00:00
|
|
|
}
|
|
|
|
|
2023-01-06 06:36:12 +00:00
|
|
|
func (t *Terminal) renderPreviewScrollbar(yoff int, barLength int, barStart int) {
|
|
|
|
height := t.pwindow.Height()
|
|
|
|
w := t.pborder.Width()
|
2023-05-14 09:28:46 +00:00
|
|
|
redraw := false
|
2023-01-06 06:36:12 +00:00
|
|
|
if len(t.previewer.bar) != height {
|
2023-05-14 09:28:46 +00:00
|
|
|
redraw = true
|
2023-01-06 06:36:12 +00:00
|
|
|
t.previewer.bar = make([]bool, height)
|
|
|
|
}
|
2023-01-15 16:22:02 +00:00
|
|
|
xshift := -1 - t.borderWidth
|
2023-01-06 06:36:12 +00:00
|
|
|
if !t.previewOpts.border.HasRight() {
|
|
|
|
xshift = -1
|
|
|
|
}
|
|
|
|
yshift := 1
|
|
|
|
if !t.previewOpts.border.HasTop() {
|
|
|
|
yshift = 0
|
|
|
|
}
|
|
|
|
for i := yoff; i < height; i++ {
|
|
|
|
x := w + xshift
|
|
|
|
y := i + yshift
|
|
|
|
|
|
|
|
// Avoid unnecessary redraws
|
|
|
|
bar := i >= yoff+barStart && i < yoff+barStart+barLength
|
2023-05-14 09:28:46 +00:00
|
|
|
if !redraw && bar == t.previewer.bar[i] && !t.tui.NeedScrollbarRedraw() {
|
2023-01-06 06:36:12 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
t.previewer.bar[i] = bar
|
|
|
|
t.pborder.Move(y, x)
|
|
|
|
if i >= yoff+barStart && i < yoff+barStart+barLength {
|
2023-05-16 14:53:10 +00:00
|
|
|
t.pborder.CPrint(tui.ColPreviewScrollbar, t.previewScrollbar)
|
2023-01-06 06:36:12 +00:00
|
|
|
} else {
|
2023-05-14 09:28:46 +00:00
|
|
|
t.pborder.CPrint(tui.ColPreviewScrollbar, " ")
|
2023-01-06 06:36:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-18 08:03:33 +00:00
|
|
|
func (t *Terminal) printPreview() {
|
2023-05-16 15:06:35 +00:00
|
|
|
if !t.hasPreviewWindow() || t.pwindow.Height() == 0 {
|
2020-10-18 08:03:33 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
numLines := len(t.previewer.lines)
|
|
|
|
height := t.pwindow.Height()
|
|
|
|
unchanged := (t.previewed.filled || numLines == t.previewed.numLines) &&
|
|
|
|
t.previewer.version == t.previewed.version &&
|
|
|
|
t.previewer.offset == t.previewed.offset
|
|
|
|
t.previewer.scrollable = t.previewer.offset > 0 || numLines > height
|
2021-03-12 10:51:28 +00:00
|
|
|
t.renderPreviewArea(unchanged)
|
2020-10-18 08:03:33 +00:00
|
|
|
t.renderPreviewSpinner()
|
|
|
|
t.previewed.numLines = numLines
|
|
|
|
t.previewed.version = t.previewer.version
|
|
|
|
t.previewed.offset = t.previewer.offset
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) printPreviewDelayed() {
|
|
|
|
if !t.hasPreviewWindow() || len(t.previewer.lines) > 0 && t.previewed.version == t.previewer.version {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
t.previewer.scrollable = false
|
2021-03-12 10:51:28 +00:00
|
|
|
t.renderPreviewArea(true)
|
2020-10-18 08:03:33 +00:00
|
|
|
|
|
|
|
message := t.trimMessage("Loading ..", t.pwindow.Width())
|
|
|
|
pos := t.pwindow.Width() - len(message)
|
|
|
|
t.pwindow.Move(0, pos)
|
2020-10-25 10:29:37 +00:00
|
|
|
t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), message)
|
2020-10-18 08:03:33 +00:00
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
func (t *Terminal) processTabs(runes []rune, prefixWidth int) (string, int) {
|
2021-05-14 02:43:32 +00:00
|
|
|
var strbuf strings.Builder
|
2015-01-18 07:59:04 +00:00
|
|
|
l := prefixWidth
|
2021-05-14 02:43:32 +00:00
|
|
|
gr := uniseg.NewGraphemes(string(runes))
|
|
|
|
for gr.Next() {
|
|
|
|
rs := gr.Runes()
|
|
|
|
str := string(rs)
|
|
|
|
var w int
|
|
|
|
if len(rs) == 1 && rs[0] == '\t' {
|
|
|
|
w = t.tabstop - l%t.tabstop
|
2015-01-18 07:59:04 +00:00
|
|
|
strbuf.WriteString(strings.Repeat(" ", w))
|
|
|
|
} else {
|
2023-03-25 01:23:05 +00:00
|
|
|
w = util.StringWidth(str)
|
2021-05-14 02:43:32 +00:00
|
|
|
strbuf.WriteString(str)
|
2015-01-18 07:59:04 +00:00
|
|
|
}
|
2021-05-14 02:43:32 +00:00
|
|
|
l += w
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-01-18 07:59:04 +00:00
|
|
|
return strbuf.String(), l
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) printAll() {
|
2023-01-07 02:21:52 +00:00
|
|
|
t.resizeWindows(false)
|
2015-01-01 19:49:30 +00:00
|
|
|
t.printList()
|
|
|
|
t.printPrompt()
|
2015-04-25 14:20:40 +00:00
|
|
|
t.printInfo()
|
2015-07-21 15:47:14 +00:00
|
|
|
t.printHeader()
|
2016-09-28 17:53:05 +00:00
|
|
|
t.printPreview()
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) refresh() {
|
2019-03-29 17:06:54 +00:00
|
|
|
t.placeCursor()
|
2015-01-07 03:46:45 +00:00
|
|
|
if !t.suppress {
|
2017-02-04 12:51:22 +00:00
|
|
|
windows := make([]tui.Window, 0, 4)
|
2020-03-05 11:15:15 +00:00
|
|
|
if t.borderShape != tui.BorderNone {
|
2017-02-04 12:51:22 +00:00
|
|
|
windows = append(windows, t.border)
|
|
|
|
}
|
2017-01-23 15:23:16 +00:00
|
|
|
if t.hasPreviewWindow() {
|
2020-08-23 08:05:45 +00:00
|
|
|
if t.pborder != nil {
|
|
|
|
windows = append(windows, t.pborder)
|
|
|
|
}
|
|
|
|
windows = append(windows, t.pwindow)
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
2017-02-04 12:51:22 +00:00
|
|
|
windows = append(windows, t.window)
|
|
|
|
t.tui.RefreshWindows(windows)
|
2015-01-07 03:46:45 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) delChar() bool {
|
|
|
|
if len(t.input) > 0 && t.cx < len(t.input) {
|
|
|
|
t.input = append(t.input[:t.cx], t.input[t.cx+1:]...)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func findLastMatch(pattern string, str string) int {
|
|
|
|
rx, err := regexp.Compile(pattern)
|
|
|
|
if err != nil {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
locs := rx.FindAllStringIndex(str, -1)
|
|
|
|
if locs == nil {
|
|
|
|
return -1
|
|
|
|
}
|
2020-07-28 03:58:37 +00:00
|
|
|
prefix := []rune(str[:locs[len(locs)-1][0]])
|
|
|
|
return len(prefix)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func findFirstMatch(pattern string, str string) int {
|
|
|
|
rx, err := regexp.Compile(pattern)
|
|
|
|
if err != nil {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
loc := rx.FindStringIndex(str)
|
|
|
|
if loc == nil {
|
|
|
|
return -1
|
|
|
|
}
|
2020-07-28 03:58:37 +00:00
|
|
|
prefix := []rune(str[:loc[0]])
|
|
|
|
return len(prefix)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func copySlice(slice []rune) []rune {
|
|
|
|
ret := make([]rune, len(slice))
|
|
|
|
copy(ret, slice)
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) rubout(pattern string) {
|
|
|
|
pcx := t.cx
|
|
|
|
after := t.input[t.cx:]
|
|
|
|
t.cx = findLastMatch(pattern, string(t.input[:t.cx])) + 1
|
|
|
|
t.yanked = copySlice(t.input[t.cx:pcx])
|
|
|
|
t.input = append(t.input[:t.cx], after...)
|
|
|
|
}
|
|
|
|
|
2020-12-29 16:59:18 +00:00
|
|
|
func keyMatch(key tui.Event, event tui.Event) bool {
|
|
|
|
return event.Type == key.Type && event.Char == key.Char ||
|
|
|
|
key.Type == tui.DoubleClick && event.Type == tui.Mouse && event.MouseEvent.Double
|
2015-03-31 13:05:02 +00:00
|
|
|
}
|
|
|
|
|
2018-03-30 02:47:46 +00:00
|
|
|
func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
|
|
|
flags := placeholderFlags{}
|
|
|
|
|
|
|
|
if match[0] == '\\' {
|
|
|
|
// Escaped placeholder pattern
|
|
|
|
return true, match[1:], flags
|
|
|
|
}
|
|
|
|
|
|
|
|
skipChars := 1
|
|
|
|
for _, char := range match[1:] {
|
|
|
|
switch char {
|
|
|
|
case '+':
|
|
|
|
flags.plus = true
|
|
|
|
skipChars++
|
|
|
|
case 's':
|
|
|
|
flags.preserveSpace = true
|
|
|
|
skipChars++
|
2019-02-18 16:12:57 +00:00
|
|
|
case 'n':
|
|
|
|
flags.number = true
|
|
|
|
skipChars++
|
2019-10-27 14:50:12 +00:00
|
|
|
case 'f':
|
|
|
|
flags.file = true
|
|
|
|
skipChars++
|
2018-06-09 16:40:22 +00:00
|
|
|
case 'q':
|
|
|
|
flags.query = true
|
Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands
This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.
* [tests] Add testing of placeholder parsing and matching
Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.
* [tests] Add more test cases of replacing placeholders focused on flags
Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.
There is at least one test for each flag, not all combinations are
tested though.
* [refactoring] Split OS-specific function quoteEntry() to corresponding source file
This is minor refactoring, and also the function's test was made
crossplatform.
* [refactoring] Simplify replacePlaceholder function
Should be equivalent to the original, but has simpler structure.
2021-10-15 13:31:59 +00:00
|
|
|
// query flag is not skipped
|
2018-03-30 02:47:46 +00:00
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
matchWithoutFlags := "{" + match[skipChars:]
|
|
|
|
|
|
|
|
return false, matchWithoutFlags, flags
|
|
|
|
}
|
|
|
|
|
2019-12-06 13:34:30 +00:00
|
|
|
func hasPreviewFlags(template string) (slot bool, plus bool, query bool) {
|
2017-01-27 07:38:42 +00:00
|
|
|
for _, match := range placeholder.FindAllString(template, -1) {
|
2018-03-30 02:47:46 +00:00
|
|
|
_, _, flags := parsePlaceholder(match)
|
|
|
|
if flags.plus {
|
2018-06-09 16:40:22 +00:00
|
|
|
plus = true
|
|
|
|
}
|
|
|
|
if flags.query {
|
|
|
|
query = true
|
2017-01-27 07:38:42 +00:00
|
|
|
}
|
2019-12-06 13:34:30 +00:00
|
|
|
slot = true
|
2017-01-27 07:38:42 +00:00
|
|
|
}
|
2018-06-09 16:40:22 +00:00
|
|
|
return
|
2017-01-27 07:38:42 +00:00
|
|
|
}
|
|
|
|
|
2019-10-27 14:50:12 +00:00
|
|
|
func writeTemporaryFile(data []string, printSep string) string {
|
2023-07-16 08:14:22 +00:00
|
|
|
f, err := os.CreateTemp("", "fzf-preview-*")
|
2019-10-27 14:50:12 +00:00
|
|
|
if err != nil {
|
|
|
|
errorExit("Unable to create temporary file")
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
f.WriteString(strings.Join(data, printSep))
|
|
|
|
f.WriteString(printSep)
|
|
|
|
activeTempFiles = append(activeTempFiles, f.Name())
|
|
|
|
return f.Name()
|
|
|
|
}
|
|
|
|
|
|
|
|
func cleanTemporaryFiles() {
|
|
|
|
for _, filename := range activeTempFiles {
|
|
|
|
os.Remove(filename)
|
|
|
|
}
|
|
|
|
activeTempFiles = []string{}
|
|
|
|
}
|
|
|
|
|
2020-07-26 15:15:25 +00:00
|
|
|
func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list []*Item) string {
|
|
|
|
return replacePlaceholder(
|
|
|
|
template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list)
|
|
|
|
}
|
|
|
|
|
2021-11-30 14:37:48 +00:00
|
|
|
func (t *Terminal) evaluateScrollOffset() int {
|
|
|
|
if t.pwindow == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// We only need the current item to calculate the scroll offset
|
2021-03-12 17:24:37 +00:00
|
|
|
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(
|
2021-11-30 14:37:48 +00:00
|
|
|
t.replacePlaceholder(t.previewOpts.scroll, false, "", []*Item{t.currentItem(), nil}), "")
|
2021-03-12 17:24:37 +00:00
|
|
|
|
|
|
|
atoi := func(s string) int {
|
|
|
|
n, e := strconv.Atoi(s)
|
|
|
|
if e != nil {
|
2020-07-26 15:15:25 +00:00
|
|
|
return 0
|
|
|
|
}
|
2021-03-12 17:24:37 +00:00
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
|
|
|
base := -1
|
2021-11-30 14:37:48 +00:00
|
|
|
height := util.Max(0, t.pwindow.Height()-t.previewOpts.headerLines)
|
2021-03-12 17:24:37 +00:00
|
|
|
for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) {
|
|
|
|
if strings.HasPrefix(component, "-/") {
|
|
|
|
component = component[1:]
|
|
|
|
}
|
|
|
|
if component[0] == '/' {
|
|
|
|
denom := atoi(component[1:])
|
2021-11-30 14:37:48 +00:00
|
|
|
if denom != 0 {
|
|
|
|
base -= height / denom
|
2020-08-23 06:57:49 +00:00
|
|
|
}
|
2021-11-30 14:37:48 +00:00
|
|
|
break
|
2020-08-23 06:57:49 +00:00
|
|
|
}
|
2021-03-12 17:24:37 +00:00
|
|
|
base += atoi(component)
|
2020-07-26 15:15:25 +00:00
|
|
|
}
|
2021-11-30 14:37:48 +00:00
|
|
|
return util.Max(0, base)
|
2020-07-26 15:15:25 +00:00
|
|
|
}
|
|
|
|
|
2019-10-27 14:50:12 +00:00
|
|
|
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
2017-01-27 07:38:42 +00:00
|
|
|
current := allItems[:1]
|
|
|
|
selected := allItems[1:]
|
|
|
|
if current[0] == nil {
|
|
|
|
current = []*Item{}
|
|
|
|
}
|
|
|
|
if selected[0] == nil {
|
|
|
|
selected = []*Item{}
|
|
|
|
}
|
Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands
This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.
* [tests] Add testing of placeholder parsing and matching
Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.
* [tests] Add more test cases of replacing placeholders focused on flags
Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.
There is at least one test for each flag, not all combinations are
tested though.
* [refactoring] Split OS-specific function quoteEntry() to corresponding source file
This is minor refactoring, and also the function's test was made
crossplatform.
* [refactoring] Simplify replacePlaceholder function
Should be equivalent to the original, but has simpler structure.
2021-10-15 13:31:59 +00:00
|
|
|
|
|
|
|
// replace placeholders one by one
|
2016-10-03 05:16:10 +00:00
|
|
|
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
|
2018-03-30 02:47:46 +00:00
|
|
|
escaped, match, flags := parsePlaceholder(match)
|
|
|
|
|
Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands
This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.
* [tests] Add testing of placeholder parsing and matching
Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.
* [tests] Add more test cases of replacing placeholders focused on flags
Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.
There is at least one test for each flag, not all combinations are
tested though.
* [refactoring] Split OS-specific function quoteEntry() to corresponding source file
This is minor refactoring, and also the function's test was made
crossplatform.
* [refactoring] Simplify replacePlaceholder function
Should be equivalent to the original, but has simpler structure.
2021-10-15 13:31:59 +00:00
|
|
|
// this function implements the effects a placeholder has on items
|
|
|
|
var replace func(*Item) string
|
2016-10-03 05:16:10 +00:00
|
|
|
|
Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands
This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.
* [tests] Add testing of placeholder parsing and matching
Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.
* [tests] Add more test cases of replacing placeholders focused on flags
Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.
There is at least one test for each flag, not all combinations are
tested though.
* [refactoring] Split OS-specific function quoteEntry() to corresponding source file
This is minor refactoring, and also the function's test was made
crossplatform.
* [refactoring] Simplify replacePlaceholder function
Should be equivalent to the original, but has simpler structure.
2021-10-15 13:31:59 +00:00
|
|
|
// placeholder types (escaped, query type, item type, token type)
|
|
|
|
switch {
|
|
|
|
case escaped:
|
|
|
|
return match
|
|
|
|
case match == "{q}":
|
2016-10-03 05:16:10 +00:00
|
|
|
return quoteEntry(query)
|
Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands
This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.
* [tests] Add testing of placeholder parsing and matching
Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.
* [tests] Add more test cases of replacing placeholders focused on flags
Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.
There is at least one test for each flag, not all combinations are
tested though.
* [refactoring] Split OS-specific function quoteEntry() to corresponding source file
This is minor refactoring, and also the function's test was made
crossplatform.
* [refactoring] Simplify replacePlaceholder function
Should be equivalent to the original, but has simpler structure.
2021-10-15 13:31:59 +00:00
|
|
|
case match == "{}":
|
|
|
|
replace = func(item *Item) string {
|
|
|
|
switch {
|
|
|
|
case flags.number:
|
2019-02-18 16:12:57 +00:00
|
|
|
n := int(item.text.Index)
|
|
|
|
if n < 0 {
|
Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands
This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.
* [tests] Add testing of placeholder parsing and matching
Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.
* [tests] Add more test cases of replacing placeholders focused on flags
Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.
There is at least one test for each flag, not all combinations are
tested though.
* [refactoring] Split OS-specific function quoteEntry() to corresponding source file
This is minor refactoring, and also the function's test was made
crossplatform.
* [refactoring] Simplify replacePlaceholder function
Should be equivalent to the original, but has simpler structure.
2021-10-15 13:31:59 +00:00
|
|
|
return ""
|
2019-02-18 16:12:57 +00:00
|
|
|
}
|
Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands
This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.
* [tests] Add testing of placeholder parsing and matching
Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.
* [tests] Add more test cases of replacing placeholders focused on flags
Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.
There is at least one test for each flag, not all combinations are
tested though.
* [refactoring] Split OS-specific function quoteEntry() to corresponding source file
This is minor refactoring, and also the function's test was made
crossplatform.
* [refactoring] Simplify replacePlaceholder function
Should be equivalent to the original, but has simpler structure.
2021-10-15 13:31:59 +00:00
|
|
|
return strconv.Itoa(n)
|
|
|
|
case flags.file:
|
|
|
|
return item.AsString(stripAnsi)
|
|
|
|
default:
|
|
|
|
return quoteEntry(item.AsString(stripAnsi))
|
2019-02-18 16:12:57 +00:00
|
|
|
}
|
2016-10-03 05:16:10 +00:00
|
|
|
}
|
Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands
This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.
* [tests] Add testing of placeholder parsing and matching
Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.
* [tests] Add more test cases of replacing placeholders focused on flags
Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.
There is at least one test for each flag, not all combinations are
tested though.
* [refactoring] Split OS-specific function quoteEntry() to corresponding source file
This is minor refactoring, and also the function's test was made
crossplatform.
* [refactoring] Simplify replacePlaceholder function
Should be equivalent to the original, but has simpler structure.
2021-10-15 13:31:59 +00:00
|
|
|
default:
|
|
|
|
// token type and also failover (below)
|
|
|
|
rangeExpressions := strings.Split(match[1:len(match)-1], ",")
|
|
|
|
ranges := make([]Range, len(rangeExpressions))
|
|
|
|
for idx, s := range rangeExpressions {
|
|
|
|
r, ok := ParseRange(&s) // ellipsis (x..y) and shorthand (x..x) range syntax
|
|
|
|
if !ok {
|
|
|
|
// Invalid expression, just return the original string in the template
|
|
|
|
return match
|
|
|
|
}
|
|
|
|
ranges[idx] = r
|
2019-10-27 14:50:12 +00:00
|
|
|
}
|
2016-10-03 05:16:10 +00:00
|
|
|
|
Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands
This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.
* [tests] Add testing of placeholder parsing and matching
Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.
* [tests] Add more test cases of replacing placeholders focused on flags
Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.
There is at least one test for each flag, not all combinations are
tested though.
* [refactoring] Split OS-specific function quoteEntry() to corresponding source file
This is minor refactoring, and also the function's test was made
crossplatform.
* [refactoring] Simplify replacePlaceholder function
Should be equivalent to the original, but has simpler structure.
2021-10-15 13:31:59 +00:00
|
|
|
replace = func(item *Item) string {
|
|
|
|
tokens := Tokenize(item.AsString(stripAnsi), delimiter)
|
|
|
|
trans := Transform(tokens, ranges)
|
|
|
|
str := joinTokens(trans)
|
|
|
|
|
|
|
|
// trim the last delimiter
|
|
|
|
if delimiter.str != nil {
|
|
|
|
str = strings.TrimSuffix(str, *delimiter.str)
|
|
|
|
} else if delimiter.regex != nil {
|
|
|
|
delims := delimiter.regex.FindAllStringIndex(str, -1)
|
|
|
|
// make sure the delimiter is at the very end of the string
|
|
|
|
if len(delims) > 0 && delims[len(delims)-1][1] == len(str) {
|
|
|
|
str = str[:delims[len(delims)-1][0]]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !flags.preserveSpace {
|
|
|
|
str = strings.TrimSpace(str)
|
|
|
|
}
|
|
|
|
if !flags.file {
|
|
|
|
str = quoteEntry(str)
|
|
|
|
}
|
|
|
|
return str
|
2016-10-03 05:16:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands
This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.
* [tests] Add testing of placeholder parsing and matching
Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.
* [tests] Add more test cases of replacing placeholders focused on flags
Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.
There is at least one test for each flag, not all combinations are
tested though.
* [refactoring] Split OS-specific function quoteEntry() to corresponding source file
This is minor refactoring, and also the function's test was made
crossplatform.
* [refactoring] Simplify replacePlaceholder function
Should be equivalent to the original, but has simpler structure.
2021-10-15 13:31:59 +00:00
|
|
|
// apply 'replace' function over proper set of items and return result
|
|
|
|
|
|
|
|
items := current
|
|
|
|
if flags.plus || forcePlus {
|
|
|
|
items = selected
|
|
|
|
}
|
|
|
|
replacements := make([]string, len(items))
|
|
|
|
|
2016-10-03 05:16:10 +00:00
|
|
|
for idx, item := range items {
|
Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands
This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.
* [tests] Add testing of placeholder parsing and matching
Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.
* [tests] Add more test cases of replacing placeholders focused on flags
Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.
There is at least one test for each flag, not all combinations are
tested though.
* [refactoring] Split OS-specific function quoteEntry() to corresponding source file
This is minor refactoring, and also the function's test was made
crossplatform.
* [refactoring] Simplify replacePlaceholder function
Should be equivalent to the original, but has simpler structure.
2021-10-15 13:31:59 +00:00
|
|
|
replacements[idx] = replace(item)
|
2019-10-27 14:50:12 +00:00
|
|
|
}
|
Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands
This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.
* [tests] Add testing of placeholder parsing and matching
Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.
* [tests] Add more test cases of replacing placeholders focused on flags
Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.
There is at least one test for each flag, not all combinations are
tested though.
* [refactoring] Split OS-specific function quoteEntry() to corresponding source file
This is minor refactoring, and also the function's test was made
crossplatform.
* [refactoring] Simplify replacePlaceholder function
Should be equivalent to the original, but has simpler structure.
2021-10-15 13:31:59 +00:00
|
|
|
|
2019-10-27 14:50:12 +00:00
|
|
|
if flags.file {
|
|
|
|
return writeTemporaryFile(replacements, printsep)
|
2016-10-03 05:16:10 +00:00
|
|
|
}
|
|
|
|
return strings.Join(replacements, " ")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-01-07 02:21:52 +00:00
|
|
|
func (t *Terminal) redraw() {
|
|
|
|
t.tui.Clear()
|
2017-04-28 13:58:08 +00:00
|
|
|
t.tui.Refresh()
|
|
|
|
t.printAll()
|
|
|
|
}
|
|
|
|
|
2023-04-22 13:01:00 +00:00
|
|
|
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool, capture bool, firstLineOnly bool) string {
|
2022-12-27 15:05:31 +00:00
|
|
|
line := ""
|
2023-02-11 11:21:10 +00:00
|
|
|
valid, list := t.buildPlusList(template, forcePlus)
|
2023-04-22 13:01:00 +00:00
|
|
|
// 'capture' is used for transform-* and we don't want to
|
2022-12-31 00:27:11 +00:00
|
|
|
// return an empty string in those cases
|
2023-04-22 13:01:00 +00:00
|
|
|
if !valid && !capture {
|
2022-12-27 15:05:31 +00:00
|
|
|
return line
|
2017-01-27 07:38:42 +00:00
|
|
|
}
|
2020-07-26 15:15:25 +00:00
|
|
|
command := t.replacePlaceholder(template, forcePlus, string(t.input), list)
|
2018-09-27 06:27:08 +00:00
|
|
|
cmd := util.ExecCommand(command, false)
|
2023-03-19 06:42:47 +00:00
|
|
|
cmd.Env = t.environ()
|
2021-03-26 07:12:54 +00:00
|
|
|
t.executing.Set(true)
|
2017-01-27 08:46:56 +00:00
|
|
|
if !background {
|
2021-03-25 10:56:18 +00:00
|
|
|
cmd.Stdin = tui.TtyIn()
|
2017-01-27 08:46:56 +00:00
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
2017-04-28 13:58:08 +00:00
|
|
|
t.tui.Pause(true)
|
2017-01-27 08:46:56 +00:00
|
|
|
cmd.Run()
|
2020-05-17 17:33:37 +00:00
|
|
|
t.tui.Resume(true, false)
|
2023-01-07 02:21:52 +00:00
|
|
|
t.redraw()
|
2017-01-27 08:46:56 +00:00
|
|
|
t.refresh()
|
|
|
|
} else {
|
2023-04-22 13:01:00 +00:00
|
|
|
if capture {
|
2022-12-27 15:05:31 +00:00
|
|
|
out, _ := cmd.StdoutPipe()
|
|
|
|
reader := bufio.NewReader(out)
|
|
|
|
cmd.Start()
|
2023-04-22 13:01:00 +00:00
|
|
|
if firstLineOnly {
|
|
|
|
line, _ = reader.ReadString('\n')
|
|
|
|
line = strings.TrimRight(line, "\r\n")
|
|
|
|
} else {
|
|
|
|
bytes, _ := io.ReadAll(reader)
|
|
|
|
line = string(bytes)
|
|
|
|
}
|
2022-12-27 15:05:31 +00:00
|
|
|
cmd.Wait()
|
|
|
|
} else {
|
|
|
|
cmd.Run()
|
|
|
|
}
|
2017-01-27 08:46:56 +00:00
|
|
|
}
|
2021-03-26 07:12:54 +00:00
|
|
|
t.executing.Set(false)
|
2019-10-27 14:50:12 +00:00
|
|
|
cleanTemporaryFiles()
|
2022-12-27 15:05:31 +00:00
|
|
|
return line
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
|
|
|
|
2017-01-23 15:23:16 +00:00
|
|
|
func (t *Terminal) hasPreviewer() bool {
|
2016-06-11 10:59:12 +00:00
|
|
|
return t.previewBox != nil
|
|
|
|
}
|
|
|
|
|
2023-02-01 09:16:58 +00:00
|
|
|
func (t *Terminal) needPreviewWindow() bool {
|
|
|
|
return t.hasPreviewer() && len(t.previewOpts.command) > 0 && t.previewOpts.Visible()
|
2017-01-23 15:23:16 +00:00
|
|
|
}
|
|
|
|
|
2023-01-07 02:21:52 +00:00
|
|
|
// Check if previewer is currently in action (invisible previewer with size 0 or visible previewer)
|
2023-02-01 09:16:58 +00:00
|
|
|
func (t *Terminal) canPreview() bool {
|
|
|
|
return t.hasPreviewer() && (!t.previewOpts.Visible() && !t.previewOpts.hidden || t.hasPreviewWindow())
|
2022-09-07 16:01:22 +00:00
|
|
|
}
|
|
|
|
|
2017-01-23 15:23:16 +00:00
|
|
|
func (t *Terminal) hasPreviewWindow() bool {
|
2023-01-07 02:21:52 +00:00
|
|
|
return t.pwindow != nil
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
|
|
|
|
2016-10-03 05:16:10 +00:00
|
|
|
func (t *Terminal) currentItem() *Item {
|
2017-01-27 07:38:42 +00:00
|
|
|
cnt := t.merger.Length()
|
2019-11-10 02:36:22 +00:00
|
|
|
if t.cy >= 0 && cnt > 0 && cnt > t.cy {
|
2017-01-27 07:38:42 +00:00
|
|
|
return t.merger.Get(t.cy).item
|
|
|
|
}
|
|
|
|
return nil
|
2016-10-03 05:16:10 +00:00
|
|
|
}
|
|
|
|
|
2023-02-11 11:21:10 +00:00
|
|
|
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
|
2017-01-27 07:38:42 +00:00
|
|
|
current := t.currentItem()
|
2020-09-29 02:31:28 +00:00
|
|
|
slot, plus, query := hasPreviewFlags(template)
|
2023-02-11 11:21:10 +00:00
|
|
|
if !(!slot || query || (forcePlus || plus) && len(t.selected) > 0) {
|
2017-01-27 07:38:42 +00:00
|
|
|
return current != nil, []*Item{current, current}
|
|
|
|
}
|
2018-06-09 16:40:22 +00:00
|
|
|
|
|
|
|
// We would still want to update preview window even if there is no match if
|
2023-07-05 05:55:53 +00:00
|
|
|
// 1. the command template contains {q}
|
2018-06-09 16:40:22 +00:00
|
|
|
// 2. or it contains {+} and we have more than one item already selected.
|
|
|
|
// To do so, we pass an empty Item instead of nil to trigger an update.
|
|
|
|
if current == nil {
|
2019-02-18 16:12:57 +00:00
|
|
|
current = &minItem
|
2018-06-09 16:40:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var sels []*Item
|
|
|
|
if len(t.selected) == 0 {
|
|
|
|
sels = []*Item{current, current}
|
|
|
|
} else {
|
|
|
|
sels = make([]*Item, len(t.selected)+1)
|
|
|
|
sels[0] = current
|
|
|
|
for i, sel := range t.sortSelected() {
|
|
|
|
sels[i+1] = sel.item
|
|
|
|
}
|
2017-01-27 07:38:42 +00:00
|
|
|
}
|
|
|
|
return true, sels
|
2015-06-14 03:25:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-02 03:55:26 +00:00
|
|
|
func (t *Terminal) selectItem(item *Item) bool {
|
|
|
|
if len(t.selected) >= t.multi {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if _, found := t.selected[item.Index()]; found {
|
2019-11-11 14:31:31 +00:00
|
|
|
return true
|
2019-11-02 03:55:26 +00:00
|
|
|
}
|
|
|
|
|
2017-07-27 16:39:25 +00:00
|
|
|
t.selected[item.Index()] = selectedItem{time.Now(), item}
|
|
|
|
t.version++
|
2019-11-02 03:55:26 +00:00
|
|
|
|
|
|
|
return true
|
2017-07-27 16:39:25 +00:00
|
|
|
}
|
|
|
|
|
2021-02-25 12:14:15 +00:00
|
|
|
func (t *Terminal) selectItemChanged(item *Item) bool {
|
|
|
|
if _, found := t.selected[item.Index()]; found {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return t.selectItem(item)
|
|
|
|
}
|
|
|
|
|
2017-07-27 16:39:25 +00:00
|
|
|
func (t *Terminal) deselectItem(item *Item) {
|
|
|
|
delete(t.selected, item.Index())
|
|
|
|
t.version++
|
|
|
|
}
|
|
|
|
|
2021-02-25 12:14:15 +00:00
|
|
|
func (t *Terminal) deselectItemChanged(item *Item) bool {
|
|
|
|
if _, found := t.selected[item.Index()]; found {
|
|
|
|
t.deselectItem(item)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-11-02 03:55:26 +00:00
|
|
|
func (t *Terminal) toggleItem(item *Item) bool {
|
2017-07-27 16:39:25 +00:00
|
|
|
if _, found := t.selected[item.Index()]; !found {
|
2019-11-02 03:55:26 +00:00
|
|
|
return t.selectItem(item)
|
2017-07-27 16:39:25 +00:00
|
|
|
}
|
2019-11-02 03:55:26 +00:00
|
|
|
t.deselectItem(item)
|
|
|
|
return true
|
2017-07-27 16:39:25 +00:00
|
|
|
}
|
|
|
|
|
2018-09-27 06:27:08 +00:00
|
|
|
func (t *Terminal) killPreview(code int) {
|
|
|
|
select {
|
|
|
|
case t.killChan <- code:
|
|
|
|
default:
|
|
|
|
if code != exitCancel {
|
2021-03-07 02:30:26 +00:00
|
|
|
t.eventBox.Set(EvtQuit, code)
|
2018-09-27 06:27:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) cancelPreview() {
|
|
|
|
t.killPreview(exitCancel)
|
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// Loop is called to start Terminal I/O
|
2015-01-01 19:49:30 +00:00
|
|
|
func (t *Terminal) Loop() {
|
2016-09-07 00:58:18 +00:00
|
|
|
// prof := profile.Start(profile.ProfilePath("/tmp/"))
|
2022-09-07 16:01:22 +00:00
|
|
|
fitpad := <-t.startChan
|
|
|
|
fit := fitpad.fit
|
|
|
|
if fit >= 0 {
|
|
|
|
pad := fitpad.pad
|
|
|
|
t.tui.Resize(func(termHeight int) int {
|
|
|
|
contentHeight := fit + t.extraLines()
|
2023-02-01 09:16:58 +00:00
|
|
|
if t.needPreviewWindow() {
|
2022-09-07 16:01:22 +00:00
|
|
|
if t.previewOpts.aboveOrBelow() {
|
|
|
|
if t.previewOpts.size.percent {
|
|
|
|
newContentHeight := int(float64(contentHeight) * 100. / (100. - t.previewOpts.size.size))
|
|
|
|
contentHeight = util.Max(contentHeight+1+borderLines(t.previewOpts.border), newContentHeight)
|
|
|
|
} else {
|
|
|
|
contentHeight += int(t.previewOpts.size.size) + borderLines(t.previewOpts.border)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Minimum height if preview window can appear
|
|
|
|
contentHeight = util.Max(contentHeight, 1+borderLines(t.previewOpts.border))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return util.Min(termHeight, contentHeight+pad)
|
|
|
|
})
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
{ // Late initialization
|
2015-10-05 14:19:26 +00:00
|
|
|
intChan := make(chan os.Signal, 1)
|
2019-07-19 04:24:46 +00:00
|
|
|
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
|
2015-10-05 14:19:26 +00:00
|
|
|
go func() {
|
2021-02-28 12:01:03 +00:00
|
|
|
for s := range intChan {
|
|
|
|
// Don't quit by SIGINT while executing because it should be for the executing command and not for fzf itself
|
|
|
|
if !(s == os.Interrupt && t.executing.Get()) {
|
|
|
|
t.reqBox.Set(reqQuit, nil)
|
|
|
|
}
|
|
|
|
}
|
2015-10-05 14:19:26 +00:00
|
|
|
}()
|
|
|
|
|
2017-04-28 13:58:08 +00:00
|
|
|
contChan := make(chan os.Signal, 1)
|
|
|
|
notifyOnCont(contChan)
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
<-contChan
|
|
|
|
t.reqBox.Set(reqReinit, nil)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2015-01-23 11:30:50 +00:00
|
|
|
resizeChan := make(chan os.Signal, 1)
|
2016-10-24 00:44:56 +00:00
|
|
|
notifyOnResize(resizeChan) // Non-portable
|
2015-01-23 11:30:50 +00:00
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
<-resizeChan
|
2021-12-04 02:46:15 +00:00
|
|
|
t.reqBox.Set(reqFullRedraw, nil)
|
2015-01-23 11:30:50 +00:00
|
|
|
}
|
|
|
|
}()
|
2015-06-17 15:42:38 +00:00
|
|
|
|
2015-11-03 13:59:57 +00:00
|
|
|
t.mutex.Lock()
|
|
|
|
t.initFunc()
|
2023-01-07 02:21:52 +00:00
|
|
|
t.resizeWindows(false)
|
2015-11-03 13:59:57 +00:00
|
|
|
t.printPrompt()
|
|
|
|
t.printInfo()
|
|
|
|
t.printHeader()
|
2018-06-25 10:07:12 +00:00
|
|
|
t.refresh()
|
2015-11-03 13:59:57 +00:00
|
|
|
t.mutex.Unlock()
|
|
|
|
go func() {
|
2016-01-16 09:07:50 +00:00
|
|
|
timer := time.NewTimer(t.initDelay)
|
2015-11-03 13:59:57 +00:00
|
|
|
<-timer.C
|
|
|
|
t.reqBox.Set(reqRefresh, nil)
|
|
|
|
}()
|
|
|
|
|
2015-06-17 15:42:38 +00:00
|
|
|
// Keep the spinner spinning
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
t.mutex.Lock()
|
|
|
|
reading := t.reading
|
|
|
|
t.mutex.Unlock()
|
|
|
|
time.Sleep(spinnerDuration)
|
2019-11-10 02:36:22 +00:00
|
|
|
if reading {
|
|
|
|
t.reqBox.Set(reqInfo, nil)
|
|
|
|
}
|
2015-06-17 15:42:38 +00:00
|
|
|
}
|
|
|
|
}()
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2017-01-23 15:23:16 +00:00
|
|
|
if t.hasPreviewer() {
|
2016-06-11 10:59:12 +00:00
|
|
|
go func() {
|
2020-10-24 07:55:55 +00:00
|
|
|
var version int64
|
2016-06-11 10:59:12 +00:00
|
|
|
for {
|
2020-06-21 13:41:33 +00:00
|
|
|
var items []*Item
|
|
|
|
var commandTemplate string
|
2020-10-18 08:03:33 +00:00
|
|
|
var pwindow tui.Window
|
2021-11-30 14:37:48 +00:00
|
|
|
initialOffset := 0
|
2016-06-11 10:59:12 +00:00
|
|
|
t.previewBox.Wait(func(events *util.Events) {
|
|
|
|
for req, value := range *events {
|
|
|
|
switch req {
|
|
|
|
case reqPreviewEnqueue:
|
2020-06-21 13:41:33 +00:00
|
|
|
request := value.(previewRequest)
|
|
|
|
commandTemplate = request.template
|
2021-11-30 14:37:48 +00:00
|
|
|
initialOffset = request.scrollOffset
|
2020-06-21 13:41:33 +00:00
|
|
|
items = request.list
|
2020-10-18 08:03:33 +00:00
|
|
|
pwindow = request.pwindow
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
events.Clear()
|
|
|
|
})
|
2020-10-18 08:03:33 +00:00
|
|
|
version++
|
2017-01-27 07:38:42 +00:00
|
|
|
// We don't display preview window if no match
|
2020-06-21 13:41:33 +00:00
|
|
|
if items[0] != nil {
|
2021-01-02 15:00:40 +00:00
|
|
|
_, query := t.Input()
|
|
|
|
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
2018-09-27 06:27:08 +00:00
|
|
|
cmd := util.ExecCommand(command, true)
|
2023-03-19 06:42:47 +00:00
|
|
|
env := t.environ()
|
2020-10-18 08:03:33 +00:00
|
|
|
if pwindow != nil {
|
|
|
|
height := pwindow.Height()
|
2020-08-23 06:57:49 +00:00
|
|
|
lines := fmt.Sprintf("LINES=%d", height)
|
2020-10-18 08:03:33 +00:00
|
|
|
columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
|
2019-02-22 05:33:29 +00:00
|
|
|
env = append(env, lines)
|
|
|
|
env = append(env, "FZF_PREVIEW_"+lines)
|
|
|
|
env = append(env, columns)
|
|
|
|
env = append(env, "FZF_PREVIEW_"+columns)
|
2017-11-30 18:27:58 +00:00
|
|
|
}
|
2023-03-19 06:42:47 +00:00
|
|
|
cmd.Env = env
|
2020-10-18 08:03:33 +00:00
|
|
|
|
|
|
|
out, _ := cmd.StdoutPipe()
|
|
|
|
cmd.Stderr = cmd.Stdout
|
|
|
|
reader := bufio.NewReader(out)
|
|
|
|
eofChan := make(chan bool)
|
|
|
|
finishChan := make(chan bool, 1)
|
2020-01-19 10:42:10 +00:00
|
|
|
err := cmd.Start()
|
2020-10-27 09:10:25 +00:00
|
|
|
if err == nil {
|
|
|
|
reapChan := make(chan bool)
|
2020-10-18 08:03:33 +00:00
|
|
|
lineChan := make(chan eachLine)
|
|
|
|
// Goroutine 1 reads process output
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
line, err := reader.ReadString('\n')
|
|
|
|
lineChan <- eachLine{line, err}
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
eofChan <- true
|
|
|
|
}()
|
2020-10-27 09:10:25 +00:00
|
|
|
|
2020-10-18 08:03:33 +00:00
|
|
|
// Goroutine 2 periodically requests rendering
|
2021-04-04 04:42:11 +00:00
|
|
|
rendered := util.NewAtomicBool(false)
|
2020-10-24 07:55:55 +00:00
|
|
|
go func(version int64) {
|
2020-10-18 08:03:33 +00:00
|
|
|
lines := []string{}
|
|
|
|
spinner := makeSpinner(t.unicode)
|
|
|
|
spinnerIndex := -1 // Delay initial rendering by an extra tick
|
|
|
|
ticker := time.NewTicker(previewChunkDelay)
|
|
|
|
offset := initialOffset
|
|
|
|
Loop:
|
|
|
|
for {
|
2018-09-27 06:27:08 +00:00
|
|
|
select {
|
2020-10-18 08:03:33 +00:00
|
|
|
case <-ticker.C:
|
|
|
|
if len(lines) > 0 && len(lines) >= initialOffset {
|
|
|
|
if spinnerIndex >= 0 {
|
|
|
|
spin := spinner[spinnerIndex%len(spinner)]
|
|
|
|
t.reqBox.Set(reqPreviewDisplay, previewResult{version, lines, offset, spin})
|
2021-04-04 04:42:11 +00:00
|
|
|
rendered.Set(true)
|
2020-10-18 08:03:33 +00:00
|
|
|
offset = -1
|
|
|
|
}
|
|
|
|
spinnerIndex++
|
|
|
|
}
|
|
|
|
case eachLine := <-lineChan:
|
|
|
|
line := eachLine.line
|
|
|
|
err := eachLine.err
|
|
|
|
if len(line) > 0 {
|
2020-10-23 12:37:20 +00:00
|
|
|
clearIndex := strings.Index(line, clearCode)
|
|
|
|
if clearIndex >= 0 {
|
|
|
|
lines = []string{}
|
|
|
|
line = line[clearIndex+len(clearCode):]
|
|
|
|
version--
|
|
|
|
offset = 0
|
|
|
|
}
|
2020-10-18 08:03:33 +00:00
|
|
|
lines = append(lines, line)
|
|
|
|
}
|
|
|
|
if err != nil {
|
2020-10-23 14:52:05 +00:00
|
|
|
t.reqBox.Set(reqPreviewDisplay, previewResult{version, lines, offset, ""})
|
2021-04-04 04:42:11 +00:00
|
|
|
rendered.Set(true)
|
2020-10-18 08:03:33 +00:00
|
|
|
break Loop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ticker.Stop()
|
|
|
|
reapChan <- true
|
|
|
|
}(version)
|
2020-10-27 09:10:25 +00:00
|
|
|
|
|
|
|
// Goroutine 3 is responsible for cancelling running preview command
|
|
|
|
go func(version int64) {
|
|
|
|
timer := time.NewTimer(previewDelayed)
|
|
|
|
Loop:
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-timer.C:
|
|
|
|
t.reqBox.Set(reqPreviewDelayed, version)
|
|
|
|
case code := <-t.killChan:
|
|
|
|
if code != exitCancel {
|
2020-10-18 08:03:33 +00:00
|
|
|
util.KillCommand(cmd)
|
2021-03-07 02:30:26 +00:00
|
|
|
t.eventBox.Set(EvtQuit, code)
|
2020-10-27 09:10:25 +00:00
|
|
|
} else {
|
2021-04-04 04:42:11 +00:00
|
|
|
// We can immediately kill a long-running preview program
|
|
|
|
// once we started rendering its partial output
|
|
|
|
delay := previewCancelWait
|
|
|
|
if rendered.Get() {
|
|
|
|
delay = 0
|
|
|
|
}
|
|
|
|
timer := time.NewTimer(delay)
|
2020-10-27 09:10:25 +00:00
|
|
|
select {
|
|
|
|
case <-timer.C:
|
|
|
|
util.KillCommand(cmd)
|
|
|
|
case <-finishChan:
|
|
|
|
}
|
|
|
|
timer.Stop()
|
2020-10-18 08:03:33 +00:00
|
|
|
}
|
2020-10-27 09:10:25 +00:00
|
|
|
break Loop
|
|
|
|
case <-finishChan:
|
|
|
|
break Loop
|
2018-09-27 06:27:08 +00:00
|
|
|
}
|
|
|
|
}
|
2020-10-27 09:10:25 +00:00
|
|
|
timer.Stop()
|
|
|
|
reapChan <- true
|
|
|
|
}(version)
|
|
|
|
|
|
|
|
<-eofChan // Goroutine 1 finished
|
|
|
|
cmd.Wait() // NOTE: We should not call Wait before EOF
|
|
|
|
finishChan <- true // Tell Goroutine 3 to stop
|
|
|
|
<-reapChan // Goroutine 2 and 3 finished
|
2020-10-18 08:03:33 +00:00
|
|
|
<-reapChan
|
2020-10-27 09:10:25 +00:00
|
|
|
} else {
|
|
|
|
// Failed to start the command. Report the error immediately.
|
|
|
|
t.reqBox.Set(reqPreviewDisplay, previewResult{version, []string{err.Error()}, 0, ""})
|
2018-09-27 06:27:08 +00:00
|
|
|
}
|
2020-10-27 09:10:25 +00:00
|
|
|
|
2019-10-27 14:50:12 +00:00
|
|
|
cleanTemporaryFiles()
|
2016-06-15 04:03:42 +00:00
|
|
|
} else {
|
2020-10-18 08:03:33 +00:00
|
|
|
t.reqBox.Set(reqPreviewDisplay, previewResult{version, nil, 0, ""})
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2020-06-21 13:41:33 +00:00
|
|
|
refreshPreview := func(command string) {
|
2023-02-01 09:16:58 +00:00
|
|
|
if len(command) > 0 && t.canPreview() {
|
2023-02-11 11:21:10 +00:00
|
|
|
_, list := t.buildPlusList(command, false)
|
2020-06-20 13:04:09 +00:00
|
|
|
t.cancelPreview()
|
2021-11-30 14:37:48 +00:00
|
|
|
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.evaluateScrollOffset(), list})
|
2020-06-20 13:04:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
go func() {
|
2018-12-13 01:58:57 +00:00
|
|
|
var focusedIndex int32 = minItem.Index()
|
2018-12-05 09:45:55 +00:00
|
|
|
var version int64 = -1
|
2021-03-07 12:43:24 +00:00
|
|
|
running := true
|
2021-03-07 15:08:10 +00:00
|
|
|
code := exitError
|
|
|
|
exit := func(getCode func() int) {
|
|
|
|
t.tui.Close()
|
|
|
|
code = getCode()
|
|
|
|
if code <= exitNoMatch && t.history != nil {
|
|
|
|
t.history.append(string(t.input))
|
|
|
|
}
|
|
|
|
running = false
|
|
|
|
t.mutex.Unlock()
|
|
|
|
}
|
|
|
|
|
2021-03-07 12:43:24 +00:00
|
|
|
for running {
|
2015-01-12 03:56:17 +00:00
|
|
|
t.reqBox.Wait(func(events *util.Events) {
|
2015-01-01 19:49:30 +00:00
|
|
|
defer events.Clear()
|
|
|
|
t.mutex.Lock()
|
2016-06-11 10:59:12 +00:00
|
|
|
for req, value := range *events {
|
2015-01-01 19:49:30 +00:00
|
|
|
switch req {
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqPrompt:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.printPrompt()
|
2019-11-14 15:39:29 +00:00
|
|
|
if t.noInfoLine() {
|
2015-04-21 14:50:53 +00:00
|
|
|
t.printInfo()
|
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqInfo:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.printInfo()
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqList:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.printList()
|
2018-12-13 01:58:57 +00:00
|
|
|
var currentIndex int32 = minItem.Index()
|
|
|
|
currentItem := t.currentItem()
|
|
|
|
if currentItem != nil {
|
|
|
|
currentIndex = currentItem.Index()
|
|
|
|
}
|
2023-01-23 07:22:25 +00:00
|
|
|
focusChanged := focusedIndex != currentIndex
|
2023-04-22 14:39:35 +00:00
|
|
|
if focusChanged && t.track == trackCurrent {
|
|
|
|
t.track = trackDisabled
|
|
|
|
t.printInfo()
|
|
|
|
}
|
2023-05-17 01:55:12 +00:00
|
|
|
if onFocus, prs := t.keymap[tui.Focus.AsEvent()]; prs && focusChanged {
|
2023-01-23 07:22:25 +00:00
|
|
|
t.serverChan <- onFocus
|
|
|
|
}
|
|
|
|
if focusChanged || version != t.version {
|
2017-07-27 16:39:25 +00:00
|
|
|
version = t.version
|
2018-12-13 01:58:57 +00:00
|
|
|
focusedIndex = currentIndex
|
2020-12-05 12:16:35 +00:00
|
|
|
refreshPreview(t.previewOpts.command)
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
2016-05-17 17:06:52 +00:00
|
|
|
case reqJump:
|
|
|
|
if t.merger.Length() == 0 {
|
|
|
|
t.jumping = jumpDisabled
|
|
|
|
}
|
|
|
|
t.printList()
|
2015-07-21 18:21:20 +00:00
|
|
|
case reqHeader:
|
|
|
|
t.printHeader()
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqRefresh:
|
2015-01-07 03:46:45 +00:00
|
|
|
t.suppress = false
|
2023-01-21 16:56:29 +00:00
|
|
|
case reqRedrawBorderLabel:
|
|
|
|
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true)
|
|
|
|
case reqRedrawPreviewLabel:
|
|
|
|
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, true)
|
2017-04-28 13:58:08 +00:00
|
|
|
case reqReinit:
|
2021-01-02 15:43:56 +00:00
|
|
|
t.tui.Resume(t.fullscreen, t.sigstop)
|
2023-01-07 02:21:52 +00:00
|
|
|
t.redraw()
|
2021-12-04 02:46:15 +00:00
|
|
|
case reqFullRedraw:
|
2023-01-07 07:06:18 +00:00
|
|
|
wasHidden := t.pwindow == nil
|
2023-01-07 02:21:52 +00:00
|
|
|
t.redraw()
|
2023-02-01 09:16:58 +00:00
|
|
|
if wasHidden && t.hasPreviewWindow() {
|
2023-01-07 07:06:18 +00:00
|
|
|
refreshPreview(t.previewOpts.command)
|
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqClose:
|
2021-03-07 15:08:10 +00:00
|
|
|
exit(func() int {
|
2017-07-19 04:17:06 +00:00
|
|
|
if t.output() {
|
|
|
|
return exitOk
|
|
|
|
}
|
|
|
|
return exitNoMatch
|
|
|
|
})
|
2021-03-07 02:30:26 +00:00
|
|
|
return
|
2016-06-11 10:59:12 +00:00
|
|
|
case reqPreviewDisplay:
|
2020-07-26 15:15:25 +00:00
|
|
|
result := value.(previewResult)
|
2020-12-05 12:16:35 +00:00
|
|
|
if t.previewer.version != result.version {
|
|
|
|
t.previewer.version = result.version
|
2023-01-12 14:12:26 +00:00
|
|
|
t.previewer.following.Force(t.previewOpts.follow)
|
|
|
|
if t.previewer.following.Enabled() {
|
|
|
|
t.previewer.offset = 0
|
|
|
|
}
|
2020-12-05 12:16:35 +00:00
|
|
|
}
|
2020-10-18 08:03:33 +00:00
|
|
|
t.previewer.lines = result.lines
|
|
|
|
t.previewer.spinner = result.spinner
|
2023-01-12 14:12:26 +00:00
|
|
|
if t.previewer.following.Enabled() {
|
|
|
|
t.previewer.offset = util.Max(t.previewer.offset, len(t.previewer.lines)-(t.pwindow.Height()-t.previewOpts.headerLines))
|
2020-12-05 12:16:35 +00:00
|
|
|
} else if result.offset >= 0 {
|
2021-03-12 10:51:28 +00:00
|
|
|
t.previewer.offset = util.Constrain(result.offset, t.previewOpts.headerLines, len(t.previewer.lines)-1)
|
2020-10-18 08:03:33 +00:00
|
|
|
}
|
2016-09-24 17:02:00 +00:00
|
|
|
t.printPreview()
|
|
|
|
case reqPreviewRefresh:
|
2016-06-11 10:59:12 +00:00
|
|
|
t.printPreview()
|
2020-10-18 08:03:33 +00:00
|
|
|
case reqPreviewDelayed:
|
2020-10-24 07:55:55 +00:00
|
|
|
t.previewer.version = value.(int64)
|
2020-10-18 08:03:33 +00:00
|
|
|
t.printPreviewDelayed()
|
2016-05-12 15:51:15 +00:00
|
|
|
case reqPrintQuery:
|
2021-03-07 15:08:10 +00:00
|
|
|
exit(func() int {
|
2017-07-19 04:17:06 +00:00
|
|
|
t.printer(string(t.input))
|
|
|
|
return exitOk
|
|
|
|
})
|
2021-03-07 02:30:26 +00:00
|
|
|
return
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqQuit:
|
2021-03-07 15:08:10 +00:00
|
|
|
exit(func() int { return exitInterrupt })
|
2021-03-07 02:30:26 +00:00
|
|
|
return
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
}
|
2018-06-25 10:07:12 +00:00
|
|
|
t.refresh()
|
2015-01-01 19:49:30 +00:00
|
|
|
t.mutex.Unlock()
|
|
|
|
})
|
|
|
|
}
|
2021-03-07 15:08:10 +00:00
|
|
|
// prof.Stop()
|
|
|
|
t.killPreview(code)
|
2015-01-01 19:49:30 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
looping := true
|
2022-10-26 15:33:05 +00:00
|
|
|
_, startEvent := t.keymap[tui.Start.AsEvent()]
|
|
|
|
|
2022-12-17 15:22:15 +00:00
|
|
|
needBarrier := true
|
|
|
|
barrier := make(chan bool)
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
<-barrier
|
2022-12-29 11:01:50 +00:00
|
|
|
t.eventChan <- t.tui.GetChar()
|
2022-12-17 15:22:15 +00:00
|
|
|
}
|
|
|
|
}()
|
2023-01-02 16:46:37 +00:00
|
|
|
previewDraggingPos := -1
|
|
|
|
barDragging := false
|
2023-01-06 06:36:12 +00:00
|
|
|
pbarDragging := false
|
2023-01-02 16:46:37 +00:00
|
|
|
wasDown := false
|
2015-01-01 19:49:30 +00:00
|
|
|
for looping {
|
2019-11-10 02:36:22 +00:00
|
|
|
var newCommand *string
|
2022-12-29 11:03:51 +00:00
|
|
|
var reloadSync bool
|
2019-11-10 02:36:22 +00:00
|
|
|
changed := false
|
2020-06-07 14:07:03 +00:00
|
|
|
beof := false
|
2019-11-10 02:36:22 +00:00
|
|
|
queryChanged := false
|
|
|
|
|
2022-10-26 15:33:05 +00:00
|
|
|
var event tui.Event
|
2022-12-17 15:22:15 +00:00
|
|
|
actions := []*action{}
|
2022-10-26 15:33:05 +00:00
|
|
|
if startEvent {
|
|
|
|
event = tui.Start.AsEvent()
|
|
|
|
startEvent = false
|
|
|
|
} else {
|
2022-12-17 15:22:15 +00:00
|
|
|
if needBarrier {
|
|
|
|
barrier <- true
|
|
|
|
}
|
|
|
|
select {
|
2022-12-29 11:01:50 +00:00
|
|
|
case event = <-t.eventChan:
|
2023-04-26 06:13:08 +00:00
|
|
|
needBarrier = !event.Is(tui.Load, tui.One, tui.Zero)
|
2022-12-25 10:53:53 +00:00
|
|
|
case actions = <-t.serverChan:
|
2022-12-17 15:22:15 +00:00
|
|
|
event = tui.Invalid.AsEvent()
|
|
|
|
needBarrier = false
|
|
|
|
}
|
2022-10-26 15:33:05 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
|
|
|
|
t.mutex.Lock()
|
|
|
|
previousInput := t.input
|
2019-03-29 06:02:31 +00:00
|
|
|
previousCx := t.cx
|
|
|
|
events := []util.EventType{}
|
2015-01-12 03:56:17 +00:00
|
|
|
req := func(evts ...util.EventType) {
|
2015-01-01 19:49:30 +00:00
|
|
|
for _, event := range evts {
|
|
|
|
events = append(events, event)
|
2015-01-11 18:01:24 +00:00
|
|
|
if event == reqClose || event == reqQuit {
|
2015-01-01 19:49:30 +00:00
|
|
|
looping = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-07 02:21:52 +00:00
|
|
|
updatePreviewWindow := func(forcePreview bool) {
|
|
|
|
t.resizeWindows(forcePreview)
|
|
|
|
req(reqPrompt, reqList, reqInfo, reqHeader)
|
2020-06-21 13:41:33 +00:00
|
|
|
}
|
2019-11-02 03:55:26 +00:00
|
|
|
toggle := func() bool {
|
2020-12-23 01:27:57 +00:00
|
|
|
current := t.currentItem()
|
|
|
|
if current != nil && t.toggleItem(current) {
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqInfo)
|
2019-11-02 03:55:26 +00:00
|
|
|
return true
|
2015-01-10 03:21:17 +00:00
|
|
|
}
|
2019-11-02 03:55:26 +00:00
|
|
|
return false
|
2015-01-10 03:21:17 +00:00
|
|
|
}
|
2020-12-31 03:54:58 +00:00
|
|
|
scrollPreviewTo := func(newOffset int) {
|
2020-10-18 08:03:33 +00:00
|
|
|
if !t.previewer.scrollable {
|
2019-03-29 09:28:45 +00:00
|
|
|
return
|
|
|
|
}
|
2020-10-18 08:03:33 +00:00
|
|
|
numLines := len(t.previewer.lines)
|
2023-01-04 13:00:00 +00:00
|
|
|
headerLines := t.previewOpts.headerLines
|
2020-12-05 12:16:35 +00:00
|
|
|
if t.previewOpts.cycle {
|
2023-01-04 13:00:00 +00:00
|
|
|
offsetRange := numLines - headerLines
|
|
|
|
newOffset = ((newOffset-headerLines)+offsetRange)%offsetRange + headerLines
|
2020-10-06 01:05:57 +00:00
|
|
|
}
|
2023-01-04 13:00:00 +00:00
|
|
|
newOffset = util.Constrain(newOffset, headerLines, numLines-1)
|
2019-03-29 06:12:46 +00:00
|
|
|
if t.previewer.offset != newOffset {
|
|
|
|
t.previewer.offset = newOffset
|
2023-01-12 14:12:26 +00:00
|
|
|
t.previewer.following.Set(t.previewer.offset >= numLines-(t.pwindow.Height()-headerLines))
|
2019-03-29 06:12:46 +00:00
|
|
|
req(reqPreviewRefresh)
|
|
|
|
}
|
2016-09-24 19:12:44 +00:00
|
|
|
}
|
2020-12-31 03:54:58 +00:00
|
|
|
scrollPreviewBy := func(amount int) {
|
|
|
|
scrollPreviewTo(t.previewer.offset + amount)
|
|
|
|
}
|
2015-06-18 15:31:48 +00:00
|
|
|
for key, ret := range t.expect {
|
2015-03-31 13:05:02 +00:00
|
|
|
if keyMatch(key, event) {
|
2015-06-18 15:31:48 +00:00
|
|
|
t.pressed = ret
|
2016-09-17 19:45:21 +00:00
|
|
|
t.reqBox.Set(reqClose, nil)
|
|
|
|
t.mutex.Unlock()
|
|
|
|
return
|
2015-03-28 17:59:32 +00:00
|
|
|
}
|
|
|
|
}
|
2015-05-20 12:25:15 +00:00
|
|
|
|
2021-12-04 02:46:15 +00:00
|
|
|
actionsFor := func(eventType tui.EventType) []*action {
|
2020-12-29 16:59:18 +00:00
|
|
|
return t.keymap[eventType.AsEvent()]
|
|
|
|
}
|
|
|
|
|
2021-12-04 02:46:15 +00:00
|
|
|
var doAction func(*action) bool
|
|
|
|
doActions := func(actions []*action) bool {
|
2017-01-21 17:32:49 +00:00
|
|
|
for _, action := range actions {
|
2020-12-29 16:59:18 +00:00
|
|
|
if !doAction(action) {
|
2017-01-21 17:32:49 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
2021-12-04 02:46:15 +00:00
|
|
|
doAction = func(a *action) bool {
|
2017-01-21 17:32:49 +00:00
|
|
|
switch a.t {
|
2015-10-12 17:24:38 +00:00
|
|
|
case actIgnore:
|
2023-02-11 11:21:10 +00:00
|
|
|
case actBecome:
|
2023-02-12 13:06:21 +00:00
|
|
|
valid, list := t.buildPlusList(a.a, false)
|
|
|
|
if valid {
|
|
|
|
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
|
|
|
shell := os.Getenv("SHELL")
|
|
|
|
if len(shell) == 0 {
|
|
|
|
shell = "sh"
|
|
|
|
}
|
|
|
|
shellPath, err := exec.LookPath(shell)
|
|
|
|
if err == nil {
|
|
|
|
t.tui.Close()
|
|
|
|
if t.history != nil {
|
|
|
|
t.history.append(string(t.input))
|
|
|
|
}
|
2023-05-21 04:51:06 +00:00
|
|
|
/*
|
|
|
|
FIXME: It is not at all clear why this is required.
|
|
|
|
The following command will report 'not a tty', unless we open
|
|
|
|
/dev/tty *twice* after closing the standard input for 'reload'
|
|
|
|
in Reader.terminate().
|
|
|
|
: | fzf --bind 'start:reload:ls' --bind 'enter:become:tty'
|
|
|
|
*/
|
|
|
|
tui.TtyIn()
|
2023-02-14 14:21:34 +00:00
|
|
|
util.SetStdin(tui.TtyIn())
|
2023-02-12 13:06:21 +00:00
|
|
|
syscall.Exec(shellPath, []string{shell, "-c", command}, os.Environ())
|
2023-02-11 11:21:10 +00:00
|
|
|
}
|
|
|
|
}
|
2017-01-27 08:46:56 +00:00
|
|
|
case actExecute, actExecuteSilent:
|
2023-04-22 13:01:00 +00:00
|
|
|
t.executeCommand(a.a, false, a.t == actExecuteSilent, false, false)
|
2015-11-08 16:42:01 +00:00
|
|
|
case actExecuteMulti:
|
2023-04-22 13:01:00 +00:00
|
|
|
t.executeCommand(a.a, true, false, false, false)
|
2015-10-12 17:24:38 +00:00
|
|
|
case actInvalid:
|
|
|
|
t.mutex.Unlock()
|
|
|
|
return false
|
2023-01-31 08:33:53 +00:00
|
|
|
case actTogglePreview, actShowPreview, actHidePreview:
|
|
|
|
var act bool
|
|
|
|
switch a.t {
|
|
|
|
case actShowPreview:
|
|
|
|
act = !t.hasPreviewWindow() && len(t.previewOpts.command) > 0
|
|
|
|
case actHidePreview:
|
|
|
|
act = t.hasPreviewWindow()
|
|
|
|
case actTogglePreview:
|
|
|
|
act = t.hasPreviewWindow() || len(t.previewOpts.command) > 0
|
|
|
|
}
|
|
|
|
if act {
|
2023-01-30 12:39:18 +00:00
|
|
|
t.activePreviewOpts.Toggle()
|
2023-01-07 02:21:52 +00:00
|
|
|
updatePreviewWindow(false)
|
2023-02-01 09:16:58 +00:00
|
|
|
if t.canPreview() {
|
2023-02-11 11:21:10 +00:00
|
|
|
valid, list := t.buildPlusList(t.previewOpts.command, false)
|
2017-01-27 07:38:42 +00:00
|
|
|
if valid {
|
2018-09-27 06:27:08 +00:00
|
|
|
t.cancelPreview()
|
2020-06-21 13:41:33 +00:00
|
|
|
t.previewBox.Set(reqPreviewEnqueue,
|
2021-11-30 14:37:48 +00:00
|
|
|
previewRequest{t.previewOpts.command, t.pwindow, t.evaluateScrollOffset(), list})
|
2017-01-27 07:38:42 +00:00
|
|
|
}
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
|
|
|
}
|
2017-02-18 14:49:00 +00:00
|
|
|
case actTogglePreviewWrap:
|
|
|
|
if t.hasPreviewWindow() {
|
2020-12-05 12:16:35 +00:00
|
|
|
t.previewOpts.wrap = !t.previewOpts.wrap
|
2021-02-01 14:14:21 +00:00
|
|
|
// Reset preview version so that full redraw occurs
|
|
|
|
t.previewed.version = 0
|
2017-02-18 14:49:00 +00:00
|
|
|
req(reqPreviewRefresh)
|
|
|
|
}
|
2022-12-31 00:27:11 +00:00
|
|
|
case actTransformPrompt:
|
2023-04-22 13:01:00 +00:00
|
|
|
prompt := t.executeCommand(a.a, false, true, true, true)
|
2022-12-31 00:27:11 +00:00
|
|
|
t.prompt, t.promptLen = t.parsePrompt(prompt)
|
|
|
|
req(reqPrompt)
|
2022-12-27 15:05:31 +00:00
|
|
|
case actTransformQuery:
|
2023-04-22 13:01:00 +00:00
|
|
|
query := t.executeCommand(a.a, false, true, true, true)
|
2022-12-27 15:05:31 +00:00
|
|
|
t.input = []rune(query)
|
|
|
|
t.cx = len(t.input)
|
2015-10-12 17:24:38 +00:00
|
|
|
case actToggleSort:
|
|
|
|
t.sort = !t.sort
|
2019-11-10 02:36:22 +00:00
|
|
|
changed = true
|
2020-12-31 03:54:58 +00:00
|
|
|
case actPreviewTop:
|
|
|
|
if t.hasPreviewWindow() {
|
|
|
|
scrollPreviewTo(0)
|
|
|
|
}
|
|
|
|
case actPreviewBottom:
|
|
|
|
if t.hasPreviewWindow() {
|
|
|
|
scrollPreviewTo(len(t.previewer.lines) - t.pwindow.Height())
|
|
|
|
}
|
2016-09-24 17:02:00 +00:00
|
|
|
case actPreviewUp:
|
2017-01-23 15:23:16 +00:00
|
|
|
if t.hasPreviewWindow() {
|
2020-12-31 03:54:58 +00:00
|
|
|
scrollPreviewBy(-1)
|
2016-09-24 17:02:00 +00:00
|
|
|
}
|
|
|
|
case actPreviewDown:
|
2017-01-23 15:23:16 +00:00
|
|
|
if t.hasPreviewWindow() {
|
2020-12-31 03:54:58 +00:00
|
|
|
scrollPreviewBy(1)
|
2016-09-24 19:12:44 +00:00
|
|
|
}
|
|
|
|
case actPreviewPageUp:
|
2017-01-23 15:23:16 +00:00
|
|
|
if t.hasPreviewWindow() {
|
2020-12-31 03:54:58 +00:00
|
|
|
scrollPreviewBy(-t.pwindow.Height())
|
2016-09-24 19:12:44 +00:00
|
|
|
}
|
|
|
|
case actPreviewPageDown:
|
2017-01-23 15:23:16 +00:00
|
|
|
if t.hasPreviewWindow() {
|
2020-12-31 03:54:58 +00:00
|
|
|
scrollPreviewBy(t.pwindow.Height())
|
2016-09-24 17:02:00 +00:00
|
|
|
}
|
2020-10-05 12:58:56 +00:00
|
|
|
case actPreviewHalfPageUp:
|
|
|
|
if t.hasPreviewWindow() {
|
2020-12-31 03:54:58 +00:00
|
|
|
scrollPreviewBy(-t.pwindow.Height() / 2)
|
2020-10-05 12:58:56 +00:00
|
|
|
}
|
|
|
|
case actPreviewHalfPageDown:
|
|
|
|
if t.hasPreviewWindow() {
|
2020-12-31 03:54:58 +00:00
|
|
|
scrollPreviewBy(t.pwindow.Height() / 2)
|
2020-10-05 12:58:56 +00:00
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actBeginningOfLine:
|
2015-07-23 12:05:33 +00:00
|
|
|
t.cx = 0
|
2015-10-12 17:24:38 +00:00
|
|
|
case actBackwardChar:
|
|
|
|
if t.cx > 0 {
|
|
|
|
t.cx--
|
2015-06-09 14:44:54 +00:00
|
|
|
}
|
2016-05-12 15:51:15 +00:00
|
|
|
case actPrintQuery:
|
|
|
|
req(reqPrintQuery)
|
2022-12-17 09:59:16 +00:00
|
|
|
case actChangeQuery:
|
|
|
|
t.input = []rune(a.a)
|
|
|
|
t.cx = len(t.input)
|
2023-04-22 13:01:00 +00:00
|
|
|
case actTransformHeader:
|
|
|
|
header := t.executeCommand(a.a, false, true, true, false)
|
|
|
|
if t.changeHeader(header) {
|
|
|
|
req(reqFullRedraw)
|
|
|
|
} else {
|
|
|
|
req(reqHeader)
|
|
|
|
}
|
|
|
|
case actChangeHeader:
|
|
|
|
if t.changeHeader(a.a) {
|
|
|
|
req(reqFullRedraw)
|
|
|
|
} else {
|
|
|
|
req(reqHeader)
|
|
|
|
}
|
2023-01-17 18:43:02 +00:00
|
|
|
case actChangeBorderLabel:
|
2023-01-21 16:56:29 +00:00
|
|
|
if t.border != nil {
|
|
|
|
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(a.a, &tui.ColBorderLabel, false)
|
|
|
|
req(reqRedrawBorderLabel)
|
|
|
|
}
|
2023-01-17 18:43:02 +00:00
|
|
|
case actChangePreviewLabel:
|
2023-01-21 16:56:29 +00:00
|
|
|
if t.pborder != nil {
|
|
|
|
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(a.a, &tui.ColPreviewLabel, false)
|
|
|
|
req(reqRedrawPreviewLabel)
|
|
|
|
}
|
|
|
|
case actTransformBorderLabel:
|
|
|
|
if t.border != nil {
|
2023-04-22 13:01:00 +00:00
|
|
|
label := t.executeCommand(a.a, false, true, true, true)
|
2023-01-21 16:56:29 +00:00
|
|
|
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
|
|
|
req(reqRedrawBorderLabel)
|
|
|
|
}
|
|
|
|
case actTransformPreviewLabel:
|
|
|
|
if t.pborder != nil {
|
2023-04-22 13:01:00 +00:00
|
|
|
label := t.executeCommand(a.a, false, true, true, true)
|
2023-01-21 16:56:29 +00:00
|
|
|
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
|
|
|
req(reqRedrawPreviewLabel)
|
|
|
|
}
|
2020-12-04 11:34:41 +00:00
|
|
|
case actChangePrompt:
|
|
|
|
t.prompt, t.promptLen = t.parsePrompt(a.a)
|
|
|
|
req(reqPrompt)
|
2020-06-21 13:41:33 +00:00
|
|
|
case actPreview:
|
2023-01-07 02:21:52 +00:00
|
|
|
updatePreviewWindow(true)
|
2020-06-21 13:41:33 +00:00
|
|
|
refreshPreview(a.a)
|
2020-06-20 13:04:09 +00:00
|
|
|
case actRefreshPreview:
|
2020-12-05 12:16:35 +00:00
|
|
|
refreshPreview(t.previewOpts.command)
|
2017-12-01 04:08:55 +00:00
|
|
|
case actReplaceQuery:
|
2020-12-23 01:27:57 +00:00
|
|
|
current := t.currentItem()
|
|
|
|
if current != nil {
|
|
|
|
t.input = current.text.ToRunes()
|
2017-12-01 04:08:55 +00:00
|
|
|
t.cx = len(t.input)
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actAbort:
|
|
|
|
req(reqQuit)
|
|
|
|
case actDeleteChar:
|
|
|
|
t.delChar()
|
|
|
|
case actDeleteCharEOF:
|
|
|
|
if !t.delChar() && t.cx == 0 {
|
|
|
|
req(reqQuit)
|
2015-06-09 14:44:54 +00:00
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actEndOfLine:
|
|
|
|
t.cx = len(t.input)
|
|
|
|
case actCancel:
|
|
|
|
if len(t.input) == 0 {
|
|
|
|
req(reqQuit)
|
|
|
|
} else {
|
|
|
|
t.yanked = t.input
|
|
|
|
t.input = []rune{}
|
|
|
|
t.cx = 0
|
2015-06-09 14:44:54 +00:00
|
|
|
}
|
2020-02-27 17:38:32 +00:00
|
|
|
case actBackwardDeleteCharEOF:
|
|
|
|
if len(t.input) == 0 {
|
|
|
|
req(reqQuit)
|
|
|
|
} else if t.cx > 0 {
|
|
|
|
t.input = append(t.input[:t.cx-1], t.input[t.cx:]...)
|
|
|
|
t.cx--
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actForwardChar:
|
|
|
|
if t.cx < len(t.input) {
|
|
|
|
t.cx++
|
|
|
|
}
|
|
|
|
case actBackwardDeleteChar:
|
2020-06-07 14:07:03 +00:00
|
|
|
beof = len(t.input) == 0
|
2015-10-12 17:24:38 +00:00
|
|
|
if t.cx > 0 {
|
|
|
|
t.input = append(t.input[:t.cx-1], t.input[t.cx:]...)
|
|
|
|
t.cx--
|
|
|
|
}
|
|
|
|
case actSelectAll:
|
2019-11-02 03:55:26 +00:00
|
|
|
if t.multi > 0 {
|
2015-10-12 17:24:38 +00:00
|
|
|
for i := 0; i < t.merger.Length(); i++ {
|
2019-11-02 03:55:26 +00:00
|
|
|
if !t.selectItem(t.merger.Get(i).item) {
|
|
|
|
break
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
}
|
|
|
|
req(reqList, reqInfo)
|
|
|
|
}
|
|
|
|
case actDeselectAll:
|
2019-11-02 03:55:26 +00:00
|
|
|
if t.multi > 0 {
|
2019-11-02 10:41:59 +00:00
|
|
|
for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ {
|
2019-11-02 10:55:05 +00:00
|
|
|
t.deselectItem(t.merger.Get(i).item)
|
2019-11-02 10:41:59 +00:00
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
req(reqList, reqInfo)
|
|
|
|
}
|
2021-02-01 15:08:54 +00:00
|
|
|
case actClose:
|
2023-01-07 02:21:52 +00:00
|
|
|
if t.hasPreviewWindow() {
|
|
|
|
t.activePreviewOpts.Toggle()
|
|
|
|
updatePreviewWindow(false)
|
2021-02-01 15:08:54 +00:00
|
|
|
} else {
|
|
|
|
req(reqQuit)
|
|
|
|
}
|
2021-02-25 12:14:15 +00:00
|
|
|
case actSelect:
|
|
|
|
current := t.currentItem()
|
|
|
|
if t.multi > 0 && current != nil && t.selectItemChanged(current) {
|
|
|
|
req(reqList, reqInfo)
|
|
|
|
}
|
|
|
|
case actDeselect:
|
|
|
|
current := t.currentItem()
|
|
|
|
if t.multi > 0 && current != nil && t.deselectItemChanged(current) {
|
|
|
|
req(reqList, reqInfo)
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actToggle:
|
2019-11-02 03:55:26 +00:00
|
|
|
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
2015-10-12 17:24:38 +00:00
|
|
|
req(reqList)
|
|
|
|
}
|
|
|
|
case actToggleAll:
|
2019-11-02 03:55:26 +00:00
|
|
|
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
|
|
|
|
if _, found := t.selected[item.Index()]; found {
|
|
|
|
prevIndexes[i] = struct{}{}
|
|
|
|
t.deselectItem(item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-12 17:24:38 +00:00
|
|
|
for i := 0; i < t.merger.Length(); i++ {
|
2019-11-02 03:55:26 +00:00
|
|
|
if _, found := prevIndexes[i]; !found {
|
|
|
|
item := t.merger.Get(i).item
|
|
|
|
if !t.selectItem(item) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
}
|
|
|
|
req(reqList, reqInfo)
|
|
|
|
}
|
2016-01-13 17:35:43 +00:00
|
|
|
case actToggleIn:
|
2018-06-09 16:41:50 +00:00
|
|
|
if t.layout != layoutDefault {
|
2021-12-04 02:46:15 +00:00
|
|
|
return doAction(&action{t: actToggleUp})
|
2016-01-13 17:35:43 +00:00
|
|
|
}
|
2021-12-04 02:46:15 +00:00
|
|
|
return doAction(&action{t: actToggleDown})
|
2016-01-13 17:35:43 +00:00
|
|
|
case actToggleOut:
|
2018-06-09 16:41:50 +00:00
|
|
|
if t.layout != layoutDefault {
|
2021-12-04 02:46:15 +00:00
|
|
|
return doAction(&action{t: actToggleDown})
|
2016-01-13 17:35:43 +00:00
|
|
|
}
|
2021-12-04 02:46:15 +00:00
|
|
|
return doAction(&action{t: actToggleUp})
|
2015-10-12 17:24:38 +00:00
|
|
|
case actToggleDown:
|
2019-11-02 03:55:26 +00:00
|
|
|
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(-1, true)
|
2015-10-12 17:24:38 +00:00
|
|
|
req(reqList)
|
|
|
|
}
|
|
|
|
case actToggleUp:
|
2019-11-02 03:55:26 +00:00
|
|
|
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(1, true)
|
2015-10-12 17:24:38 +00:00
|
|
|
req(reqList)
|
|
|
|
}
|
|
|
|
case actDown:
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(-1, true)
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqList)
|
2015-10-12 17:24:38 +00:00
|
|
|
case actUp:
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(1, true)
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqList)
|
2015-10-12 17:24:38 +00:00
|
|
|
case actAccept:
|
|
|
|
req(reqClose)
|
2017-12-01 17:27:02 +00:00
|
|
|
case actAcceptNonEmpty:
|
|
|
|
if len(t.selected) > 0 || t.merger.Length() > 0 || !t.reading && t.count == 0 {
|
|
|
|
req(reqClose)
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actClearScreen:
|
2021-12-04 02:46:15 +00:00
|
|
|
req(reqFullRedraw)
|
2019-12-07 05:44:24 +00:00
|
|
|
case actClearQuery:
|
|
|
|
t.input = []rune{}
|
|
|
|
t.cx = 0
|
|
|
|
case actClearSelection:
|
|
|
|
if t.multi > 0 {
|
|
|
|
t.selected = make(map[int32]selectedItem)
|
|
|
|
t.version++
|
|
|
|
req(reqList, reqInfo)
|
|
|
|
}
|
2020-12-29 16:51:25 +00:00
|
|
|
case actFirst:
|
2017-05-22 08:07:05 +00:00
|
|
|
t.vset(0)
|
|
|
|
req(reqList)
|
2020-12-29 16:51:25 +00:00
|
|
|
case actLast:
|
|
|
|
t.vset(t.merger.Length() - 1)
|
|
|
|
req(reqList)
|
2022-12-26 16:01:06 +00:00
|
|
|
case actPosition:
|
|
|
|
if n, e := strconv.Atoi(a.a); e == nil {
|
|
|
|
if n > 0 {
|
|
|
|
n--
|
|
|
|
} else if n < 0 {
|
|
|
|
n += t.merger.Length()
|
|
|
|
}
|
|
|
|
t.vset(n)
|
|
|
|
req(reqList)
|
|
|
|
}
|
2022-12-27 10:54:46 +00:00
|
|
|
case actPut:
|
|
|
|
str := []rune(a.a)
|
|
|
|
suffix := copySlice(t.input[t.cx:])
|
|
|
|
t.input = append(append(t.input[:t.cx], str...), suffix...)
|
|
|
|
t.cx += len(str)
|
2015-10-12 17:24:38 +00:00
|
|
|
case actUnixLineDiscard:
|
2020-06-07 14:07:03 +00:00
|
|
|
beof = len(t.input) == 0
|
2015-10-12 17:24:38 +00:00
|
|
|
if t.cx > 0 {
|
|
|
|
t.yanked = copySlice(t.input[:t.cx])
|
|
|
|
t.input = t.input[t.cx:]
|
|
|
|
t.cx = 0
|
2015-07-26 15:06:44 +00:00
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actUnixWordRubout:
|
2020-06-07 14:07:03 +00:00
|
|
|
beof = len(t.input) == 0
|
2015-10-12 17:24:38 +00:00
|
|
|
if t.cx > 0 {
|
|
|
|
t.rubout("\\s\\S")
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actBackwardKillWord:
|
2020-06-07 14:07:03 +00:00
|
|
|
beof = len(t.input) == 0
|
2015-10-12 17:24:38 +00:00
|
|
|
if t.cx > 0 {
|
2017-01-15 10:42:28 +00:00
|
|
|
t.rubout(t.wordRubout)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actYank:
|
|
|
|
suffix := copySlice(t.input[t.cx:])
|
|
|
|
t.input = append(append(t.input[:t.cx], t.yanked...), suffix...)
|
|
|
|
t.cx += len(t.yanked)
|
|
|
|
case actPageUp:
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(t.maxItems()-1, false)
|
2015-10-12 17:24:38 +00:00
|
|
|
req(reqList)
|
|
|
|
case actPageDown:
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(-(t.maxItems() - 1), false)
|
2015-10-12 17:24:38 +00:00
|
|
|
req(reqList)
|
2017-01-16 02:58:13 +00:00
|
|
|
case actHalfPageUp:
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(t.maxItems()/2, false)
|
2017-01-16 02:58:13 +00:00
|
|
|
req(reqList)
|
|
|
|
case actHalfPageDown:
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(-(t.maxItems() / 2), false)
|
2017-01-16 02:58:13 +00:00
|
|
|
req(reqList)
|
2016-05-17 17:06:52 +00:00
|
|
|
case actJump:
|
|
|
|
t.jumping = jumpEnabled
|
|
|
|
req(reqJump)
|
|
|
|
case actJumpAccept:
|
|
|
|
t.jumping = jumpAcceptEnabled
|
|
|
|
req(reqJump)
|
2015-10-12 17:24:38 +00:00
|
|
|
case actBackwardWord:
|
2017-01-15 10:42:28 +00:00
|
|
|
t.cx = findLastMatch(t.wordRubout, string(t.input[:t.cx])) + 1
|
2015-10-12 17:24:38 +00:00
|
|
|
case actForwardWord:
|
2017-01-15 10:42:28 +00:00
|
|
|
t.cx += findFirstMatch(t.wordNext, string(t.input[t.cx:])) + 1
|
2015-10-12 17:24:38 +00:00
|
|
|
case actKillWord:
|
|
|
|
ncx := t.cx +
|
2017-01-15 10:42:28 +00:00
|
|
|
findFirstMatch(t.wordNext, string(t.input[t.cx:])) + 1
|
2015-10-12 17:24:38 +00:00
|
|
|
if ncx > t.cx {
|
|
|
|
t.yanked = copySlice(t.input[t.cx:ncx])
|
|
|
|
t.input = append(t.input[:t.cx], t.input[ncx:]...)
|
|
|
|
}
|
|
|
|
case actKillLine:
|
|
|
|
if t.cx < len(t.input) {
|
|
|
|
t.yanked = copySlice(t.input[t.cx:])
|
|
|
|
t.input = t.input[:t.cx]
|
|
|
|
}
|
|
|
|
case actRune:
|
|
|
|
prefix := copySlice(t.input[:t.cx])
|
|
|
|
t.input = append(append(prefix, event.Char), t.input[t.cx:]...)
|
|
|
|
t.cx++
|
2022-12-10 13:42:56 +00:00
|
|
|
case actPrevHistory:
|
2015-10-12 17:24:38 +00:00
|
|
|
if t.history != nil {
|
|
|
|
t.history.override(string(t.input))
|
2017-08-09 14:25:32 +00:00
|
|
|
t.input = trimQuery(t.history.previous())
|
2015-10-12 17:24:38 +00:00
|
|
|
t.cx = len(t.input)
|
|
|
|
}
|
|
|
|
case actNextHistory:
|
|
|
|
if t.history != nil {
|
|
|
|
t.history.override(string(t.input))
|
2017-08-09 14:25:32 +00:00
|
|
|
t.input = trimQuery(t.history.next())
|
2015-10-12 17:24:38 +00:00
|
|
|
t.cx = len(t.input)
|
|
|
|
}
|
2021-01-02 15:00:40 +00:00
|
|
|
case actToggleSearch:
|
|
|
|
t.paused = !t.paused
|
|
|
|
changed = !t.paused
|
|
|
|
req(reqPrompt)
|
2023-04-22 06:48:51 +00:00
|
|
|
case actToggleTrack:
|
2023-04-22 14:39:35 +00:00
|
|
|
switch t.track {
|
|
|
|
case trackEnabled:
|
|
|
|
t.track = trackDisabled
|
|
|
|
case trackDisabled:
|
|
|
|
t.track = trackEnabled
|
|
|
|
}
|
|
|
|
req(reqInfo)
|
2023-07-25 13:11:15 +00:00
|
|
|
case actToggleHeader:
|
|
|
|
t.headerVisible = !t.headerVisible
|
|
|
|
req(reqList, reqInfo, reqPrompt, reqHeader)
|
2023-04-22 14:39:35 +00:00
|
|
|
case actTrack:
|
|
|
|
if t.track == trackDisabled {
|
|
|
|
t.track = trackCurrent
|
|
|
|
}
|
2023-04-22 06:48:51 +00:00
|
|
|
req(reqInfo)
|
2021-01-02 15:00:40 +00:00
|
|
|
case actEnableSearch:
|
|
|
|
t.paused = false
|
|
|
|
changed = true
|
|
|
|
req(reqPrompt)
|
|
|
|
case actDisableSearch:
|
|
|
|
t.paused = true
|
|
|
|
req(reqPrompt)
|
2017-04-28 13:58:08 +00:00
|
|
|
case actSigStop:
|
|
|
|
p, err := os.FindProcess(os.Getpid())
|
|
|
|
if err == nil {
|
2021-01-02 15:43:56 +00:00
|
|
|
t.sigstop = true
|
2017-04-28 13:58:08 +00:00
|
|
|
t.tui.Clear()
|
|
|
|
t.tui.Pause(t.fullscreen)
|
|
|
|
notifyStop(p)
|
|
|
|
t.mutex.Unlock()
|
|
|
|
return false
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actMouse:
|
|
|
|
me := event.MouseEvent
|
|
|
|
mx, my := me.X, me.Y
|
2023-01-02 16:46:37 +00:00
|
|
|
clicked := !wasDown && me.Down
|
|
|
|
wasDown = me.Down
|
|
|
|
if !me.Down {
|
|
|
|
barDragging = false
|
2023-01-06 06:36:12 +00:00
|
|
|
pbarDragging = false
|
2023-01-02 16:46:37 +00:00
|
|
|
previewDraggingPos = -1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scrolling
|
2015-10-12 17:24:38 +00:00
|
|
|
if me.S != 0 {
|
2016-06-11 10:59:12 +00:00
|
|
|
if t.window.Enclose(my, mx) && t.merger.Length() > 0 {
|
2019-11-02 03:55:26 +00:00
|
|
|
if t.multi > 0 && me.Mod {
|
2015-07-26 14:02:04 +00:00
|
|
|
toggle()
|
|
|
|
}
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(me.S, true)
|
2015-07-26 14:02:04 +00:00
|
|
|
req(reqList)
|
2017-01-23 15:23:16 +00:00
|
|
|
} else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) {
|
2020-12-31 03:54:58 +00:00
|
|
|
scrollPreviewBy(-me.S)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2023-01-02 16:46:37 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Preview dragging
|
2023-01-06 06:36:12 +00:00
|
|
|
if me.Down && (previewDraggingPos >= 0 || clicked && t.hasPreviewWindow() && t.pwindow.Enclose(my, mx)) {
|
2023-01-02 16:46:37 +00:00
|
|
|
if previewDraggingPos > 0 {
|
|
|
|
scrollPreviewBy(previewDraggingPos - my)
|
2015-10-12 17:24:38 +00:00
|
|
|
}
|
2023-01-02 16:46:37 +00:00
|
|
|
previewDraggingPos = my
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2023-02-22 15:36:04 +00:00
|
|
|
// Preview scrollbar dragging
|
2023-01-06 06:36:12 +00:00
|
|
|
headerLines := t.previewOpts.headerLines
|
|
|
|
pbarDragging = me.Down && (pbarDragging || clicked && t.hasPreviewWindow() && my >= t.pwindow.Top()+headerLines && my < t.pwindow.Top()+t.pwindow.Height() && mx == t.pwindow.Left()+t.pwindow.Width())
|
|
|
|
if pbarDragging {
|
|
|
|
effectiveHeight := t.pwindow.Height() - headerLines
|
|
|
|
numLines := len(t.previewer.lines) - headerLines
|
|
|
|
barLength, _ := getScrollbar(numLines, effectiveHeight, util.Min(numLines-effectiveHeight, t.previewer.offset-headerLines))
|
|
|
|
if barLength > 0 {
|
|
|
|
y := my - t.pwindow.Top() - headerLines - barLength/2
|
|
|
|
y = util.Constrain(y, 0, effectiveHeight-barLength)
|
|
|
|
// offset = (total - maxItems) * barStart / (maxItems - barLength)
|
|
|
|
t.previewer.offset = headerLines + int(math.Ceil(float64(y)*float64(numLines-effectiveHeight)/float64(effectiveHeight-barLength)))
|
2023-01-12 14:12:26 +00:00
|
|
|
t.previewer.following.Set(t.previewer.offset >= numLines-effectiveHeight)
|
2023-01-06 06:36:12 +00:00
|
|
|
req(reqPreviewRefresh)
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2023-01-02 16:46:37 +00:00
|
|
|
// Ignored
|
|
|
|
if !t.window.Enclose(my, mx) && !barDragging {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Translate coordinates
|
|
|
|
mx -= t.window.Left()
|
|
|
|
my -= t.window.Top()
|
2023-07-25 13:11:15 +00:00
|
|
|
min := 2 + t.visibleHeaderLines()
|
2023-01-02 16:46:37 +00:00
|
|
|
if t.noInfoLine() {
|
|
|
|
min--
|
|
|
|
}
|
|
|
|
h := t.window.Height()
|
|
|
|
switch t.layout {
|
|
|
|
case layoutDefault:
|
|
|
|
my = h - my - 1
|
|
|
|
case layoutReverseList:
|
|
|
|
if my < h-min {
|
|
|
|
my += min
|
|
|
|
} else {
|
2018-06-09 16:41:50 +00:00
|
|
|
my = h - my - 1
|
2023-01-02 16:46:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scrollbar dragging
|
|
|
|
barDragging = me.Down && (barDragging || clicked && my >= min && mx == t.window.Width()-1)
|
|
|
|
if barDragging {
|
|
|
|
barLength, barStart := t.getScrollbar()
|
|
|
|
if barLength > 0 {
|
|
|
|
maxItems := t.maxItems()
|
|
|
|
if newBarStart := util.Constrain(my-min-barLength/2, 0, maxItems-barLength); newBarStart != barStart {
|
|
|
|
total := t.merger.Length()
|
|
|
|
prevOffset := t.offset
|
|
|
|
// barStart = (maxItems - barLength) * t.offset / (total - maxItems)
|
|
|
|
t.offset = int(math.Ceil(float64(newBarStart) * float64(total-maxItems) / float64(maxItems-barLength)))
|
|
|
|
t.cy = t.offset + t.cy - prevOffset
|
|
|
|
req(reqList)
|
2018-06-09 16:41:50 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-02 16:46:37 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Double-click on an item
|
|
|
|
if me.Double && mx < t.window.Width()-1 {
|
|
|
|
// Double-click
|
|
|
|
if my >= min {
|
|
|
|
if t.vset(t.offset+my-min) && t.cy < t.merger.Length() {
|
|
|
|
return doActions(actionsFor(tui.DoubleClick))
|
2023-01-01 09:50:01 +00:00
|
|
|
}
|
2023-01-02 16:46:37 +00:00
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if me.Down {
|
|
|
|
mx = util.Constrain(mx-t.promptLen, 0, len(t.input))
|
|
|
|
if my == t.promptLine() && mx >= 0 {
|
|
|
|
// Prompt
|
|
|
|
t.cx = mx + t.xoffset
|
|
|
|
} else if my >= min {
|
|
|
|
// List
|
|
|
|
if t.vset(t.offset+my-min) && t.multi > 0 && me.Mod {
|
|
|
|
toggle()
|
2023-01-01 05:48:14 +00:00
|
|
|
}
|
2023-01-02 16:46:37 +00:00
|
|
|
req(reqList)
|
|
|
|
if me.Left {
|
|
|
|
return doActions(actionsFor(tui.LeftClick))
|
2015-10-12 17:24:38 +00:00
|
|
|
}
|
2023-01-02 16:46:37 +00:00
|
|
|
return doActions(actionsFor(tui.RightClick))
|
2015-10-12 17:24:38 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2022-12-29 11:03:51 +00:00
|
|
|
case actReload, actReloadSync:
|
2019-11-10 02:36:22 +00:00
|
|
|
t.failed = nil
|
|
|
|
|
2023-02-11 11:21:10 +00:00
|
|
|
valid, list := t.buildPlusList(a.a, false)
|
2019-11-10 02:36:22 +00:00
|
|
|
if !valid {
|
2019-12-06 13:34:30 +00:00
|
|
|
// We run the command even when there's no match
|
|
|
|
// 1. If the template doesn't have any slots
|
|
|
|
// 2. If the template has {q}
|
|
|
|
slot, _, query := hasPreviewFlags(a.a)
|
|
|
|
valid = !slot || query
|
2019-11-10 02:36:22 +00:00
|
|
|
}
|
|
|
|
if valid {
|
2020-07-26 15:15:25 +00:00
|
|
|
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
2019-11-10 02:36:22 +00:00
|
|
|
newCommand = &command
|
2022-12-29 11:03:51 +00:00
|
|
|
reloadSync = a.t == actReloadSync
|
2021-10-15 13:13:57 +00:00
|
|
|
t.reading = true
|
2019-11-10 02:36:22 +00:00
|
|
|
}
|
2021-05-22 04:13:55 +00:00
|
|
|
case actUnbind:
|
|
|
|
keys := parseKeyChords(a.a, "PANIC")
|
|
|
|
for key := range keys {
|
|
|
|
delete(t.keymap, key)
|
|
|
|
}
|
2022-04-04 12:54:22 +00:00
|
|
|
case actRebind:
|
|
|
|
keys := parseKeyChords(a.a, "PANIC")
|
|
|
|
for key := range keys {
|
|
|
|
if originalAction, found := t.keymapOrg[key]; found {
|
|
|
|
t.keymap[key] = originalAction
|
|
|
|
}
|
|
|
|
}
|
2021-11-30 14:37:48 +00:00
|
|
|
case actChangePreview:
|
|
|
|
if t.previewOpts.command != a.a {
|
|
|
|
t.previewOpts.command = a.a
|
2023-02-01 09:16:58 +00:00
|
|
|
updatePreviewWindow(false)
|
2021-11-30 14:37:48 +00:00
|
|
|
refreshPreview(t.previewOpts.command)
|
|
|
|
}
|
|
|
|
case actChangePreviewWindow:
|
|
|
|
currentPreviewOpts := t.previewOpts
|
|
|
|
|
|
|
|
// Reset preview options and apply the additional options
|
|
|
|
t.previewOpts = t.initialPreviewOpts
|
2021-12-04 02:46:15 +00:00
|
|
|
|
|
|
|
// Split window options
|
|
|
|
tokens := strings.Split(a.a, "|")
|
2023-05-05 06:08:08 +00:00
|
|
|
if len(tokens[0]) > 0 && t.initialPreviewOpts.hidden {
|
|
|
|
t.previewOpts.hidden = false
|
|
|
|
}
|
2021-12-04 02:46:15 +00:00
|
|
|
parsePreviewWindow(&t.previewOpts, tokens[0])
|
|
|
|
if len(tokens) > 1 {
|
|
|
|
a.a = strings.Join(append(tokens[1:], tokens[0]), "|")
|
|
|
|
}
|
2021-11-30 14:37:48 +00:00
|
|
|
|
2023-01-07 02:21:52 +00:00
|
|
|
// Full redraw
|
|
|
|
if !currentPreviewOpts.sameLayout(t.previewOpts) {
|
|
|
|
wasHidden := t.pwindow == nil
|
|
|
|
updatePreviewWindow(false)
|
2023-02-01 09:16:58 +00:00
|
|
|
if wasHidden && t.hasPreviewWindow() {
|
2023-01-07 02:21:52 +00:00
|
|
|
refreshPreview(t.previewOpts.command)
|
|
|
|
} else {
|
2021-11-30 14:37:48 +00:00
|
|
|
req(reqPreviewRefresh)
|
|
|
|
}
|
2023-01-07 02:21:52 +00:00
|
|
|
} else if !currentPreviewOpts.sameContentLayout(t.previewOpts) {
|
|
|
|
t.previewed.version = 0
|
|
|
|
req(reqPreviewRefresh)
|
|
|
|
}
|
2021-11-30 14:37:48 +00:00
|
|
|
|
2023-01-07 02:21:52 +00:00
|
|
|
// Adjust scroll offset
|
|
|
|
if t.hasPreviewWindow() && currentPreviewOpts.scroll != t.previewOpts.scroll {
|
|
|
|
scrollPreviewTo(t.evaluateScrollOffset())
|
2021-11-30 14:37:48 +00:00
|
|
|
}
|
2023-01-09 02:08:06 +00:00
|
|
|
|
|
|
|
// Resume following
|
2023-01-12 14:12:26 +00:00
|
|
|
t.previewer.following.Force(t.previewOpts.follow)
|
2022-12-10 15:59:34 +00:00
|
|
|
case actNextSelected, actPrevSelected:
|
|
|
|
if len(t.selected) > 0 {
|
|
|
|
total := t.merger.Length()
|
|
|
|
for i := 1; i < total; i++ {
|
|
|
|
y := (t.cy + i) % total
|
|
|
|
if t.layout == layoutDefault && a.t == actNextSelected ||
|
|
|
|
t.layout != layoutDefault && a.t == actPrevSelected {
|
|
|
|
y = (t.cy - i + total) % total
|
|
|
|
}
|
|
|
|
if _, found := t.selected[t.merger.Get(y).item.Index()]; found {
|
|
|
|
t.vset(y)
|
|
|
|
req(reqList)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
return true
|
|
|
|
}
|
2020-12-29 16:59:18 +00:00
|
|
|
|
2022-12-20 14:24:24 +00:00
|
|
|
if t.jumping == jumpDisabled || len(actions) > 0 {
|
|
|
|
// Break out of jump mode if any action is submitted to the server
|
|
|
|
if t.jumping != jumpDisabled {
|
|
|
|
t.jumping = jumpDisabled
|
|
|
|
req(reqList)
|
|
|
|
}
|
2022-12-17 15:22:15 +00:00
|
|
|
if len(actions) == 0 {
|
|
|
|
actions = t.keymap[event.Comparable()]
|
|
|
|
}
|
2020-12-29 16:59:18 +00:00
|
|
|
if len(actions) == 0 && event.Type == tui.Rune {
|
2021-12-04 02:46:15 +00:00
|
|
|
doAction(&action{t: actRune})
|
2020-12-29 16:59:18 +00:00
|
|
|
} else if !doActions(actions) {
|
2016-05-17 17:06:52 +00:00
|
|
|
continue
|
|
|
|
}
|
2017-02-18 14:17:29 +00:00
|
|
|
t.truncateQuery()
|
2019-11-10 02:36:22 +00:00
|
|
|
queryChanged = string(previousInput) != string(t.input)
|
|
|
|
changed = changed || queryChanged
|
2020-12-29 16:59:18 +00:00
|
|
|
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs {
|
|
|
|
if !doActions(onChanges) {
|
2017-05-22 08:07:05 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2020-12-29 16:59:18 +00:00
|
|
|
if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs {
|
|
|
|
if !doActions(onEOFs) {
|
2020-06-07 14:07:03 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2016-05-17 17:06:52 +00:00
|
|
|
} else {
|
2020-12-29 16:59:18 +00:00
|
|
|
if event.Type == tui.Rune {
|
2016-05-18 13:45:34 +00:00
|
|
|
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
|
2016-05-17 17:06:52 +00:00
|
|
|
t.cy = idx + t.offset
|
|
|
|
if t.jumping == jumpAcceptEnabled {
|
|
|
|
req(reqClose)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
t.jumping = jumpDisabled
|
|
|
|
req(reqList)
|
2015-10-12 17:36:11 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
|
2023-02-01 09:16:58 +00:00
|
|
|
if queryChanged && t.canPreview() && len(t.previewOpts.command) > 0 {
|
|
|
|
_, _, q := hasPreviewFlags(t.previewOpts.command)
|
|
|
|
if q {
|
|
|
|
t.version++
|
2018-06-09 16:40:22 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2019-03-29 06:02:31 +00:00
|
|
|
|
2019-11-10 02:36:22 +00:00
|
|
|
if queryChanged || t.cx != previousCx {
|
2019-03-29 06:02:31 +00:00
|
|
|
req(reqPrompt)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
2019-03-29 17:06:54 +00:00
|
|
|
|
2019-11-10 02:36:22 +00:00
|
|
|
if changed || newCommand != nil {
|
2023-04-29 12:27:30 +00:00
|
|
|
t.eventBox.Set(EvtSearchNew, searchRequest{sort: t.sort, sync: reloadSync, command: newCommand, changed: changed})
|
2019-03-29 17:06:54 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
for _, event := range events {
|
|
|
|
t.reqBox.Set(event, nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) constrain() {
|
2021-01-03 19:20:31 +00:00
|
|
|
// count of items to display allowed by filtering
|
2015-01-09 16:06:08 +00:00
|
|
|
count := t.merger.Length()
|
2021-01-03 19:20:31 +00:00
|
|
|
// count of lines can be displayed
|
2015-04-21 14:50:53 +00:00
|
|
|
height := t.maxItems()
|
2015-01-01 19:49:30 +00:00
|
|
|
|
2015-01-12 03:56:17 +00:00
|
|
|
t.cy = util.Constrain(t.cy, 0, count-1)
|
2021-01-03 19:20:31 +00:00
|
|
|
|
2021-11-02 12:18:29 +00:00
|
|
|
minOffset := util.Max(t.cy-height+1, 0)
|
2021-01-03 19:20:31 +00:00
|
|
|
maxOffset := util.Max(util.Min(count-height, t.cy), 0)
|
|
|
|
t.offset = util.Constrain(t.offset, minOffset, maxOffset)
|
2021-11-02 12:18:29 +00:00
|
|
|
if t.scrollOff == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
scrollOff := util.Min(height/2, t.scrollOff)
|
|
|
|
for {
|
|
|
|
prevOffset := t.offset
|
|
|
|
if t.cy-t.offset < scrollOff {
|
|
|
|
t.offset = util.Max(minOffset, t.offset-1)
|
|
|
|
}
|
|
|
|
if t.cy-t.offset >= height-scrollOff {
|
|
|
|
t.offset = util.Min(maxOffset, t.offset+1)
|
|
|
|
}
|
|
|
|
if t.offset == prevOffset {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2017-05-23 17:43:39 +00:00
|
|
|
func (t *Terminal) vmove(o int, allowCycle bool) {
|
2018-06-09 16:41:50 +00:00
|
|
|
if t.layout != layoutDefault {
|
2015-06-16 14:14:57 +00:00
|
|
|
o *= -1
|
|
|
|
}
|
|
|
|
dest := t.cy + o
|
2017-05-23 17:43:39 +00:00
|
|
|
if t.cycle && allowCycle {
|
2015-06-16 14:14:57 +00:00
|
|
|
max := t.merger.Length() - 1
|
|
|
|
if dest > max {
|
|
|
|
if t.cy == max {
|
|
|
|
dest = 0
|
|
|
|
}
|
|
|
|
} else if dest < 0 {
|
|
|
|
if t.cy == 0 {
|
|
|
|
dest = max
|
|
|
|
}
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-06-16 14:14:57 +00:00
|
|
|
t.vset(dest)
|
2015-01-10 05:50:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) vset(o int) bool {
|
2015-01-12 03:56:17 +00:00
|
|
|
t.cy = util.Constrain(o, 0, t.merger.Length()-1)
|
2015-01-10 05:50:24 +00:00
|
|
|
return t.cy == o
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-04-21 14:50:53 +00:00
|
|
|
func (t *Terminal) maxItems() int {
|
2023-07-25 13:11:15 +00:00
|
|
|
max := t.window.Height() - 2 - t.visibleHeaderLines()
|
2019-11-14 15:39:29 +00:00
|
|
|
if t.noInfoLine() {
|
2015-08-02 04:06:15 +00:00
|
|
|
max++
|
2015-04-21 14:50:53 +00:00
|
|
|
}
|
2015-07-22 04:45:38 +00:00
|
|
|
return util.Max(max, 0)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|