summaryrefslogtreecommitdiff
path: root/src
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 /src
parentb88eb72ac29b92c82a0d7c7f8d7b65380720b02c (diff)
downloadfzf-ef67a45702c01ff93e0ea99a51594c8160f66cc1.tar.gz
Add --ellipsis=.. option
Close #2432 Also see - #1769 - https://github.com/junegunn/fzf/pull/1844#issuecomment-586663660
Diffstat (limited to 'src')
-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
4 files changed, 73 insertions, 20 deletions
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)
+ }
+}