Compare commits

..

7 Commits

Author SHA1 Message Date
Junegunn Choi
5759d50d4a 0.13.2 2016-06-16 02:16:13 +09:00
Junegunn Choi
e455836cc9 Fix race condition where preview window is not properly cleared 2016-06-15 13:15:17 +09:00
Junegunn Choi
8a90f26c8a 0.13.1 2016-06-14 21:53:00 +09:00
Junegunn Choi
24e1fabf2e Do not process ANSI codes in --preview output at once
Close #598
2016-06-14 21:52:47 +09:00
Junegunn Choi
c39c039e15 [shell] Add $FZF_CTRL_T_OPTS and $FZF_ALT_C_OPTS
Close #596
2016-06-12 20:48:23 +09:00
Junegunn Choi
07f176f426 Merge pull request #595 from aykamko/speed-up-fzf-completion
Optimize fzf_default_completion binding
2016-06-12 11:56:49 +09:00
Aleks Kamko
19339e3a6d optimize fzf_default_completion binding 2016-06-11 15:19:16 -07:00
15 changed files with 74 additions and 54 deletions

View File

@@ -1,6 +1,14 @@
CHANGELOG CHANGELOG
========= =========
0.13.2
------
- Fixed race condition where preview window is not properly cleared
0.13.1
------
- Fixed UI issue with large `--preview` output with many ANSI codes
0.13.0 0.13.0
------ ------
- Added preview feature - Added preview feature

View File

@@ -178,11 +178,14 @@ fish.
- `CTRL-T` - Paste the selected files and directories onto the command line - `CTRL-T` - Paste the selected files and directories onto the command line
- Set `FZF_CTRL_T_COMMAND` to override the default command - Set `FZF_CTRL_T_COMMAND` to override the default command
- Set `FZF_CTRL_T_OPTS` to pass additional options
- `CTRL-R` - Paste the selected command from history onto the command line - `CTRL-R` - Paste the selected command from history onto the command line
- Sort is disabled by default to respect chronological ordering - Sort is disabled by default to respect chronological ordering
- Press `CTRL-R` again to toggle sort - Press `CTRL-R` again to toggle sort
- Set `FZF_CTRL_R_OPTS` to pass additional options
- `ALT-C` - cd into the selected directory - `ALT-C` - cd into the selected directory
- Set `FZF_ALT_C_COMMAND` to override the default command - Set `FZF_ALT_C_COMMAND` to override the default command
- Set `FZF_ALT_C_OPTS` to pass additional options
If you're on a tmux session, fzf will start in a split pane. You may disable If you're on a tmux session, fzf will start in a split pane. You may disable
this tmux integration by setting `FZF_TMUX` to 0, or change the height of the this tmux integration by setting `FZF_TMUX` to 0, or change the height of the

View File

@@ -2,8 +2,8 @@
set -u set -u
[[ "$@" =~ --pre ]] && version=0.13.0 pre=1 || [[ "$@" =~ --pre ]] && version=0.13.2 pre=1 ||
version=0.13.0 pre=0 version=0.13.2 pre=0
auto_completion= auto_completion=
key_bindings= key_bindings=

View File

@@ -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 "Jun 2016" "fzf 0.13.0" "fzf - a command-line fuzzy finder" .TH fzf 1 "Jun 2016" "fzf 0.13.2" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder

View File

@@ -181,8 +181,11 @@ fzf-completion() {
fi fi
} }
[ -z "$fzf_default_completion" ] && [ -z "$fzf_default_completion" ] && {
fzf_default_completion=$(bindkey '^I' | \grep -v undefined-key | awk '{print $2}') binding=$(bindkey '^I')
[[ $binding =~ 'undefined-key' ]] || fzf_default_completion=$binding[(w)2]
unset binding
}
zle -N fzf-completion zle -N fzf-completion
bindkey '^I' fzf-completion bindkey '^I' fzf-completion

View File

