diff --git a/ADVANCED.md b/ADVANCED.md index 2636b15f..69c738a9 100644 --- a/ADVANCED.md +++ b/ADVANCED.md @@ -529,7 +529,10 @@ TRANSFORMER=' # Otherwise, if the query does not end with a space, # restart ripgrep and reload the list elif ! [[ $FZF_QUERY =~ \ $ ]]; then - echo "reload:sleep 0.1; $RG_PREFIX \"${words[0]}\" || true" + pat=${words[0]} + echo "reload:sleep 0.1; $RG_PREFIX \"$pat\" || true" + else + echo search: fi ' fzf --ansi --disabled --query "$INITIAL_QUERY" \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 8aae63db..3126bdaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ CHANGELOG echo "$FZF_CLICK_HEADER_WORD> " )' ``` + - `kill` completion for bash and zsh were updated to use this feature - Added `search(...)` and `transform-search(...)` action to trigger an fzf search with an arbitrary query string. This can be used to extend the search syntax of fzf. In the following example, fzf will use the first word of the query to trigger ripgrep search, and use the rest of the query to perform fzf search within the result. ```sh TRANSFORMER=' @@ -40,6 +41,8 @@ CHANGELOG # restart ripgrep and reload the list elif ! [[ $FZF_QUERY =~ \ $ ]]; then echo "reload:rg --column --color=always --smart-case \"${words[0]}\"" + else + echo search: fi ' fzf --ansi --disabled \ diff --git a/shell/completion.bash b/shell/completion.bash index aa15b8ca..0c53e8e6 100644 --- a/shell/completion.bash +++ b/shell/completion.bash @@ -409,8 +409,32 @@ _fzf_complete_kill() { } _fzf_proc_completion() { + local transformer + transformer=' + if [[ $FZF_KEY =~ ctrl|alt|shift ]] && [[ -n $FZF_NTH ]]; then + nths=( $(tr , " " <<< "$FZF_NTH") ) + new_nths=() + found=0 + for nth in ${nths[@]}; do + if [[ $nth = $FZF_CLICK_HEADER_NTH ]]; then + found=1 + else + new_nths+=($nth) + fi + done + [[ $found = 0 ]] && new_nths+=($FZF_CLICK_HEADER_NTH) + new_nths=$(echo ${new_nths[@]} | tr " " ,) + echo "change-nth($new_nths)+change-prompt($new_nths> )" + else + if [[ $FZF_NTH = $FZF_CLICK_HEADER_NTH ]]; then + echo "change-nth()+change-prompt(> )" + else + echo "change-nth($FZF_CLICK_HEADER_NTH)+change-prompt($FZF_CLICK_HEADER_WORD> )" + fi + fi + ' _fzf_complete -m --header-lines=1 --no-preview --wrap --color fg:dim,nth:regular \ - --bind 'click-header:transform:echo "change-nth($FZF_CLICK_HEADER_NTH)+change-prompt($FZF_CLICK_HEADER_WORD> )"' -- "$@" < <( + --bind "click-header:transform:$transformer" -- "$@" < <( command ps -eo user,pid,ppid,start,time,command 2> /dev/null || command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox command ps --everyone --full --windows # For cygwin diff --git a/shell/completion.zsh b/shell/completion.zsh index 9979933e..1199ac54 100644 --- a/shell/completion.zsh +++ b/shell/completion.zsh @@ -290,8 +290,32 @@ _fzf_complete_unalias() { } _fzf_complete_kill() { + local transformer + transformer=' + if [[ $FZF_KEY =~ ctrl|alt|shift ]] && [[ -n $FZF_NTH ]]; then + nths=( $(tr , " " <<< "$FZF_NTH") ) + new_nths=() + found=0 + for nth in ${nths[@]}; do + if [[ $nth = $FZF_CLICK_HEADER_NTH ]]; then + found=1 + else + new_nths+=($nth) + fi + done + [[ $found = 0 ]] && new_nths+=($FZF_CLICK_HEADER_NTH) + new_nths=$(echo ${new_nths[@]} | tr " " ,) + echo "change-nth($new_nths)+change-prompt($new_nths> )" + else + if [[ $FZF_NTH = $FZF_CLICK_HEADER_NTH ]]; then + echo "change-nth()+change-prompt(> )" + else + echo "change-nth($FZF_CLICK_HEADER_NTH)+change-prompt($FZF_CLICK_HEADER_WORD> )" + fi + fi + ' _fzf_complete -m --header-lines=1 --no-preview --wrap --color fg:dim,nth:regular \ - --bind 'click-header:transform:echo "change-nth($FZF_CLICK_HEADER_NTH)+change-prompt($FZF_CLICK_HEADER_WORD> )"' -- "$@" < <( + --bind "click-header:transform:$transformer" -- "$@" < <( command ps -eo user,pid,ppid,start,time,command 2> /dev/null || command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox command ps --everyone --full --windows # For cygwin diff --git a/src/terminal.go b/src/terminal.go index 56b70e9b..5ff9f8de 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -4620,6 +4620,7 @@ func (t *Terminal) Loop() error { pbarDragging := false pborderDragging := -1 wasDown := false + pmx, pmy := -1, -1 needBarrier := true // If an action is bound to 'start', we're going to process it before reading @@ -5422,25 +5423,30 @@ func (t *Terminal) Loop() error { case actMouse: me := event.MouseEvent mx, my := me.X, me.Y - clicked := !wasDown && me.Down + click := !wasDown && me.Down + clicked := wasDown && !me.Down && (mx == pmx && my == pmy) wasDown = me.Down + if click { + pmx, pmy = mx, my + } if !me.Down { barDragging = false pbarDragging = false pborderDragging = -1 previewDraggingPos = -1 + pmx, pmy = -1, -1 } // Scrolling if me.S != 0 { if t.window.Enclose(my, mx) && t.merger.Length() > 0 { evt := tui.ScrollUp - if me.Mod { + if me.Mod() { evt = tui.SScrollUp } if me.S < 0 { evt = tui.ScrollDown - if me.Mod { + if me.Mod() { evt = tui.SScrollDown } } @@ -5456,7 +5462,7 @@ func (t *Terminal) Loop() error { } // Preview dragging - if me.Down && (previewDraggingPos >= 0 || clicked && t.hasPreviewWindow() && t.pwindow.Enclose(my, mx)) { + if me.Down && (previewDraggingPos >= 0 || click && t.hasPreviewWindow() && t.pwindow.Enclose(my, mx)) { if previewDraggingPos > 0 { scrollPreviewBy(previewDraggingPos - my) } @@ -5466,7 +5472,7 @@ func (t *Terminal) Loop() error { // Preview scrollbar dragging headerLines := t.activePreviewOpts.headerLines - pbarDragging = me.Down && (pbarDragging || clicked && t.hasPreviewWindow() && my >= t.pwindow.Top()+headerLines && my < t.pwindow.Top()+t.pwindow.Height() && mx == t.pwindow.Left()+t.pwindow.Width()) + pbarDragging = me.Down && (pbarDragging || click && t.hasPreviewWindow() && my >= t.pwindow.Top()+headerLines && my < t.pwindow.Top()+t.pwindow.Height() && mx == t.pwindow.Left()+t.pwindow.Width()) if pbarDragging { effectiveHeight := t.pwindow.Height() - headerLines numLines := len(t.previewer.lines) - headerLines @@ -5483,7 +5489,7 @@ func (t *Terminal) Loop() error { } // Preview border dragging (resizing) - if pborderDragging < 0 && clicked && t.hasPreviewWindow() { + if pborderDragging < 0 && click && t.hasPreviewWindow() { switch t.activePreviewOpts.position { case posUp: if t.pborder.Enclose(my, mx) && my == t.pborder.Top()+t.pborder.Height()-1 { @@ -5564,9 +5570,7 @@ func (t *Terminal) Loop() error { } // Inside the header window - // TODO: Should we trigger this on mouse up instead? - // Should we still trigger it when the position has changed from the down event? - if t.headerVisible && t.headerWindow != nil && t.headerWindow.Enclose(my, mx) { + if clicked && t.headerVisible && t.headerWindow != nil && t.headerWindow.Enclose(my, mx) { mx -= t.headerWindow.Left() + t.headerIndent(t.headerBorderShape) my -= t.headerWindow.Top() if mx < 0 { @@ -5580,7 +5584,7 @@ func (t *Terminal) Loop() error { return doActions(actionsFor(tui.ClickHeader)) } - if t.headerVisible && t.headerLinesWindow != nil && t.headerLinesWindow.Enclose(my, mx) { + if clicked && t.headerVisible && t.headerLinesWindow != nil && t.headerLinesWindow.Enclose(my, mx) { mx -= t.headerLinesWindow.Left() + t.headerIndent(t.headerLinesShape) my -= t.headerLinesWindow.Top() if mx < 0 { @@ -5616,7 +5620,7 @@ func (t *Terminal) Loop() error { } // Scrollbar dragging - barDragging = me.Down && (barDragging || clicked && my >= min && mx == t.window.Width()-1) + barDragging = me.Down && (barDragging || click && my >= min && mx == t.window.Width()-1) if barDragging { barLength, barStart := t.getScrollbar() if barLength > 0 { @@ -5649,7 +5653,6 @@ func (t *Terminal) Loop() error { return doActions(actionsFor(tui.DoubleClick)) } } - break } if me.Down { @@ -5661,40 +5664,40 @@ func (t *Terminal) Loop() error { t.vset(cy) req(reqList) evt := tui.RightClick - if me.Mod { + if me.Mod() { evt = tui.SRightClick } if me.Left { evt = tui.LeftClick - if me.Mod { + if me.Mod() { evt = tui.SLeftClick } } return doActions(actionsFor(evt)) - } else if t.headerVisible && t.headerWindow == nil { - // Header - // TODO: Should we trigger this on mouse up instead? - numLines := t.visibleHeaderLinesInList() - lineOffset := 0 - if t.inputWindow == nil && !t.headerFirst { - // offset for info line - if t.noSeparatorLine() { - lineOffset = 1 - } else { - lineOffset = 2 - } + } + } + if clicked && t.headerVisible && t.headerWindow == nil { + // Header + numLines := t.visibleHeaderLinesInList() + lineOffset := 0 + if t.inputWindow == nil && !t.headerFirst { + // offset for info line + if t.noSeparatorLine() { + lineOffset = 1 + } else { + lineOffset = 2 } - my -= lineOffset - mx -= t.pointerLen + t.markerLen - if my >= 0 && my < numLines && mx >= 0 { - if t.layout == layoutReverse { - t.clickHeaderLine = my + 1 - } else { - t.clickHeaderLine = numLines - my - } - t.clickHeaderColumn = mx + 1 - return doActions(actionsFor(tui.ClickHeader)) + } + my -= lineOffset + mx -= t.pointerLen + t.markerLen + if my >= 0 && my < numLines && mx >= 0 { + if t.layout == layoutReverse { + t.clickHeaderLine = my + 1 + } else { + t.clickHeaderLine = numLines - my } + t.clickHeaderColumn = mx + 1 + return doActions(actionsFor(tui.ClickHeader)) } } case actReload, actReloadSync: diff --git a/src/tui/light.go b/src/tui/light.go index 56e9ae0b..54c38c18 100644 --- a/src/tui/light.go +++ b/src/tui/light.go @@ -626,15 +626,13 @@ func (r *LightRenderer) mouseSequence(sz *int) Event { // middle := t & 0b1 left := t&0b11 == 0 - - // shift := t & 0b100 - // ctrl := t & 0b1000 - mod := t&0b1100 > 0 - - drag := t&0b100000 > 0 + ctrl := t&0b10000 > 0 + alt := t&0b01000 > 0 + shift := t&0b00100 > 0 + drag := t&0b100000 > 0 // 32 if scroll != 0 { - return Event{Mouse, 0, &MouseEvent{y, x, scroll, false, false, false, mod}} + return Event{Mouse, 0, &MouseEvent{y, x, scroll, false, false, false, ctrl, alt, shift}} } double := false @@ -658,7 +656,7 @@ func (r *LightRenderer) mouseSequence(sz *int) Event { } } } - return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}} + return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, ctrl, alt, shift}} } func (r *LightRenderer) smcup() { diff --git a/src/tui/tcell.go b/src/tui/tcell.go index 3c3e13fc..3738214a 100644 --- a/src/tui/tcell.go +++ b/src/tui/tcell.go @@ -266,7 +266,11 @@ func (r *FullscreenRenderer) GetChar() Event { // so mouse click is three consecutive events, but the first and last are indistinguishable from movement events (with released buttons) // dragging has same structure, it only repeats the middle (main) event appropriately x, y := ev.Position() - mod := ev.Modifiers() != 0 + + mod := ev.Modifiers() + ctrl := (mod & tcell.ModCtrl) > 0 + alt := (mod & tcell.ModAlt) > 0 + shift := (mod & tcell.ModShift) > 0 // since we dont have mouse down events (unlike LightRenderer), we need to track state in prevButton prevButton, button := _prevMouseButton, ev.Buttons() @@ -275,9 +279,9 @@ func (r *FullscreenRenderer) GetChar() Event { switch { case button&tcell.WheelDown != 0: - return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, mod}} + return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, ctrl, alt, shift}} case button&tcell.WheelUp != 0: - return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, mod}} + return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, ctrl, alt, shift}} case button&tcell.Button1 != 0: double := false if !drag { @@ -300,9 +304,9 @@ func (r *FullscreenRenderer) GetChar() Event { } } // fire single or double click event - return Event{Mouse, 0, &MouseEvent{y, x, 0, true, !double, double, mod}} + return Event{Mouse, 0, &MouseEvent{y, x, 0, true, !double, double, ctrl, alt, shift}} case button&tcell.Button2 != 0: - return Event{Mouse, 0, &MouseEvent{y, x, 0, false, true, false, mod}} + return Event{Mouse, 0, &MouseEvent{y, x, 0, false, true, false, ctrl, alt, shift}} default: // double and single taps on Windows don't quite work due to // the console acting on the events and not allowing us @@ -311,7 +315,11 @@ func (r *FullscreenRenderer) GetChar() Event { down := left || button&tcell.Button3 != 0 double := false - return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}} + // No need to report mouse movement events when no button is pressed + if drag { + return Event{Invalid, 0, nil} + } + return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, ctrl, alt, shift}} } // process keyboard: diff --git a/src/tui/tui.go b/src/tui/tui.go index eab5ad7e..fe8fc243 100644 --- a/src/tui/tui.go +++ b/src/tui/tui.go @@ -150,6 +150,10 @@ func (e Event) Comparable() Event { } func (e Event) KeyName() string { + if me := e.MouseEvent; me != nil { + return me.Name() + } + if e.Type >= Invalid { return "" } @@ -367,7 +371,37 @@ type MouseEvent struct { Left bool Down bool Double bool - Mod bool + Ctrl bool + Alt bool + Shift bool +} + +func (e MouseEvent) Mod() bool { + return e.Ctrl || e.Alt || e.Shift +} + +func (e MouseEvent) Name() string { + name := "" + if e.Down { + return name + } + + if e.Ctrl { + name += "ctrl-" + } + if e.Alt { + name += "alt-" + } + if e.Shift { + name += "shift-" + } + if e.Double { + name += "double-" + } + if !e.Left { + name += "right-" + } + return name + "click" } type BorderShape int