From a442fe0fd0b42c76c5506e84cbf60ac1f8bec9c3 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Mon, 5 Dec 2016 02:13:47 +0900 Subject: [PATCH] Truncate long lines in preview window Add `:wrap` to --preview-window to wrap lines instead Close #756 --- src/options.go | 67 ++++++++++++++++++++++++--------------------- src/options_test.go | 19 ++++++++++--- src/terminal.go | 26 ++++++++++++++++-- src/tui/ncurses.go | 8 ++++++ src/tui/tcell.go | 10 ++++++- 5 files changed, 92 insertions(+), 38 deletions(-) diff --git a/src/options.go b/src/options.go index 694084e2..6fd3f6c6 100644 --- a/src/options.go +++ b/src/options.go @@ -66,7 +66,7 @@ const usage = `usage: fzf [options] Preview --preview=COMMAND Command to preview highlighted line ({}) --preview-window=OPT Preview window layout (default: right:50%) - [up|down|left|right][:SIZE[%]][:hidden] + [up|down|left|right][:SIZE[%]][:wrap][:hidden] Scripting -q, --query=STR Start the finder with the given query @@ -126,6 +126,7 @@ type previewOpts struct { position windowPosition size sizeSpec hidden bool + wrap bool } // Options stores the values of command-line options @@ -207,7 +208,7 @@ func defaultOptions() *Options { Expect: make(map[int]string), Keymap: make(map[int]actionType), Execmap: make(map[int]string), - Preview: previewOpts{"", posRight, sizeSpec{50, true}, false}, + Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false}, PrintQuery: false, ReadZero: false, Printer: func(str string) { fmt.Println(str) }, @@ -760,39 +761,43 @@ func parseSize(str string, maxPercent float64, label string) sizeSpec { } func parsePreviewWindow(opts *previewOpts, input string) { - layout := input + // Default + opts.position = posRight + opts.size = sizeSpec{50, true} opts.hidden = false - if strings.HasSuffix(layout, ":hidden") { - opts.hidden = true - layout = strings.TrimSuffix(layout, ":hidden") - } - - tokens := strings.Split(layout, ":") - if len(tokens) == 0 || len(tokens) > 2 { - errorExit("invalid window layout: " + input) - } - - if len(tokens) > 1 { - opts.size = parseSize(tokens[1], 99, "window size") - } else { - opts.size = sizeSpec{50, true} + opts.wrap = false + + tokens := strings.Split(input, ":") + sizeRegex := regexp.MustCompile("^[1-9][0-9]*%?$") + for _, token := range tokens { + switch token { + case "hidden": + opts.hidden = true + case "wrap": + opts.wrap = true + case "up", "top": + opts.position = posUp + case "down", "bottom": + opts.position = posDown + case "left": + opts.position = posLeft + case "right": + opts.position = posRight + default: + if sizeRegex.MatchString(token) { + opts.size = parseSize(token, 99, "window size") + } else { + errorExit("invalid preview window layout: " + input) + } + } } if !opts.size.percent && opts.size.size > 0 { // Adjust size for border opts.size.size += 2 - } - - switch tokens[0] { - case "up": - opts.position = posUp - case "down": - opts.position = posDown - case "left": - opts.position = posLeft - case "right": - opts.position = posRight - default: - errorExit("invalid window position: " + input) + // And padding + if opts.position == posLeft || opts.position == posRight { + opts.size.size += 2 + } } } @@ -997,7 +1002,7 @@ func parseOptions(opts *Options, allArgs []string) { opts.Preview.command = "" case "--preview-window": parsePreviewWindow(&opts.Preview, - nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]]")) + nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:wrap][:hidden]")) case "--no-margin": opts.Margin = defaultMargin() case "--margin": diff --git a/src/options_test.go b/src/options_test.go index 092efe4d..07616fcf 100644 --- a/src/options_test.go +++ b/src/options_test.go @@ -378,26 +378,37 @@ func TestPreviewOpts(t *testing.T) { opts := optsFor() if !(opts.Preview.command == "" && opts.Preview.hidden == false && + opts.Preview.wrap == false && opts.Preview.position == posRight && opts.Preview.size.percent == true && opts.Preview.size.size == 50) { t.Error() } - opts = optsFor("--preview", "cat {}", "--preview-window=left:15:hidden") + opts = optsFor("--preview", "cat {}", "--preview-window=left:15:hidden:wrap") if !(opts.Preview.command == "cat {}" && opts.Preview.hidden == true && + opts.Preview.wrap == true && opts.Preview.position == posLeft && opts.Preview.size.percent == false && - opts.Preview.size.size == 15+2) { + opts.Preview.size.size == 15+2+2) { t.Error(opts.Preview) } - - opts = optsFor("--preview-window=left:15:hidden", "--preview-window=down") + opts = optsFor("--preview-window=up:15:wrap:hidden", "--preview-window=down") if !(opts.Preview.command == "" && opts.Preview.hidden == false && + opts.Preview.wrap == false && opts.Preview.position == posDown && opts.Preview.size.percent == true && opts.Preview.size.size == 50) { t.Error(opts.Preview) } + opts = optsFor("--preview-window=up:15:wrap:hidden") + if !(opts.Preview.command == "" && + opts.Preview.hidden == true && + opts.Preview.wrap == true && + opts.Preview.position == posUp && + opts.Preview.size.percent == false && + opts.Preview.size.size == 15+2) { + t.Error(opts.Preview) + } } diff --git a/src/terminal.go b/src/terminal.go index 971cd2b5..8a52980d 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -488,7 +488,14 @@ func (t *Terminal) resizeWindows() { if t.isPreviewEnabled() { createPreviewWindow := func(y int, x int, w int, h int) { t.bwindow = tui.NewWindow(y, x, w, h, true) - t.pwindow = tui.NewWindow(y+1, x+2, w-4, h-2, false) + pwidth := w - 4 + // ncurses auto-wraps the line when the cursor reaches the right-end of + // the window. To prevent unintended line-wraps, we use the width one + // column larger than the desired value. + if !t.preview.wrap && tui.DoesAutoWrap() { + pwidth += 1 + } + t.pwindow = tui.NewWindow(y+1, x+2, pwidth, h-2, false) } switch t.preview.position { case posUp: @@ -657,7 +664,7 @@ func trimRight(runes []rune, width int) ([]rune, int) { l := 0 for idx, r := range runes { l += runeWidth(r, l) - if idx > 0 && l > width { + if l > width { return runes[:idx], len(runes) - idx } } @@ -828,6 +835,21 @@ func (t *Terminal) printPreview() { return true } } + if !t.preview.wrap { + lines := strings.Split(str, "\n") + for i, line := range lines { + limit := t.pwindow.Width + if tui.DoesAutoWrap() { + limit -= 1 + } + if i == 0 { + limit -= t.pwindow.X() + } + trimmed, _ := trimRight([]rune(line), limit) + lines[i] = string(trimmed) + } + str = strings.Join(lines, "\n") + } if ansi != nil && ansi.colored() { return t.pwindow.CFill(str, ansi.fg, ansi.bg, ansi.attr) } diff --git a/src/tui/ncurses.go b/src/tui/ncurses.go index 6a09b24d..2c1947f1 100644 --- a/src/tui/ncurses.go +++ b/src/tui/ncurses.go @@ -273,6 +273,14 @@ func (w *Window) Erase() { C.werase(w.win()) } +func (w *Window) X() int { + return int(C.getcurx(w.win())) +} + +func DoesAutoWrap() bool { + return true +} + func (w *Window) Fill(str string) bool { return C.waddstr(w.win(), C.CString(str)) == C.OK } diff --git a/src/tui/tcell.go b/src/tui/tcell.go index 4a8f502d..1793836a 100644 --- a/src/tui/tcell.go +++ b/src/tui/tcell.go @@ -172,6 +172,14 @@ func (w *Window) win() *WindowTcell { return (*WindowTcell)(w.impl) } +func (w *Window) X() int { + return w.impl.LastX +} + +func DoesAutoWrap() bool { + return false +} + func Clear() { _screen.Sync() _screen.Clear() @@ -521,7 +529,7 @@ func (w *Window) FillString(text string, pair ColorPair, a Attr) bool { var xPos = w.Left + w.win().LastX + lx // word wrap: - if xPos > (w.Left + w.Width) { + if xPos >= (w.Left + w.Width) { w.win().LastY++ w.win().LastX = 0 lx = 0