@@ -5,7 +5,7 @@ __fzf_select__() {
-o -type f -print \ -o -type f -print \
-o -type d -print \ -o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}" -o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
eval "$cmd" | fzf -m | while read -r item; do eval "$cmd | fzf -m $FZF_CTRL_T_OPTS" | while read -r item; do
printf '%q ' "$item" printf '%q ' "$item"
done done
echo echo
@@ -26,7 +26,7 @@ __fzf_select_tmux__() {
height="-l $height" height="-l $height"
fi fi
tmux split-window $height "cd $(printf %q "$PWD"); FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS") PATH=$(printf %q "$PATH") FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") bash -c 'source \"${BASH_SOURCE[0]}\"; tmux send-keys -t $TMUX_PANE \"\$(__fzf_select__)\"'" tmux split-window $height "cd $(printf %q "$PWD"); FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS") PATH=$(printf %q "$PATH") FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") FZF_CTRL_T_OPTS=$(printf %q "$FZF_CTRL_T_OPTS") bash -c 'source \"${BASH_SOURCE[0]}\"; tmux send-keys -t $TMUX_PANE \"\$(__fzf_select__)\"'"
} }
fzf-file-widget() { fzf-file-widget() {
@@ -43,7 +43,7 @@ __fzf_cd__() {
local cmd dir local cmd dir
cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \ cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"}" -o -type d -print 2> /dev/null | sed 1d | cut -b3-"}"
dir=$(eval "$cmd" | $(__fzfcmd) +m) && printf 'cd %q' "$dir" dir=$(eval "$cmd | $(__fzfcmd) +m $FZF_ALT_C_OPTS") && printf 'cd %q' "$dir"
} }
__fzf_history__() ( __fzf_history__() (

View File

@@ -19,7 +19,7 @@ function fzf_key_bindings
-o -type f -print \ -o -type f -print \
-o -type d -print \ -o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-" -o -type l -print 2> /dev/null | sed 1d | cut -b3-"
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)" -m > $TMPDIR/fzf.result" eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)" -m $FZF_CTRL_T_OPTS > $TMPDIR/fzf.result"
and for i in (seq 20); commandline -i (cat $TMPDIR/fzf.result | __fzf_escape) 2> /dev/null; and break; sleep 0.1; end and for i in (seq 20); commandline -i (cat $TMPDIR/fzf.result | __fzf_escape) 2> /dev/null; and break; sleep 0.1; end
commandline -f repaint commandline -f repaint
rm -f $TMPDIR/fzf.result rm -f $TMPDIR/fzf.result
@@ -37,7 +37,7 @@ function fzf_key_bindings
command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \ command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | sed 1d | cut -b3-" -o -type d -print 2> /dev/null | sed 1d | cut -b3-"
# Fish hangs if the command before pipe redirects (2> /dev/null) # Fish hangs if the command before pipe redirects (2> /dev/null)
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)" +m > $TMPDIR/fzf.result" eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)" +m $FZF_ALT_C_OPTS > $TMPDIR/fzf.result"
[ (cat $TMPDIR/fzf.result | wc -l) -gt 0 ] [ (cat $TMPDIR/fzf.result | wc -l) -gt 0 ]
and cd (cat $TMPDIR/fzf.result) and cd (cat $TMPDIR/fzf.result)
commandline -f repaint commandline -f repaint

View File

@@ -8,7 +8,7 @@ __fsel() {
-o -type f -print \ -o -type f -print \
-o -type d -print \ -o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}" -o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
eval "$cmd" | $(__fzfcmd) -m | while read item; do eval "$cmd | $(__fzfcmd) -m $FZF_CTRL_T_OPTS" | while read item; do
echo -n "${(q)item} " echo -n "${(q)item} "
done done
echo echo
@@ -29,7 +29,7 @@ bindkey '^T' fzf-file-widget
fzf-cd-widget() { fzf-cd-widget() {
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \ local cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"}" -o -type d -print 2> /dev/null | sed 1d | cut -b3-"}"
cd "${$(eval "$cmd" | $(__fzfcmd) +m):-.}" cd "${$(eval "$cmd | $(__fzfcmd) +m $FZF_ALT_C_OPTS"):-.}"
zle reset-prompt zle reset-prompt
} }
zle -N fzf-cd-widget zle -N fzf-cd-widget

View File

