Skip to content

Commit d658454

Browse files
committed
Make click-header export $FZF_CLICK_HEADER_{NTH,WORD}
1 parent c13228f commit d658454

File tree

6 files changed

+157
-51
lines changed

6 files changed

+157
-51
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ CHANGELOG
1414
--bind 'ctrl-r:reload(ps -ef)' --header 'Press CTRL-R to reload' \
1515
--header-lines-border bottom --no-list-border
1616
```
17+
- `click-header` event will also set `$FZF_CLICK_HEADER_WORD` and `$FZF_CLICK_HEADER_NTH`. You can use it to implement a clickable header that changes the search scope using the new `transform-nth` action.
18+
```sh
19+
# Click on the header line to limit search scope
20+
ps -ef | fzf --style full --layout reverse --header-lines 1 \
21+
--header-lines-border bottom --no-list-border \
22+
--color fg:dim,nth:regular \
23+
--bind 'click-header:transform-nth(
24+
echo $FZF_CLICK_HEADER_NTH
25+
)+transform-prompt(
26+
echo "$FZF_CLICK_HEADER_WORD> "
27+
)'
28+
```
1729
- 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.
1830
```sh
1931
TRANSFORMER='

man/man1/fzf.1

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,11 +1519,21 @@ e.g.
15191519

15201520
\fIclick\-header\fR
15211521
.RS
1522-
Triggered when a mouse click occurs within the header. Sets \fBFZF_CLICK_HEADER_LINE\fR and \fBFZF_CLICK_HEADER_COLUMN\fR environment variables starting from 1.
1522+
Triggered when a mouse click occurs within the header. Sets
1523+
\fBFZF_CLICK_HEADER_LINE\fR and \fBFZF_CLICK_HEADER_COLUMN\fR environment
1524+
variables starting from 1. It optionally sets \fBFZF_CLICK_HEADER_WORD\fR and
1525+
\fBFZF_CLICK_HEADER_NTH\fR if clicked on a word.
15231526

15241527
e.g.
1525-
\fBprintf "head1\\nhead2" | fzf \-\-header\-lines=2 \-\-bind 'click\-header:transform\-prompt:printf ${FZF_CLICK_HEADER_LINE}x${FZF_CLICK_HEADER_COLUMN}'\fR
1526-
1528+
\fB# Click on the header line to limit search scope
1529+
ps \-ef | fzf \-\-style full \-\-layout reverse \-\-header\-lines 1 \\
1530+
\-\-header\-lines\-border bottom \-\-no\-list\-border \\
1531+
\-\-color fg:dim,nth:regular \\
1532+
\-\-bind 'click\-header:transform\-nth(
1533+
echo $FZF_CLICK_HEADER_NTH
1534+
)+transform\-prompt(
1535+
echo "$FZF_CLICK_HEADER_WORD> "
1536+
)'\fR
15271537
.RE
15281538

15291539
.SS AVAILABLE ACTIONS:
@@ -1637,6 +1647,7 @@ A key or an event can be bound to one or more of the following actions.
16371647
\fBtransform\-header\-label(...)\fR (transform header label using an external command)
16381648
\fBtransform\-input\-label(...)\fR (transform input label using an external command)
16391649
\fBtransform\-list\-label(...)\fR (transform list label using an external command)
1650+
\fBtransform\-nth(...)\fR (transform nth using an external command)
16401651
\fBtransform\-preview\-label(...)\fR (transform preview label using an external command)
16411652
\fBtransform\-prompt(...)\fR (transform prompt string using an external command)
16421653
\fBtransform\-query(...)\fR (transform query string using an external command)

src/actiontype_string.go

