mirror of
https://github.com/junegunn/fzf.git
synced 2025-07-31 04:02:01 -07:00
Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
9f30ca2923 | ||
|
37f2d8f795 | ||
|
400e443a0a | ||
|
0a8d2996dc | ||
|
cfdb00b971 | ||
|
9b9ad39143 | ||
|
0541c0dbcf | ||
|
47b11cb8b4 | ||
|
d3da310b92 | ||
|
93e0a6a9de | ||
|
ac549a853a | ||
|
053af9a1c8 | ||
|
60112def02 | ||
|
2134c0c8a9 | ||
|
3222d62ddf | ||
|
aeb957a285 | ||
|
154cf22ffa | ||
|
51f532697e | ||
|
01b88539ba | ||
|
3066b206af | ||
|
04492bab10 |
20
CHANGELOG.md
20
CHANGELOG.md
@@ -1,6 +1,26 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.15.5
|
||||
------
|
||||
- Setting foreground color will no longer set background color to black
|
||||
- e.g. `fzf --color fg:153`
|
||||
- `--tiebreak=end` will consider relative position instead of absolute distance
|
||||
- Updated `fzf#wrap` function to respect `g:fzf_colors`
|
||||
|
||||
0.15.4
|
||||
------
|
||||
- Added support for range expression in preview and execute action
|
||||
- e.g. `ls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1`
|
||||
- `{q}` will be replaced to the single-quoted string of the current query
|
||||
- Fixed to properly handle unicode whitespace characters
|
||||
- Display scroll indicator in preview window
|
||||
- Inverse search term will use exact matcher by default
|
||||
- This is a breaking change, but I believe it makes much more sense. It is
|
||||
almost impossible to predict which entries will be filtered out due to
|
||||
a fuzzy inverse term. You can still perform inverse-fuzzy-match by
|
||||
prepending `!'` to the term.
|
||||
|
||||
0.15.3
|
||||
------
|
||||
- Added support for more ANSI attributes: dim, underline, blink, and reverse
|
||||
|
25
README.md
25
README.md
@@ -113,16 +113,16 @@ vim $(fzf)
|
||||
|
||||
Unless otherwise specified, fzf starts in "extended-search mode" where you can
|
||||
type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt
|
||||
!rmx`
|
||||
!fire`
|
||||
|
||||
| Token | Match type | Description |
|
||||
| -------- | -------------------- | -------------------------------- |
|
||||
| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` |
|
||||
| `^music` | prefix-exact-match | Items that start with `music` |
|
||||
| `.mp3$` | suffix-exact-match | Items that end with `.mp3` |
|
||||
| `'wild` | exact-match (quoted) | Items that include `wild` |
|
||||
| `!rmx` | inverse-fuzzy-match | Items that do not match `rmx` |
|
||||
| `!'fire` | inverse-exact-match | Items that do not include `fire` |
|
||||
| Token | Match type | Description |
|
||||
| -------- | -------------------------- | --------------------------------- |
|
||||
| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` |
|
||||
| `^music` | prefix-exact-match | Items that start with `music` |
|
||||
| `.mp3$` | suffix-exact-match | Items that end with `.mp3` |
|
||||
| `'wild` | exact-match (quoted) | Items that include `wild` |
|
||||
| `!fire` | inverse-exact-match | Items that do not include `fire` |
|
||||
| `!.mp3$` | inverse-suffix-exact-match | Items that do not end with `.mp3` |
|
||||
|
||||
If you don't prefer fuzzy matching and do not wish to "quote" every word,
|
||||
start fzf with `-e` or `--exact` option. Note that when `--exact` is set,
|
||||
@@ -305,7 +305,7 @@ If you have set up fzf for Vim, `:FZF` command will be added.
|
||||
:FZF ~
|
||||
|
||||
" With options
|
||||
:FZF --no-sort -m /tmp
|
||||
:FZF --no-sort --reverse --inline-info /tmp
|
||||
|
||||
" Bang version starts in fullscreen instead of using tmux pane or Neovim split
|
||||
:FZF!
|
||||
@@ -319,7 +319,7 @@ Note that the environment variables `FZF_DEFAULT_COMMAND` and
|
||||
`FZF_DEFAULT_OPTS` also apply here. Refer to [the wiki page][fzf-config] for
|
||||
customization.
|
||||
|
||||
[fzf-config]: https://github.com/junegunn/fzf/wiki/Configuring-FZF-command-(vim)
|
||||
[fzf-config]: https://github.com/junegunn/fzf/wiki/Configuring-Vim-plugin
|
||||
|
||||
#### `fzf#run`
|
||||
|
||||
@@ -347,7 +347,8 @@ page](https://github.com/junegunn/fzf/wiki/Examples-(vim)).
|
||||
|
||||
`fzf#wrap([name string,] [opts dict,] [fullscreen boolean])` is a helper
|
||||
function that decorates the options dictionary so that it understands
|
||||
`g:fzf_layout`, `g:fzf_action`, and `g:fzf_history_dir` like `:FZF`.
|
||||
`g:fzf_layout`, `g:fzf_action`, `g:fzf_colors`, and `g:fzf_history_dir` like
|
||||
`:FZF`.
|
||||
|
||||
```vim
|
||||
command! -bang MyStuff
|
||||
|
@@ -17,6 +17,7 @@ swap=""
|
||||
close=""
|
||||
term=""
|
||||
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines)
|
||||
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols)
|
||||
|
||||
help() {
|
||||
>&2 echo 'usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
||||
@@ -83,7 +84,7 @@ while [[ $# -gt 0 ]]; do
|
||||
else
|
||||
if [[ -n "$swap" ]]; then
|
||||
if [[ "$arg" =~ ^.l ]]; then
|
||||
[[ -n "$COLUMNS" ]] && max=$COLUMNS || max=$(tput cols)
|
||||
max=$columns
|
||||
else
|
||||
max=$lines
|
||||
fi
|
||||
@@ -108,7 +109,7 @@ while [[ $# -gt 0 ]]; do
|
||||
[[ -n "$skip" ]] && args+=("$arg")
|
||||
done
|
||||
|
||||
if [[ -z "$TMUX" ]] || [[ "$lines" -le 15 ]]; then
|
||||
if [[ -z "$TMUX" || "$opt" =~ ^-h && "$columns" -le 40 || ! "$opt" =~ ^-h && "$lines" -le 15 ]]; then
|
||||
"$fzf" "${args[@]}"
|
||||
exit $?
|
||||
fi
|
||||
|
4
install
4
install
@@ -2,8 +2,8 @@
|
||||
|
||||
set -u
|
||||
|
||||
[[ "$@" =~ --pre ]] && version=0.15.3 pre=1 ||
|
||||
version=0.15.3 pre=0
|
||||
[[ "$@" =~ --pre ]] && version=0.15.5 pre=1 ||
|
||||
version=0.15.5 pre=0
|
||||
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
|
@@ -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 "Sep 2016" "fzf 0.15.3" "fzf-tmux - open fzf in tmux split pane"
|
||||
.TH fzf-tmux 1 "Oct 2016" "fzf 0.15.5" "fzf-tmux - open fzf in tmux split pane"
|
||||
|
||||
.SH NAME
|
||||
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
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Sep 2016" "fzf 0.15.3" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Oct 2016" "fzf 0.15.5" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -232,11 +232,17 @@ automatically truncated when the number of the lines exceeds the value.
|
||||
.TP
|
||||
.BI "--preview=" "COMMAND"
|
||||
Execute the given command for the current line and display the result on the
|
||||
preview window. \fB{}\fR is the placeholder for the quoted string of the
|
||||
current line.
|
||||
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
|
||||
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
|
||||
query string.
|
||||
|
||||
.RS
|
||||
e.g. \fBfzf --preview="head -$LINES {}"\fR
|
||||
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
||||
|
||||
Note that you can escape a placeholder pattern by prepending a backslash.
|
||||
.RE
|
||||
.TP
|
||||
.BI "--preview-window=" "[POSITION][:SIZE[%]][:hidden]"
|
||||
@@ -358,7 +364,7 @@ with the given string. An anchored-match term is also an exact-match term.
|
||||
|
||||
.SS Negation
|
||||
If a term is prefixed by \fB!\fR, fzf will exclude the lines that satisfy the
|
||||
term from the result.
|
||||
term from the result. In this case, fzf performs exact match by default.
|
||||
|
||||
.SS Exact-match by default
|
||||
If you don't prefer fuzzy matching and do not wish to "quote" (prefixing with
|
||||
@@ -460,9 +466,11 @@ binding \fBenter\fR key to \fBless\fR command like follows.
|
||||
|
||||
\fBfzf --bind "enter:execute(less {})"\fR
|
||||
|
||||
\fB{}\fR is the placeholder for the quoted string of the current line.
|
||||
If the command contains parentheses, you can use any of the following
|
||||
alternative notations to avoid parse errors.
|
||||
You can use the same placeholder expressions as in \fB--preview\fR.
|
||||
|
||||
If the command contains parentheses, fzf may fail to parse the expression. In
|
||||
that case, you can use any of the following alternative notations to avoid
|
||||
parse errors.
|
||||
|
||||
\fBexecute[...]\fR
|
||||
\fBexecute~...~\fR
|
||||
@@ -481,7 +489,7 @@ alternative notations to avoid parse errors.
|
||||
.RS
|
||||
This is the special form that frees you from parse errors as it does not expect
|
||||
the closing character. The catch is that it should be the last one in the
|
||||
comma-separated list.
|
||||
comma-separated list of key-action pairs.
|
||||
.RE
|
||||
|
||||
\fBexecute-multi(...)\fR is an alternative action that executes the command
|
||||
|
@@ -21,6 +21,11 @@
|
||||
" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
if exists('g:loaded_fzf')
|
||||
finish
|
||||
endif
|
||||
let g:loaded_fzf = 1
|
||||
|
||||
let s:default_layout = { 'down': '~40%' }
|
||||
let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
|
||||
let s:fzf_go = expand('<sfile>:h:h').'/bin/fzf'
|
||||
@@ -154,6 +159,22 @@ function! s:common_sink(action, lines) abort
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:get_color(attr, ...)
|
||||
for group in a:000
|
||||
let code = synIDattr(synIDtrans(hlID(group)), a:attr, 'cterm')
|
||||
if code =~ '^[0-9]\+$'
|
||||
return code
|
||||
endif
|
||||
endfor
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! s:defaults()
|
||||
let rules = copy(get(g:, 'fzf_colors', {}))
|
||||
let colors = join(map(items(filter(map(rules, 'call("s:get_color", v:val)'), '!empty(v:val)')), 'join(v:val, ":")'), ',')
|
||||
return empty(colors) ? '' : ('--color='.colors)
|
||||
endfunction
|
||||
|
||||
" [name string,] [opts dict,] [fullscreen boolean]
|
||||
function! fzf#wrap(...)
|
||||
let args = ['', {}, 0]
|
||||
@@ -185,8 +206,10 @@ function! fzf#wrap(...)
|
||||
endif
|
||||
endif
|
||||
|
||||
" Colors: g:fzf_colors
|
||||
let opts.options = s:defaults() .' '. get(opts, 'options', '')
|
||||
|
||||
" History: g:fzf_history_dir
|
||||
let opts.options = get(opts, 'options', '')
|
||||
if len(name) && len(get(g:, 'fzf_history_dir', ''))
|
||||
let dir = expand(g:fzf_history_dir)
|
||||
if !isdirectory(dir)
|
||||
@@ -289,7 +312,8 @@ function! s:fzf_tmux(dict)
|
||||
endfunction
|
||||
|
||||
function! s:splittable(dict)
|
||||
return s:present(a:dict, 'up', 'down', 'left', 'right')
|
||||
return s:present(a:dict, 'up', 'down') && &lines > 15 ||
|
||||
\ s:present(a:dict, 'left', 'right') && &columns > 40
|
||||
endfunction
|
||||
|
||||
function! s:pushd(dict)
|
||||
@@ -405,24 +429,25 @@ function! s:split(dict)
|
||||
\ 'right': ['vertical botright', 'vertical resize', &columns] }
|
||||
let ppos = s:getpos()
|
||||
try
|
||||
for [dir, triple] in items(directions)
|
||||
let val = get(a:dict, dir, '')
|
||||
if !empty(val)
|
||||
let [cmd, resz, max] = triple
|
||||
if (dir == 'up' || dir == 'down') && val[0] == '~'
|
||||
let sz = s:calc_size(max, val, a:dict)
|
||||
else
|
||||
let sz = s:calc_size(max, val, {})
|
||||
endif
|
||||
execute cmd sz.'new'
|
||||
execute resz sz
|
||||
return [ppos, {}]
|
||||
endif
|
||||
endfor
|
||||
if s:present(a:dict, 'window')
|
||||
execute a:dict.window
|
||||
else
|
||||
elseif !s:splittable(a:dict)
|
||||
execute (tabpagenr()-1).'tabnew'
|
||||
else
|
||||
for [dir, triple] in items(directions)
|
||||
let val = get(a:dict, dir, '')
|
||||
if !empty(val)
|
||||
let [cmd, resz, max] = triple
|
||||
if (dir == 'up' || dir == 'down') && val[0] == '~'
|
||||
let sz = s:calc_size(max, val, a:dict)
|
||||
else
|
||||
let sz = s:calc_size(max, val, {})
|
||||
endif
|
||||
execute cmd sz.'new'
|
||||
execute resz sz
|
||||
return [ppos, {}]
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
return [ppos, { '&l:wfw': &l:wfw, '&l:wfh': &l:wfh }]
|
||||
finally
|
||||
@@ -558,11 +583,15 @@ let s:default_action = {
|
||||
|
||||
function! s:cmd(bang, ...) abort
|
||||
let args = copy(a:000)
|
||||
let opts = {}
|
||||
let opts = { 'options': '--multi ' }
|
||||
if len(args) && isdirectory(expand(args[-1]))
|
||||
let opts.dir = substitute(remove(args, -1), '\\\(["'']\)', '\1', 'g')
|
||||
let opts.dir = substitute(substitute(remove(args, -1), '\\\(["'']\)', '\1', 'g'), '/*$', '/', '')
|
||||
let opts.options .= ' --prompt '.shellescape(opts.dir)
|
||||
else
|
||||
let opts.options .= ' --prompt '.shellescape(pathshorten(getcwd()).'/')
|
||||
endif
|
||||
call fzf#run(fzf#wrap('FZF', extend({'options': join(args)}, opts), a:bang))
|
||||
let opts.options .= ' '.join(args)
|
||||
call fzf#run(fzf#wrap('FZF', opts, a:bang))
|
||||
endfunction
|
||||
|
||||
command! -nargs=* -complete=dir -bang FZF call s:cmd(<bang>0, <f-args>)
|
||||
|
@@ -44,7 +44,7 @@ __fzf_generic_path_completion() {
|
||||
setopt localoptions nonomatch
|
||||
dir="$base"
|
||||
while [ 1 ]; do
|
||||
if [ -z "$dir" -o -d ${~dir} ]; then
|
||||
if [[ -z "$dir" || -d ${~dir} ]]; then
|
||||
leftover=${base/#"$dir"}
|
||||
leftover=${leftover/#\/}
|
||||
[ -z "$dir" ] && dir='.'
|
||||
@@ -111,7 +111,7 @@ _fzf_complete_telnet() {
|
||||
|
||||
_fzf_complete_ssh() {
|
||||
_fzf_complete '+m' "$@" < <(
|
||||
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*') \
|
||||
command cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*') \
|
||||
<(command grep -oE '^[^ ]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \
|
||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||
|
@@ -26,8 +26,8 @@ function fzf_key_bindings
|
||||
end
|
||||
|
||||
function fzf-history-widget
|
||||
history | eval (__fzfcmd) +s +m --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS > $TMPDIR/fzf.result
|
||||
and commandline (cat $TMPDIR/fzf.result)
|
||||
history | eval (__fzfcmd) +s +m --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS -q '(commandline)' > $TMPDIR/fzf.result
|
||||
and commandline -- (cat $TMPDIR/fzf.result)
|
||||
commandline -f repaint
|
||||
rm -f $TMPDIR/fzf.result
|
||||
end
|
||||
|
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
const (
|
||||
// Current version
|
||||
version = "0.15.3"
|
||||
version = "0.15.5"
|
||||
|
||||
// Core
|
||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||
|
@@ -113,7 +113,8 @@ const (
|
||||
|
||||
// Pallete
|
||||
const (
|
||||
ColNormal = iota
|
||||
_ = iota
|
||||
ColNormal
|
||||
ColPrompt
|
||||
ColMatch
|
||||
ColCurrent
|
||||
@@ -134,7 +135,6 @@ const (
|
||||
)
|
||||
|
||||
type ColorTheme struct {
|
||||
UseDefault bool
|
||||
Fg int16
|
||||
Bg int16
|
||||
DarkBg int16
|
||||
@@ -168,7 +168,8 @@ type MouseEvent struct {
|
||||
var (
|
||||
_buf []byte
|
||||
_in *os.File
|
||||
_color func(int, Attr) C.int
|
||||
_color bool
|
||||
_colorFn func(int, Attr) C.int
|
||||
_colorMap map[int]int
|
||||
_prevDownTime time.Time
|
||||
_clickY []int
|
||||
@@ -176,10 +177,6 @@ var (
|
||||
Default16 *ColorTheme
|
||||
Dark256 *ColorTheme
|
||||
Light256 *ColorTheme
|
||||
FG int
|
||||
CurrentFG int
|
||||
BG int
|
||||
DarkBG int
|
||||
)
|
||||
|
||||
type Window struct {
|
||||
@@ -192,12 +189,16 @@ type Window struct {
|
||||
|
||||
func NewWindow(top int, left int, width int, height int, border bool) *Window {
|
||||
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
|
||||
if _color {
|
||||
C.wbkgd(win, C.chtype(C.COLOR_PAIR(ColNormal)))
|
||||
}
|
||||
if border {
|
||||
attr := _color(ColBorder, 0)
|
||||
attr := _colorFn(ColBorder, 0)
|
||||
C.wattron(win, attr)
|
||||
C.box(win, 0, 0)
|
||||
C.wattroff(win, attr)
|
||||
}
|
||||
|
||||
return &Window{
|
||||
win: win,
|
||||
Top: top,
|
||||
@@ -209,7 +210,6 @@ func NewWindow(top int, left int, width int, height int, border bool) *Window {
|
||||
|
||||
func EmptyTheme() *ColorTheme {
|
||||
return &ColorTheme{
|
||||
UseDefault: true,
|
||||
Fg: colUndefined,
|
||||
Bg: colUndefined,
|
||||
DarkBg: colUndefined,
|
||||
@@ -230,9 +230,8 @@ func init() {
|
||||
_clickY = []int{}
|
||||
_colorMap = make(map[int]int)
|
||||
Default16 = &ColorTheme{
|
||||
UseDefault: true,
|
||||
Fg: 15,
|
||||
Bg: 0,
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
DarkBg: C.COLOR_BLACK,
|
||||
Prompt: C.COLOR_BLUE,
|
||||
Match: C.COLOR_GREEN,
|
||||
@@ -245,9 +244,8 @@ func init() {
|
||||
Header: C.COLOR_CYAN,
|
||||
Border: C.COLOR_BLACK}
|
||||
Dark256 = &ColorTheme{
|
||||
UseDefault: true,
|
||||
Fg: 15,
|
||||
Bg: 0,
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
DarkBg: 236,
|
||||
Prompt: 110,
|
||||
Match: 108,
|
||||
@@ -260,9 +258,8 @@ func init() {
|
||||
Header: 109,
|
||||
Border: 59}
|
||||
Light256 = &ColorTheme{
|
||||
UseDefault: true,
|
||||
Fg: 15,
|
||||
Bg: 0,
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
DarkBg: 251,
|
||||
Prompt: 25,
|
||||
Match: 66,
|
||||
@@ -278,7 +275,7 @@ func init() {
|
||||
|
||||
func attrColored(pair int, a Attr) C.int {
|
||||
var attr C.int
|
||||
if pair > ColNormal {
|
||||
if pair > 0 {
|
||||
attr = C.COLOR_PAIR(C.int(pair))
|
||||
}
|
||||
return attr | C.int(a)
|
||||
@@ -344,7 +341,8 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
|
||||
C.noecho()
|
||||
C.raw() // stty dsusp undef
|
||||
|
||||
if theme != nil {
|
||||
_color = theme != nil
|
||||
if _color {
|
||||
C.start_color()
|
||||
var baseTheme *ColorTheme
|
||||
if C.tigetnum(C.CString("colors")) >= 256 {
|
||||
@@ -353,52 +351,57 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
|
||||
baseTheme = Default16
|
||||
}
|
||||
initPairs(baseTheme, theme, black)
|
||||
_color = attrColored
|
||||
C.bkgd(C.chtype(C.COLOR_PAIR(ColNormal)))
|
||||
_colorFn = attrColored
|
||||
} else {
|
||||
_color = attrMono
|
||||
_colorFn = attrMono
|
||||
}
|
||||
}
|
||||
|
||||
func override(a int16, b int16) C.short {
|
||||
if b == colUndefined {
|
||||
return C.short(a)
|
||||
func override(baseTheme *ColorTheme, theme *ColorTheme) {
|
||||
o := func(a int16, b int16) int16 {
|
||||
if b == colUndefined {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
return C.short(b)
|
||||
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
||||
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
||||
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
|
||||
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
|
||||
theme.Match = o(baseTheme.Match, theme.Match)
|
||||
theme.Current = o(baseTheme.Current, theme.Current)
|
||||
theme.CurrentMatch = o(baseTheme.CurrentMatch, theme.CurrentMatch)
|
||||
theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
|
||||
theme.Info = o(baseTheme.Info, theme.Info)
|
||||
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
|
||||
theme.Selected = o(baseTheme.Selected, theme.Selected)
|
||||
theme.Header = o(baseTheme.Header, theme.Header)
|
||||
theme.Border = o(baseTheme.Border, theme.Border)
|
||||
}
|
||||
|
||||
func initPairs(baseTheme *ColorTheme, theme *ColorTheme, black bool) {
|
||||
fg := override(baseTheme.Fg, theme.Fg)
|
||||
bg := override(baseTheme.Bg, theme.Bg)
|
||||
if black {
|
||||
bg = C.COLOR_BLACK
|
||||
} else if theme.UseDefault {
|
||||
fg = colDefault
|
||||
bg = colDefault
|
||||
C.use_default_colors()
|
||||
}
|
||||
if theme.UseDefault {
|
||||
FG = colDefault
|
||||
BG = colDefault
|
||||
} else {
|
||||
FG = int(fg)
|
||||
BG = int(bg)
|
||||
C.assume_default_colors(C.int(override(baseTheme.Fg, theme.Fg)), C.int(bg))
|
||||
theme.Bg = C.COLOR_BLACK
|
||||
}
|
||||
// Updates theme
|
||||
override(baseTheme, theme)
|
||||
|
||||
currentFG := override(baseTheme.Current, theme.Current)
|
||||
darkBG := override(baseTheme.DarkBg, theme.DarkBg)
|
||||
CurrentFG = int(currentFG)
|
||||
DarkBG = int(darkBG)
|
||||
C.init_pair(ColPrompt, override(baseTheme.Prompt, theme.Prompt), bg)
|
||||
C.init_pair(ColMatch, override(baseTheme.Match, theme.Match), bg)
|
||||
C.init_pair(ColCurrent, currentFG, darkBG)
|
||||
C.init_pair(ColCurrentMatch, override(baseTheme.CurrentMatch, theme.CurrentMatch), darkBG)
|
||||
C.init_pair(ColSpinner, override(baseTheme.Spinner, theme.Spinner), bg)
|
||||
C.init_pair(ColInfo, override(baseTheme.Info, theme.Info), bg)
|
||||
C.init_pair(ColCursor, override(baseTheme.Cursor, theme.Cursor), darkBG)
|
||||
C.init_pair(ColSelected, override(baseTheme.Selected, theme.Selected), darkBG)
|
||||
C.init_pair(ColHeader, override(baseTheme.Header, theme.Header), bg)
|
||||
C.init_pair(ColBorder, override(baseTheme.Border, theme.Border), bg)
|
||||
C.assume_default_colors(C.int(theme.Fg), C.int(theme.Bg))
|
||||
initPair := func(group C.short, fg int16, bg int16) {
|
||||
C.init_pair(group, C.short(fg), C.short(bg))
|
||||
}
|
||||
initPair(ColNormal, theme.Fg, theme.Bg)
|
||||
initPair(ColPrompt, theme.Prompt, theme.Bg)
|
||||
initPair(ColMatch, theme.Match, theme.Bg)
|
||||
initPair(ColCurrent, theme.Current, theme.DarkBg)
|
||||
initPair(ColCurrentMatch, theme.CurrentMatch, theme.DarkBg)
|
||||
initPair(ColSpinner, theme.Spinner, theme.Bg)
|
||||
initPair(ColInfo, theme.Info, theme.Bg)
|
||||
initPair(ColCursor, theme.Cursor, theme.DarkBg)
|
||||
initPair(ColSelected, theme.Selected, theme.DarkBg)
|
||||
initPair(ColHeader, theme.Header, theme.Bg)
|
||||
initPair(ColBorder, theme.Border, theme.Bg)
|
||||
}
|
||||
|
||||
func Close() {
|
||||
@@ -656,7 +659,7 @@ func (w *Window) Print(text string) {
|
||||
}
|
||||
|
||||
func (w *Window) CPrint(pair int, a Attr, text string) {
|
||||
attr := _color(pair, a)
|
||||
attr := _colorFn(pair, a)
|
||||
C.wattron(w.win, attr)
|
||||
w.Print(text)
|
||||
C.wattroff(w.win, attr)
|
||||
@@ -683,7 +686,7 @@ func (w *Window) Fill(str string) bool {
|
||||
}
|
||||
|
||||
func (w *Window) CFill(str string, fg int, bg int, a Attr) bool {
|
||||
attr := _color(PairFor(fg, bg), a)
|
||||
attr := _colorFn(PairFor(fg, bg), a)
|
||||
C.wattron(w.win, attr)
|
||||
ret := w.Fill(str)
|
||||
C.wattroff(w.win, attr)
|
||||
|
@@ -246,7 +246,7 @@ func nextString(args []string, i *int, message string) string {
|
||||
}
|
||||
|
||||
func optionalNextString(args []string, i *int) string {
|
||||
if len(args) > *i+1 {
|
||||
if len(args) > *i+1 && !strings.HasPrefix(args[*i+1], "-") {
|
||||
*i++
|
||||
return args[*i]
|
||||
}
|
||||
@@ -499,10 +499,8 @@ func parseTheme(defaultTheme *curses.ColorTheme, str string) *curses.ColorTheme
|
||||
switch pair[0] {
|
||||
case "fg":
|
||||
theme.Fg = ansi
|
||||
theme.UseDefault = theme.UseDefault && ansi < 0
|
||||
case "bg":
|
||||
theme.Bg = ansi
|
||||
theme.UseDefault = theme.UseDefault && ansi < 0
|
||||
case "fg+":
|
||||
theme.Current = ansi
|
||||
case "bg+":
|
||||
|
@@ -299,20 +299,14 @@ func TestColorSpec(t *testing.T) {
|
||||
}
|
||||
customized.Fg = curses.Dark256.Fg
|
||||
customized.Bg = curses.Dark256.Bg
|
||||
if *curses.Dark256 == *customized {
|
||||
t.Errorf("colors should now be equivalent")
|
||||
if *curses.Dark256 != *customized {
|
||||
t.Errorf("colors should now be equivalent: %v, %v", curses.Dark256, customized)
|
||||
}
|
||||
|
||||
customized = parseTheme(theme, "fg:231,dark,bg:232")
|
||||
if customized.Fg != curses.Dark256.Fg || customized.Bg == curses.Dark256.Bg {
|
||||
t.Errorf("color not customized")
|
||||
}
|
||||
if customized.UseDefault {
|
||||
t.Errorf("not using default colors")
|
||||
}
|
||||
if !curses.Dark256.UseDefault {
|
||||
t.Errorf("using default colors")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNilTheme(t *testing.T) {
|
||||
|
@@ -163,12 +163,13 @@ func parseTerms(fuzzy bool, caseMode Case, str string) []termSet {
|
||||
|
||||
if strings.HasPrefix(text, "!") {
|
||||
inv = true
|
||||
typ = termExact
|
||||
text = text[1:]
|
||||
}
|
||||
|
||||
if strings.HasPrefix(text, "'") {
|
||||
// Flip exactness
|
||||
if fuzzy {
|
||||
if fuzzy && !inv {
|
||||
typ = termExact
|
||||
text = text[1:]
|
||||
} else {
|
||||
|
@@ -22,15 +22,15 @@ func TestParseTermsExtended(t *testing.T) {
|
||||
terms[1][0].typ != termExact || terms[1][0].inv ||
|
||||
terms[2][0].typ != termPrefix || terms[2][0].inv ||
|
||||
terms[3][0].typ != termSuffix || terms[3][0].inv ||
|
||||
terms[4][0].typ != termFuzzy || !terms[4][0].inv ||
|
||||
terms[5][0].typ != termExact || !terms[5][0].inv ||
|
||||
terms[4][0].typ != termExact || !terms[4][0].inv ||
|
||||
terms[5][0].typ != termFuzzy || !terms[5][0].inv ||
|
||||
terms[6][0].typ != termPrefix || !terms[6][0].inv ||
|
||||
terms[7][0].typ != termSuffix || !terms[7][0].inv ||
|
||||
terms[7][1].typ != termEqual || terms[7][1].inv ||
|
||||
terms[8][0].typ != termPrefix || terms[8][0].inv ||
|
||||
terms[8][1].typ != termExact || terms[8][1].inv ||
|
||||
terms[8][2].typ != termSuffix || terms[8][2].inv ||
|
||||
terms[8][3].typ != termFuzzy || !terms[8][3].inv {
|
||||
terms[8][3].typ != termExact || !terms[8][3].inv {
|
||||
t.Errorf("%s", terms)
|
||||
}
|
||||
for idx, termSet := range terms[:8] {
|
||||
|
@@ -3,6 +3,7 @@ package fzf
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
"unicode"
|
||||
|
||||
"github.com/junegunn/fzf/src/curses"
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
@@ -56,21 +57,21 @@ func buildResult(item *Item, offsets []Offset, score int, trimLen int) *Result {
|
||||
case byLength:
|
||||
// If offsets is empty, trimLen will be 0, but we don't care
|
||||
val = util.AsUint16(trimLen)
|
||||
case byBegin:
|
||||
case byBegin, byEnd:
|
||||
if validOffsetFound {
|
||||
whitePrefixLen := 0
|
||||
for idx := 0; idx < numChars; idx++ {
|
||||
r := item.text.Get(idx)
|
||||
whitePrefixLen = idx
|
||||
if idx == minBegin || r != ' ' && r != '\t' {
|
||||
if idx == minBegin || !unicode.IsSpace(r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
val = util.AsUint16(minBegin - whitePrefixLen)
|
||||
}
|
||||
case byEnd:
|
||||
if validOffsetFound {
|
||||
val = util.AsUint16(1 + numChars - maxEnd)
|
||||
if criterion == byBegin {
|
||||
val = util.AsUint16(minBegin - whitePrefixLen)
|
||||
} else {
|
||||
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/trimLen)
|
||||
}
|
||||
}
|
||||
}
|
||||
result.rank.points[idx] = val
|
||||
@@ -91,13 +92,13 @@ func minRank() rank {
|
||||
return rank{index: 0, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
|
||||
}
|
||||
|
||||
func (result *Result) colorOffsets(matchOffsets []Offset, color int, attr curses.Attr, current bool) []colorOffset {
|
||||
func (result *Result) colorOffsets(matchOffsets []Offset, theme *curses.ColorTheme, color int, attr curses.Attr, current bool) []colorOffset {
|
||||
itemColors := result.item.Colors()
|
||||
|
||||
// No ANSI code, or --color=no
|
||||
if len(itemColors) == 0 {
|
||||
var offsets []colorOffset
|
||||
for _, off := range matchOffsets {
|
||||
|
||||
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: color, attr: attr})
|
||||
}
|
||||
return offsets
|
||||
@@ -148,17 +149,17 @@ func (result *Result) colorOffsets(matchOffsets []Offset, color int, attr curses
|
||||
fg := ansi.color.fg
|
||||
if fg == -1 {
|
||||
if current {
|
||||
fg = curses.CurrentFG
|
||||
fg = int(theme.Current)
|
||||
} else {
|
||||
fg = curses.FG
|
||||
fg = int(theme.Fg)
|
||||
}
|
||||
}
|
||||
bg := ansi.color.bg
|
||||
if bg == -1 {
|
||||
if current {
|
||||
bg = curses.DarkBG
|
||||
bg = int(theme.DarkBg)
|
||||
} else {
|
||||
bg = curses.BG
|
||||
bg = int(theme.Bg)
|
||||
}
|
||||
}
|
||||
colors = append(colors, colorOffset{
|
||||
|
@@ -103,7 +103,7 @@ func TestColorOffset(t *testing.T) {
|
||||
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, curses.Bold}}}}}
|
||||
// [{[0 5] 9 false} {[5 15] 99 false} {[15 20] 9 false} {[22 25] 10 true} {[25 35] 99 false} {[35 40] 11 true}]
|
||||
|
||||
colors := item.colorOffsets(offsets, 99, 0, true)
|
||||
colors := item.colorOffsets(offsets, curses.Dark256, 99, 0, true)
|
||||
assert := func(idx int, b int32, e int32, c int, bold bool) {
|
||||
var attr curses.Attr
|
||||
if bold {
|
||||
|
122
src/terminal.go
122
src/terminal.go
@@ -20,6 +20,12 @@ import (
|
||||
|
||||
// import "github.com/pkg/profile"
|
||||
|
||||
var placeholder *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
placeholder = regexp.MustCompile("\\\\?(?:{[0-9,-.]*}|{q})")
|
||||
}
|
||||
|
||||
type jumpMode int
|
||||
|
||||
const (
|
||||
@@ -51,6 +57,7 @@ type Terminal struct {
|
||||
multi bool
|
||||
sort bool
|
||||
toggleSort bool
|
||||
delimiter Delimiter
|
||||
expect map[int]string
|
||||
keymap map[int]actionType
|
||||
execmap map[int]string
|
||||
@@ -83,20 +90,16 @@ type Terminal struct {
|
||||
suppress bool
|
||||
startChan chan bool
|
||||
slab *util.Slab
|
||||
theme *C.ColorTheme
|
||||
}
|
||||
|
||||
type selectedItem struct {
|
||||
at time.Time
|
||||
text string
|
||||
item *Item
|
||||
}
|
||||
|
||||
type byTimeOrder []selectedItem
|
||||
|
||||
type previewRequest struct {
|
||||
ok bool
|
||||
str string
|
||||
}
|
||||
|
||||
func (a byTimeOrder) Len() int {
|
||||
return len(a)
|
||||
}
|
||||
@@ -267,6 +270,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
multi: opts.Multi,
|
||||
sort: opts.Sort > 0,
|
||||
toggleSort: opts.ToggleSort,
|
||||
delimiter: opts.Delimiter,
|
||||
expect: opts.Expect,
|
||||
keymap: opts.Keymap,
|
||||
execmap: opts.Execmap,
|
||||
@@ -292,6 +296,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
mutex: sync.Mutex{},
|
||||
suppress: true,
|
||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||
theme: opts.Theme,
|
||||
startChan: make(chan bool, 1),
|
||||
initFunc: func() {
|
||||
C.Init(opts.Theme, opts.Black, opts.Mouse)
|
||||
@@ -373,7 +378,7 @@ func (t *Terminal) output() bool {
|
||||
}
|
||||
} else {
|
||||
for _, sel := range t.sortSelected() {
|
||||
t.printer(sel.text)
|
||||
t.printer(sel.item.AsString(t.ansi))
|
||||
}
|
||||
}
|
||||
return found
|
||||
@@ -634,7 +639,7 @@ func (t *Terminal) printItem(result *Result, i int, current bool) {
|
||||
} else {
|
||||
t.window.Print(" ")
|
||||
}
|
||||
t.printHighlighted(result, 0, 0, C.ColMatch, false)
|
||||
t.printHighlighted(result, 0, C.ColNormal, C.ColMatch, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -715,7 +720,7 @@ func (t *Terminal) printHighlighted(result *Result, attr C.Attr, col1 int, col2
|
||||
maxe = util.Max(maxe, int(offset[1]))
|
||||
}
|
||||
|
||||
offsets := result.colorOffsets(charOffsets, col2, attr, current)
|
||||
offsets := result.colorOffsets(charOffsets, t.theme, col2, attr, current)
|
||||
maxWidth := t.window.Width - 3
|
||||
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
|
||||
if overflow(text, maxWidth) {
|
||||
@@ -819,6 +824,11 @@ func (t *Terminal) printPreview() {
|
||||
}
|
||||
return t.pwindow.Fill(str)
|
||||
})
|
||||
if t.previewer.offset > 0 {
|
||||
offset := fmt.Sprintf("%d/%d", t.previewer.offset+1, t.previewer.lines)
|
||||
t.pwindow.Move(0, t.pwindow.Width-len(offset))
|
||||
t.pwindow.CPrint(C.ColInfo, C.Reverse, offset)
|
||||
}
|
||||
}
|
||||
|
||||
func processTabs(runes []rune, prefixWidth int) (string, int) {
|
||||
@@ -912,8 +922,60 @@ func quoteEntry(entry string) string {
|
||||
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
||||
}
|
||||
|
||||
func (t *Terminal) executeCommand(template string, replacement string) {
|
||||
command := strings.Replace(template, "{}", replacement, -1)
|
||||
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, query string, items []*Item) string {
|
||||
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
|
||||
// Escaped pattern
|
||||
if match[0] == '\\' {
|
||||
return match[1:]
|
||||
}
|
||||
|
||||
// Current query
|
||||
if match == "{q}" {
|
||||
return quoteEntry(query)
|
||||
}
|
||||
|
||||
replacements := make([]string, len(items))
|
||||
|
||||
if match == "{}" {
|
||||
for idx, item := range items {
|
||||
replacements[idx] = quoteEntry(item.AsString(stripAnsi))
|
||||
}
|
||||
return strings.Join(replacements, " ")
|
||||
}
|
||||
|
||||
tokens := strings.Split(match[1:len(match)-1], ",")
|
||||
ranges := make([]Range, len(tokens))
|
||||
for idx, s := range tokens {
|
||||
r, ok := ParseRange(&s)
|
||||
if !ok {
|
||||
// Invalid expression, just return the original string in the template
|
||||
return match
|
||||
}
|
||||
ranges[idx] = r
|
||||
}
|
||||
|
||||
for idx, item := range items {
|
||||
chars := util.RunesToChars([]rune(item.AsString(stripAnsi)))
|
||||
tokens := Tokenize(chars, delimiter)
|
||||
trans := Transform(tokens, ranges)
|
||||
str := string(joinTokens(trans))
|
||||
if delimiter.str != nil {
|
||||
str = strings.TrimSuffix(str, *delimiter.str)
|
||||
} else if delimiter.regex != nil {
|
||||
delims := delimiter.regex.FindAllStringIndex(str, -1)
|
||||
if len(delims) > 0 && delims[len(delims)-1][1] == len(str) {
|
||||
str = str[:delims[len(delims)-1][0]]
|
||||
}
|
||||
}
|
||||
str = strings.TrimSpace(str)
|
||||
replacements[idx] = quoteEntry(str)
|
||||
}
|
||||
return strings.Join(replacements, " ")
|
||||
})
|
||||
}
|
||||
|
||||
func (t *Terminal) executeCommand(template string, items []*Item) {
|
||||
command := replacePlaceholder(template, t.ansi, t.delimiter, string(t.input), items)
|
||||
cmd := util.ExecCommand(command)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
@@ -931,8 +993,12 @@ func (t *Terminal) isPreviewEnabled() bool {
|
||||
return t.previewBox != nil && t.previewer.enabled
|
||||
}
|
||||
|
||||
func (t *Terminal) currentItem() *Item {
|
||||
return t.merger.Get(t.cy).item
|
||||
}
|
||||
|
||||
func (t *Terminal) current() string {
|
||||
return t.merger.Get(t.cy).item.AsString(t.ansi)
|
||||
return t.currentItem().AsString(t.ansi)
|
||||
}
|
||||
|
||||
// Loop is called to start Terminal I/O
|
||||
@@ -989,18 +1055,19 @@ func (t *Terminal) Loop() {
|
||||
if t.hasPreviewWindow() {
|
||||
go func() {
|
||||
for {
|
||||
request := previewRequest{false, ""}
|
||||
var request *Item
|
||||
t.previewBox.Wait(func(events *util.Events) {
|
||||
for req, value := range *events {
|
||||
switch req {
|
||||
case reqPreviewEnqueue:
|
||||
request = value.(previewRequest)
|
||||
request = value.(*Item)
|
||||
}
|
||||
}
|
||||
events.Clear()
|
||||
})
|
||||
if request.ok {
|
||||
command := strings.Replace(t.preview.command, "{}", quoteEntry(request.str), -1)
|
||||
if request != nil {
|
||||
command := replacePlaceholder(t.preview.command,
|
||||
t.ansi, t.delimiter, string(t.input), []*Item{request})
|
||||
cmd := util.ExecCommand(command)
|
||||
out, _ := cmd.CombinedOutput()
|
||||
t.reqBox.Set(reqPreviewDisplay, string(out))
|
||||
@@ -1020,7 +1087,7 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
|
||||
go func() {
|
||||
focused := previewRequest{false, ""}
|
||||
var focused *Item
|
||||
for {
|
||||
t.reqBox.Wait(func(events *util.Events) {
|
||||
defer events.Clear()
|
||||
@@ -1037,11 +1104,11 @@ func (t *Terminal) Loop() {
|
||||
case reqList:
|
||||
t.printList()
|
||||
cnt := t.merger.Length()
|
||||
var currentFocus previewRequest
|
||||
var currentFocus *Item
|
||||
if cnt > 0 && cnt > t.cy {
|
||||
currentFocus = previewRequest{true, t.current()}
|
||||
currentFocus = t.currentItem()
|
||||
} else {
|
||||
currentFocus = previewRequest{false, ""}
|
||||
currentFocus = nil
|
||||
}
|
||||
if currentFocus != focused {
|
||||
focused = currentFocus
|
||||
@@ -1109,7 +1176,7 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
selectItem := func(item *Item) bool {
|
||||
if _, found := t.selected[item.Index()]; !found {
|
||||
t.selected[item.Index()] = selectedItem{time.Now(), item.AsString(t.ansi)}
|
||||
t.selected[item.Index()] = selectedItem{time.Now(), item}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -1128,7 +1195,7 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
scrollPreview := func(amount int) {
|
||||
t.previewer.offset = util.Constrain(
|
||||
t.previewer.offset+amount, 0, t.previewer.lines-t.pwindow.Height)
|
||||
t.previewer.offset+amount, 0, t.previewer.lines-1)
|
||||
req(reqPreviewRefresh)
|
||||
}
|
||||
for key, ret := range t.expect {
|
||||
@@ -1146,16 +1213,15 @@ func (t *Terminal) Loop() {
|
||||
case actIgnore:
|
||||
case actExecute:
|
||||
if t.cy >= 0 && t.cy < t.merger.Length() {
|
||||
item := t.merger.Get(t.cy).item
|
||||
t.executeCommand(t.execmap[mapkey], quoteEntry(item.AsString(t.ansi)))
|
||||
t.executeCommand(t.execmap[mapkey], []*Item{t.currentItem()})
|
||||
}
|
||||
case actExecuteMulti:
|
||||
if len(t.selected) > 0 {
|
||||
sels := make([]string, len(t.selected))
|
||||
sels := make([]*Item, len(t.selected))
|
||||
for i, sel := range t.sortSelected() {
|
||||
sels[i] = quoteEntry(sel.text)
|
||||
sels[i] = sel.item
|
||||
}
|
||||
t.executeCommand(t.execmap[mapkey], strings.Join(sels, " "))
|
||||
t.executeCommand(t.execmap[mapkey], sels)
|
||||
} else {
|
||||
return doAction(actExecute, mapkey)
|
||||
}
|
||||
@@ -1168,7 +1234,7 @@ func (t *Terminal) Loop() {
|
||||
t.resizeWindows()
|
||||
cnt := t.merger.Length()
|
||||
if t.previewer.enabled && cnt > 0 && cnt > t.cy {
|
||||
t.previewBox.Set(reqPreviewEnqueue, previewRequest{true, t.current()})
|
||||
t.previewBox.Set(reqPreviewEnqueue, t.currentItem())
|
||||
}
|
||||
req(reqList, reqInfo)
|
||||
}
|
||||
|
73
src/terminal_test.go
Normal file
73
src/terminal_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package fzf
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
)
|
||||
|
||||
func newItem(str string) *Item {
|
||||
bytes := []byte(str)
|
||||
trimmed, _, _ := extractColor(str, nil, nil)
|
||||
return &Item{origText: &bytes, text: util.RunesToChars([]rune(trimmed))}
|
||||
}
|
||||
|
||||
func TestReplacePlaceholder(t *testing.T) {
|
||||
items1 := []*Item{newItem(" foo'bar \x1b[31mbaz\x1b[m")}
|
||||
items2 := []*Item{
|
||||
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
||||
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
||||
|
||||
var result string
|
||||
check := func(expected string) {
|
||||
if result != expected {
|
||||
t.Errorf("expected: %s, actual: %s", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
// {}, preserve ansi
|
||||
result = replacePlaceholder("echo {}", false, Delimiter{}, "query", items1)
|
||||
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
|
||||
|
||||
// {}, strip ansi
|
||||
result = replacePlaceholder("echo {}", true, Delimiter{}, "query", items1)
|
||||
check("echo ' foo'\\''bar baz'")
|
||||
|
||||
// {}, with multiple items
|
||||
result = replacePlaceholder("echo {}", true, Delimiter{}, "query", items2)
|
||||
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
|
||||
|
||||
// {..}, strip leading whitespaces, preserve ansi
|
||||
result = replacePlaceholder("echo {..}", false, Delimiter{}, "query", items1)
|
||||
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
|
||||
|
||||
// {..}, strip leading whitespaces, strip ansi
|
||||
result = replacePlaceholder("echo {..}", true, Delimiter{}, "query", items1)
|
||||
check("echo 'foo'\\''bar baz'")
|
||||
|
||||
// {q}
|
||||
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, "query", items1)
|
||||
check("echo ' foo'\\''bar baz' 'query'")
|
||||
|
||||
// {q}, multiple items
|
||||
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, "query 'string'", items2)
|
||||
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)
|
||||
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)
|
||||
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
||||
|
||||
// String delimiter
|
||||
delim := "'"
|
||||
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, "query", items1)
|
||||
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
|
||||
|
||||
// Regex delimiter
|
||||
regex := regexp.MustCompile("[oa]+")
|
||||
// foo'bar baz
|
||||
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, "query", items1)
|
||||
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
@@ -63,7 +64,7 @@ func (chars *Chars) TrimLength() int {
|
||||
len := chars.Length()
|
||||
for i = len - 1; i >= 0; i-- {
|
||||
char := chars.Get(i)
|
||||
if char != ' ' && char != '\t' {
|
||||
if !unicode.IsSpace(char) {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -75,7 +76,7 @@ func (chars *Chars) TrimLength() int {
|
||||
var j int
|
||||
for j = 0; j < len; j++ {
|
||||
char := chars.Get(j)
|
||||
if char != ' ' && char != '\t' {
|
||||
if !unicode.IsSpace(char) {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -86,7 +87,7 @@ func (chars *Chars) TrailingWhitespaces() int {
|
||||
whitespaces := 0
|
||||
for i := chars.Length() - 1; i >= 0; i-- {
|
||||
char := chars.Get(i)
|
||||
if char != ' ' && char != '\t' {
|
||||
if !unicode.IsSpace(char) {
|
||||
break
|
||||
}
|
||||
whitespaces++
|
||||
|
@@ -143,6 +143,10 @@ Execute (fzf#wrap):
|
||||
Assert opts.options =~ '--history /tmp/foobar'
|
||||
Assert opts.options =~ '--color light'
|
||||
|
||||
let g:fzf_colors = { 'fg': ['fg', 'Error'] }
|
||||
let opts = fzf#wrap({})
|
||||
Assert opts.options =~ '^--color=fg:'
|
||||
|
||||
Execute (Cleanup):
|
||||
unlet g:dir
|
||||
Restore
|
||||
|
@@ -136,8 +136,10 @@ class Tmux
|
||||
def prepare
|
||||
tries = 0
|
||||
begin
|
||||
self.send_keys 'C-u', 'hello', 'Right'
|
||||
self.until { |lines| lines[-1].end_with?('hello') }
|
||||
self.until do |lines|
|
||||
self.send_keys 'C-u', 'hello'
|
||||
lines[-1].end_with?('hello')
|
||||
end
|
||||
rescue Exception
|
||||
(tries += 1) < 5 ? retry : raise
|
||||
end
|
||||
@@ -604,8 +606,8 @@ class TestGoFZF < TestBase
|
||||
], `#{FZF} -fo --tiebreak=end < #{tempname}`.split($/)
|
||||
|
||||
assert_equal [
|
||||
' xxxxoxxx',
|
||||
'xxxxxoxxx',
|
||||
' xxxxoxxx',
|
||||
'xxxxoxxxx',
|
||||
'xxxoxxxxxx',
|
||||
'xxoxxxxxxx',
|
||||
|
Reference in New Issue
Block a user