mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-14 06:13:47 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d85a69a709 | ||
|
|
a425e96fb2 | ||
|
|
7763fdf6ba | ||
|
|
dd156b59fc | ||
|
|
36dceecd58 | ||
|
|
421b9b271a | ||
|
|
ed57dcb924 | ||
|
|
95c77bfb98 | ||
|
|
2e3e721344 | ||
|
|
da2c28d5c2 | ||
|
|
dbddee9de9 | ||
|
|
8731d75607 |
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,6 +1,19 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.16.3
|
||||||
|
------
|
||||||
|
- Fixed a bug where fzf incorrectly display the lines when straddling tab
|
||||||
|
characters are trimmed
|
||||||
|
- Placeholder expression used in `--preview` and `execute` action can
|
||||||
|
optionally take `+` flag to be used with multiple selections
|
||||||
|
- e.g. `git log --oneline | fzf --multi --preview 'git show {+1}'`
|
||||||
|
- Added `execute-silent` action for executing a command silently without
|
||||||
|
switching to the alternate screen. This is useful when the process is
|
||||||
|
short-lived and you're not interested in its output.
|
||||||
|
- e.g. `fzf --bind 'ctrl-y:execute!(echo -n {} | pbcopy)'`
|
||||||
|
- `ctrl-space` is allowed in `--bind`
|
||||||
|
|
||||||
0.16.2
|
0.16.2
|
||||||
------
|
------
|
||||||
- Dropped ncurses dependency
|
- Dropped ncurses dependency
|
||||||
|
|||||||
2
install
2
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.16.2
|
version=0.16.3
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
|||||||
@@ -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 "Jan 2017" "fzf 0.16.2" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Jan 2017" "fzf 0.16.3" "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 "Jan 2017" "fzf 0.16.2" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Jan 2017" "fzf 0.16.3" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -262,13 +262,21 @@ Execute the given command for the current line and display the result on the
|
|||||||
preview window. \fB{}\fR in the command is the placeholder that is replaced to
|
preview window. \fB{}\fR in the command is the placeholder that is replaced to
|
||||||
the single-quoted string of the current line. To transform the replacement
|
the single-quoted string of the current line. To transform the replacement
|
||||||
string, specify field index expressions between the braces (See \fBFIELD INDEX
|
string, specify field index expressions between the braces (See \fBFIELD INDEX
|
||||||
EXPRESSION\fR for the details). Also, \fB{q}\fR is replaced to the current
|
EXPRESSION\fR for the details).
|
||||||
query string.
|
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --preview="head -$LINES {}"\fR
|
e.g. \fBfzf --preview="head -$LINES {}"\fR
|
||||||
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
||||||
|
|
||||||
|
A placeholder expression starting with \fB+\fR flag will be replaced to the
|
||||||
|
space-separated list of the selected lines (or the current line if no selection
|
||||||
|
was made) individually quoted.
|
||||||
|
|
||||||
|
e.g. \fBfzf --multi --preview="head -10 {+}"\fR
|
||||||
|
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
|
||||||
|
|
||||||
|
Also, \fB{q}\fR is replaced to the current query string.
|
||||||
|
|
||||||
Note that you can escape a placeholder pattern by prepending a backslash.
|
Note that you can escape a placeholder pattern by prepending a backslash.
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
@@ -323,10 +331,10 @@ e.g. \fBfzf --expect=ctrl-v,ctrl-t,alt-s,f1,f2,~,@\fR
|
|||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--read0"
|
.B "--read0"
|
||||||
Read input delimited by ASCII NUL character instead of newline character
|
Read input delimited by ASCII NUL characters instead of newline characters
|
||||||
.TP
|
.TP
|
||||||
.B "--print0"
|
.B "--print0"
|
||||||
Print output delimited by ASCII NUL character instead of newline character
|
Print output delimited by ASCII NUL characters instead of newline characters
|
||||||
.TP
|
.TP
|
||||||
.B "--sync"
|
.B "--sync"
|
||||||
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
||||||
@@ -418,6 +426,7 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
|
|
||||||
.B AVAILABLE KEYS: (SYNONYMS)
|
.B AVAILABLE KEYS: (SYNONYMS)
|
||||||
\fIctrl-[a-z]\fR
|
\fIctrl-[a-z]\fR
|
||||||
|
\fIctrl-space\fR
|
||||||
\fIalt-[a-z]\fR
|
\fIalt-[a-z]\fR
|
||||||
\fIalt-[0-9]\fR
|
\fIalt-[0-9]\fR
|
||||||
\fIf[1-12]\fR
|
\fIf[1-12]\fR
|
||||||
@@ -461,7 +470,8 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||||
\fBend-of-line\fR \fIctrl-e end\fR
|
\fBend-of-line\fR \fIctrl-e end\fR
|
||||||
\fBexecute(...)\fR (see below for the details)
|
\fBexecute(...)\fR (see below for the details)
|
||||||
\fBexecute-multi(...)\fR (see below for the details)
|
\fBexecute-silent(...)\fR (see below for the details)
|
||||||
|
\fRexecute-multi(...)\fR (deprecated in favor of \fB{+}\fR expression)
|
||||||
\fBforward-char\fR \fIctrl-f right\fR
|
\fBforward-char\fR \fIctrl-f right\fR
|
||||||
\fBforward-word\fR \fIalt-f shift-right\fR
|
\fBforward-word\fR \fIalt-f shift-right\fR
|
||||||
\fBignore\fR
|
\fBignore\fR
|
||||||
@@ -487,7 +497,7 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||||
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||||
\fBtoggle-preview\fR
|
\fBtoggle-preview\fR
|
||||||
\fBtoggle-sort\fR (equivalent to \fB--toggle-sort\fR)
|
\fBtoggle-sort\fR
|
||||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||||
@@ -530,10 +540,12 @@ the closing character. The catch is that it should be the last one in the
|
|||||||
comma-separated list of key-action pairs.
|
comma-separated list of key-action pairs.
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
\fBexecute-multi(...)\fR is an alternative action that executes the command
|
fzf switches to the alternate screen when executing a command. However, if the
|
||||||
with the selected entries when multi-select is enabled (\fB--multi\fR). With
|
command is expected to complete quickly, and you are not interested in its
|
||||||
this action, \fB{}\fR is replaced with the quoted strings of the selected
|
output, you might want to use \fBexecute-silent\fR instead, which silently
|
||||||
entries separated by spaces.
|
executes the command without the switching. Note that fzf will not be
|
||||||
|
responsible until the command is complete. For asynchronous execution, start
|
||||||
|
your command as a background process (i.e. appending \fB&\fR).
|
||||||
|
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
" Copyright (c) 2016 Junegunn Choi
|
" Copyright (c) 2017 Junegunn Choi
|
||||||
"
|
"
|
||||||
" MIT License
|
" MIT License
|
||||||
"
|
"
|
||||||
@@ -301,23 +301,28 @@ try
|
|||||||
let prefix = ''
|
let prefix = ''
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0)
|
||||||
let use_height = has_key(dict, 'down') &&
|
let use_height = has_key(dict, 'down') &&
|
||||||
\ !(has('nvim') || has('win32') || has('win64') || s:present(dict, 'up', 'left', 'right')) &&
|
\ !(has('nvim') || has('win32') || has('win64') || s:present(dict, 'up', 'left', 'right')) &&
|
||||||
\ executable('tput') && filereadable('/dev/tty')
|
\ executable('tput') && filereadable('/dev/tty')
|
||||||
let tmux = !use_height && (!has('nvim') || get(g:, 'fzf_prefer_tmux', 0)) && s:tmux_enabled() && s:splittable(dict)
|
let use_term = has('nvim')
|
||||||
let term = has('nvim') && !tmux
|
let use_tmux = (!use_height && !use_term || prefer_tmux) && s:tmux_enabled() && s:splittable(dict)
|
||||||
|
if prefer_tmux && use_tmux
|
||||||
|
let use_height = 0
|
||||||
|
let use_term = 0
|
||||||
|
endif
|
||||||
if use_height
|
if use_height
|
||||||
let optstr .= ' --height='.s:calc_size(&lines, dict.down, dict)
|
let optstr .= ' --height='.s:calc_size(&lines, dict.down, dict)
|
||||||
elseif term
|
elseif use_term
|
||||||
let optstr .= ' --no-height'
|
let optstr .= ' --no-height'
|
||||||
endif
|
endif
|
||||||
let command = prefix.(tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
||||||
|
|
||||||
if term
|
if use_term
|
||||||
return s:execute_term(dict, command, temps)
|
return s:execute_term(dict, command, temps)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
let lines = tmux ? s:execute_tmux(dict, command, temps)
|
let lines = use_tmux ? s:execute_tmux(dict, command, temps)
|
||||||
\ : s:execute(dict, command, use_height, temps)
|
\ : s:execute(dict, command, use_height, temps)
|
||||||
call s:callback(dict, lines)
|
call s:callback(dict, lines)
|
||||||
return lines
|
return lines
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ __fzf_history__() (
|
|||||||
shopt -u nocaseglob nocasematch
|
shopt -u nocaseglob nocasematch
|
||||||
line=$(
|
line=$(
|
||||||
HISTTIMEFORMAT= history |
|
HISTTIMEFORMAT= history |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
||||||
command grep '^ *[0-9]') &&
|
command grep '^ *[0-9]') &&
|
||||||
if [[ $- =~ H ]]; then
|
if [[ $- =~ H ]]; then
|
||||||
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ function fzf_key_bindings
|
|||||||
function fzf-history-widget -d "Show command history"
|
function fzf-history-widget -d "Show command history"
|
||||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS +s --tiebreak=index $FZF_CTRL_R_OPTS +m"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS +s --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
|
||||||
history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
||||||
and commandline -- $result
|
and commandline -- $result
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ fzf-history-widget() {
|
|||||||
local selected num
|
local selected num
|
||||||
setopt localoptions noglobsubst pipefail 2> /dev/null
|
setopt localoptions noglobsubst pipefail 2> /dev/null
|
||||||
selected=( $(fc -l 1 |
|
selected=( $(fc -l 1 |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS --query=${(q)LBUFFER} +m" $(__fzfcmd)) )
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(q)LBUFFER} +m" $(__fzfcmd)) )
|
||||||
local ret=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
num=$selected[1]
|
num=$selected[1]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Junegunn Choi
|
Copyright (c) 2017 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Current version
|
// Current version
|
||||||
version = "0.16.2"
|
version = "0.16.3"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Package fzf implements fzf, a command-line fuzzy finder.
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Junegunn Choi
|
Copyright (c) 2017 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ const usage = `usage: fzf [options]
|
|||||||
-f, --filter=STR Filter mode. Do not start interactive finder.
|
-f, --filter=STR Filter mode. Do not start interactive finder.
|
||||||
--print-query Print query as the first line
|
--print-query Print query as the first line
|
||||||
--expect=KEYS Comma-separated list of keys to complete fzf
|
--expect=KEYS Comma-separated list of keys to complete fzf
|
||||||
|
--read0 Read input delimited by ASCII NUL characters
|
||||||
|
--print0 Print output delimited by ASCII NUL characters
|
||||||
--sync Synchronous search for multi-staged filtering
|
--sync Synchronous search for multi-staged filtering
|
||||||
|
|
||||||
Environment variables
|
Environment variables
|
||||||
@@ -391,6 +393,8 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = tui.AltZ + int(' ')
|
chord = tui.AltZ + int(' ')
|
||||||
case "bspace", "bs":
|
case "bspace", "bs":
|
||||||
chord = tui.BSpace
|
chord = tui.BSpace
|
||||||
|
case "ctrl-space":
|
||||||
|
chord = tui.CtrlSpace
|
||||||
case "alt-enter", "alt-return":
|
case "alt-enter", "alt-return":
|
||||||
chord = tui.AltEnter
|
chord = tui.AltEnter
|
||||||
case "alt-space":
|
case "alt-space":
|
||||||
@@ -579,18 +583,25 @@ const (
|
|||||||
escapedPlus = 2
|
escapedPlus = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseKeymap(keymap map[int][]action, str string) {
|
func init() {
|
||||||
if executeRegexp == nil {
|
|
||||||
// Backreferences are not supported.
|
// Backreferences are not supported.
|
||||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
"(?si):execute(-multi)?:.+|:execute(-multi)?(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
"(?si):(execute(?:-multi|-silent)?):.+|:(execute(?:-multi|-silent)?)(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseKeymap(keymap map[int][]action, str string) {
|
||||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||||
if src[len(":execute")] == '-' {
|
prefix := ":execute"
|
||||||
return ":execute-multi(" + strings.Repeat(" ", len(src)-len(":execute-multi()")) + ")"
|
if src[len(prefix)] == '-' {
|
||||||
|
c := src[len(prefix)+1]
|
||||||
|
if c == 's' || c == 'S' {
|
||||||
|
prefix += "-silent"
|
||||||
|
} else {
|
||||||
|
prefix += "-multi"
|
||||||
}
|
}
|
||||||
return ":execute(" + strings.Repeat(" ", len(src)-len(":execute()")) + ")"
|
}
|
||||||
|
return prefix + "(" + strings.Repeat(" ", len(src)-len(prefix)-2) + ")"
|
||||||
})
|
})
|
||||||
masked = strings.Replace(masked, "::", string([]rune{escapedColon, ':'}), -1)
|
masked = strings.Replace(masked, "::", string([]rune{escapedColon, ':'}), -1)
|
||||||
masked = strings.Replace(masked, ",:", string([]rune{escapedComma, ':'}), -1)
|
masked = strings.Replace(masked, ",:", string([]rune{escapedComma, ':'}), -1)
|
||||||
@@ -726,9 +737,12 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
errorExit("unknown action: " + spec)
|
errorExit("unknown action: " + spec)
|
||||||
} else {
|
} else {
|
||||||
var offset int
|
var offset int
|
||||||
if t == actExecuteMulti {
|
switch t {
|
||||||
|
case actExecuteSilent:
|
||||||
|
offset = len("execute-silent")
|
||||||
|
case actExecuteMulti:
|
||||||
offset = len("execute-multi")
|
offset = len("execute-multi")
|
||||||
} else {
|
default:
|
||||||
offset = len("execute")
|
offset = len("execute")
|
||||||
}
|
}
|
||||||
if spec[offset] == ':' {
|
if spec[offset] == ':' {
|
||||||
@@ -750,23 +764,21 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isExecuteAction(str string) actionType {
|
func isExecuteAction(str string) actionType {
|
||||||
t := actExecute
|
matches := executeRegexp.FindAllStringSubmatch(":"+str, -1)
|
||||||
if !strings.HasPrefix(str, "execute") || len(str) < len("execute(") {
|
if matches == nil || len(matches) != 1 {
|
||||||
return actIgnore
|
return actIgnore
|
||||||
}
|
}
|
||||||
|
prefix := matches[0][1]
|
||||||
b := str[len("execute")]
|
if len(prefix) == 0 {
|
||||||
if strings.HasPrefix(str, "execute-multi") {
|
prefix = matches[0][2]
|
||||||
if len(str) < len("execute-multi(") {
|
|
||||||
return actIgnore
|
|
||||||
}
|
}
|
||||||
t = actExecuteMulti
|
switch prefix {
|
||||||
b = str[len("execute-multi")]
|
case "execute":
|
||||||
}
|
return actExecute
|
||||||
e := str[len(str)-1]
|
case "execute-silent":
|
||||||
if b == ':' || b == '(' && e == ')' || b == '[' && e == ']' ||
|
return actExecuteSilent
|
||||||
b == e && strings.ContainsAny(string(b), "~!@#$%^&*;/|") {
|
case "execute-multi":
|
||||||
return t
|
return actExecuteMulti
|
||||||
}
|
}
|
||||||
return actIgnore
|
return actIgnore
|
||||||
}
|
}
|
||||||
|
|||||||
118
src/terminal.go
118
src/terminal.go
@@ -24,7 +24,7 @@ import (
|
|||||||
var placeholder *regexp.Regexp
|
var placeholder *regexp.Regexp
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
placeholder = regexp.MustCompile("\\\\?(?:{[0-9,-.]*}|{q})")
|
placeholder = regexp.MustCompile("\\\\?(?:{\\+?[0-9,-.]*}|{q})")
|
||||||
}
|
}
|
||||||
|
|
||||||
type jumpMode int
|
type jumpMode int
|
||||||
@@ -204,7 +204,8 @@ const (
|
|||||||
actPreviousHistory
|
actPreviousHistory
|
||||||
actNextHistory
|
actNextHistory
|
||||||
actExecute
|
actExecute
|
||||||
actExecuteMulti
|
actExecuteSilent
|
||||||
|
actExecuteMulti // Deprecated
|
||||||
)
|
)
|
||||||
|
|
||||||
func toActions(types ...actionType) []action {
|
func toActions(types ...actionType) []action {
|
||||||
@@ -436,9 +437,9 @@ func (t *Terminal) output() bool {
|
|||||||
}
|
}
|
||||||
found := len(t.selected) > 0
|
found := len(t.selected) > 0
|
||||||
if !found {
|
if !found {
|
||||||
cnt := t.merger.Length()
|
current := t.currentItem()
|
||||||
if cnt > 0 && cnt > t.cy {
|
if current != nil {
|
||||||
t.printer(t.current())
|
t.printer(current.AsString(t.ansi))
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -847,6 +848,7 @@ func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.Colo
|
|||||||
offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
|
offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
displayWidth = t.displayWidthWithLimit(text, 0, displayWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
var index int32
|
var index int32
|
||||||
@@ -1043,7 +1045,27 @@ func quoteEntry(entry string) string {
|
|||||||
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
||||||
}
|
}
|
||||||
|
|
||||||
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, query string, items []*Item) string {
|
func hasPlusFlag(template string) bool {
|
||||||
|
for _, match := range placeholder.FindAllString(template, -1) {
|
||||||
|
if match[0] == '\\' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if match[1] == '+' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, forcePlus bool, query string, allItems []*Item) string {
|
||||||
|
current := allItems[:1]
|
||||||
|
selected := allItems[1:]
|
||||||
|
if current[0] == nil {
|
||||||
|
current = []*Item{}
|
||||||
|
}
|
||||||
|
if selected[0] == nil {
|
||||||
|
selected = []*Item{}
|
||||||
|
}
|
||||||
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
|
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
|
||||||
// Escaped pattern
|
// Escaped pattern
|
||||||
if match[0] == '\\' {
|
if match[0] == '\\' {
|
||||||
@@ -1055,6 +1077,16 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, qu
|
|||||||
return quoteEntry(query)
|
return quoteEntry(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plusFlag := forcePlus
|
||||||
|
if match[1] == '+' {
|
||||||
|
match = "{" + match[2:]
|
||||||
|
plusFlag = true
|
||||||
|
}
|
||||||
|
items := current
|
||||||
|
if plusFlag {
|
||||||
|
items = selected
|
||||||
|
}
|
||||||
|
|
||||||
replacements := make([]string, len(items))
|
replacements := make([]string, len(items))
|
||||||
|
|
||||||
if match == "{}" {
|
if match == "{}" {
|
||||||
@@ -1095,18 +1127,27 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, qu
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) executeCommand(template string, items []*Item) {
|
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool) {
|
||||||
command := replacePlaceholder(template, t.ansi, t.delimiter, string(t.input), items)
|
valid, list := t.buildPlusList(template, forcePlus)
|
||||||
|
if !valid {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
command := replacePlaceholder(template, t.ansi, t.delimiter, forcePlus, string(t.input), list)
|
||||||
cmd := util.ExecCommand(command)
|
cmd := util.ExecCommand(command)
|
||||||
|
if !background {
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
t.tui.Pause()
|
t.tui.Pause()
|
||||||
cmd.Run()
|
cmd.Run()
|
||||||
if t.tui.Resume() {
|
if t.tui.Resume() {
|
||||||
|
t.tui.Clear()
|
||||||
t.printAll()
|
t.printAll()
|
||||||
}
|
}
|
||||||
t.refresh()
|
t.refresh()
|
||||||
|
} else {
|
||||||
|
cmd.Run()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) hasPreviewer() bool {
|
func (t *Terminal) hasPreviewer() bool {
|
||||||
@@ -1122,11 +1163,24 @@ func (t *Terminal) hasPreviewWindow() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) currentItem() *Item {
|
func (t *Terminal) currentItem() *Item {
|
||||||
|
cnt := t.merger.Length()
|
||||||
|
if cnt > 0 && cnt > t.cy {
|
||||||
return t.merger.Get(t.cy).item
|
return t.merger.Get(t.cy).item
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Terminal) current() string {
|
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
|
||||||
return t.currentItem().AsString(t.ansi)
|
current := t.currentItem()
|
||||||
|
if !forcePlus && !hasPlusFlag(template) || len(t.selected) == 0 {
|
||||||
|
return current != nil, []*Item{current, current}
|
||||||
|
}
|
||||||
|
sels := make([]*Item, len(t.selected)+1)
|
||||||
|
sels[0] = current
|
||||||
|
for i, sel := range t.sortSelected() {
|
||||||
|
sels[i+1] = sel.item
|
||||||
|
}
|
||||||
|
return true, sels
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop is called to start Terminal I/O
|
// Loop is called to start Terminal I/O
|
||||||
@@ -1183,19 +1237,20 @@ func (t *Terminal) Loop() {
|
|||||||
if t.hasPreviewer() {
|
if t.hasPreviewer() {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
var request *Item
|
var request []*Item
|
||||||
t.previewBox.Wait(func(events *util.Events) {
|
t.previewBox.Wait(func(events *util.Events) {
|
||||||
for req, value := range *events {
|
for req, value := range *events {
|
||||||
switch req {
|
switch req {
|
||||||
case reqPreviewEnqueue:
|
case reqPreviewEnqueue:
|
||||||
request = value.(*Item)
|
request = value.([]*Item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
if request != nil {
|
// We don't display preview window if no match
|
||||||
|
if request[0] != nil {
|
||||||
command := replacePlaceholder(t.preview.command,
|
command := replacePlaceholder(t.preview.command,
|
||||||
t.ansi, t.delimiter, string(t.input), []*Item{request})
|
t.ansi, t.delimiter, false, string(t.input), request)
|
||||||
cmd := util.ExecCommand(command)
|
cmd := util.ExecCommand(command)
|
||||||
out, _ := cmd.CombinedOutput()
|
out, _ := cmd.CombinedOutput()
|
||||||
t.reqBox.Set(reqPreviewDisplay, string(out))
|
t.reqBox.Set(reqPreviewDisplay, string(out))
|
||||||
@@ -1231,17 +1286,12 @@ func (t *Terminal) Loop() {
|
|||||||
t.printInfo()
|
t.printInfo()
|
||||||
case reqList:
|
case reqList:
|
||||||
t.printList()
|
t.printList()
|
||||||
cnt := t.merger.Length()
|
currentFocus := t.currentItem()
|
||||||
var currentFocus *Item
|
|
||||||
if cnt > 0 && cnt > t.cy {
|
|
||||||
currentFocus = t.currentItem()
|
|
||||||
} else {
|
|
||||||
currentFocus = nil
|
|
||||||
}
|
|
||||||
if currentFocus != focused {
|
if currentFocus != focused {
|
||||||
focused = currentFocus
|
focused = currentFocus
|
||||||
if t.isPreviewEnabled() {
|
if t.isPreviewEnabled() {
|
||||||
t.previewBox.Set(reqPreviewEnqueue, focused)
|
_, list := t.buildPlusList(t.preview.command, false)
|
||||||
|
t.previewBox.Set(reqPreviewEnqueue, list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case reqJump:
|
case reqJump:
|
||||||
@@ -1346,20 +1396,10 @@ func (t *Terminal) Loop() {
|
|||||||
doAction = func(a action, mapkey int) bool {
|
doAction = func(a action, mapkey int) bool {
|
||||||
switch a.t {
|
switch a.t {
|
||||||
case actIgnore:
|
case actIgnore:
|
||||||
case actExecute:
|
case actExecute, actExecuteSilent:
|
||||||
if t.cy >= 0 && t.cy < t.merger.Length() {
|
t.executeCommand(a.a, false, a.t == actExecuteSilent)
|
||||||
t.executeCommand(a.a, []*Item{t.currentItem()})
|
|
||||||
}
|
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
if len(t.selected) > 0 {
|
t.executeCommand(a.a, true, false)
|
||||||
sels := make([]*Item, len(t.selected))
|
|
||||||
for i, sel := range t.sortSelected() {
|
|
||||||
sels[i] = sel.item
|
|
||||||
}
|
|
||||||
t.executeCommand(a.a, sels)
|
|
||||||
} else {
|
|
||||||
return doAction(action{t: actExecute, a: a.a}, mapkey)
|
|
||||||
}
|
|
||||||
case actInvalid:
|
case actInvalid:
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
@@ -1368,9 +1408,11 @@ func (t *Terminal) Loop() {
|
|||||||
t.previewer.enabled = !t.previewer.enabled
|
t.previewer.enabled = !t.previewer.enabled
|
||||||
t.tui.Clear()
|
t.tui.Clear()
|
||||||
t.resizeWindows()
|
t.resizeWindows()
|
||||||
cnt := t.merger.Length()
|
if t.previewer.enabled {
|
||||||
if t.previewer.enabled && cnt > 0 && cnt > t.cy {
|
valid, list := t.buildPlusList(t.preview.command, false)
|
||||||
t.previewBox.Set(reqPreviewEnqueue, t.currentItem())
|
if valid {
|
||||||
|
t.previewBox.Set(reqPreviewEnqueue, list)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
req(reqList, reqInfo, reqHeader)
|
req(reqList, reqInfo, reqHeader)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ func newItem(str string) *Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReplacePlaceholder(t *testing.T) {
|
func TestReplacePlaceholder(t *testing.T) {
|
||||||
items1 := []*Item{newItem(" foo'bar \x1b[31mbaz\x1b[m")}
|
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
|
||||||
|
items1 := []*Item{item1, item1}
|
||||||
items2 := []*Item{
|
items2 := []*Item{
|
||||||
|
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
||||||
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
||||||
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
||||||
|
|
||||||
@@ -27,47 +29,65 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// {}, preserve ansi
|
// {}, preserve ansi
|
||||||
result = replacePlaceholder("echo {}", false, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {}", false, Delimiter{}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
|
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
|
||||||
|
|
||||||
// {}, strip ansi
|
// {}, strip ansi
|
||||||
result = replacePlaceholder("echo {}", true, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'")
|
check("echo ' foo'\\''bar baz'")
|
||||||
|
|
||||||
// {}, with multiple items
|
// {}, with multiple items
|
||||||
result = replacePlaceholder("echo {}", true, Delimiter{}, "query", items2)
|
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items2)
|
||||||
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
|
check("echo 'foo'\\''bar baz'")
|
||||||
|
|
||||||
// {..}, strip leading whitespaces, preserve ansi
|
// {..}, strip leading whitespaces, preserve ansi
|
||||||
result = replacePlaceholder("echo {..}", false, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {..}", false, Delimiter{}, false, "query", items1)
|
||||||
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
|
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
|
||||||
|
|
||||||
// {..}, strip leading whitespaces, strip ansi
|
// {..}, strip leading whitespaces, strip ansi
|
||||||
result = replacePlaceholder("echo {..}", true, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {..}", true, Delimiter{}, false, "query", items1)
|
||||||
check("echo 'foo'\\''bar baz'")
|
check("echo 'foo'\\''bar baz'")
|
||||||
|
|
||||||
// {q}
|
// {q}
|
||||||
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz' 'query'")
|
check("echo ' foo'\\''bar baz' 'query'")
|
||||||
|
|
||||||
// {q}, multiple items
|
// {q}, multiple items
|
||||||
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, "query 'string'", items2)
|
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, false, "query 'string'", items2)
|
||||||
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ''query '\\''string'\\''''foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
|
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ''query '\\''string'\\''''foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, false, "query 'string'", items2)
|
||||||
|
check("echo 'foo'\\''bar baz''query '\\''string'\\''''foo'\\''bar baz'")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items1)
|
||||||
check("echo 'foo'\\''bar'/'baz'/'bazfoo'\\''bar'/'baz'/'foo'\\''bar'/' foo'\\''bar baz'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
check("echo 'foo'\\''bar'/'baz'/'bazfoo'\\''bar'/'baz'/'foo'\\''bar'/' foo'\\''bar baz'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, "query", items2)
|
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items2)
|
||||||
|
check("echo 'foo'\\''bar'/'baz'/'baz'/'foo'\\''bar'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, false, "query", items2)
|
||||||
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
||||||
|
|
||||||
|
// forcePlus
|
||||||
|
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, true, "query", items2)
|
||||||
|
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
||||||
|
|
||||||
|
// No match
|
||||||
|
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, nil})
|
||||||
|
check("echo /")
|
||||||
|
|
||||||
|
// No match, but with selections
|
||||||
|
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, item1})
|
||||||
|
check("echo /' foo'\\''bar baz'")
|
||||||
|
|
||||||
// String delimiter
|
// String delimiter
|
||||||
delim := "'"
|
delim := "'"
|
||||||
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, "query", items1)
|
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
|
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
|
||||||
|
|
||||||
// Regex delimiter
|
// Regex delimiter
|
||||||
regex := regexp.MustCompile("[oa]+")
|
regex := regexp.MustCompile("[oa]+")
|
||||||
// foo'bar baz
|
// foo'bar baz
|
||||||
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, "query", items1)
|
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -322,6 +322,8 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
return Event{CtrlQ, 0, nil}
|
return Event{CtrlQ, 0, nil}
|
||||||
case 127:
|
case 127:
|
||||||
return Event{BSpace, 0, nil}
|
return Event{BSpace, 0, nil}
|
||||||
|
case 0:
|
||||||
|
return Event{CtrlSpace, 0, nil}
|
||||||
case ESC:
|
case ESC:
|
||||||
ev := r.escSequence(&sz)
|
ev := r.escSequence(&sz)
|
||||||
// Second chance
|
// Second chance
|
||||||
@@ -532,6 +534,7 @@ func (r *LightRenderer) Pause() {
|
|||||||
r.rmcup()
|
r.rmcup()
|
||||||
} else {
|
} else {
|
||||||
r.smcup()
|
r.smcup()
|
||||||
|
r.csi("H")
|
||||||
}
|
}
|
||||||
r.flush()
|
r.flush()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -475,6 +475,8 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return escSequence()
|
return escSequence()
|
||||||
case 127:
|
case 127:
|
||||||
return Event{BSpace, 0, nil}
|
return Event{BSpace, 0, nil}
|
||||||
|
case 0:
|
||||||
|
return Event{CtrlSpace, 0, nil}
|
||||||
}
|
}
|
||||||
// CTRL-A ~ CTRL-Z
|
// CTRL-A ~ CTRL-Z
|
||||||
if c >= CtrlA && c <= CtrlZ {
|
if c >= CtrlA && c <= CtrlZ {
|
||||||
|
|||||||
@@ -270,6 +270,8 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{CtrlY, 0, nil}
|
return Event{CtrlY, 0, nil}
|
||||||
case tcell.KeyCtrlZ:
|
case tcell.KeyCtrlZ:
|
||||||
return Event{CtrlZ, 0, nil}
|
return Event{CtrlZ, 0, nil}
|
||||||
|
case tcell.KeyCtrlSpace:
|
||||||
|
return Event{CtrlSpace, 0, nil}
|
||||||
case tcell.KeyBackspace, tcell.KeyBackspace2:
|
case tcell.KeyBackspace, tcell.KeyBackspace2:
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltBS, 0, nil}
|
return Event{AltBS, 0, nil}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const (
|
|||||||
CtrlY
|
CtrlY
|
||||||
CtrlZ
|
CtrlZ
|
||||||
ESC
|
ESC
|
||||||
|
CtrlSpace
|
||||||
|
|
||||||
Invalid
|
Invalid
|
||||||
Resize
|
Resize
|
||||||
|
|||||||
@@ -879,7 +879,7 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_execute_multi
|
def test_execute_multi
|
||||||
output = '/tmp/fzf-test-execute-multi'
|
output = '/tmp/fzf-test-execute-multi'
|
||||||
opts = %[--multi --bind \\"alt-a:execute-multi(echo {}/{} >> #{output}; sync)\\"]
|
opts = %[--multi --bind \\"alt-a:execute-multi(echo {}/{+} >> #{output}; sync)\\"]
|
||||||
writelines tempname, %w[foo'bar foo"bar foo$bar foobar]
|
writelines tempname, %w[foo'bar foo"bar foo$bar foobar]
|
||||||
tmux.send_keys "cat #{tempname} | #{fzf opts}", :Enter
|
tmux.send_keys "cat #{tempname} | #{fzf opts}", :Enter
|
||||||
tmux.until { |lines| lines[-2].include? '4/4' }
|
tmux.until { |lines| lines[-2].include? '4/4' }
|
||||||
@@ -902,6 +902,43 @@ class TestGoFZF < TestBase
|
|||||||
File.unlink output rescue nil
|
File.unlink output rescue nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_execute_plus_flag
|
||||||
|
output = tempname + ".tmp"
|
||||||
|
File.unlink output rescue nil
|
||||||
|
writelines tempname, ["foo bar", "123 456"]
|
||||||
|
|
||||||
|
tmux.send_keys "cat #{tempname} | #{FZF} --multi --bind 'x:execute(echo {+}/{}/{+2}/{2} >> #{output})'", :Enter
|
||||||
|
|
||||||
|
execute = lambda do
|
||||||
|
tmux.send_keys 'x', 'y'
|
||||||
|
tmux.until { |lines| lines[-2].include? '0/2' }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| lines[-2].include? '2/2' }
|
||||||
|
end
|
||||||
|
|
||||||
|
tmux.until { |lines| lines[-2].include? '2/2' }
|
||||||
|
execute.call
|
||||||
|
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
execute.call
|
||||||
|
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
execute.call
|
||||||
|
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.prepare
|
||||||
|
readonce
|
||||||
|
|
||||||
|
assert_equal [
|
||||||
|
%[foo bar/foo bar/bar/bar],
|
||||||
|
%[123 456/foo bar/456/bar],
|
||||||
|
%[123 456 foo bar/foo bar/456 bar/bar]
|
||||||
|
], File.readlines(output).map(&:chomp)
|
||||||
|
rescue
|
||||||
|
File.unlink output rescue nil
|
||||||
|
end
|
||||||
|
|
||||||
def test_execute_shell
|
def test_execute_shell
|
||||||
# Custom script to use as $SHELL
|
# Custom script to use as $SHELL
|
||||||
output = tempname + '.out'
|
output = tempname + '.out'
|
||||||
@@ -1198,7 +1235,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_preview
|
def test_preview
|
||||||
tmux.send_keys %[seq 1000 | sed s/^2$// | #{FZF} --preview 'sleep 0.2; echo {{}-{}}' --bind ?:toggle-preview], :Enter
|
tmux.send_keys %[seq 1000 | sed s/^2$// | #{FZF} -m --preview 'sleep 0.2; echo {{}-{+}}' --bind ?:toggle-preview], :Enter
|
||||||
tmux.until { |lines| lines[1].include?(' {1-1}') }
|
tmux.until { |lines| lines[1].include?(' {1-1}') }
|
||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
tmux.until { |lines| lines[1].include?(' {-}') }
|
tmux.until { |lines| lines[1].include?(' {-}') }
|
||||||
@@ -1212,6 +1249,17 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| lines[-2].start_with? ' 28/1000' }
|
tmux.until { |lines| lines[-2].start_with? ' 28/1000' }
|
||||||
tmux.send_keys 'foobar'
|
tmux.send_keys 'foobar'
|
||||||
tmux.until { |lines| !lines[1].include?('{') }
|
tmux.until { |lines| !lines[1].include?('{') }
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.until { |lines| lines.match_count == 1000 }
|
||||||
|
tmux.until { |lines| lines[1].include?(' {1-1}') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| lines[1].include?(' {-1}') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| lines[1].include?(' {3-1 }') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| lines[1].include?(' {4-1 3}') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| lines[1].include?(' {5-1 3 4}') }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_preview_hidden
|
def test_preview_hidden
|
||||||
|
|||||||
Reference in New Issue
Block a user