Lines changed: 43 additions & 42 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/options.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1332,7 +1332,7 @@ const (
13321332

13331333
func init() {
13341334
executeRegexp = regexp.MustCompile(
1335-
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header)-label|header|search)|transform|change-(?:preview-window|preview|multi|nth)|(?:re|un)bind|pos|put|print|search)`)
1335+
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header)-label|header|search|nth)|transform|change-(?:preview-window|preview|multi)|(?:re|un)bind|pos|put|print|search)`)
13361336
splitRegexp = regexp.MustCompile("[,:]+")
13371337
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
13381338
}
@@ -1740,6 +1740,8 @@ func isExecuteAction(str string) actionType {
17401740
return actTransformHeaderLabel
17411741
case "transform-header":
17421742
return actTransformHeader
1743+
case "transform-nth":
1744+
return actTransformNth
17431745
case "transform-prompt":
17441746
return actTransformPrompt
17451747
case "transform-query":

src/terminal.go

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ const (
531531
actTransformInputLabel
532532
actTransformHeader
533533
actTransformHeaderLabel
534+
actTransformNth
534535
actTransformPreviewLabel
535536
actTransformPrompt
536537
actTransformQuery
@@ -1065,6 +1066,7 @@ func (t *Terminal) environImpl(forPreview bool) []string {
10651066
env = append(env, fmt.Sprintf("FZF_POS=%d", util.Min(t.merger.Length(), t.cy+1)))
10661067
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_LINE=%d", t.clickHeaderLine))
10671068
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_COLUMN=%d", t.clickHeaderColumn))
1069+
env = t.addClickHeaderWord(env)
10681070

10691071
// Add preview environment variables if preview is enabled
10701072
pwindowSize := t.pwindowSize()
@@ -1393,6 +1395,8 @@ func (t *Terminal) changeHeader(header string) bool {
13931395
}
13941396
needFullRedraw := len(t.header0) != len(lines)
13951397
t.header0 = lines
1398+
t.clickHeaderLine = 0
1399+
t.clickHeaderColumn = 0
13961400
return needFullRedraw
13971401
}
13981402

@@ -4089,6 +4093,64 @@ func (t *Terminal) currentIndex() int32 {
40894093
return minItem.Index()
40904094
}
40914095

4096+
func (t *Terminal) addClickHeaderWord(env []string) []string {
4097+
/*
4098+
* echo $'HL1\nHL2' | fzf --header-lines 3 --header $'H1\nH2' --header-lines-border --bind 'click-header:preview:env | grep FZF_CLICK'
4099+
*
4100+
* REVERSE DEFAULT
4101+
* H1 1 1
4102+
* H2 2 HL2 2
4103+
* ------- HL1 3
4104+
* HL1 3 -------
4105+
* HL2 4 H1 4
4106+
* 5 H2 5
4107+
*/
4108+
lineNum := t.clickHeaderLine - 1
4109+
if lineNum < 0 {
4110+
// Never clicked on the header
4111+
return env
4112+
}
4113+
4114+
var line string
4115+
if t.layout == layoutReverse {
4116+
if lineNum < len(t.header0) {
4117+
line = t.header0[lineNum]
4118+
} else if lineNum-len(t.header0) < len(t.header) {
4119+
line = t.header[lineNum-len(t.header0)]
4120+
}
4121+
} else {
4122+
// NOTE: t.header is padded with empty strings so that its size is equal to t.headerLines
4123+
if lineNum < len(t.header) {
4124+
line = t.header[len(t.header)-lineNum-1]
4125+
} else if lineNum-len(t.header) < len(t.header0) {
4126+
line = t.header0[lineNum-len(t.header)]
4127+
}
4128+
}
4129+
if len(line) == 0 {
4130+
return env
4131+
}
4132+
4133+
colNum := t.clickHeaderColumn - 1
4134+
words := Tokenize(line, t.delimiter)
4135+
for idx, token := range words {
4136+
prefixWidth := int(token.prefixLength)
4137+
word := token.text.ToString()
4138+
trimmed := strings.TrimSpace(word)
4139+
trimWidth, _ := util.RunesWidth([]rune(trimmed), prefixWidth, t.tabstop, math.MaxInt32)
4140+
4141+
if colNum >= prefixWidth && colNum < prefixWidth+trimWidth {
4142+
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_WORD=%s", trimmed))
4143+
nth := fmt.Sprintf("FZF_CLICK_HEADER_NTH=%d", idx+1)
4144+
if idx == len(words)-1 {
4145+
nth += ".."
4146+
}
4147+
env = append(env, nth)
4148+
return env
4149+
}
4150+
}
4151+
return env
4152+
}
4153+
40924154
// Loop is called to start Terminal I/O
40934155
func (t *Terminal) Loop() error {
40944156
// prof := profile.Start(profile.ProfilePath("/tmp/"))
@@ -4833,24 +4895,30 @@ func (t *Terminal) Loop() error {
48334895
}
48344896
t.multi = multi
48354897
req(reqList, reqInfo)
4836-
case actChangeNth:
4837-
changed = true
4898+
case actChangeNth, actTransformNth:
4899+
expr := a.a
4900+
if a.t == actTransformNth {
4901+
expr = t.captureLine(a.a)
4902+
}
48384903

48394904
// Split nth expression
4840-
tokens := strings.Split(a.a, "|")
4905+
tokens := strings.Split(expr, "|")
48414906
if nth, err := splitNth(tokens[0]); err == nil {
48424907
// Changed
48434908
newNth = &nth
48444909
} else {
48454910
// The default
48464911
newNth = &t.nth
48474912
}
4848-
t.nthCurrent = *newNth
48494913
// Cycle
48504914
if len(tokens) > 1 {
48514915
a.a = strings.Join(append(tokens[1:], tokens[0]), "|")
48524916
}
4853-
t.forceRerenderList()
4917+
if !compareRanges(t.nthCurrent, *newNth) {
4918+
changed = true
4919+
t.nthCurrent = *newNth
4920+
t.forceRerenderList()
4921+
}
48544922
case actChangeQuery:
48554923
t.input = []rune(a.a)
48564924
t.cx = len(t.input)

src/tokenizer.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ func (r Range) IsFull() bool {
2222
return r.begin == rangeEllipsis && r.end == rangeEllipsis
2323
}
2424

25+
func compareRanges(r1 []Range, r2 []Range) bool {
26+
if len(r1) != len(r2) {
27+
return false
28+
}
29+
for idx := range r1 {
30+
if r1[idx] != r2[idx] {
31+
return false
32+
}
33+
}
34+
return true
35+
}
36+
2537
func RangesToString(ranges []Range) string {
2638
strs := []string{}
2739
for _, r := range ranges {

0 commit comments

Comments
 (0)