@@ -36,7 +36,7 @@ func init() {
ansiRegex = regexp.MustCompile("\x1b\\[[0-9;]*[mK]") ansiRegex = regexp.MustCompile("\x1b\\[[0-9;]*[mK]")
} }
func extractColor(str string, state *ansiState) (string, []ansiOffset, *ansiState) { func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, []ansiOffset, *ansiState) {
var offsets []ansiOffset var offsets []ansiOffset
var output bytes.Buffer var output bytes.Buffer
@@ -46,7 +46,11 @@ func extractColor(str string, state *ansiState) (string, []ansiOffset, *ansiStat
idx := 0 idx := 0
for _, offset := range ansiRegex.FindAllStringIndex(str, -1) { for _, offset := range ansiRegex.FindAllStringIndex(str, -1) {
output.WriteString(str[idx:offset[0]]) prev := str[idx:offset[0]]
output.WriteString(prev)
if proc != nil && !proc(prev, state) {
break
}
newState := interpretCode(str[offset[0]:offset[1]], state) newState := interpretCode(str[offset[0]:offset[1]], state)
if !newState.equals(state) { if !newState.equals(state) {
@@ -77,6 +81,9 @@ func extractColor(str string, state *ansiState) (string, []ansiOffset, *ansiStat
(&offsets[len(offsets)-1]).offset[1] = int32(utf8.RuneCount(output.Bytes())) (&offsets[len(offsets)-1]).offset[1] = int32(utf8.RuneCount(output.Bytes()))
} }
} }
if proc != nil {
proc(rest, state)
}
return output.String(), offsets, state return output.String(), offsets, state
} }

View File

@@ -17,7 +17,7 @@ func TestExtractColor(t *testing.T) {
var state *ansiState var state *ansiState
clean := "\x1b[0m" clean := "\x1b[0m"
check := func(assertion func(ansiOffsets []ansiOffset, state *ansiState)) { check := func(assertion func(ansiOffsets []ansiOffset, state *ansiState)) {
output, ansiOffsets, newState := extractColor(src, state) output, ansiOffsets, newState := extractColor(src, state, nil)
state = newState state = newState
if output != "hello world" { if output != "hello world" {
t.Errorf("Invalid output: {}", output) t.Errorf("Invalid output: {}", output)

View File

@@ -8,7 +8,7 @@ import (
const ( const (
// Current version // Current version
version = "0.13.0" version = "0.13.2"
// Core // Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond coordinatorDelayMax time.Duration = 100 * time.Millisecond

View File

@@ -73,7 +73,7 @@ func Run(opts *Options) {
if opts.Theme != nil { if opts.Theme != nil {
var state *ansiState var state *ansiState
ansiProcessor = func(data []byte) ([]rune, []ansiOffset) { ansiProcessor = func(data []byte) ([]rune, []ansiOffset) {
trimmed, offsets, newState := extractColor(string(data), state) trimmed, offsets, newState := extractColor(string(data), state, nil)
state = newState state = newState
return []rune(trimmed), offsets return []rune(trimmed), offsets
} }
@@ -81,7 +81,7 @@ func Run(opts *Options) {
// When color is disabled but ansi option is given, // When color is disabled but ansi option is given,
// we simply strip out ANSI codes from the input // we simply strip out ANSI codes from the input
ansiProcessor = func(data []byte) ([]rune, []ansiOffset) { ansiProcessor = func(data []byte) ([]rune, []ansiOffset) {
trimmed, _, _ := extractColor(string(data), nil) trimmed, _, _ := extractColor(string(data), nil, nil)
return []rune(trimmed), nil return []rune(trimmed), nil
} }
} }

View File

@@ -128,7 +128,7 @@ func (item *Item) AsString(stripAnsi bool) string {
func (item *Item) StringPtr(stripAnsi bool) *string { func (item *Item) StringPtr(stripAnsi bool) *string {
if item.origText != nil { if item.origText != nil {
if stripAnsi { if stripAnsi {
trimmed, _, _ := extractColor(string(*item.origText), nil) trimmed, _, _ := extractColor(string(*item.origText), nil, nil)
return &trimmed return &trimmed
} }
orig := string(*item.origText) orig := string(*item.origText)

View File

@@ -82,6 +82,11 @@ type selectedItem struct {
type byTimeOrder []selectedItem type byTimeOrder []selectedItem
type previewRequest struct {
ok bool
str string
}
func (a byTimeOrder) Len() int { func (a byTimeOrder) Len() int {
return len(a) return len(a)
} }
@@ -556,7 +561,7 @@ func (t *Terminal) printHeader() {
if line >= max { if line >= max {
continue continue
} }
trimmed, colors, newState := extractColor(lineStr, state) trimmed, colors, newState := extractColor(lineStr, state, nil)
state = newState state = newState
item := &Item{ item := &Item{
text: []rune(trimmed), text: []rune(trimmed),
@@ -730,25 +735,13 @@ func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, c
} }
func (t *Terminal) printPreview() { func (t *Terminal) printPreview() {
trimmed, ansiOffsets, _ := extractColor(t.previewTxt, nil)
var index int32
t.pwindow.Erase() t.pwindow.Erase()
for _, o := range ansiOffsets { extractColor(t.previewTxt, nil, func(str string, ansi *ansiState) bool {
b := o.offset[0] if ansi != nil && ansi.colored() {
e := o.offset[1] return t.pwindow.CFill(str, ansi.fg, ansi.bg, ansi.bold)
if b > index {
if !t.pwindow.Fill(trimmed[index:b]) {
return
}
} }
if !t.pwindow.CFill(trimmed[b:e], o.color.fg, o.color.bg, o.color.bold) { return t.pwindow.Fill(str)
return })
}
index = e
}
if int(index) < len(trimmed) {
t.pwindow.Fill(trimmed[index:])
}
} }
func processTabs(runes []rune, prefixWidth int) (string, int) { func processTabs(runes []rune, prefixWidth int) (string, int) {
@@ -920,21 +913,23 @@ func (t *Terminal) Loop() {
if t.hasPreviewWindow() { if t.hasPreviewWindow() {
go func() { go func() {
for { for {
focused := "" request := previewRequest{false, ""}
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:
focused = value.(string) request = value.(previewRequest)
} }
} }
events.Clear() events.Clear()
}) })
if len(focused) > 0 { if request.ok {
command := strings.Replace(t.preview.command, "{}", quoteEntry(focused), -1) command := strings.Replace(t.preview.command, "{}", quoteEntry(request.str), -1)
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))
} else {
t.reqBox.Set(reqPreviewDisplay, "")
} }
} }
}() }()
@@ -948,7 +943,7 @@ func (t *Terminal) Loop() {
} }
go func() { go func() {
focused := "" focused := previewRequest{false, ""}
for { for {
t.reqBox.Wait(func(events *util.Events) { t.reqBox.Wait(func(events *util.Events) {
defer events.Clear() defer events.Clear()
@@ -965,19 +960,17 @@ func (t *Terminal) Loop() {
case reqList: case reqList:
t.printList() t.printList()
cnt := t.merger.Length() cnt := t.merger.Length()
var currentFocus previewRequest
if cnt > 0 && cnt > t.cy { if cnt > 0 && cnt > t.cy {
currentFocus := t.current() currentFocus = previewRequest{true, t.current()}
if currentFocus != focused {
focused = currentFocus
if t.isPreviewEnabled() {
t.previewBox.Set(reqPreviewEnqueue, focused)
}
}
} else { } else {
if focused != "" && t.isPreviewEnabled() { currentFocus = previewRequest{false, ""}
t.pwindow.Erase() }
if currentFocus != focused {
focused = currentFocus
if t.isPreviewEnabled() {
t.previewBox.Set(reqPreviewEnqueue, focused)
} }
focused = ""
} }
case reqJump: case reqJump:
if t.merger.Length() == 0 { if t.merger.Length() == 0 {
@@ -1088,7 +1081,7 @@ func (t *Terminal) Loop() {
t.resizeWindows() t.resizeWindows()
cnt := t.merger.Length() cnt := t.merger.Length()
if t.previewing && cnt > 0 && cnt > t.cy { if t.previewing && cnt > 0 && cnt > t.cy {
t.previewBox.Set(reqPreviewEnqueue, t.current()) t.previewBox.Set(reqPreviewEnqueue, previewRequest{true, t.current()})
} }
req(reqList, reqInfo) req(reqList, reqInfo)
} }

View File

@@ -1229,14 +1229,20 @@ class TestGoFZF < TestBase
end end
def test_preview def test_preview
tmux.send_keys %[seq 1000 | #{FZF} --preview 'echo {{}-{}}' --bind ?:toggle-preview], :Enter tmux.send_keys %[seq 1000 | sed s/^2$// | #{FZF} --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.until { |lines| lines[1].include?(' {-}') }
tmux.send_keys '555' tmux.send_keys '555'
tmux.until { |lines| lines[1].include?(' {555-555}') } tmux.until { |lines| lines[1].include?(' {555-555}') }
tmux.send_keys '?' tmux.send_keys '?'
tmux.until { |lines| !lines[1].include?(' {555-555}') } tmux.until { |lines| !lines[1].include?(' {555-555}') }
tmux.send_keys '?' tmux.send_keys '?'
tmux.until { |lines| lines[1].include?(' {555-555}') } tmux.until { |lines| lines[1].include?(' {555-555}') }
tmux.send_keys :BSpace
tmux.until { |lines| lines[-2].start_with? ' 28/1000' }
tmux.send_keys 'foobar'
tmux.until { |lines| !lines[1].include?('{') }
end end
def test_preview_hidden def test_preview_hidden