summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2022-03-29 21:35:36 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2022-03-29 21:35:36 +0900
commitef67a45702c01ff93e0ea99a51594c8160f66cc1 (patch)
treef7339a0133e7328dd8fbafdf56d8fc5e0dadfabd
parentb88eb72ac29b92c82a0d7c7f8d7b65380720b02c (diff)
Add --ellipsis=.. option
Close #2432 Also see - #1769 - https://github.com/junegunn/fzf/pull/1844#issuecomment-586663660
-rw-r--r--CHANGELOG.md13
-rw-r--r--man/man1/fzf-tmux.12
-rw-r--r--man/man1/fzf.15
-rw-r--r--src/options.go18
-rw-r--r--src/terminal.go48
-rw-r--r--src/util/util.go17
-rw-r--r--src/util/util_test.go10
-rwxr-xr-xtest/test_go.rb6
8 files changed, 97 insertions, 22 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cbcbea98..616d4384 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,19 @@
CHANGELOG
=========
+0.30.0
+------
+- Added `--ellipsis` option. You can take advantage of it to make fzf
+ effectively search non-visible parts of the item.
+ ```sh
+ # Search against hidden line numbers on the far right
+ nl /usr/share/dict/words |
+ awk '{printf "%s%1000s\n", $2, $1}' |
+ fzf --nth=-1 --no-hscroll --ellipsis='' |
+ awk '{print $2}'
+ ```
+- Increased TTY buffer limit (#2748)
+
0.29.0
------
- Added `change-preview(...)` action to change the `--preview` command
diff --git a/man/man1/fzf-tmux.1 b/man/man1/fzf-tmux.1
index 2601d5bf..d34edb40 100644
--- a/man/man1/fzf-tmux.1
+++ b/man/man1/fzf-tmux.1
@@ -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 "Dec 2021" "fzf 0.29.0" "fzf-tmux - open fzf in tmux split pane"
+.TH fzf-tmux 1 "Mar 2022" "fzf 0.30.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME
fzf-tmux - open fzf in tmux split pane
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index 4920dbac..d5b35390 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -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 "Dec 2021" "fzf 0.29.0" "fzf - a command-line fuzzy finder"
+.TH fzf 1 "Mar 2022" "fzf 0.30.0" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -302,6 +302,9 @@ lines that follow.
.TP
.B "--header-first"
Print header before the prompt line
+.TP
+.BI "--ellipsis=" "STR"
+Ellipsis to show when line is truncated (default: '..')
.SS Display
.TP
.B "--ansi"
diff --git a/src/options.go b/src/options.go
index b3bcdeab..cc77143d 100644
--- a/src/options.go
+++ b/src/options.go
@@ -70,6 +70,7 @@ const usage = `usage: fzf [options]
--header=STR String to print as header
--header-lines=N The first N lines of the input are treated as header
--header-first Print header before the prompt line
+ --ellipsis=STR Ellipsis to show when line is truncated (default: '..')
Display
--ansi Enable processing of ANSI color codes
@@ -235,6 +236,7 @@ type Options struct {
Header []string
HeaderLines int
HeaderFirst bool
+ Ellipsis string
Margin [4]sizeSpec
Padding [4]sizeSpec
BorderShape tui.BorderShape
@@ -298,6 +300,7 @@ func defaultOptions() *Options {
Header: make([]string, 0),
HeaderLines: 0,
HeaderFirst: false,
+ Ellipsis: "..",
Margin: defaultMargin(),
Padding: defaultMargin(),
Unicode: true,
@@ -1280,6 +1283,7 @@ func parseOptions(opts *Options, allArgs []string) {
validateJumpLabels := false
validatePointer := false
validateMarker := false
+ validateEllipsis := false
for i := 0; i < len(allArgs); i++ {
arg := allArgs[i]
switch arg {
@@ -1465,6 +1469,9 @@ func parseOptions(opts *Options, allArgs []string) {
opts.HeaderFirst = true
case "--no-header-first":
opts.HeaderFirst = false
+ case "--ellipsis":
+ opts.Ellipsis = nextString(allArgs, &i, "ellipsis string required")
+ validateEllipsis = true
case "--preview":
opts.Preview.command = nextString(allArgs, &i, "preview command required")
case "--no-preview":
@@ -1562,6 +1569,9 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Header = strLines(value)
} else if match, value := optString(arg, "--header-lines="); match {
opts.HeaderLines = atoi(value)
+ } else if match, value := optString(arg, "--ellipsis="); match {
+ opts.Ellipsis = value
+ validateEllipsis = true
} else if match, value := optString(arg, "--preview="); match {
opts.Preview.command = value
} else if match, value := optString(arg, "--preview-window="); match {
@@ -1624,6 +1634,14 @@ func parseOptions(opts *Options, allArgs []string) {
errorExit(err.Error())
}
}
+
+ if validateEllipsis {
+ for _, r := range opts.Ellipsis {
+ if !unicode.IsGraphic(r) {
+ errorExit("invalid character in ellipsis")
+ }
+ }
+ }
}
func validateSign(sign string, signOptName string) error {
diff --git a/src/terminal.go b/src/terminal.go
index e4823ad5..9ea0c1a9 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -50,7 +50,6 @@ var offsetComponentRegex *regexp.Regexp
var offsetTrimCharsRegex *regexp.Regexp
var activeTempFiles []string
-const ellipsis string = ".."
const clearCode string = "\x1b[2J"
func init() {
@@ -145,6 +144,7 @@ type Terminal struct {
headerLines int
header []string
header0 []string
+ ellipsis string
ansi bool
tabstop int
margin [4]sizeSpec
@@ -541,6 +541,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
headerLines: opts.HeaderLines,
header: header,
header0: header,
+ ellipsis: opts.Ellipsis,
ansi: opts.Ansi,
tabstop: opts.Tabstop,
reading: true,
@@ -1261,47 +1262,54 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
offsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current)
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
- maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
+ ellipsis, ellipsisWidth := util.Truncate(t.ellipsis, maxWidth/2)
+ maxe = util.Constrain(maxe+util.Min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(text))
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
if displayWidth > maxWidth {
- transformOffsets := func(diff int32) {
+ transformOffsets := func(diff int32, rightTrim bool) {
for idx, offset := range offsets {
b, e := offset.offset[0], offset.offset[1]
- b += 2 - diff
- e += 2 - diff
- b = util.Max32(b, 2)
+ el := int32(len(ellipsis))
+ b += el - diff
+ e += el - diff
+ b = util.Max32(b, el)
+ if rightTrim {
+ e = util.Min32(e, int32(maxWidth-ellipsisWidth))
+ }
offsets[idx].offset[0] = b
offsets[idx].offset[1] = util.Max32(b, e)
}
}
if t.hscroll {
if t.keepRight && pos == nil {
- trimmed, diff := t.trimLeft(text, maxWidth-2)
- transformOffsets(diff)
- text = append([]rune(ellipsis), trimmed...)
- } else if !t.overflow(text[:maxe], maxWidth-2) {
+ trimmed, diff := t.trimLeft(text, maxWidth-ellipsisWidth)
+ transformOffsets(diff, false)
+ text = append(ellipsis, trimmed...)
+ } else if !t.overflow(text[:maxe], maxWidth-ellipsisWidth) {
// Stri..
- text, _ = t.trimRight(text, maxWidth-2)
- text = append(text, []rune(ellipsis)...)
+ text, _ = t.trimRight(text, maxWidth-ellipsisWidth)
+ text = append(text, ellipsis...)
} else {
// Stri..
- if t.overflow(text[maxe:], 2) {
- text = append(text[:maxe], []rune(ellipsis)...)
+ rightTrim := false
+ if t.overflow(text[maxe:], ellipsisWidth) {
+ text = append(text[:maxe], ellipsis...)
+ rightTrim = true
}
// ..ri..
var diff int32
- text, diff = t.trimLeft(text, maxWidth-2)
+ text, diff = t.trimLeft(text, maxWidth-ellipsisWidth)
// Transform offsets
- transformOffsets(diff)
- text = append([]rune(ellipsis), text...)
+ transformOffsets(diff, rightTrim)
+ text = append(ellipsis, text...)
}
} else {
- text, _ = t.trimRight(text, maxWidth-2)
- text = append(text, []rune(ellipsis)...)
+ text, _ = t.trimRight(text, maxWidth-ellipsisWidth)
+ text = append(text, ellipsis...)
for idx, offset := range offsets {
- offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-2))
+ offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-len(ellipsis)))
offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
}
}
diff --git a/src/util/util.go b/src/util/util.go
index c3995bfd..a1c37f7a 100644
--- a/src/util/util.go
+++ b/src/util/util.go
@@ -34,6 +34,23 @@ func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int
return width, -1
}
+// Truncate returns the truncated runes and its width
+func Truncate(input string, limit int) ([]rune, int) {
+ runes := []rune{}
+ width := 0
+ gr := uniseg.NewGraphemes(input)
+ for gr.Next() {
+ rs := gr.Runes()
+ w := runewidth.StringWidth(string(rs))
+ if width+w > limit {
+ return runes, width
+ }
+ width += w
+ runes = append(runes, rs...)
+ }
+ return runes, width
+}
+
// Max returns the largest integer
func Max(first int, second int) int {
if first >= second {
diff --git a/src/util/util_test.go b/src/util/util_test.go
index 45a5a2d0..20bdb92a 100644
--- a/src/util/util_test.go
+++ b/src/util/util_test.go
@@ -54,3 +54,13 @@ func TestRunesWidth(t *testing.T) {
}
}
}
+
+func TestTruncate(t *testing.T) {
+ truncated, width := Truncate("가나다라마", 7)
+ if string(truncated) != "가나다" {
+ t.Errorf("Expected: 가나다, actual: %s", string(truncated))
+ }
+ if width != 6 {
+ t.Errorf("Expected: 6, actual: %d", width)
+ }
+}
diff --git a/test/test_go.rb b/test/test_go.rb
index 95759bfa..bd950604 100755
--- a/test/test_go.rb
+++ b/test/test_go.rb
@@ -2208,6 +2208,12 @@ class TestGoFZF < TestBase
tmux.send_keys 'a'
end
end
+
+ def test_ellipsis
+ tmux.send_keys 'seq 1000 | tr "\n" , | fzf --ellipsis=SNIPSNIP -e -q500', :Enter
+ tmux.until { |lines| assert_equal 1, lines.match_count }
+ tmux.until { |lines| assert_match(/^> SNIPSNIP.*SNIPSNIP$/, lines[-3]) }
+ end
end
module TestShell