mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-12-23 11:29:01 +00:00
Add --ellipsis=.. option
Close #2432 Also see - #1769 - https://github.com/junegunn/fzf/pull/1844#issuecomment-586663660
This commit is contained in:
parent
b88eb72ac2
commit
ef67a45702
13
CHANGELOG.md
13
CHANGELOG.md
@ -1,6 +1,19 @@
|
|||||||
CHANGELOG
|
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
|
0.29.0
|
||||||
------
|
------
|
||||||
- Added `change-preview(...)` action to change the `--preview` command
|
- Added `change-preview(...)` action to change the `--preview` command
|
||||||
|
@ -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 "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
|
.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 "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
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@ -302,6 +302,9 @@ lines that follow.
|
|||||||
.TP
|
.TP
|
||||||
.B "--header-first"
|
.B "--header-first"
|
||||||
Print header before the prompt line
|
Print header before the prompt line
|
||||||
|
.TP
|
||||||
|
.BI "--ellipsis=" "STR"
|
||||||
|
Ellipsis to show when line is truncated (default: '..')
|
||||||
.SS Display
|
.SS Display
|
||||||
.TP
|
.TP
|
||||||
.B "--ansi"
|
.B "--ansi"
|
||||||
|
@ -70,6 +70,7 @@ const usage = `usage: fzf [options]
|
|||||||
--header=STR String to print as header
|
--header=STR String to print as header
|
||||||
--header-lines=N The first N lines of the input are treated as header
|
--header-lines=N The first N lines of the input are treated as header
|
||||||
--header-first Print header before the prompt line
|
--header-first Print header before the prompt line
|
||||||
|
--ellipsis=STR Ellipsis to show when line is truncated (default: '..')
|
||||||
|
|
||||||
Display
|
Display
|
||||||
--ansi Enable processing of ANSI color codes
|
--ansi Enable processing of ANSI color codes
|
||||||
@ -235,6 +236,7 @@ type Options struct {
|
|||||||
Header []string
|
Header []string
|
||||||
HeaderLines int
|
HeaderLines int
|
||||||
HeaderFirst bool
|
HeaderFirst bool
|
||||||
|
Ellipsis string
|
||||||
Margin [4]sizeSpec
|
Margin [4]sizeSpec
|
||||||
Padding [4]sizeSpec
|
Padding [4]sizeSpec
|
||||||
BorderShape tui.BorderShape
|
BorderShape tui.BorderShape
|
||||||
@ -298,6 +300,7 @@ func defaultOptions() *Options {
|
|||||||
Header: make([]string, 0),
|
Header: make([]string, 0),
|
||||||
HeaderLines: 0,
|
HeaderLines: 0,
|
||||||
HeaderFirst: false,
|
HeaderFirst: false,
|
||||||
|
Ellipsis: "..",
|
||||||
Margin: defaultMargin(),
|
Margin: defaultMargin(),
|
||||||
Padding: defaultMargin(),
|
Padding: defaultMargin(),
|
||||||
Unicode: true,
|
Unicode: true,
|
||||||
@ -1280,6 +1283,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
validateJumpLabels := false
|
validateJumpLabels := false
|
||||||
validatePointer := false
|
validatePointer := false
|
||||||
validateMarker := false
|
validateMarker := false
|
||||||
|
validateEllipsis := false
|
||||||
for i := 0; i < len(allArgs); i++ {
|
for i := 0; i < len(allArgs); i++ {
|
||||||
arg := allArgs[i]
|
arg := allArgs[i]
|
||||||
switch arg {
|
switch arg {
|
||||||
@ -1465,6 +1469,9 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.HeaderFirst = true
|
opts.HeaderFirst = true
|
||||||
case "--no-header-first":
|
case "--no-header-first":
|
||||||
opts.HeaderFirst = false
|
opts.HeaderFirst = false
|
||||||
|
case "--ellipsis":
|
||||||
|
opts.Ellipsis = nextString(allArgs, &i, "ellipsis string required")
|
||||||
|
validateEllipsis = true
|
||||||
case "--preview":
|
case "--preview":
|
||||||
opts.Preview.command = nextString(allArgs, &i, "preview command required")
|
opts.Preview.command = nextString(allArgs, &i, "preview command required")
|
||||||
case "--no-preview":
|
case "--no-preview":
|
||||||
@ -1562,6 +1569,9 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Header = strLines(value)
|
opts.Header = strLines(value)
|
||||||
} else if match, value := optString(arg, "--header-lines="); match {
|
} else if match, value := optString(arg, "--header-lines="); match {
|
||||||
opts.HeaderLines = atoi(value)
|
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 {
|
} else if match, value := optString(arg, "--preview="); match {
|
||||||
opts.Preview.command = value
|
opts.Preview.command = value
|
||||||
} else if match, value := optString(arg, "--preview-window="); match {
|
} else if match, value := optString(arg, "--preview-window="); match {
|
||||||
@ -1624,6 +1634,14 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
errorExit(err.Error())
|
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 {
|
func validateSign(sign string, signOptName string) error {
|
||||||
|
@ -50,7 +50,6 @@ var offsetComponentRegex *regexp.Regexp
|
|||||||
var offsetTrimCharsRegex *regexp.Regexp
|
var offsetTrimCharsRegex *regexp.Regexp
|
||||||
var activeTempFiles []string
|
var activeTempFiles []string
|
||||||
|
|
||||||
const ellipsis string = ".."
|
|
||||||
const clearCode string = "\x1b[2J"
|
const clearCode string = "\x1b[2J"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -145,6 +144,7 @@ type Terminal struct {
|
|||||||
headerLines int
|
headerLines int
|
||||||
header []string
|
header []string
|
||||||
header0 []string
|
header0 []string
|
||||||
|
ellipsis string
|
||||||
ansi bool
|
ansi bool
|
||||||
tabstop int
|
tabstop int
|
||||||
margin [4]sizeSpec
|
margin [4]sizeSpec
|
||||||
@ -541,6 +541,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
headerLines: opts.HeaderLines,
|
headerLines: opts.HeaderLines,
|
||||||
header: header,
|
header: header,
|
||||||
header0: header,
|
header0: header,
|
||||||
|
ellipsis: opts.Ellipsis,
|
||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
tabstop: opts.Tabstop,
|
tabstop: opts.Tabstop,
|
||||||
reading: true,
|
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)
|
offsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current)
|
||||||
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
|
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)
|
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
|
||||||
if displayWidth > maxWidth {
|
if displayWidth > maxWidth {
|
||||||
transformOffsets := func(diff int32) {
|
transformOffsets := func(diff int32, rightTrim bool) {
|
||||||
for idx, offset := range offsets {
|
for idx, offset := range offsets {
|
||||||
b, e := offset.offset[0], offset.offset[1]
|
b, e := offset.offset[0], offset.offset[1]
|
||||||
b += 2 - diff
|
el := int32(len(ellipsis))
|
||||||
e += 2 - diff
|
b += el - diff
|
||||||
b = util.Max32(b, 2)
|
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[0] = b
|
||||||
offsets[idx].offset[1] = util.Max32(b, e)
|
offsets[idx].offset[1] = util.Max32(b, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t.hscroll {
|
if t.hscroll {
|
||||||
if t.keepRight && pos == nil {
|
if t.keepRight && pos == nil {
|
||||||
trimmed, diff := t.trimLeft(text, maxWidth-2)
|
trimmed, diff := t.trimLeft(text, maxWidth-ellipsisWidth)
|
||||||
transformOffsets(diff)
|
transformOffsets(diff, false)
|
||||||
text = append([]rune(ellipsis), trimmed...)
|
text = append(ellipsis, trimmed...)
|
||||||
} else if !t.overflow(text[:maxe], maxWidth-2) {
|
} else if !t.overflow(text[:maxe], maxWidth-ellipsisWidth) {
|
||||||
// Stri..
|
// Stri..
|
||||||
text, _ = t.trimRight(text, maxWidth-2)
|
text, _ = t.trimRight(text, maxWidth-ellipsisWidth)
|
||||||
text = append(text, []rune(ellipsis)...)
|
text = append(text, ellipsis...)
|
||||||
} else {
|
} else {
|
||||||
// Stri..
|
// Stri..
|
||||||
if t.overflow(text[maxe:], 2) {
|
rightTrim := false
|
||||||
text = append(text[:maxe], []rune(ellipsis)...)
|
if t.overflow(text[maxe:], ellipsisWidth) {
|
||||||
|
text = append(text[:maxe], ellipsis...)
|
||||||
|
rightTrim = true
|
||||||
}
|
}
|
||||||
// ..ri..
|
// ..ri..
|
||||||
var diff int32
|
var diff int32
|
||||||
text, diff = t.trimLeft(text, maxWidth-2)
|
text, diff = t.trimLeft(text, maxWidth-ellipsisWidth)
|
||||||
|
|
||||||
// Transform offsets
|
// Transform offsets
|
||||||
transformOffsets(diff)
|
transformOffsets(diff, rightTrim)
|
||||||
text = append([]rune(ellipsis), text...)
|
text = append(ellipsis, text...)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
text, _ = t.trimRight(text, maxWidth-2)
|
text, _ = t.trimRight(text, maxWidth-ellipsisWidth)
|
||||||
text = append(text, []rune(ellipsis)...)
|
text = append(text, ellipsis...)
|
||||||
|
|
||||||
for idx, offset := range offsets {
|
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))
|
offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,23 @@ func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int
|
|||||||
return width, -1
|
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
|
// Max returns the largest integer
|
||||||
func Max(first int, second int) int {
|
func Max(first int, second int) int {
|
||||||
if first >= second {
|
if first >= second {
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2208,6 +2208,12 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys 'a'
|
tmux.send_keys 'a'
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
|
Loading…
Reference in New Issue
Block a user