mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2025-02-02 03:58:30 +00:00
Enhance click-header event
* Expose the name of the mouse action as $FZF_KEY * Trigger click-header on mouse up * Enhanced clickable header for `kill` completion
This commit is contained in:
parent
2c15cd7923
commit
e91f10ab16
@ -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" \
|
||||
|
@ -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 \
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,19 +5664,20 @@ 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 {
|
||||
}
|
||||
}
|
||||
if clicked && 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 {
|
||||
@ -5696,7 +5700,6 @@ func (t *Terminal) Loop() error {
|
||||
return doActions(actionsFor(tui.ClickHeader))
|
||||
}
|
||||
}
|
||||
}
|
||||
case actReload, actReloadSync:
|
||||
t.failed = nil
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user