Compare commits

..

7 Commits

Author SHA1 Message Date
Junegunn Choi
c0d407f7ce 0.60.2 2025-02-23 19:52:57 +09:00
Junegunn Choi
461115afde Add support for {n} in --with-nth and --accept-nth templates
Close #4275
2025-02-23 19:47:56 +09:00
junegunn
bae1965231 Deploying to master from @ junegunn/fzf@b89c77ec9a 🚀 2025-02-23 00:02:08 +00:00
Junegunn Choi
b89c77ec9a Mention that actions after accept or abort are ignored (#4271) 2025-02-22 22:19:16 +09:00
Junegunn Choi
1ca5f09d7b Explain the difference of template from a single field index expression
Close #4272
2025-02-22 22:14:49 +09:00
Junegunn Choi
d79902ae59 Fix 'jump' when pointer is empty
Fix #4270
2025-02-22 19:05:30 +09:00
phanium
77568e114f Don't trim last field when delimiter is regex (#4266) 2025-02-21 22:21:55 +09:00
11 changed files with 85 additions and 28 deletions

View File

@@ -1,6 +1,13 @@
CHANGELOG
=========
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
------
- Bug fixes and minor improvements

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@
set -u
version=0.60.1
version=0.60.2
auto_completion=
key_bindings=
update_config=2

View File

@@ -1,4 +1,4 @@
$version="0.60.1"
$version="0.60.2"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition

View File

@@ -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
THE SOFTWARE.
..
.TH fzf\-tmux 1 "Feb 2025" "fzf 0.60.1" "fzf\-tmux - open fzf in tmux split pane"
.TH fzf\-tmux 1 "Feb 2025" "fzf 0.60.2" "fzf\-tmux - open fzf in tmux split pane"
.SH NAME
fzf\-tmux - open fzf in tmux split pane

View File

@@ -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
THE SOFTWARE.
..
.TH fzf 1 "Feb 2025" "fzf 0.60.1" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Feb 2025" "fzf 0.60.2" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -122,7 +122,9 @@ fields.
.BI "\-\-with\-nth=" "N[,..] or TEMPLATE"
Transform the presentation of each line using the field index expressions.
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
e.g.
@@ -130,13 +132,16 @@ e.g.
echo foo bar baz | fzf --with-nth 2..
# 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
.TP
.BI "\-\-accept\-nth=" "N[,..] or TEMPLATE"
Define which fields to print on accept. The last delimiter is stripped from the
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
e.g.
@@ -144,7 +149,7 @@ e.g.
echo foo bar baz | fzf --accept-nth 2
# 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
.TP
.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' \-\-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
An action denoted with \fB(...)\fR suffix takes an argument.

View File

@@ -128,7 +128,7 @@ func Run(opts *Options) (int, error) {
}
}
}
transformed := nthTransformer(tokens)
transformed := nthTransformer(tokens, itemIndex)
if len(header) < opts.HeaderLines {
header = append(header, transformed)
eventBox.Set(EvtHeader, header)

View File

@@ -544,8 +544,8 @@ type Options struct {
Case Case
Normalize bool
Nth []Range
WithNth func(Delimiter) func([]Token) string
AcceptNth func(Delimiter) func([]Token) string
WithNth func(Delimiter) func([]Token, int32) string
AcceptNth func(Delimiter) func([]Token, int32) string
Delimiter Delimiter
Sort int
Track trackOption
@@ -769,30 +769,31 @@ func splitNth(str string) ([]Range, error) {
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,-.]+$"
if match, _ := regexp.MatchString("^[0-9,-.]+$", str); match {
nth, err := splitNth(str)
if err != nil {
return nil, err
}
return func(Delimiter) func([]Token) string {
return func(tokens []Token) string {
return func(Delimiter) func([]Token, int32) string {
return func(tokens []Token, index int32) string {
return JoinTokens(Transform(tokens, nth))
}
}, nil
}
// {...} {...} ...
placeholder := regexp.MustCompile("{[0-9,-.]+}")
placeholder := regexp.MustCompile("{[0-9,-.]+}|{n}")
indexes := placeholder.FindAllStringIndex(str, -1)
if indexes == nil {
return nil, errors.New("template should include at least 1 placeholder: " + str)
}
type NthParts struct {
str string
nth []Range
str string
index bool
nth []Range
}
parts := make([]NthParts, len(indexes))
@@ -801,7 +802,10 @@ func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) {
if 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})
}
idx = index[1]
@@ -810,12 +814,16 @@ func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) {
parts = append(parts, NthParts{str: str[idx:]})
}
return func(delimiter Delimiter) func([]Token) string {
return func(tokens []Token) string {
return func(delimiter Delimiter) func([]Token, int32) string {
return func(tokens []Token, index int32) string {
str := ""
for _, holder := range parts {
if holder.nth != nil {
str += StripLastDelimiter(JoinTokens(Transform(tokens, holder.nth)), delimiter)
} else if holder.index {
if index >= 0 {
str += strconv.Itoa(int(index))
}
} else {
str += holder.str
}

View File

@@ -305,7 +305,7 @@ type Terminal struct {
nthAttr tui.Attr
nth []Range
nthCurrent []Range
acceptNth func([]Token) string
acceptNth func([]Token, int32) string
tabstop int
margin [4]sizeSpec
padding [4]sizeSpec
@@ -1576,7 +1576,7 @@ func (t *Terminal) output() bool {
if t.acceptNth != nil {
transform = func(item *Item) string {
tokens := Tokenize(item.AsString(t.ansi), t.delimiter)
transformed := t.acceptNth(tokens)
transformed := t.acceptNth(tokens, item.Index())
return StripLastDelimiter(transformed, t.delimiter)
}
}
@@ -2755,11 +2755,15 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
item := result.item
_, selected := t.selected[item.Index()]
label := ""
extraWidth := 0
if t.jumping != jumpDisabled {
if index < len(t.jumpLabels) {
// Striped
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 {
label = t.pointer
@@ -2788,6 +2792,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
postTask := func(lineNum int, width int, wrapped bool, forceRedraw bool) {
width += extraWidth
if (current || selected) && t.highlightLine {
color := tui.ColSelected
if current {

View File

@@ -225,7 +225,9 @@ func StripLastDelimiter(str string, delimiter Delimiter) string {
locs := delimiter.regex.FindAllStringIndex(str, -1)
if len(locs) > 0 {
lastLoc := locs[len(locs)-1]
str = str[:lastLoc[0]]
if lastLoc[1] == len(str) {
str = str[:lastLoc[0]]
}
}
}
return strings.TrimRightFunc(str, unicode.IsSpace)

View File

@@ -827,6 +827,24 @@ class TestCore < TestInteractive
tmux.until { |lines| assert(lines.any? { it.include?('jump cancelled at 3') }) }
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
tmux.send_keys "seq 10 | #{fzf("--pointer '>>'")}", :Enter
# Assert that specified pointer is displayed
@@ -1773,12 +1791,21 @@ class TestCore < TestInteractive
end
end
def test_accept_nth_template
tmux.send_keys %(echo "foo ,bar,baz" | #{FZF} -d, --accept-nth '1st: {1}, 3rd: {3}, 2nd: {2}' --sync --bind start:accept > #{tempname}), :Enter
def test_accept_nth_regex_delimiter_strip_last
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
assert_path_exists tempname
# 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