mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-17 13:33:49 -07:00
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0012183ede | ||
|
8916cbc6ab | ||
|
21ce70054f | ||
|
3ba82b6d87 | ||
|
e771c5d057 | ||
|
4e5e925e39 | ||
|
b7248d4115 | ||
|
639253840f | ||
|
710ebdf9c1 | ||
|
bb64d84ce4 | ||
|
cd1da27ff2 | ||
|
c1accc2e5b | ||
|
e4489dcbc1 | ||
|
c0d407f7ce | ||
|
461115afde | ||
|
bae1965231 | ||
|
b89c77ec9a | ||
|
1ca5f09d7b | ||
|
d79902ae59 | ||
|
77568e114f |
19
CHANGELOG.md
19
CHANGELOG.md
@@ -1,6 +1,25 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.60.3
|
||||||
|
------
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- [fish] Enable multiple history commands insertion (#4280) (@bitraid)
|
||||||
|
- [walker] Append '/' to directory entries on MSYS2 (#4281)
|
||||||
|
- Trim trailing whitespaces after processing ANSI sequences (#4282)
|
||||||
|
- Remove temp files before `become` when using `--tmux` option (#4283)
|
||||||
|
- Fix condition for using item numlines cache (#4285) (@alex-huff)
|
||||||
|
- Make `--accept-nth` compatible with `--select-1` (#4287)
|
||||||
|
- Increase the query length limit from 300 to 1000 (#4292)
|
||||||
|
- [windows] Prevent fzf from consuming user input while paused (#4260)
|
||||||
|
|
||||||
|
0.60.2
|
||||||
|
------
|
||||||
|
- Template for `--with-nth` and `--accept-nth` now supports `{n}` which evaluates to the zero-based ordinal index of the item
|
||||||
|
- Fixed a regression that caused the last field in the "nth" expression to be trimmed when a regular expression delimiter is used
|
||||||
|
- Thanks to @phanen for the fix
|
||||||
|
- Fixed 'jump' action when the pointer is an empty string
|
||||||
|
|
||||||
0.60.1
|
0.60.1
|
||||||
------
|
------
|
||||||
- Bug fixes and minor improvements
|
- Bug fixes and minor improvements
|
||||||
|
2
install
2
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.60.1
|
version=0.60.3
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
$version="0.60.1"
|
$version="0.60.3"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf\-tmux 1 "Feb 2025" "fzf 0.60.1" "fzf\-tmux - open fzf in tmux split pane"
|
.TH fzf\-tmux 1 "Mar 2025" "fzf 0.60.3" "fzf\-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf\-tmux - open fzf in tmux split pane
|
fzf\-tmux - open fzf in tmux split pane
|
||||||
|
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Feb 2025" "fzf 0.60.1" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Mar 2025" "fzf 0.60.3" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -122,7 +122,9 @@ fields.
|
|||||||
.BI "\-\-with\-nth=" "N[,..] or TEMPLATE"
|
.BI "\-\-with\-nth=" "N[,..] or TEMPLATE"
|
||||||
Transform the presentation of each line using the field index expressions.
|
Transform the presentation of each line using the field index expressions.
|
||||||
For advanced transformation, you can provide a template containing field index
|
For advanced transformation, you can provide a template containing field index
|
||||||
expressions in curly braces.
|
expressions in curly braces. When you use a template, the trailing delimiter is
|
||||||
|
stripped from each expression, giving you more control over the output.
|
||||||
|
\fB{n}\fR in template evaluates to the zero-based ordinal index of the line.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g.
|
e.g.
|
||||||
@@ -130,13 +132,16 @@ e.g.
|
|||||||
echo foo bar baz | fzf --with-nth 2..
|
echo foo bar baz | fzf --with-nth 2..
|
||||||
|
|
||||||
# Use template to rearrange fields
|
# Use template to rearrange fields
|
||||||
echo foo,bar,baz | fzf --delimiter , --with-nth '{1},{3},{2},{1..2}'
|
echo foo,bar,baz | fzf --delimiter , --with-nth '{n},{1},{3},{2},{1..2}'
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-accept\-nth=" "N[,..] or TEMPLATE"
|
.BI "\-\-accept\-nth=" "N[,..] or TEMPLATE"
|
||||||
Define which fields to print on accept. The last delimiter is stripped from the
|
Define which fields to print on accept. The last delimiter is stripped from the
|
||||||
output. For advanced transformation, you can provide a template containing
|
output. For advanced transformation, you can provide a template containing
|
||||||
field index expressions in curly braces.
|
field index expressions in curly braces. When you use a template, the trailing
|
||||||
|
delimiter is stripped from each expression, giving you more control over the
|
||||||
|
output. \fB{n}\fR in template evaluates to the zero-based ordinal index of the
|
||||||
|
line.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g.
|
e.g.
|
||||||
@@ -144,7 +149,7 @@ e.g.
|
|||||||
echo foo bar baz | fzf --accept-nth 2
|
echo foo bar baz | fzf --accept-nth 2
|
||||||
|
|
||||||
# Template
|
# Template
|
||||||
echo foo bar baz | fzf --accept-nth '1st: {1}, 2nd: {2}, 3rd: {3}'
|
echo foo bar baz | fzf --accept-nth 'Index: {n}, 1st: {1}, 2nd: {2}, 3rd: {3}'
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "+s, \-\-no\-sort"
|
.B "+s, \-\-no\-sort"
|
||||||
@@ -1719,6 +1724,9 @@ e.g.
|
|||||||
\fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all+accept'\fR
|
\fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all+accept'\fR
|
||||||
\fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all' \-\-bind 'ctrl\-a:+accept'\fR
|
\fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all' \-\-bind 'ctrl\-a:+accept'\fR
|
||||||
|
|
||||||
|
Any action after a terminal action that exits fzf, such as \fBaccept\fR or
|
||||||
|
\fBabort\fR, is ignored.
|
||||||
|
|
||||||
.SS ACTION ARGUMENT
|
.SS ACTION ARGUMENT
|
||||||
|
|
||||||
An action denoted with \fB(...)\fR suffix takes an argument.
|
An action denoted with \fB(...)\fR suffix takes an argument.
|
||||||
|
@@ -116,10 +116,9 @@ function fzf_key_bindings
|
|||||||
set -l fzf_query (commandline | string escape)
|
set -l fzf_query (commandline | string escape)
|
||||||
|
|
||||||
set -lx FZF_DEFAULT_OPTS (__fzf_defaults '' \
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults '' \
|
||||||
'--nth=2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign="\t↳ "' \
|
'--nth=2..,.. --scheme=history --multi --wrap-sign="\t↳ "' \
|
||||||
"--highlight-line --no-multi $FZF_CTRL_R_OPTS --read0 --print0" \
|
"--bind=ctrl-r:toggle-sort --highlight-line $FZF_CTRL_R_OPTS" \
|
||||||
"--bind='enter:become:string replace -a -- \n\t \n {2..} | string collect'" \
|
'--accept-nth=2.. --read0 --print0 --with-shell='(status fish-path)\\ -c)
|
||||||
'--with-shell='(status fish-path)\\ -c)
|
|
||||||
|
|
||||||
set -lx FZF_DEFAULT_OPTS_FILE
|
set -lx FZF_DEFAULT_OPTS_FILE
|
||||||
set -lx FZF_DEFAULT_COMMAND
|
set -lx FZF_DEFAULT_COMMAND
|
||||||
@@ -138,8 +137,12 @@ function fzf_key_bindings
|
|||||||
# Merge history from other sessions before searching
|
# Merge history from other sessions before searching
|
||||||
test -z "$fish_private_mode"; and builtin history merge
|
test -z "$fish_private_mode"; and builtin history merge
|
||||||
|
|
||||||
set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --query=$fzf_query)
|
if set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --query=$fzf_query | string split0)
|
||||||
and commandline -- $result
|
commandline -- (string replace -a -- \n\t \n $result[1])
|
||||||
|
test (count $result) -gt 1; and for i in $result[2..-1]
|
||||||
|
commandline -i -- (string replace -a -- \n\t \n \n$i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
end
|
end
|
||||||
|
@@ -26,7 +26,7 @@ const (
|
|||||||
previewCancelWait = 500 * time.Millisecond
|
previewCancelWait = 500 * time.Millisecond
|
||||||
previewChunkDelay = 100 * time.Millisecond
|
previewChunkDelay = 100 * time.Millisecond
|
||||||
previewDelayed = 500 * time.Millisecond
|
previewDelayed = 500 * time.Millisecond
|
||||||
maxPatternLength = 300
|
maxPatternLength = 1000
|
||||||
maxMulti = math.MaxInt32
|
maxMulti = math.MaxInt32
|
||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
|
14
src/core.go
14
src/core.go
@@ -128,13 +128,14 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transformed := nthTransformer(tokens)
|
transformed := nthTransformer(tokens, itemIndex)
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, transformed)
|
header = append(header, transformed)
|
||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
item.text, item.colors = ansiProcessor(stringBytes(transformed))
|
item.text, item.colors = ansiProcessor(stringBytes(transformed))
|
||||||
|
item.text.TrimTrailingWhitespaces()
|
||||||
item.text.Index = itemIndex
|
item.text.Index = itemIndex
|
||||||
item.origText = &data
|
item.origText = &data
|
||||||
itemIndex++
|
itemIndex++
|
||||||
@@ -476,8 +477,17 @@ func Run(opts *Options) (int, error) {
|
|||||||
if len(opts.Expect) > 0 {
|
if len(opts.Expect) > 0 {
|
||||||
opts.Printer("")
|
opts.Printer("")
|
||||||
}
|
}
|
||||||
|
transformer := func(item *Item) string {
|
||||||
|
return item.AsString(opts.Ansi)
|
||||||
|
}
|
||||||
|
if opts.AcceptNth != nil {
|
||||||
|
fn := opts.AcceptNth(opts.Delimiter)
|
||||||
|
transformer = func(item *Item) string {
|
||||||
|
return item.acceptNth(opts.Ansi, opts.Delimiter, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
|
opts.Printer(transformer(val.Get(i).item))
|
||||||
}
|
}
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
exitCode = ExitNoMatch
|
exitCode = ExitNoMatch
|
||||||
|
@@ -51,3 +51,9 @@ func (item *Item) AsString(stripAnsi bool) string {
|
|||||||
}
|
}
|
||||||
return item.text.ToString()
|
return item.text.ToString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (item *Item) acceptNth(stripAnsi bool, delimiter Delimiter, transformer func([]Token, int32) string) string {
|
||||||
|
tokens := Tokenize(item.AsString(stripAnsi), delimiter)
|
||||||
|
transformed := transformer(tokens, item.Index())
|
||||||
|
return StripLastDelimiter(transformed, delimiter)
|
||||||
|
}
|
||||||
|
@@ -544,8 +544,8 @@ type Options struct {
|
|||||||
Case Case
|
Case Case
|
||||||
Normalize bool
|
Normalize bool
|
||||||
Nth []Range
|
Nth []Range
|
||||||
WithNth func(Delimiter) func([]Token) string
|
WithNth func(Delimiter) func([]Token, int32) string
|
||||||
AcceptNth func(Delimiter) func([]Token) string
|
AcceptNth func(Delimiter) func([]Token, int32) string
|
||||||
Delimiter Delimiter
|
Delimiter Delimiter
|
||||||
Sort int
|
Sort int
|
||||||
Track trackOption
|
Track trackOption
|
||||||
@@ -769,22 +769,22 @@ func splitNth(str string) ([]Range, error) {
|
|||||||
return ranges, nil
|
return ranges, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) {
|
func nthTransformer(str string) (func(Delimiter) func([]Token, int32) string, error) {
|
||||||
// ^[0-9,-.]+$"
|
// ^[0-9,-.]+$"
|
||||||
if match, _ := regexp.MatchString("^[0-9,-.]+$", str); match {
|
if match, _ := regexp.MatchString("^[0-9,-.]+$", str); match {
|
||||||
nth, err := splitNth(str)
|
nth, err := splitNth(str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return func(Delimiter) func([]Token) string {
|
return func(Delimiter) func([]Token, int32) string {
|
||||||
return func(tokens []Token) string {
|
return func(tokens []Token, index int32) string {
|
||||||
return JoinTokens(Transform(tokens, nth))
|
return JoinTokens(Transform(tokens, nth))
|
||||||
}
|
}
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// {...} {...} ...
|
// {...} {...} ...
|
||||||
placeholder := regexp.MustCompile("{[0-9,-.]+}")
|
placeholder := regexp.MustCompile("{[0-9,-.]+}|{n}")
|
||||||
indexes := placeholder.FindAllStringIndex(str, -1)
|
indexes := placeholder.FindAllStringIndex(str, -1)
|
||||||
if indexes == nil {
|
if indexes == nil {
|
||||||
return nil, errors.New("template should include at least 1 placeholder: " + str)
|
return nil, errors.New("template should include at least 1 placeholder: " + str)
|
||||||
@@ -792,6 +792,7 @@ func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) {
|
|||||||
|
|
||||||
type NthParts struct {
|
type NthParts struct {
|
||||||
str string
|
str string
|
||||||
|
index bool
|
||||||
nth []Range
|
nth []Range
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -801,7 +802,10 @@ func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) {
|
|||||||
if idx < index[0] {
|
if idx < index[0] {
|
||||||
parts = append(parts, NthParts{str: str[idx:index[0]]})
|
parts = append(parts, NthParts{str: str[idx:index[0]]})
|
||||||
}
|
}
|
||||||
if nth, err := splitNth(str[index[0]+1 : index[1]-1]); err == nil {
|
expr := str[index[0]+1 : index[1]-1]
|
||||||
|
if expr == "n" {
|
||||||
|
parts = append(parts, NthParts{index: true})
|
||||||
|
} else if nth, err := splitNth(expr); err == nil {
|
||||||
parts = append(parts, NthParts{nth: nth})
|
parts = append(parts, NthParts{nth: nth})
|
||||||
}
|
}
|
||||||
idx = index[1]
|
idx = index[1]
|
||||||
@@ -810,12 +814,16 @@ func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) {
|
|||||||
parts = append(parts, NthParts{str: str[idx:]})
|
parts = append(parts, NthParts{str: str[idx:]})
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(delimiter Delimiter) func([]Token) string {
|
return func(delimiter Delimiter) func([]Token, int32) string {
|
||||||
return func(tokens []Token) string {
|
return func(tokens []Token, index int32) string {
|
||||||
str := ""
|
str := ""
|
||||||
for _, holder := range parts {
|
for _, holder := range parts {
|
||||||
if holder.nth != nil {
|
if holder.nth != nil {
|
||||||
str += StripLastDelimiter(JoinTokens(Transform(tokens, holder.nth)), delimiter)
|
str += StripLastDelimiter(JoinTokens(Transform(tokens, holder.nth)), delimiter)
|
||||||
|
} else if holder.index {
|
||||||
|
if index >= 0 {
|
||||||
|
str += strconv.Itoa(int(index))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
str += holder.str
|
str += holder.str
|
||||||
}
|
}
|
||||||
|
@@ -59,12 +59,12 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool)
|
|||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var command string
|
var command, input string
|
||||||
commandPrefix += ` --no-force-tty-in --proxy-script "$0"`
|
commandPrefix += ` --no-force-tty-in --proxy-script "$0"`
|
||||||
if opts.Input == nil && (opts.ForceTtyIn || util.IsTty(os.Stdin)) {
|
if opts.Input == nil && (opts.ForceTtyIn || util.IsTty(os.Stdin)) {
|
||||||
command = fmt.Sprintf(`%s > %q`, commandPrefix, output)
|
command = fmt.Sprintf(`%s > %q`, commandPrefix, output)
|
||||||
} else {
|
} else {
|
||||||
input, err := fifo("proxy-input")
|
input, err = fifo("proxy-input")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ExitError, err
|
return ExitError, err
|
||||||
}
|
}
|
||||||
@@ -148,6 +148,9 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ExitError, err
|
return ExitError, err
|
||||||
}
|
}
|
||||||
|
os.Remove(temp)
|
||||||
|
os.Remove(input)
|
||||||
|
os.Remove(output)
|
||||||
executor.Become(ttyin, env, command)
|
executor.Become(ttyin, env, command)
|
||||||
}
|
}
|
||||||
return code, err
|
return code, err
|
||||||
|
@@ -277,6 +277,9 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
|
|||||||
ignoresFull := []string{}
|
ignoresFull := []string{}
|
||||||
ignoresSuffix := []string{}
|
ignoresSuffix := []string{}
|
||||||
sep := string(os.PathSeparator)
|
sep := string(os.PathSeparator)
|
||||||
|
if _, ok := os.LookupEnv("MSYSTEM"); ok {
|
||||||
|
sep = "/"
|
||||||
|
}
|
||||||
for _, ignore := range ignores {
|
for _, ignore := range ignores {
|
||||||
if strings.ContainsRune(ignore, os.PathSeparator) {
|
if strings.ContainsRune(ignore, os.PathSeparator) {
|
||||||
if strings.HasPrefix(ignore, sep) {
|
if strings.HasPrefix(ignore, sep) {
|
||||||
|
@@ -305,7 +305,7 @@ type Terminal struct {
|
|||||||
nthAttr tui.Attr
|
nthAttr tui.Attr
|
||||||
nth []Range
|
nth []Range
|
||||||
nthCurrent []Range
|
nthCurrent []Range
|
||||||
acceptNth func([]Token) string
|
acceptNth func([]Token, int32) string
|
||||||
tabstop int
|
tabstop int
|
||||||
margin [4]sizeSpec
|
margin [4]sizeSpec
|
||||||
padding [4]sizeSpec
|
padding [4]sizeSpec
|
||||||
@@ -1347,7 +1347,7 @@ func (t *Terminal) numItemLines(item *Item, atMost int) (int, bool) {
|
|||||||
}
|
}
|
||||||
if cached, prs := t.numLinesCache[item.Index()]; prs {
|
if cached, prs := t.numLinesCache[item.Index()]; prs {
|
||||||
// Can we use this cache? Let's be conservative.
|
// Can we use this cache? Let's be conservative.
|
||||||
if cached.atMost >= atMost {
|
if cached.atMost <= atMost {
|
||||||
return cached.numLines, false
|
return cached.numLines, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1575,9 +1575,7 @@ func (t *Terminal) output() bool {
|
|||||||
}
|
}
|
||||||
if t.acceptNth != nil {
|
if t.acceptNth != nil {
|
||||||
transform = func(item *Item) string {
|
transform = func(item *Item) string {
|
||||||
tokens := Tokenize(item.AsString(t.ansi), t.delimiter)
|
return item.acceptNth(t.ansi, t.delimiter, t.acceptNth)
|
||||||
transformed := t.acceptNth(tokens)
|
|
||||||
return StripLastDelimiter(transformed, t.delimiter)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
found := len(t.selected) > 0
|
found := len(t.selected) > 0
|
||||||
@@ -2288,7 +2286,11 @@ func (t *Terminal) move(y int, x int, clear bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) truncateQuery() {
|
func (t *Terminal) truncateQuery() {
|
||||||
t.input, _ = t.trimRight(t.input, maxPatternLength)
|
// We're limiting the length of the query not to make fzf unresponsive when
|
||||||
|
// the user accidentally pastes a huge chunk of text. Therefore, we're not
|
||||||
|
// interested in the exact display width of the query. We just limit the
|
||||||
|
// number of runes.
|
||||||
|
t.input = t.input[:util.Min(len(t.input), maxPatternLength)]
|
||||||
t.cx = util.Constrain(t.cx, 0, len(t.input))
|
t.cx = util.Constrain(t.cx, 0, len(t.input))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2755,11 +2757,15 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
|||||||
item := result.item
|
item := result.item
|
||||||
_, selected := t.selected[item.Index()]
|
_, selected := t.selected[item.Index()]
|
||||||
label := ""
|
label := ""
|
||||||
|
extraWidth := 0
|
||||||
if t.jumping != jumpDisabled {
|
if t.jumping != jumpDisabled {
|
||||||
if index < len(t.jumpLabels) {
|
if index < len(t.jumpLabels) {
|
||||||
// Striped
|
// Striped
|
||||||
current = index%2 == 0
|
current = index%2 == 0
|
||||||
label = t.jumpLabels[index:index+1] + strings.Repeat(" ", t.pointerLen-1)
|
label = t.jumpLabels[index:index+1] + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
|
||||||
|
if t.pointerLen == 0 {
|
||||||
|
extraWidth = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if current {
|
} else if current {
|
||||||
label = t.pointer
|
label = t.pointer
|
||||||
@@ -2788,6 +2794,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
|||||||
|
|
||||||
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
|
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
|
||||||
postTask := func(lineNum int, width int, wrapped bool, forceRedraw bool) {
|
postTask := func(lineNum int, width int, wrapped bool, forceRedraw bool) {
|
||||||
|
width += extraWidth
|
||||||
if (current || selected) && t.highlightLine {
|
if (current || selected) && t.highlightLine {
|
||||||
color := tui.ColSelected
|
color := tui.ColSelected
|
||||||
if current {
|
if current {
|
||||||
|
@@ -225,9 +225,11 @@ func StripLastDelimiter(str string, delimiter Delimiter) string {
|
|||||||
locs := delimiter.regex.FindAllStringIndex(str, -1)
|
locs := delimiter.regex.FindAllStringIndex(str, -1)
|
||||||
if len(locs) > 0 {
|
if len(locs) > 0 {
|
||||||
lastLoc := locs[len(locs)-1]
|
lastLoc := locs[len(locs)-1]
|
||||||
|
if lastLoc[1] == len(str) {
|
||||||
str = str[:lastLoc[0]]
|
str = str[:lastLoc[0]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return strings.TrimRightFunc(str, unicode.IsSpace)
|
return strings.TrimRightFunc(str, unicode.IsSpace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
@@ -95,7 +96,6 @@ func (r *LightRenderer) flushRaw(sequence string) {
|
|||||||
|
|
||||||
// Light renderer
|
// Light renderer
|
||||||
type LightRenderer struct {
|
type LightRenderer struct {
|
||||||
closed *util.AtomicBool
|
|
||||||
theme *ColorTheme
|
theme *ColorTheme
|
||||||
mouse bool
|
mouse bool
|
||||||
forceBlack bool
|
forceBlack bool
|
||||||
@@ -120,6 +120,7 @@ type LightRenderer struct {
|
|||||||
showCursor bool
|
showCursor bool
|
||||||
|
|
||||||
// Windows only
|
// Windows only
|
||||||
|
mutex sync.Mutex
|
||||||
ttyinChannel chan byte
|
ttyinChannel chan byte
|
||||||
inHandle uintptr
|
inHandle uintptr
|
||||||
outHandle uintptr
|
outHandle uintptr
|
||||||
@@ -151,7 +152,6 @@ func NewLightRenderer(ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse
|
|||||||
out = os.Stderr
|
out = os.Stderr
|
||||||
}
|
}
|
||||||
r := LightRenderer{
|
r := LightRenderer{
|
||||||
closed: util.NewAtomicBool(false),
|
|
||||||
theme: theme,
|
theme: theme,
|
||||||
forceBlack: forceBlack,
|
forceBlack: forceBlack,
|
||||||
mouse: mouse,
|
mouse: mouse,
|
||||||
@@ -775,9 +775,8 @@ func (r *LightRenderer) Close() {
|
|||||||
}
|
}
|
||||||
r.disableMouse()
|
r.disableMouse()
|
||||||
r.flush()
|
r.flush()
|
||||||
r.closePlatform()
|
|
||||||
r.restoreTerminal()
|
r.restoreTerminal()
|
||||||
r.closed.Set(true)
|
r.closePlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Top() int {
|
func (r *LightRenderer) Top() int {
|
||||||
|
@@ -18,6 +18,7 @@ const (
|
|||||||
var (
|
var (
|
||||||
consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)
|
consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)
|
||||||
consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)
|
consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)
|
||||||
|
counter = uint64(0)
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsLightRendererSupported checks to see if the Light renderer is supported
|
// IsLightRendererSupported checks to see if the Light renderer is supported
|
||||||
@@ -61,27 +62,11 @@ func (r *LightRenderer) initPlatform() error {
|
|||||||
}
|
}
|
||||||
r.inHandle = uintptr(inHandle)
|
r.inHandle = uintptr(inHandle)
|
||||||
|
|
||||||
r.setupTerminal()
|
|
||||||
|
|
||||||
// channel for non-blocking reads. Buffer to make sure
|
// channel for non-blocking reads. Buffer to make sure
|
||||||
// we get the ESC sets:
|
// we get the ESC sets:
|
||||||
r.ttyinChannel = make(chan byte, 1024)
|
r.ttyinChannel = make(chan byte, 1024)
|
||||||
|
|
||||||
// the following allows for non-blocking IO.
|
r.setupTerminal()
|
||||||
// syscall.SetNonblock() is a NOOP under Windows.
|
|
||||||
go func() {
|
|
||||||
fd := int(r.inHandle)
|
|
||||||
b := make([]byte, 1)
|
|
||||||
for !r.closed.Get() {
|
|
||||||
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
|
||||||
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
|
||||||
|
|
||||||
_, err := util.Read(fd, b)
|
|
||||||
if err == nil {
|
|
||||||
r.ttyinChannel <- b[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -100,18 +85,42 @@ func openTtyOut() (*os.File, error) {
|
|||||||
return os.Stderr, nil
|
return os.Stderr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) setupTerminal() error {
|
func (r *LightRenderer) setupTerminal() {
|
||||||
if err := windows.SetConsoleMode(windows.Handle(r.outHandle), consoleFlagsOutput); err != nil {
|
windows.SetConsoleMode(windows.Handle(r.outHandle), consoleFlagsOutput)
|
||||||
return err
|
windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
||||||
|
|
||||||
|
// The following allows for non-blocking IO.
|
||||||
|
// syscall.SetNonblock() is a NOOP under Windows.
|
||||||
|
current := counter
|
||||||
|
go func() {
|
||||||
|
fd := int(r.inHandle)
|
||||||
|
b := make([]byte, 1)
|
||||||
|
for {
|
||||||
|
if _, err := util.Read(fd, b); err == nil {
|
||||||
|
r.mutex.Lock()
|
||||||
|
// This condition prevents the goroutine from running after the renderer
|
||||||
|
// has been closed or paused.
|
||||||
|
if current != counter {
|
||||||
|
r.mutex.Unlock()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
return windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
r.ttyinChannel <- b[0]
|
||||||
|
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
||||||
|
windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
||||||
|
r.mutex.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) restoreTerminal() error {
|
func (r *LightRenderer) restoreTerminal() {
|
||||||
if err := windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput); err != nil {
|
r.mutex.Lock()
|
||||||
return err
|
counter++
|
||||||
}
|
// We're setting ENABLE_VIRTUAL_TERMINAL_INPUT to allow escape sequences to be read during 'execute'.
|
||||||
return windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
|
// e.g. fzf --bind 'enter:execute:less {}'
|
||||||
|
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput|windows.ENABLE_VIRTUAL_TERMINAL_INPUT)
|
||||||
|
windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
|
||||||
|
r.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Size() TermSize {
|
func (r *LightRenderer) Size() TermSize {
|
||||||
|
@@ -184,6 +184,11 @@ func (chars *Chars) TrailingWhitespaces() int {
|
|||||||
return whitespaces
|
return whitespaces
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) TrimTrailingWhitespaces() {
|
||||||
|
whitespaces := chars.TrailingWhitespaces()
|
||||||
|
chars.slice = chars.slice[0 : len(chars.slice)-whitespaces]
|
||||||
|
}
|
||||||
|
|
||||||
func (chars *Chars) TrimSuffix(runes []rune) {
|
func (chars *Chars) TrimSuffix(runes []rune) {
|
||||||
lastIdx := len(chars.slice)
|
lastIdx := len(chars.slice)
|
||||||
firstIdx := lastIdx - len(runes)
|
firstIdx := lastIdx - len(runes)
|
||||||
|
@@ -238,6 +238,11 @@ class TestCore < TestInteractive
|
|||||||
assert_equal %w[5555 55], fzf_output_lines
|
assert_equal %w[5555 55], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_select_1_accept_nth
|
||||||
|
tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 5555, :'1', :accept_nth, '"{1} // {1}"')}", :Enter
|
||||||
|
assert_equal ['5555', '55 // 55'], fzf_output_lines
|
||||||
|
end
|
||||||
|
|
||||||
def test_exit_0
|
def test_exit_0
|
||||||
tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 555_555, :'0')}", :Enter
|
tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 555_555, :'0')}", :Enter
|
||||||
assert_equal %w[555555], fzf_output_lines
|
assert_equal %w[555555], fzf_output_lines
|
||||||
@@ -827,6 +832,24 @@ class TestCore < TestInteractive
|
|||||||
tmux.until { |lines| assert(lines.any? { it.include?('jump cancelled at 3') }) }
|
tmux.until { |lines| assert(lines.any? { it.include?('jump cancelled at 3') }) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_jump_no_pointer
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --pointer= --jump-labels 12345 --bind ctrl-j:jump", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.send_keys 'C-j'
|
||||||
|
tmux.until { |lines| assert_equal '5 5', lines[-7] }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.until { |lines| assert_equal ' 5', lines[-7] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_jump_no_pointer_no_marker
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --pointer= --marker= --jump-labels 12345 --bind ctrl-j:jump", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.send_keys 'C-j'
|
||||||
|
tmux.until { |lines| assert_equal '55', lines[-7] }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.until { |lines| assert_equal '5', lines[-7] }
|
||||||
|
end
|
||||||
|
|
||||||
def test_pointer
|
def test_pointer
|
||||||
tmux.send_keys "seq 10 | #{fzf("--pointer '>>'")}", :Enter
|
tmux.send_keys "seq 10 | #{fzf("--pointer '>>'")}", :Enter
|
||||||
# Assert that specified pointer is displayed
|
# Assert that specified pointer is displayed
|
||||||
@@ -1773,12 +1796,21 @@ class TestCore < TestInteractive
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_accept_nth_template
|
def test_accept_nth_regex_delimiter_strip_last
|
||||||
tmux.send_keys %(echo "foo ,bar,baz" | #{FZF} -d, --accept-nth '1st: {1}, 3rd: {3}, 2nd: {2}' --sync --bind start:accept > #{tempname}), :Enter
|
tmux.send_keys %((echo "foo:,bar:,baz"; echo "foo:,bar:,baz:,qux:,") | #{FZF} --multi --delimiter='[:,]+' --accept-nth 2.. --sync --bind 'load:select-all+accept' > #{tempname}), :Enter
|
||||||
wait do
|
wait do
|
||||||
assert_path_exists tempname
|
assert_path_exists tempname
|
||||||
# Last delimiter and the whitespaces are removed
|
# Last delimiter and the whitespaces are removed
|
||||||
assert_equal ['1st: foo, 3rd: baz, 2nd: bar'], File.readlines(tempname, chomp: true)
|
assert_equal ['bar:,baz', 'bar:,baz:,qux'], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_accept_nth_template
|
||||||
|
tmux.send_keys %(echo "foo ,bar,baz" | #{FZF} -d, --accept-nth '[{n}] 1st: {1}, 3rd: {3}, 2nd: {2}' --sync --bind start:accept > #{tempname}), :Enter
|
||||||
|
wait do
|
||||||
|
assert_path_exists tempname
|
||||||
|
# Last delimiter and the whitespaces are removed
|
||||||
|
assert_equal ['[0] 1st: foo, 3rd: baz, 2nd: bar'], File.readlines(tempname, chomp: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -482,4 +482,36 @@ class TestFish < TestBase
|
|||||||
tmux.send_keys "set -g #{name} '#{val}'", :Enter
|
tmux.send_keys "set -g #{name} '#{val}'", :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_ctrl_r_multi
|
||||||
|
tmux.send_keys ':', :Enter
|
||||||
|
tmux.send_keys 'echo "foo', :Enter, 'bar"', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'echo "bar', :Enter, 'foo"', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'C-l', 'C-r'
|
||||||
|
block = <<~BLOCK
|
||||||
|
echo "foo
|
||||||
|
bar"
|
||||||
|
echo "bar
|
||||||
|
foo"
|
||||||
|
BLOCK
|
||||||
|
tmux.until do |lines|
|
||||||
|
block.lines.each_with_index do |line, idx|
|
||||||
|
assert_includes lines[-6 + idx], line.chomp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
tmux.send_keys :BTab, :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[-2], '(2)' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
echo "bar
|
||||||
|
foo"
|
||||||
|
echo "foo
|
||||||
|
bar"
|
||||||
|
BLOCK
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal block.lines.map(&:chomp), lines
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user