Projects STRLCPY fzf Commits f0bfeba7
🤬
  • Add new tiebreak: 'chunk'

    Favors the line with shorter matched chunk. A chunk is a set of
    consecutive non-whitespace characters.
    
    Unlike the default `length`, this new scheme works well with tabular input.
    
      # length prefers item #1, because the whole line is shorter,
      # chunk prefers item #2, because the matched chunk ("foo") is shorter
      fzf --height=6 --header-lines=2 --tiebreak=chunk --reverse --query=fo << "EOF"
      N | Field1 | Field2 | Field3
      - | ------ | ------ | ------
      1 | hello  | foobar | baz
      2 | world  | foo    | bazbaz
      EOF
    
    If the input does not contain any spaces, `chunk` is equivalent to
    `length`. But we're not going to set it as the default because it is
    computationally more expensive.
    
    Close #2285
    Close #2537
    - Not the exact solution to --tiebreak=length not taking --nth into account,
      but this should work. And the added benefit is that it works well even
      when --nth is not provided.
    - Adding a bonus point to the last character of a word didn't turn out great.
      The order of the result suddenly changes when you type in the last
      character in the word producing a jarring effect.
  • Loading...
  • Junegunn Choi committed 2 years ago
    f0bfeba7
    1 parent c3a7a24e
  • ■ ■ ■ ■ ■
    CHANGELOG.md
    skipped 8 lines
    9 9   - Word after whitespace characters or beginning of the string
    10 10   - Word after common delimiter characters (`/,:;|`)
    11 11   - Word after other non-word characters
    12  - ````sh
     12 + ```sh
    13 13   # foo/bar.sh` is preferred over `foo-bar.sh` on `bar`
    14  - fzf --query bar --height 4 << EOF
     14 + fzf --query=bar --height=4 << EOF
    15 15   foo-bar.sh
    16 16   foo/bar.sh
    17 17   EOF
    18 18   ```
     19 +- Added a new tiebreak `chunk`
     20 + - Favors the line with shorter matched chunk. A chunk is a set of
     21 + consecutive non-whitespace characters.
     22 + - Unlike the default `length`, this scheme works well with tabular input
     23 + ```sh
     24 + # length prefers item #1, because the whole line is shorter,
     25 + # chunk prefers item #2, because the matched chunk ("foo") is shorter
     26 + fzf --height=6 --header-lines=2 --tiebreak=chunk --reverse --query=fo << "EOF"
     27 + N | Field1 | Field2 | Field3
     28 + - | ------ | ------ | ------
     29 + 1 | hello | foobar | baz
     30 + 2 | world | foo | bazbaz
     31 + EOF
     32 + ```
     33 + - If the input does not contain any spaces, `chunk` is equivalent to
     34 + `length`. But we're not going to set it as the default because it is
     35 + computationally more expensive.
    19 36  - Bug fixes and improvements
    20 37   
    21 38  0.31.0
    skipped 1238 lines
  • ■ ■ ■ ■
    man/man1/fzf-tmux.1
    skipped 20 lines
    21 21  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    22 22  THE SOFTWARE.
    23 23  ..
    24  -.TH fzf-tmux 1 "Jul 2022" "fzf 0.31.0" "fzf-tmux - open fzf in tmux split pane"
     24 +.TH fzf-tmux 1 "Aug 2022" "fzf 0.32.0" "fzf-tmux - open fzf in tmux split pane"
    25 25   
    26 26  .SH NAME
    27 27  fzf-tmux - open fzf in tmux split pane
    skipped 42 lines
  • ■ ■ ■ ■ ■
    man/man1/fzf.1
    skipped 20 lines
    21 21  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    22 22  THE SOFTWARE.
    23 23  ..
    24  -.TH fzf 1 "Jul 2022" "fzf 0.31.0" "fzf - a command-line fuzzy finder"
     24 +.TH fzf 1 "Aug 2022" "fzf 0.32.0" "fzf - a command-line fuzzy finder"
    25 25   
    26 26  .SH NAME
    27 27  fzf - a command-line fuzzy finder
    skipped 66 lines
    94 94   
    95 95  .br
    96 96  .BR length " Prefers line with shorter length"
     97 +.br
     98 +.BR chunk " Prefers line with shorter matched chunk (delimited by whitespaces)"
    97 99  .br
    98 100  .BR begin " Prefers line with matched substring closer to the beginning"
    99 101  .br
    skipped 933 lines
  • ■ ■ ■ ■ ■
    src/options.go
    skipped 34 lines
    35 35   --tac Reverse the order of the input
    36 36   --disabled Do not perform search
    37 37   --tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
    38  - when the scores are tied [length|begin|end|index]
     38 + when the scores are tied [length|chunk|begin|end|index]
    39 39   (default: length)
    40 40   
    41 41   Interface
    skipped 83 lines
    125 125   
    126 126  const (
    127 127   byScore criterion = iota
     128 + byChunk
    128 129   byLength
    129 130   byBegin
    130 131   byEnd
    skipped 480 lines
    611 612  func parseTiebreak(str string) []criterion {
    612 613   criteria := []criterion{byScore}
    613 614   hasIndex := false
     615 + hasChunk := false
    614 616   hasLength := false
    615 617   hasBegin := false
    616 618   hasEnd := false
    skipped 10 lines
    627 629   switch str {
    628 630   case "index":
    629 631   check(&hasIndex, "index")
     632 + case "chunk":
     633 + check(&hasChunk, "chunk")
     634 + criteria = append(criteria, byChunk)
    630 635   case "length":
    631 636   check(&hasLength, "length")
    632 637   criteria = append(criteria, byLength)
    skipped 6 lines
    639 644   default:
    640 645   errorExit("invalid sort criterion: " + str)
    641 646   }
     647 + }
     648 + if len(criteria) > 4 {
     649 + errorExit("at most 3 tiebreaks are allowed: " + str)
    642 650   }
    643 651   return criteria
    644 652  }
    skipped 1132 lines
  • ■ ■ ■ ■ ■ ■
    src/result.go
    skipped 48 lines
    49 49   case byScore:
    50 50   // Higher is better
    51 51   val = math.MaxUint16 - util.AsUint16(score)
     52 + case byChunk:
     53 + b := minBegin
     54 + e := maxEnd
     55 + l := item.text.Length()
     56 + for ; b >= 1; b-- {
     57 + if unicode.IsSpace(item.text.Get(b - 1)) {
     58 + break
     59 + }
     60 + }
     61 + for ; e < l; e++ {
     62 + if unicode.IsSpace(item.text.Get(e)) {
     63 + break
     64 + }
     65 + }
     66 + val = util.AsUint16(e - b)
    52 67   case byLength:
    53 68   val = item.TrimLength()
    54 69   case byBegin, byEnd:
    skipped 190 lines
  • ■ ■ ■ ■ ■
    src/result_test.go
    skipped 53 lines
    54 54   // FIXME global
    55 55   sortCriteria = []criterion{byScore, byLength}
    56 56   
    57  - strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
     57 + str := []rune("foo")
    58 58   item1 := buildResult(
    59  - withIndex(&Item{text: util.RunesToChars(strs[0])}, 1), []Offset{}, 2)
     59 + withIndex(&Item{text: util.RunesToChars(str)}, 1), []Offset{}, 2)
    60 60   if item1.points[3] != math.MaxUint16-2 || // Bonus
    61 61   item1.points[2] != 3 || // Length
    62 62   item1.points[1] != 0 || // Unused
    skipped 2 lines
    65 65   t.Error(item1)
    66 66   }
    67 67   // Only differ in index
    68  - item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2)
     68 + item2 := buildResult(&Item{text: util.RunesToChars(str)}, []Offset{}, 2)
    69 69   
    70 70   items := []Result{item1, item2}
    71 71   sort.Sort(ByRelevance(items))
    skipped 24 lines
    96 96   items[4] == item2 && items[5] == item1) {
    97 97   t.Error(items, item1, item2, item3, item4, item5, item6)
    98 98   }
     99 +}
     100 + 
     101 +func TestChunkTiebreak(t *testing.T) {
     102 + // FIXME global
     103 + sortCriteria = []criterion{byScore, byChunk}
     104 + 
     105 + score := 100
     106 + test := func(input string, offset Offset, chunk string) {
     107 + item := buildResult(withIndex(&Item{text: util.RunesToChars([]rune(input))}, 1), []Offset{offset}, score)
     108 + if !(item.points[3] == math.MaxUint16-uint16(score) && item.points[2] == uint16(len(chunk))) {
     109 + t.Error(item.points)
     110 + }
     111 + }
     112 + test("hello foobar goodbye", Offset{8, 9}, "foobar")
     113 + test("hello foobar goodbye", Offset{7, 18}, "foobar goodbye")
     114 + test("hello foobar goodbye", Offset{0, 1}, "hello")
     115 + test("hello foobar goodbye", Offset{5, 7}, "hello foobar") // TBD
    99 116  }
    100 117   
    101 118  func TestColorOffset(t *testing.T) {
    skipped 57 lines
  • ■ ■ ■ ■ ■ ■
    test/test_go.rb
    skipped 753 lines
    754 754   assert_equal output, `#{FZF} -fh -n2 -d: < #{tempname}`.lines(chomp: true)
    755 755   end
    756 756   
     757 + def test_tiebreak_chunk
     758 + writelines(tempname, [
     759 + '1 foobarbaz baz',
     760 + '2 foobar baz',
     761 + '3 foo barbaz'
     762 + ])
     763 + 
     764 + assert_equal [
     765 + '3 foo barbaz',
     766 + '2 foobar baz',
     767 + '1 foobarbaz baz'
     768 + ], `#{FZF} -fo --tiebreak=chunk < #{tempname}`.lines(chomp: true)
     769 + end
     770 + 
    757 771   def test_invalid_cache
    758 772   tmux.send_keys "(echo d; echo D; echo x) | #{fzf('-q d')}", :Enter
    759 773   tmux.until { |lines| assert_equal ' 2/3', lines[-2] }
    skipped 1951 lines
Please wait...
Page is in error, reload to recover