From 1b08f43f82af507165ec773922f4793da3cd9b4b Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Sat, 13 Mar 2021 02:24:37 +0900 Subject: [PATCH] Advanced preview scroll offset expression to better support fixed header --- CHANGELOG.md | 15 +++++++++++++ man/man1/fzf-tmux.1 | 2 +- man/man1/fzf.1 | 35 ++++++++++++++++++------------ src/options.go | 8 +++---- src/options_test.go | 6 +++--- src/terminal.go | 52 +++++++++++++++++++-------------------------- 6 files changed, 67 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8242290..78fed7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,21 @@ CHANGELOG # Display top 3 lines as the fixed header fzf --preview 'bat --style=header,grid --color=always {}' --preview-window '~3' ``` +- More advanced preview offset expression to better support the fixed header + ```sh + # Preview with bat, matching line in the middle of the window below + # the fixed header of the top 3 lines + # + # ~3 Top 3 lines as the fixed header + # +{2} Base scroll offset extracted from the second field + # +3 Extra offset to compensate for the 3-line header + # /2 Put in the middle of the preview area + # + git grep --line-number '' | + fzf --delimiter : \ + --preview 'bat --style=full --color=always --highlight-line {2} {1}' \ + --preview-window '~3:+{2}+3/2' + ``` - Added `select` and `deselect` action for unconditionally selecting or deselecting a single item in `--multi` mode. Complements `toggle` action. - Sigificant performance improvement in ANSI code processing diff --git a/man/man1/fzf-tmux.1 b/man/man1/fzf-tmux.1 index 21cd013..7aae95a 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 "Feb 2021" "fzf 0.25.1" "fzf-tmux - open fzf in tmux split pane" +.TH fzf-tmux 1 "Mar 2021" "fzf 0.26.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 d438894..54f9fe0 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 "Feb 2021" "fzf 0.25.1" "fzf - a command-line fuzzy finder" +.TH fzf 1 "Mar 2021" "fzf 0.26.0" "fzf - a command-line fuzzy finder" .SH NAME fzf - a command-line fuzzy finder @@ -442,7 +442,7 @@ e.g. done'\fR .RE .TP -.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]follow][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:~HEADER_LINES][:default]" +.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]follow][:[no]cycle][:[no]hidden][:+SCROLL[OFFSETS][/DENOM]][:~HEADER_LINES][:default]" .RS .B POSITION: (default: right) @@ -480,12 +480,14 @@ e.g. \fBrounded\fR (border with rounded edges, default), \fBsharp\fR (border with sharp edges), or \fBnoborder\fR (no border). -* \fB+SCROLL[-OFFSET]\fR determines the initial scroll offset of the preview -window. \fBSCROLL\fR can be either a numeric integer or a single-field index -expression that refers to a numeric integer. The optional \fB-OFFSET\fR part is -for adjusting the base offset so that you can see the text above it. It should -be given as a numeric integer (\fB-INTEGER\fR), or as a denominator form -(\fB-/INTEGER\fR) for specifying a fraction of the preview window height. +* \fB[:+SCROLL[OFFSETS][/DENOM]]\fR determines the initial scroll offset of the +preview window. + + - \fBSCROLL\fR can be either a numeric integer or a single-field index expression that refers to a numeric integer. + + - The optional \fBOFFSETS\fR part is for adjusting the base offset. It should be given as a series of signed integers (\fB-INTEGER\fR or \fB+INTEGER\fR). + + - The final \fB/DENOM\fR part is for specifying a fraction of the preview window height. * \fB~HEADER_LINES\fR keeps the top N lines as the fixed header so that they are always visible. @@ -501,16 +503,23 @@ e.g. # Initial scroll offset is set to the line number of each line of # git grep output *minus* 5 lines (-5) git grep --line-number '' | - fzf --delimiter : --preview 'nl {1}' --preview-window +{2}-5 + fzf --delimiter : --preview 'nl {1}' --preview-window '+{2}-5' - # Preview with bat, matching line in the middle of the window (-/2) + # Preview with bat, matching line in the middle of the window below + # the fixed header of the top 3 lines + # + # ~3 Top 3 lines as the fixed header + # +{2} Base scroll offset extracted from the second field + # +3 Extra offset to compensate for the 3-line header + # /2 Put in the middle of the preview area + # git grep --line-number '' | fzf --delimiter : \\ - --preview 'bat --style=numbers --color=always --highlight-line {2} {1}' \\ - --preview-window +{2}-/2\fR + --preview 'bat --style=full --color=always --highlight-line {2} {1}' \\ + --preview-window '~3:+{2}+3/2' # Display top 3 lines as the fixed header - fzf --preview 'bat --style=header,grid --color=always {}' --preview-window '~3' + fzf --preview 'bat --style=full --color=always {}' --preview-window '~3'\fR .RE .SS Scripting diff --git a/src/options.go b/src/options.go index 29c9037..8d0afe0 100644 --- a/src/options.go +++ b/src/options.go @@ -84,7 +84,7 @@ const usage = `usage: fzf [options] [up|down|left|right][:SIZE[%]] [:[no]wrap][:[no]cycle][:[no]follow][:[no]hidden] [:rounded|sharp|noborder] - [:+SCROLL[-OFFSET]][:~HEADER_LINES] + [:+SCROLL[OFFSETS][/DENOM]][:~HEADER_LINES] [:default] Scripting @@ -1078,7 +1078,7 @@ func parseInfoStyle(str string) infoStyle { func parsePreviewWindow(opts *previewOpts, input string) { tokens := strings.Split(input, ":") sizeRegex := regexp.MustCompile("^[0-9]+%?$") - offsetRegex := regexp.MustCompile("^\\+([0-9]+|{-?[0-9]+})(-[0-9]+|-/[1-9][0-9]*)?$") + offsetRegex := regexp.MustCompile(`^(\+{-?[0-9]+})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`) headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$") for _, token := range tokens { switch token { @@ -1121,7 +1121,7 @@ func parsePreviewWindow(opts *previewOpts, input string) { } else if sizeRegex.MatchString(token) { opts.size = parseSize(token, 99, "window size") } else if offsetRegex.MatchString(token) { - opts.scroll = token[1:] + opts.scroll = token } else { errorExit("invalid preview window option: " + token) } @@ -1368,7 +1368,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[%]][:rounded|sharp|noborder][:wrap][:cycle][:hidden][:+SCROLL[-OFFSET]][:~HEADER_LINES][:default]")) + nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:rounded|sharp|noborder][:wrap][:cycle][:hidden][:+SCROLL[OFFSETS][/DENOM]][:~HEADER_LINES][:default]")) case "--height": opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]")) case "--min-height": diff --git a/src/options_test.go b/src/options_test.go index 791b55d..ce13541 100644 --- a/src/options_test.go +++ b/src/options_test.go @@ -389,18 +389,18 @@ func TestPreviewOpts(t *testing.T) { opts.Preview.hidden == true && opts.Preview.wrap == true && opts.Preview.position == posLeft && - opts.Preview.scroll == "{1}-/2" && + opts.Preview.scroll == "+{1}-/2" && opts.Preview.size.percent == false && opts.Preview.size.size == 15) { t.Error(opts.Preview) } - opts = optsFor("--preview-window=up:15:wrap:hidden:+{1}-/2", "--preview-window=down", "--preview-window=cycle") + opts = optsFor("--preview-window=up:15:wrap:hidden:+{1}+3-1-2/2", "--preview-window=down", "--preview-window=cycle") if !(opts.Preview.command == "" && opts.Preview.hidden == true && opts.Preview.wrap == true && opts.Preview.cycle == true && opts.Preview.position == posDown && - opts.Preview.scroll == "{1}-/2" && + opts.Preview.scroll == "+{1}+3-1-2/2" && opts.Preview.size.percent == false && opts.Preview.size.size == 15) { t.Error(opts.Preview.size.size) diff --git a/src/terminal.go b/src/terminal.go index 9fa1121..53a3276 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -22,8 +22,9 @@ import ( // import "github.com/pkg/profile" var placeholder *regexp.Regexp -var numericPrefix *regexp.Regexp var whiteSuffix *regexp.Regexp +var offsetComponentRegex *regexp.Regexp +var offsetTrimCharsRegex *regexp.Regexp var activeTempFiles []string const ellipsis string = ".." @@ -31,8 +32,9 @@ const clearCode string = "\x1b[2J" func init() { placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`) - numericPrefix = regexp.MustCompile(`^[[:punct:]]*([0-9]+)`) whiteSuffix = regexp.MustCompile(`\s*$`) + offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`) + offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`) activeTempFiles = []string{} } @@ -1591,43 +1593,33 @@ func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input str template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list) } -// Ascii to positive integer -func atopi(s string) int { - matches := numericPrefix.FindStringSubmatch(s) - if len(matches) < 2 { - return 0 - } - n, e := strconv.Atoi(matches[1]) - if e != nil || n < 1 { - return 0 - } - return n -} - func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int { - offsetExpr := t.replacePlaceholder(t.previewOpts.scroll, false, "", list) - nums := strings.Split(offsetExpr, "-") - switch len(nums) { - case 0: - return 0 - case 1, 2: - base := atopi(nums[0]) - if base == 0 { + offsetExpr := offsetTrimCharsRegex.ReplaceAllString( + t.replacePlaceholder(t.previewOpts.scroll, false, "", list), "") + + atoi := func(s string) int { + n, e := strconv.Atoi(s) + if e != nil { return 0 - } else if len(nums) == 1 { - return base - 1 } - if nums[1][0] == '/' { - denom := atopi(nums[1][1:]) + return n + } + + base := -1 + for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) { + if strings.HasPrefix(component, "-/") { + component = component[1:] + } + if component[0] == '/' { + denom := atoi(component[1:]) if denom == 0 { return base } return base - height/denom } - return base - atopi(nums[1]) - 1 - default: - return 0 + base += atoi(component) } + return base } func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {