Compare commits

..

137 Commits

Author SHA1 Message Date
Junegunn Choi
65db7352b7 0.58.0 2025-01-20 02:00:03 +09:00
Junegunn Choi
a4db8bd7b5 Make 'current-fg' inherit from 'fg' to simplify configuration
If you do not want 'current-fg' to inherit attributes of 'fg', prefix it
with 'regular:' to reset them.

  # italic and underline
  fzf --color fg:italic,current-fg:underline

  # only underline
  fzf --color fg:italic,current-fg:regular:underline
2025-01-20 01:02:58 +09:00
dependabot[bot]
f1c1b02d77 Bump github.com/gdamore/tcell/v2 from 2.7.4 to 2.8.1 (#4175)
Bumps [github.com/gdamore/tcell/v2](https://github.com/gdamore/tcell) from 2.7.4 to 2.8.1.
- [Release notes](https://github.com/gdamore/tcell/releases)
- [Changelog](https://github.com/gdamore/tcell/blob/main/CHANGESv2.md)
- [Commits](https://github.com/gdamore/tcell/compare/v2.7.4...v2.8.1)

---
updated-dependencies:
- dependency-name: github.com/gdamore/tcell/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 01:01:59 +09:00
Elliott Sales de Andrade
6580f32b43 Fix a non-constant format string (#4189)
Go 1.24 now has a vet check about this that causes `go test` to fail:
https://github.com/golang/go/issues/60529
2025-01-20 00:32:50 +09:00
Junegunn Choi
b028cbd8bd Clarify print(...) action 2025-01-19 13:55:35 +09:00
junegunn
a1a5418318 Deploying to master from @ junegunn/fzf@5a32634b74 🚀 2025-01-19 00:02:12 +00:00
bitraid
5a32634b74 [fish] Allow setting multi-select and list reload for history (#4179)
* [fish] Drop support for versions older than 3.0b1

* [fish] Use `set` instead of `read` for `$result`

This effectively makes CTRL-R non-blocking (the previous attempt was
unsuccessful).

* [fish] Allow FZF_CTRL_R_OPTS to set multi-select
2025-01-19 01:38:18 +09:00
Junegunn Choi
c1875af70b Add 'gap-line' color for the horizontal line on each gap
Color inheritance: border >> list-border >> gap-line
2025-01-18 13:48:46 +09:00
Junegunn Choi
0a10d14e19 [fish] CTRL-R: Make loading non-blocking 2025-01-18 02:33:28 +09:00
Junegunn Choi
ed8ceec66f Add FZF_NTH to man page 2025-01-17 23:17:58 +09:00
piguagua
03760011d7 chore: fix comment (#4181)
Signed-off-by: piguagua <piguagua@aliyun.com>
2025-01-17 14:31:07 +09:00
Junegunn Choi
0d5aebb806 Allow setting border styles at once with --style full:STYLE 2025-01-17 13:12:51 +09:00
Junegunn Choi
1313510890 Do not apply nth style when the whole range is covered 2025-01-16 10:06:11 +09:00
Junegunn Choi
b712f2bb6a Export the current nth value as $FZF_NTH 2025-01-16 09:23:25 +09:00
Junegunn Choi
938c15ec63 Skip merging nth offsets when unnecessary 2025-01-16 09:05:59 +09:00
Junegunn Choi
3e7f032ec2 Allow displaying --nth parts in a different text style
Close #4183
2025-01-16 01:38:45 +09:00
Junegunn Choi
b42f5bfb19 Add --gap-line to --help output and man page 2025-01-15 23:40:42 +09:00
Junegunn Choi
717562b264 Disallow incorrect wrapping range expression for --nth 2025-01-15 22:39:48 +09:00
Junegunn Choi
9d6637c1b3 Add gap line
Close #4182
2025-01-15 22:23:52 +09:00
Junegunn Choi
56fef7c8df Simplify nth comparison when reusing transformed tokens 2025-01-13 17:37:50 +09:00
Junegunn Choi
ba0935c71f Fix change-nth
* Proper clean-up of caches
* Force rerender list after the action
2025-01-13 12:45:01 +09:00
Junegunn Choi
d83eb2800a Add change-nth action
Example:
  # Start with --nth 1, then 2, then 3, then back to the default, 1
  echo 'foo foobar foobarbaz' | fzf --bind 'space:change-nth(2|3|)' --nth 1 -q foo

Close #4172
Close #3109
2025-01-13 00:13:31 +09:00
Junegunn Choi
6f943112a9 Align header with the list 2025-01-12 14:58:55 +09:00
Junegunn Choi
f422893b8e Add --style to the CHANGELOG 2025-01-12 10:29:15 +09:00
bitraid
22b498489c [fish] Optimize history formatting without perl (#4171) 2025-01-12 10:27:26 +09:00
Junegunn Choi
5460517bd2 Treat a single-character delimiter as a plain string delimiter
even if it's a regular expression meta-character

Close #4170
2025-01-12 10:23:43 +09:00
junegunn
9a6e557e52 Deploying to master from @ junegunn/fzf@4fdc07927f 🚀 2025-01-12 00:02:26 +00:00
Junegunn Choi
4fdc07927f Refactor --preview-border=line 2025-01-11 19:34:26 +09:00
Junegunn Choi
9030b67e4f Fix window sizing with borders on the right 2025-01-11 11:39:51 +09:00
Junegunn Choi
43eafdf4b7 Fix preview scrollbar with '--preview-window bottom,border-line' 2025-01-11 00:53:07 +09:00
Junegunn Choi
dfb88edb5e Make preview-scrollbar color conditionally inherit from scrollbar color 2025-01-11 00:51:49 +09:00
Junegunn Choi
bd3e65df4d Trim unsupported OSC sequences (#4169)
Fix #4169
2025-01-10 20:53:47 +09:00
Junegunn Choi
d7b13f3408 Add a test case for the mixed delimiter ANSI sequence (#4169) 2025-01-10 20:31:51 +09:00
Junegunn Choi
14ef8e8051 Support ANSI sequences with mixed ; and : delimiters (#4169)
`make bench` shows no loss of performance.
2025-01-10 17:43:13 +09:00
bitraid
cc1d9f124e [fish] Fix history formatting when perl is missing (#4166)
Don't add tab after escaped newline.
2025-01-10 14:03:21 +09:00
Kid
93c0299606 [fish] remove defunct bind feature detection (#4165) 2025-01-09 19:16:24 +09:00
Junegunn Choi
55e3c73221 fzf-preview.sh: Support FILEPATH:LINE[:COL] argument 2025-01-09 17:00:46 +09:00
Junegunn Choi
6783417504 Do not export $LINES and $COLUMNS for non-preview processes
Fix #4164
2025-01-08 10:00:57 +09:00
Junegunn Choi
fa3f706e71 Refactor option parser 2025-01-07 19:16:41 +09:00
Junegunn Choi
9c2f6cae88 Fix adaptive height with --header-border 2025-01-07 19:16:16 +09:00
Junegunn Choi
a30181e240 Update man page sections 2025-01-07 00:20:36 +09:00
dependabot[bot]
b4ccf64e62 Bump golang.org/x/term from 0.27.0 to 0.28.0 (#4162)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.27.0 to 0.28.0.
- [Commits](https://github.com/golang/term/compare/v0.27.0...v0.28.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-06 23:36:41 +09:00
Junegunn Choi
88d768bf6b Restructure --help output 2025-01-06 23:34:14 +09:00
Junegunn Choi
6444cc7905 Render preview label if possible when --preview-border=line 2025-01-06 10:09:59 +09:00
Junegunn Choi
328af1f397 Remove header indentation when unnecessary
# Indent the header to align with the entries in the list
  fzf --header 'Hello' --header-border --list-border

  # No extra indentation required
  fzf --header 'Hello' --header-border
2025-01-06 09:57:58 +09:00
Junegunn Choi
5ae60e2e80 Add style presets: --style=[default|minimal|full]
Close #4160
2025-01-06 02:10:44 +09:00
Junegunn Choi
0e0b868342 Add preview border style 'line'
It draws a single line between the preview window and the rest of the
interface. i.e. automatically choose between 'left', 'right', 'top', and
'bottom' depending on the position of the preview window.
2025-01-06 00:44:59 +09:00
Junegunn Choi
a5beb08ed7 Border around the header section
Close #4159
2025-01-05 23:02:52 +09:00
Junegunn Choi
45fc7b903d [install] Unset FZF_DEFAULT_OPTS when checking the binary 2025-01-05 11:33:40 +09:00
junegunn
4f2c274942 Deploying to master from @ junegunn/fzf@93415493b4 🚀 2025-01-05 00:02:19 +00:00
phanium
93415493b4 fix: make header align with list (#4158) 2025-01-05 01:13:23 +09:00
Junegunn Choi
8e4d338de9 Fix adaptive height in the presence of --list-border and --input-border
seq 10 | fzf --height=~100%
2025-01-04 19:19:18 +09:00
Junegunn Choi
8a71e091a8 Fix '--tmux border-native' 2025-01-04 18:47:00 +09:00
Andreas Auernhammer
120cd7f25a Add border-native option to --tmux flag (#4157)
This commit adds the `border-native` resulting in the following:

```
--tmux[=[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]][,border-native]]
```

By default, when not specified, the `-B` flag is passed to the
`tmux popup-window` command such that no border is drawn around
the tmux popup window.

When the `border-native` option is present, the `-B` flag is omitted
and the popup window is drawn using the border style configured in
the tmux config file.

Fixes #4156

Signed-off-by: Andreas Auernhammer <github@aead.dev>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2025-01-04 18:30:32 +09:00
Junegunn Choi
fb3bf6c984 Fix cursor placement of tcell renderer 2025-01-03 19:56:07 +09:00
dependabot[bot]
d57e1f8baa Bump crate-ci/typos from 1.28.2 to 1.28.4 (#4141)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.28.2 to 1.28.4.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.28.2...v1.28.4)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-03 13:49:18 +09:00
Minseo Kim
15ca9ad8eb Replace bash to sh in Makefile (#4138)
Some operating systems do not ship with bash by default, e.g. BSDs,
which breaks the build.
2025-01-03 13:48:51 +09:00
Junegunn Choi
c2e1861747 Update --help output 2025-01-02 23:54:59 +09:00
Junegunn Choi
543d41f3dd Do not try to print anything is screen height is zero 2025-01-02 23:44:47 +09:00
Junegunn Choi
e5cfc988ec Fix RuboCop error 2025-01-02 16:55:56 +09:00
Junegunn Choi
ee3916be17 Border around the input section (prompt + info)
Close #4154
2025-01-02 16:25:00 +09:00
Junegunn Choi
fd513f8af8 Add missing --list-border=* parser
Patch suggested by @bitraid
2024-12-31 19:39:46 +09:00
Junegunn Choi
9a2b7f559c Add --list-border for additional border around the list section
Close #4148
2024-12-31 17:05:14 +09:00
junegunn
b8d2b0df7e Deploying to master from @ junegunn/fzf@fe3a9c603e 🚀 2024-12-29 00:02:16 +00:00
Hong Xu
fe3a9c603e fzf-preview.sh: Don't include the file name in type information (#4143)
Reduce the changes of misjudging the type, e.g., when file is under an `image/`
directory.
2024-12-26 14:58:10 +09:00
junegunn
97030d4cb1 Deploying to master from @ junegunn/fzf@b2c3e567da 🚀 2024-12-22 00:02:14 +00:00
bitraid
b2c3e567da [fish] Partly revert change of 0167691 (#4137)
Don't use the `-f` switch of `string split`, because it was added in
fish version 3.2.0.
2024-12-20 10:05:09 +09:00
Junegunn Choi
ca5e633399 Add toggle-hscroll 2024-12-19 21:05:26 +09:00
Junegunn Choi
e60a9a628b Add toggle-multi-line action 2024-12-19 21:05:26 +09:00
bitraid
0167691941 [fish] Small syntax modification of some commands
No actual change, just for consistency with the rest of the code.
2024-12-19 20:50:04 +09:00
bitraid
3b0f976380 [fish] Enable home dir expansion of leading ~/
Enable expanding to user's home directory, when pressing <Ctrl-T> or
<Alt-C>, and the current command line token starts with `~/`.
2024-12-19 20:50:04 +09:00
bitraid
7bd298b536 [fish] Don't strip leading dot (.) character
Fix the removal of the leading dot character from the query, when
<Ctrl-T> was pressed and the current command line token started with a
dot. It was also removed when <Alt-C> was pressed and the directory
didn't exist under the current path.
2024-12-19 20:50:04 +09:00
Junegunn Choi
0476a65fca 0.57.0 2024-12-15 17:04:04 +09:00
junegunn
2cb2af115a Deploying to master from @ junegunn/fzf@789226ff6d 🚀 2024-12-15 00:02:31 +00:00
Junegunn Choi
789226ff6d Fix test failure
cdcab26 removed excessive clearing of the windows. But it caused the
problem where the right side of the preview window border was not
cleared when hiding the preview window with the scrollbar disabled.
2024-12-14 22:42:40 +09:00
Junegunn Choi
805efc5bf1 Remove unused interface 2024-12-14 22:31:39 +09:00
Junegunn Choi
cdcab26766 Fix redundant clearing of the windows with non-default bg color 2024-12-14 22:06:14 +09:00
Junegunn Choi
ec3acb1932 Update CHANGELOG 2024-12-12 13:53:58 +09:00
Junegunn Choi
d30e37434e Less flickering of the candidate list when resizing the preview window 2024-12-12 13:53:08 +09:00
Junegunn Choi
20d5b2e20e Avoid redrawing the windows on the first click on the border 2024-12-12 13:53:08 +09:00
Junegunn Choi
6c6be4ab1a Simplify resize code 2024-12-12 13:53:08 +09:00
Junegunn Choi
d004eb1f7c Redraw preview scrollbar when window width changes 2024-12-12 13:53:08 +09:00
Junegunn Choi
3148b0f3e8 Restore previous behavior 2024-12-12 13:53:08 +09:00
Junegunn Choi
3fc0bd26a5 Disallow dragging the wrong sides of the border 2024-12-12 13:53:08 +09:00
Junegunn Choi
6c9025ff17 Update comments 2024-12-12 13:53:08 +09:00
Junegunn Choi
289997e373 Refactor 2024-12-12 13:53:08 +09:00
Junegunn Choi
db44cbdff0 Change test case expectation (hard-coded minimum width removed) 2024-12-12 13:53:08 +09:00
Junegunn Choi
da9179335c Respect the properties of the currently active preview window options 2024-12-12 13:53:08 +09:00
Julian Prein
cdf641fa3e Use Has{Top,Right,Bottom,Left}() where possible
De-duplicate code and reduce the amount of code that has to be changed
when new BorderShapes are being added. This also adds and uses the
missing HasBottom().
2024-12-12 13:53:08 +09:00
Julian Prein
66dbee10f5 Fix minimum preview width without left/right borders
When the chosen preview border shape has no left and/or right border,
the minimum total preview window size decreases. But due to the
hardcoded value for the minimum size of the preview window the size
could not be decreased further than 5.
2024-12-12 13:53:08 +09:00
Julian Prein
19e9b620ba Fix maximum preview height without horizontal separator
The minimum window height decreases when no extra line for the
horizontal separator is used (e.g. with `--info=inline --no-separator`).
In this case the preview window should be able to occupy this extra
line.
2024-12-12 13:53:08 +09:00
Julian Prein
e4e4700aff Make the preview window resizable by mouse drag
Enable resizing the preview window by dragging its border with the
mouse. This works with all border styles except for `none`.
Counter-intuitively, having the border only on the opposite side of the
window works too - dragging from it will first decrease the preview size
to its minimum.
2024-12-12 13:53:08 +09:00
dependabot[bot]
bb55045596 Bump golang.org/x/term from 0.26.0 to 0.27.0 (#4124)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.26.0 to 0.27.0.
- [Commits](https://github.com/golang/term/compare/v0.26.0...v0.27.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 23:06:49 +09:00
dependabot[bot]
d7e51cdeb5 Bump crate-ci/typos from 1.28.1 to 1.28.2 (#4123)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.28.1 to 1.28.2.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.28.1...v1.28.2)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 23:06:32 +09:00
junegunn
7f4964b366 Deploying to master from @ junegunn/fzf@a6957aba11 🚀 2024-12-08 00:02:15 +00:00
LangLangBart
a6957aba11 chore: completion test command sequence (#4115)
cleanup zsh global scope
2024-12-03 20:34:26 +09:00
dependabot[bot]
b5f94f961d Bump crate-ci/typos from 1.27.3 to 1.28.1 (#4114)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.27.3 to 1.28.1.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.27.3...v1.28.1)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 00:25:33 +09:00
Junegunn Choi
e182d3db7a Fix line wrap toggle when switching between screens
Fix #4099
2024-12-02 22:25:23 +09:00
Junegunn Choi
3e6e0528a6 [install] grep -> \grep 2024-12-01 23:22:36 +09:00
buttering
ac508a1ce4 Enhance install script to handle commented and uncommented lines (#3632) (#4112)
* Enhance install script to handle commented and uncommented lines (#3632)

Resolves #3632

Enhance install script to handle commented and uncommented lines in shell file with user prompts for modification.
- Track commented and uncommented lines in the file.
- Prompt user to append or skip if the line is commented.
- Ensure new lines are added only when necessary, based on user input.
- To the `fish_user_key_bindings.fish`, the original logic would append the line to the end if no corresponding statement was found. I’ve adopted the same behavior for commented lines.

* Refactor append_line function to improve line existence check.

- Replaced `lno` variable with `lines` to store matching lines and simplified the logic.
- Improved line existence check, now prints all matching lines directly and handles commented lines separately.
- Removed unnecessary variables like `all_commented`, `commented_lines`, and `non_commented_lines`.

* Fix indentation

---------

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2024-12-01 23:21:12 +09:00
junegunn
d7fc1e09b1 Deploying to master from @ junegunn/fzf@3b0c86e401 🚀 2024-12-01 00:02:24 +00:00
Junegunn Choi
3b0c86e401 Much faster image processing
Fix #3984
2024-11-29 00:26:12 +09:00
Junegunn Choi
61d10d8ffa Update README and CHANGELOG
Close #4022
2024-11-28 19:46:56 +09:00
Junegunn Choi
7d9548919e Extend --walker-skip to support multi-component patterns
fzf --walker-skip 'foo/bar'

Close #4107
2024-11-26 17:26:16 +09:00
msabathier
bee80a730f Allow walking multiple root directories (#4109)
Co-authored-by: Martin Sabathier <martin.sabathier.ext@corys.fr>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2024-11-25 19:25:30 +09:00
Junegunn Choi
ac3e24c99c Export FZF_PREVIEW_* variables to other processes as well
Close #4098
2024-11-24 18:49:10 +09:00
junegunn
e7e852bdb3 Deploying to master from @ junegunn/fzf@2b7f168571 🚀 2024-11-24 00:03:09 +00:00
bitraid
2b7f168571 [fish] Enable keys for scripts that use read
Remove the check that exits when the shell is non-interactive, so that
the key bindings will work for the read command, when used by scripts.
2024-11-18 19:08:34 +09:00
bitraid
5b3da1d878 [fish] Use more native syntax
Mainly, replace [ with test. Also, change $FZF_TMUX check from numeric
to string, so that it won't show error if doesn't contain a number.
2024-11-18 19:08:34 +09:00
bitraid
99f1bc0177 [fish] Format history using builtins if perl is missing 2024-11-18 19:08:34 +09:00
bitraid
ed76f076dd [fish] Replace external commands with builtins
- Use `string collect` instead of cat to get the contents of
  $FZF_DEFAULT_OPTS_FILE. Also, check if the file is readable first.
- Use `string split` instead of cut to set $FISH_MAJOR, $FISH_MINOR.
- Use `string replace` instead of perl to strip leading tabs.
2024-11-18 19:08:34 +09:00
bitraid
4d357d1063 [fish] Improve commandline parsing
- Enable using unescaped quotes for exact-match, exact-boundary-match.
- Enable suffix-exact-match.
- Enable inverse-exact-match, inverse-prefix/suffix-exact-match.
- Allow searching for double quotes and backslashes.
- Combine multiple consecutive slashes into one.
- Workaround for test command bug, allowing $dir or $commandline be a
  single `!`.
2024-11-18 19:08:34 +09:00
junegunn
961ae1541c Deploying to master from @ junegunn/fzf@add1aec685 🚀 2024-11-17 00:02:20 +00:00
Junegunn Choi
add1aec685 0.56.3 2024-11-15 10:06:01 +09:00
LangLangBart
03d6ba7496 fix(zsh): handle backtick trigger edge case (#4090) 2024-11-14 16:07:52 +09:00
LangLangBart
71e4d5cc51 revert(zsh): remove 'fc -RI' call in the history widget (#4093) 2024-11-14 10:38:05 +09:00
Junegunn Choi
215ab48222 0.56.2 2024-11-12 00:57:55 +09:00
林千里
0c64c68781 Fix zsh $+name syntax does not work with setopt ksh_arrays (#4084) 2024-11-12 00:53:36 +09:00
Junegunn Choi
3ec035c68b Fix incorrect overflow detection when --wrap is set
Fix #4083
2024-11-12 00:33:07 +09:00
dependabot[bot]
20c7dcfbca Bump golang.org/x/term from 0.25.0 to 0.26.0 (#4085)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.25.0 to 0.26.0.
- [Commits](https://github.com/golang/term/compare/v0.25.0...v0.26.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-12 00:32:37 +09:00
dependabot[bot]
c1b8780b9c Bump crate-ci/typos from 1.26.0 to 1.27.3 (#4087)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.26.0 to 1.27.3.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.26.0...v1.27.3)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-12 00:32:12 +09:00
Junegunn Choi
64c61603e9 0.56.1 2024-11-10 23:15:35 +09:00
LangLangBart
57c08d925f Enhance command extraction in zsh completion (#4082)
Fix #1992
2024-11-10 17:40:59 +09:00
junegunn
51623a5f6a Deploying to master from @ junegunn/fzf@ca3f6181d7 🚀 2024-11-10 00:02:06 +00:00
Junegunn Choi
ca3f6181d7 page-up/down: undo last up/down if items are skipped
Fix #4069
2024-11-09 11:54:41 +09:00
Junegunn Choi
9c94f9c3d0 Another attempt to fix (half-)page-up/down for multi-line items
Fix #4069
2024-11-08 20:18:42 +09:00
Junegunn Choi
4a85843bcf Fix (half-)page-up/down in the presence of multi-line items
Fix #4069
2024-11-07 22:21:07 +09:00
jaydee-coder
d4d9b99879 Allow specifying '{n}' as the OFFSET in the preview-window flag (#4079)
* fzf: Allow '{n}' to be used as the OFFSET in the preview-window flag

* man: Document using '{n}' as the OFFSET in the preview-window flag
2024-11-04 22:27:59 +09:00
jaydee-coder
6816b7d95b docker: fix dockerfile warnings (#4080)
The following warnings were emitted when running `make docker-test`:
```
 2 warnings found (use docker --debug to expand):
 - LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format (line 11)
 - JSONArgsRecommended: JSON arguments recommended for CMD to prevent unintended behavior related to OS signals (line 12)
```

This change fixes both of these 2 trivial warnings.
2024-11-04 22:26:19 +09:00
Junegunn Choi
acdf265d7a Fix reader regression (#4070) 2024-11-03 20:32:26 +09:00
Junegunn Choi
19495eb9bb Remove possible races (#4070) 2024-11-03 20:12:47 +09:00
Junegunn Choi
bacc8609ee Fix characters from previous preview not being cleared
Fix #4075
2024-11-03 15:07:17 +09:00
junegunn
99163f5afa Deploying to master from @ junegunn/fzf@0607227730 🚀 2024-11-03 00:02:14 +00:00
LangLangBart
0607227730 fix(zsh): move 'fc -RI' inside command substitution (#4073)
* fix(zsh): move 'fc -RI' inside command substitution

* chore: follow existing option check format
2024-11-02 10:41:17 +09:00
LangLangBart
d938fdc496 fix(zsh): history loading with shared option (#4071)
Fix #4061
2024-11-01 00:19:47 +09:00
Junegunn Choi
dcb4c3d84a Fix race in reload action
Fix #4070
2024-10-31 19:40:40 +09:00
Junegunn Choi
82ebcd9209 Fix (half-)page-up/down in the presence of multi-line items
Fix #4069
2024-10-30 16:52:42 +09:00
45 changed files with 3796 additions and 1604 deletions

View File

@@ -7,4 +7,4 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: crate-ci/typos@v1.26.0 - uses: crate-ci/typos@v1.28.4

View File

@@ -128,7 +128,7 @@ fzf --height 70% --tmux 70%
You can also specify the position, width, and height of the popup window in You can also specify the position, width, and height of the popup window in
the following format: the following format:
* `[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]` * `[center|top|bottom|left|right][,SIZE[%]][,SIZE[%][,border-native]]`
```sh ```sh
# 100% width and 60% height # 100% width and 60% height

View File

@@ -1,6 +1,141 @@
CHANGELOG CHANGELOG
========= =========
0.58.0
------
_Release highlights: https://junegunn.github.io/fzf/releases/0.58.0/_
This version introduces three new border types, `--list-border`, `--input-border`, and `--header-border`, offering much greater flexibility for customizing the user interface.
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-4-borders.png" />
Also, fzf now offers "style presets" for quick customization, which can be activated using the `--style` option.
| Preset | Screenshot |
| :--- | :--- |
| `default` | <img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-style-default.png"/> |
| `full` | <img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-style-full.png"/> |
| `minimal` | <img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-style-minimal.png"/> |
- Style presets (#4160)
- `--style=full[:BORDER_STYLE]`
- `--style=default`
- `--style=minimal`
- Border and label for the list section (#4148)
- Options
- `--list-border[=STYLE]`
- `--list-label=LABEL`
- `--list-label-pos=COL[:bottom]`
- Colors
- `list-fg`
- `list-bg`
- `list-border`
- `list-label`
- Actions
- `change-list-label`
- `transform-list-label`
- Border and label for the input section (prompt line and info line) (#4154)
- Options
- `--input-border[=STYLE]`
- `--input-label=LABEL`
- `--input-label-pos=COL[:bottom]`
- Colors
- `input-fg` (`query`)
- `input-bg`
- `input-border`
- `input-label`
- Actions
- `change-input-label`
- `transform-input-label`
- Border and label for the header section (#4159)
- Options
- `--header-border[=STYLE]`
- `--header-label=LABEL`
- `--header-label-pos=COL[:bottom]`
- Colors
- `header-fg` (`header`)
- `header-bg`
- `header-border`
- `header-label`
- Actions
- `change-header-label`
- `transform-header-label`
- Added `--preview-border[=STYLE]` as short for `--preview-window=border[-STYLE]`
- Added new preview border style `line` which draws a single separator line between the preview window and the rest of the interface
- fzf will now render a dashed line (`┈┈`) in each `--gap` for better visual separation.
```sh
# All bash/zsh functions, highlighted
declare -f |
perl -0 -pe 's/^}\n/}\0/gm' |
bat --plain --language bash --color always |
fzf --read0 --ansi --layout reverse --multi --highlight-line --gap
```
* You can customize the line using `--gap-line[=STR]`.
- You can specify `border-native` to `--tmux` so that native tmux border is used instead of `--border`. This can be useful if you start a different program from inside the popup.
```sh
fzf --tmux border-native --bind 'enter:execute:less {}'
```
- Added `toggle-multi-line` action
- Added `toggle-hscroll` action
- Added `change-nth` action for dynamically changing the value of the `--nth` option
```sh
# Start with --nth 1, then 2, then 3, then back to the default, 1
echo 'foo foobar foobarbaz' | fzf --bind 'space:change-nth(2|3|)' --nth 1 -q foo
```
- `--nth` parts of each line can now be rendered in a different text style
```sh
# nth in a different style
ls -al | fzf --nth -1 --color nth:italic
ls -al | fzf --nth -1 --color nth:reverse
ls -al | fzf --nth -1 --color nth:reverse:bold
# Dim the other parts
ls -al | fzf --nth -1 --color nth:regular,fg:dim
# With 'change-nth'. The current nth option is exported as $FZF_NTH.
ps -ef | fzf --reverse --header-lines 1 --header-border bottom --input-border \
--color nth:regular,fg:dim \
--bind 'ctrl-n:change-nth(8..|1|2|3|4|5|6|7|)' \
--bind 'result:transform-prompt:echo "${FZF_NTH}> "'
```
- A single-character delimiter is now treated as a plain string delimiter rather than a regular expression delimiter, even if it's a regular expression meta-character.
- This means you can just write `--delimiter '|'` instead of escaping it as `--delimiter '\|'`
- Bug fixes
- Bug fixes and improvements in fish scripts (thanks to @bitraid)
0.57.0
------
- You can now resize the preview window by dragging the border
- Built-in walker improvements
- `--walker-root` can take multiple directory arguments. e.g. `--walker-root include src lib`
- `--walker-skip` can handle multi-component patterns. e.g. `--walker-skip target/build`
- Removed long processing delay when displaying images in the preview window
- `FZF_PREVIEW_*` environment variables are exported to all child processes (#4098)
- Bug fixes in fish scripts
0.56.3
------
- Bug fixes in zsh scripts
- fix(zsh): handle backtick trigger edge case (#4090)
- revert(zsh): remove 'fc -RI' call in the history widget (#4093)
- Thanks to @LangLangBart for the contributions
0.56.2
------
- Bug fixes
- Fixed abnormal scrolling behavior when `--wrap` is set (#4083)
- [zsh] Fixed warning message when `ksh_arrays` is set (#4084)
0.56.1
------
- Bug fixes and improvements
- Fixed a race condition which would cause fzf to present stale results after `reload` (#4070)
- `page-up` and `page-down` actions now work correctly with multi-line items (#4069)
- `{n}` is allowed in `SCROLL` expression in `--preview-window` (#4079)
- [zsh] Fixed regression in history loading with shared option (#4071)
- [zsh] Better command extraction in zsh completion (#4082)
- Thanks to @LangLangBart, @jaydee-coder, @alex-huff, and @vejkse for the contributions
0.56.0 0.56.0
------ ------
- Added `--gap[=N]` option to display empty lines between items. - Added `--gap[=N]` option to display empty lines between items.

View File

@@ -8,5 +8,5 @@ RUN echo '. ~/.bashrc' >> ~/.bash_profile
RUN rm -f /etc/bash.bashrc RUN rm -f /etc/bash.bashrc
COPY . /fzf COPY . /fzf
RUN cd /fzf && make install && ./install --all RUN cd /fzf && make install && ./install --all
ENV LANG C.UTF-8 ENV LANG=C.UTF-8
CMD tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ] CMD ["bash", "-ic", "tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]"]

View File

@@ -1,4 +1,3 @@
SHELL := bash
GO ?= go GO ?= go
GOOS ?= $(shell $(GO) env GOOS) GOOS ?= $(shell $(GO) env GOOS)
@@ -14,7 +13,7 @@ endif
ifeq ($(VERSION),) ifeq ($(VERSION),)
$(error Not on git repository; cannot determine $$FZF_VERSION) $(error Not on git repository; cannot determine $$FZF_VERSION)
endif endif
VERSION_TRIM := $(shell sed "s/^v//; s/-.*//" <<< $(VERSION)) VERSION_TRIM := $(shell echo $(VERSION) | sed "s/^v//; s/-.*//")
VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM)) VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM))
ifdef FZF_REVISION ifdef FZF_REVISION

File diff suppressed because one or more lines are too long

View File

@@ -9,12 +9,24 @@
# - https://iterm2.com/utilities/imgcat # - https://iterm2.com/utilities/imgcat
if [[ $# -ne 1 ]]; then if [[ $# -ne 1 ]]; then
>&2 echo "usage: $0 FILENAME" >&2 echo "usage: $0 FILENAME[:LINENO][:IGNORED]"
exit 1 exit 1
fi fi
file=${1/#\~\//$HOME/} file=${1/#\~\//$HOME/}
type=$(file --dereference --mime -- "$file")
center=0
if [[ ! -r $file ]]; then
if [[ $file =~ ^(.+):([0-9]+)\ *$ ]] && [[ -r ${BASH_REMATCH[1]} ]]; then
file=${BASH_REMATCH[1]}
center=${BASH_REMATCH[2]}
elif [[ $file =~ ^(.+):([0-9]+):[0-9]+\ *$ ]] && [[ -r ${BASH_REMATCH[1]} ]]; then
file=${BASH_REMATCH[1]}
center=${BASH_REMATCH[2]}
fi
fi
type=$(file --brief --dereference --mime -- "$file")
if [[ ! $type =~ image/ ]]; then if [[ ! $type =~ image/ ]]; then
if [[ $type =~ =binary ]]; then if [[ $type =~ =binary ]]; then
@@ -32,7 +44,7 @@ if [[ ! $type =~ image/ ]]; then
exit exit
fi fi
${batname} --style="${BAT_STYLE:-numbers}" --color=always --pager=never -- "$file" ${batname} --style="${BAT_STYLE:-numbers}" --color=always --pager=never --highlight-line="${center:-0}" -- "$file"
exit exit
fi fi

12
go.mod
View File

@@ -2,19 +2,19 @@ module github.com/junegunn/fzf
require ( require (
github.com/charlievieth/fastwalk v1.0.9 github.com/charlievieth/fastwalk v1.0.9
github.com/gdamore/tcell/v2 v2.7.4 github.com/gdamore/tcell/v2 v2.8.1
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97 github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20
github.com/rivo/uniseg v0.4.7 github.com/rivo/uniseg v0.4.7
golang.org/x/sys v0.26.0 golang.org/x/sys v0.29.0
golang.org/x/term v0.25.0 golang.org/x/term v0.28.0
) )
require ( require (
github.com/gdamore/encoding v1.0.0 // indirect github.com/gdamore/encoding v1.0.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.21.0 // indirect
) )
go 1.20 go 1.20

50
go.sum
View File

@@ -1,17 +1,18 @@
github.com/charlievieth/fastwalk v1.0.9 h1:Odb92AfoReO3oFBfDGT5J+nwgzQPF/gWAw6E6/lkor0= github.com/charlievieth/fastwalk v1.0.9 h1:Odb92AfoReO3oFBfDGT5J+nwgzQPF/gWAw6E6/lkor0=
github.com/charlievieth/fastwalk v1.0.9/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI= github.com/charlievieth/fastwalk v1.0.9/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU= github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97 h1:rqzLixVo1c/GQW6px9j1xQmlvQIn+lf/V6M1UQ7IFzw= github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97 h1:rqzLixVo1c/GQW6px9j1xQmlvQIn+lf/V6M1UQ7IFzw=
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97/go.mod h1:6EILKtGpo5t+KLb85LNZLAF6P9LKp78hJI80PXMcn3c= github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97/go.mod h1:6EILKtGpo5t+KLb85LNZLAF6P9LKp78hJI80PXMcn3c=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -19,15 +20,29 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -35,23 +50,36 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

43
install
View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.56.0 version=0.58.0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
@@ -83,7 +83,7 @@ ask() {
check_binary() { check_binary() {
echo -n " - Checking fzf executable ... " echo -n " - Checking fzf executable ... "
local output local output
output=$("$fzf_base"/bin/fzf --version 2>&1) output=$(FZF_DEFAULT_OPTS= "$fzf_base"/bin/fzf --version 2>&1)
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "Error: $output" echo "Error: $output"
binary_error="Invalid binary" binary_error="Invalid binary"
@@ -295,35 +295,44 @@ EOF
fi fi
append_line() { append_line() {
set -e local update line file pat lines
local update line file pat lno
update="$1" update="$1"
line="$2" line="$2"
file="$3" file="$3"
pat="${4:-}" pat="${4:-}"
lno="" lines=""
echo "Update $file:" echo "Update $file:"
echo " - $line" echo " - $line"
if [ -f "$file" ]; then if [ -f "$file" ]; then
if [ $# -lt 4 ]; then if [ $# -lt 4 ]; then
lno=$(\grep -nF "$line" "$file" | sed 's/:.*//' | tr '\n' ' ') lines=$(\grep -nF "$line" "$file")
else else
lno=$(\grep -nF "$pat" "$file" | sed 's/:.*//' | tr '\n' ' ') lines=$(\grep -nF "$pat" "$file")
fi fi
fi fi
if [ -n "$lno" ]; then
echo " - Already exists: line #$lno" if [ -n "$lines" ]; then
echo " - Already exists:"
sed 's/^/ Line /' <<< "$lines"
update=0
if ! \grep -qv "^[0-9]*:[[:space:]]*#" <<< "$lines" ; then
echo " - But they all seem to be commented"
ask " - Continue modifying $file?"
update=$?
fi
fi
set -e
if [ "$update" -eq 1 ]; then
[ -f "$file" ] && echo >> "$file"
echo "$line" >> "$file"
echo " + Added"
else else
if [ $update -eq 1 ]; then echo " ~ Skipped"
[ -f "$file" ] && echo >> "$file"
echo "$line" >> "$file"
echo " + Added"
else
echo " ~ Skipped"
fi
fi fi
echo echo
set +e set +e
} }

View File

@@ -1,4 +1,4 @@
$version="0.56.0" $version="0.58.0"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition $fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition

View File

@@ -11,7 +11,7 @@ import (
"github.com/junegunn/fzf/src/protector" "github.com/junegunn/fzf/src/protector"
) )
var version = "0.56" var version = "0.58"
var revision = "devel" var revision = "devel"
//go:embed shell/key-bindings.bash //go:embed shell/key-bindings.bash

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\-tmux 1 "Oct 2024" "fzf 0.56.0" "fzf\-tmux - open fzf in tmux split pane" .TH fzf\-tmux 1 "Jan 2025" "fzf 0.58.0" "fzf\-tmux - open fzf in tmux split pane"
.SH NAME .SH NAME
fzf\-tmux - open fzf in tmux split pane fzf\-tmux - open fzf in tmux split pane

File diff suppressed because it is too large Load Diff

View File

@@ -120,25 +120,18 @@ __fzf_comprun() {
fi fi
} }
# Extract the name of the command. e.g. foo=1 bar baz**<tab> # Extract the name of the command. e.g. ls; foo=1 ssh **<tab>
__fzf_extract_command() { __fzf_extract_command() {
local token tokens # Control completion with the "compstate" parameter, insert and list nothing
tokens=(${(z)1}) compstate[insert]=
for token in $tokens; do compstate[list]=
token=${(Q)token} cmd_word="${(Q)words[1]}"
if [[ "$token" =~ [[:alnum:]] && ! "$token" =~ "=" ]]; then
echo "$token"
return
fi
done
echo "${tokens[1]}"
} }
__fzf_generic_path_completion() { __fzf_generic_path_completion() {
local base lbuf cmd compgen fzf_opts suffix tail dir leftover matches local base lbuf compgen fzf_opts suffix tail dir leftover matches
base=$1 base=$1
lbuf=$2 lbuf=$2
cmd=$(__fzf_extract_command "$lbuf")
compgen=$3 compgen=$3
fzf_opts=$4 fzf_opts=$4
suffix=$5 suffix=$5
@@ -161,7 +154,7 @@ __fzf_generic_path_completion() {
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}") FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}")
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
if declare -f "$compgen" > /dev/null; then if declare -f "$compgen" > /dev/null; then
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
else else
if [[ $compgen =~ dir ]]; then if [[ $compgen =~ dir ]]; then
walker=dir,follow walker=dir,follow
@@ -170,7 +163,7 @@ __fzf_generic_path_completion() {
walker=file,dir,follow,hidden walker=file,dir,follow,hidden
rest=${FZF_COMPLETION_PATH_OPTS-} rest=${FZF_COMPLETION_PATH_OPTS-}
fi fi
__fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty __fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty
fi | while read -r item; do fi | while read -r item; do
item="${item%$suffix}$suffix" item="${item%$suffix}$suffix"
echo -n -E "${(q)item} " echo -n -E "${(q)item} "
@@ -227,10 +220,9 @@ _fzf_complete() {
rest=("$@") rest=("$@")
fi fi
local fifo lbuf cmd matches post local fifo lbuf matches post
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$" fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
lbuf=${rest[0]} lbuf=${rest[0]}
cmd=$(__fzf_extract_command "$lbuf")
post="${funcstack[1]}_post" post="${funcstack[1]}_post"
type $post > /dev/null 2>&1 || post=cat type $post > /dev/null 2>&1 || post=cat
@@ -238,7 +230,7 @@ _fzf_complete() {
matches=$( matches=$(
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \ FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
FZF_DEFAULT_OPTS_FILE='' \ FZF_DEFAULT_OPTS_FILE='' \
__fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ') __fzf_comprun "$cmd_word" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches" LBUFFER="$lbuf$matches"
fi fi
@@ -310,7 +302,7 @@ _fzf_complete_kill_post() {
} }
fzf-completion() { fzf-completion() {
local tokens cmd prefix trigger tail matches lbuf d_cmds local tokens prefix trigger tail matches lbuf d_cmds cursor_pos cmd_word
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
# http://zsh.sourceforge.net/FAQ/zshfaq03.html # http://zsh.sourceforge.net/FAQ/zshfaq03.html
@@ -321,11 +313,9 @@ fzf-completion() {
return return
fi fi
cmd=$(__fzf_extract_command "$LBUFFER")
# Explicitly allow for empty trigger. # Explicitly allow for empty trigger.
trigger=${FZF_COMPLETION_TRIGGER-'**'} trigger=${FZF_COMPLETION_TRIGGER-'**'}
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("") [[ -z $trigger && ${LBUFFER[-1]} == ' ' ]] && tokens+=("")
# When the trigger starts with ';', it becomes a separate token # When the trigger starts with ';', it becomes a separate token
if [[ ${LBUFFER} = *"${tokens[-2]-}${tokens[-1]}" ]]; then if [[ ${LBUFFER} = *"${tokens[-2]-}${tokens[-1]}" ]]; then
@@ -340,16 +330,37 @@ fzf-completion() {
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}) d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir})
{
cursor_pos=$CURSOR
# Move the cursor before the trigger to preserve word array elements when
# trigger chars like ';' or '`' would otherwise reset the 'words' array.
CURSOR=$((cursor_pos - ${#trigger} - 1))
# Check if at least one completion system (old or new) is active.
# If at least one user-defined completion widget is detected, nothing will
# be completed if neither the old nor the new completion system is enabled.
# In such cases, the 'zsh/compctl' module is loaded as a fallback.
if ! zmodload -F zsh/parameter p:functions 2>/dev/null || ! (( ${+functions[compdef]} )); then
zmodload -F zsh/compctl 2>/dev/null
fi
# Create a completion widget to access the 'words' array (man zshcompwid)
zle -C __fzf_extract_command .complete-word __fzf_extract_command
zle __fzf_extract_command
} always {
CURSOR=$cursor_pos
# Delete the completion widget
zle -D __fzf_extract_command 2>/dev/null
}
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}} [ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
return return
fi fi
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}} [ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
if eval "type _fzf_complete_${cmd} > /dev/null"; then if eval "noglob type _fzf_complete_${cmd_word} >/dev/null"; then
prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf} prefix="$prefix" eval _fzf_complete_${cmd_word} ${(q)lbuf}
zle reset-prompt zle reset-prompt
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then elif [ ${d_cmds[(i)$cmd_word]} -le ${#d_cmds} ]; then
_fzf_dir_completion "$prefix" "$lbuf" _fzf_dir_completion "$prefix" "$lbuf"
else else
_fzf_path_completion "$prefix" "$lbuf" _fzf_path_completion "$prefix" "$lbuf"
@@ -366,6 +377,7 @@ fzf-completion() {
unset binding unset binding
} }
# Normal widget
zle -N fzf-completion zle -N fzf-completion
bindkey '^I' fzf-completion bindkey '^I' fzf-completion
fi fi

View File

@@ -11,8 +11,6 @@
# - $FZF_ALT_C_COMMAND # - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS # - $FZF_ALT_C_OPTS
status is-interactive; or exit 0
# Key bindings # Key bindings
# ------------ # ------------
@@ -23,7 +21,7 @@ function fzf_key_bindings
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS # $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
echo "--height $FZF_TMUX_HEIGHT --bind=ctrl-z:ignore" $argv[1] echo "--height $FZF_TMUX_HEIGHT --bind=ctrl-z:ignore" $argv[1]
command cat "$FZF_DEFAULT_OPTS_FILE" 2> /dev/null test -r "$FZF_DEFAULT_OPTS_FILE"; and string collect -N -- <$FZF_DEFAULT_OPTS_FILE
echo $FZF_DEFAULT_OPTS $argv[2] echo $FZF_DEFAULT_OPTS $argv[2]
end end
@@ -33,15 +31,16 @@ function fzf_key_bindings
set -lx dir $commandline[1] set -lx dir $commandline[1]
set -l fzf_query $commandline[2] set -l fzf_query $commandline[2]
set -l prefix $commandline[3] set -l prefix $commandline[3]
set -l result
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin begin
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root='$dir'" "$FZF_CTRL_T_OPTS") set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root=$dir" "$FZF_CTRL_T_OPTS")
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND" set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
set -lx FZF_DEFAULT_OPTS_FILE '' set -lx FZF_DEFAULT_OPTS_FILE ''
eval (__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end set result (eval (__fzfcmd) -m --query=$fzf_query)
end end
if [ -z "$result" ] if test -z "$result"
commandline -f repaint commandline -f repaint
return return
else else
@@ -50,7 +49,7 @@ function fzf_key_bindings
end end
for i in $result for i in $result
commandline -it -- $prefix commandline -it -- $prefix
commandline -it -- (string escape $i) commandline -it -- (string escape -- $i)
commandline -it -- ' ' commandline -it -- ' '
end end
commandline -f repaint commandline -f repaint
@@ -59,33 +58,25 @@ function fzf_key_bindings
function fzf-history-widget -d "Show command history" function fzf-history-widget -d "Show command history"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin begin
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
set -l FISH_MINOR (echo $version | cut -f2 -d.)
# merge history from other sessions before searching # merge history from other sessions before searching
if test -z "$fish_private_mode" test -z "$fish_private_mode"; and builtin history merge
builtin history merge
end
# history's -z flag is needed for multi-line support. set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line +m $FZF_CTRL_R_OPTS")
# history's -z flag was added in fish 2.4.0, so don't use it for versions set -lx FZF_DEFAULT_OPTS_FILE ''
# before 2.4.0. set -lx FZF_DEFAULT_COMMAND
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ]; string match -q -r -- '/fish$' $SHELL; or set -lx SHELL (type -p fish)
if type -P perl > /dev/null 2>&1 if type -q perl
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line $FZF_CTRL_R_OPTS +m") set -a FZF_DEFAULT_OPTS '--tac'
set -lx FZF_DEFAULT_OPTS_FILE '' set FZF_DEFAULT_COMMAND 'builtin history -z --reverse | command perl -0 -pe \'s/^/$.\t/g; s/\n/\n\t/gm\''
builtin history -z --reverse | command perl -0 -pe 's/^/$.\t/g; s/\n/\n\t/gm' | eval (__fzfcmd) --tac --read0 --print0 -q '(commandline)' | command perl -pe 's/^\d*\t//' | read -lz result
and commandline -- $result
else
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "--scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line $FZF_CTRL_R_OPTS +m")
set -lx FZF_DEFAULT_OPTS_FILE ''
builtin history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result
and commandline -- $result
end
else else
builtin history | eval (__fzfcmd) -q '(commandline)' | read -l result set FZF_DEFAULT_COMMAND \
and commandline -- $result 'set -l h (builtin history -z --reverse | string split0);' \
'for i in (seq (count $h) -1 1);' \
'string join0 -- $i\t(string replace -a -- \n \n\t $h[$i] | string collect);' \
'end'
end end
set -l result (eval "$FZF_DEFAULT_COMMAND | $(__fzfcmd) --read0 --print0 -q (commandline) --bind='enter:become:string replace -a -- \n\t \n {2..} | string collect'")
and commandline -- $result
end end
commandline -f repaint commandline -f repaint
end end
@@ -98,12 +89,12 @@ function fzf_key_bindings
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin begin
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path --walker-root='$dir'" "$FZF_ALT_C_OPTS") set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path --walker-root=$dir" "$FZF_ALT_C_OPTS")
set -lx FZF_DEFAULT_OPTS_FILE '' set -lx FZF_DEFAULT_OPTS_FILE ''
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND" set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result set -l result (eval (__fzfcmd) +m --query=$fzf_query)
if [ -n "$result" ] if test -n "$result"
cd -- $result cd -- $result
# Remove last token from commandline. # Remove last token from commandline.
@@ -118,9 +109,9 @@ function fzf_key_bindings
function __fzfcmd function __fzfcmd
test -n "$FZF_TMUX"; or set FZF_TMUX 0 test -n "$FZF_TMUX"; or set FZF_TMUX 0
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
if [ -n "$FZF_TMUX_OPTS" ] if test -n "$FZF_TMUX_OPTS"
echo "fzf-tmux $FZF_TMUX_OPTS -- " echo "fzf-tmux $FZF_TMUX_OPTS -- "
else if [ $FZF_TMUX -eq 1 ] else if test "$FZF_TMUX" = "1"
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- " echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
else else
echo "fzf" echo "fzf"
@@ -135,14 +126,12 @@ function fzf_key_bindings
bind \ec fzf-cd-widget bind \ec fzf-cd-widget
end end
if bind -M insert > /dev/null 2>&1 bind -M insert \cr fzf-history-widget
bind -M insert \cr fzf-history-widget if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND" bind -M insert \ct fzf-file-widget
bind -M insert \ct fzf-file-widget end
end if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND" bind -M insert \ec fzf-cd-widget
bind -M insert \ec fzf-cd-widget
end
end end
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix' function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
@@ -152,40 +141,53 @@ function fzf_key_bindings
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline) set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
set commandline (string replace -- "$prefix" '' $commandline) set commandline (string replace -- "$prefix" '' $commandline)
# Enable home directory expansion of leading ~/
set commandline (string replace -r -- '^~/' '\$HOME/' $commandline)
# escape special characters, except for the $ sign of valid variable names,
# so that after eval, the original string is returned, but with the
# variable names replaced by their values.
set commandline (string escape -n -- $commandline)
set commandline (string replace -r -a -- '\x5c\$(?=[\w])' '\$' $commandline)
# eval is used to do shell expansion on paths # eval is used to do shell expansion on paths
eval set commandline $commandline eval set commandline $commandline
if [ -z $commandline ] # Combine multiple consecutive slashes into one
set commandline (string replace -r -a -- '/+' '/' $commandline)
if test -z "$commandline"
# Default to current directory with no --query # Default to current directory with no --query
set dir '.' set dir '.'
set fzf_query '' set fzf_query ''
else else
set dir (__fzf_get_dir $commandline) set dir (__fzf_get_dir $commandline)
if [ "$dir" = "." -a (string sub -l 1 -- $commandline) != '.' ] # BUG: on combined expressions, if a left argument is a single `!`, the
# builtin test command of fish will treat it as the ! operator. To
# overcome this, have the variable parts on the right.
if test "." = "$dir" -a "./" != (string sub -l 2 -- $commandline)
# if $dir is "." but commandline is not a relative path, this means no file path found # if $dir is "." but commandline is not a relative path, this means no file path found
set fzf_query $commandline set fzf_query $commandline
else else
# Also remove trailing slash after dir, to "split" input properly # Also remove trailing slash after dir, to "split" input properly
set fzf_query (string replace -r "^$dir/?" -- '' "$commandline") set fzf_query (string replace -r -- "^$dir/?" '' $commandline)
end end
end end
echo $dir echo (string escape -- $dir)
echo $fzf_query echo (string escape -- $fzf_query)
echo $prefix echo $prefix
end end
function __fzf_get_dir -d 'Find the longest existing filepath from input string' function __fzf_get_dir -d 'Find the longest existing filepath from input string'
set dir $argv set dir $argv
# Strip all trailing slashes. Ignore if $dir is root dir (/) # Strip trailing slash, unless $dir is root dir (/)
if [ (string length -- $dir) -gt 1 ] set dir (string replace -r -- '(?<!^)/$' '' $dir)
set dir (string replace -r '/*$' -- '' $dir)
end
# Iteratively check if dir exists and strip tail end of path # Iteratively check if dir exists and strip tail end of path
while [ ! -d "$dir" ] while test ! -d "$dir"
# If path is absolute, this can keep going until ends up at / # If path is absolute, this can keep going until ends up at /
# If path is relative, this can keep going until entire input is consumed, dirname returns "." # If path is relative, this can keep going until entire input is consumed, dirname returns "."
set dir (dirname -- "$dir") set dir (dirname -- "$dir")

View File

@@ -108,9 +108,10 @@ fi
fzf-history-widget() { fzf-history-widget() {
local selected local selected
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null
# Ensure the associative history array, which maps event numbers to the full # Ensure the module is loaded if not already, and the required features, such
# history lines, is loaded, and that Perl is installed for multi-line output. # as the associative 'history' array, which maps event numbers to full history
if zmodload -F zsh/parameter p:history 2>/dev/null && (( ${#commands[perl]} )); then # lines, are set. Also, make sure Perl is installed for multi-line output.
if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( ${+commands[perl]} )); then
selected="$(printf '%s\t%s\000' "${(kv)history[@]}" | selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' | perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' |
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \ FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \

View File

@@ -25,107 +25,116 @@ func _() {
_ = x[actBackwardWord-14] _ = x[actBackwardWord-14]
_ = x[actCancel-15] _ = x[actCancel-15]
_ = x[actChangeBorderLabel-16] _ = x[actChangeBorderLabel-16]
_ = x[actChangeHeader-17] _ = x[actChangeListLabel-17]
_ = x[actChangeMulti-18] _ = x[actChangeInputLabel-18]
_ = x[actChangePreviewLabel-19] _ = x[actChangeHeader-19]
_ = x[actChangePrompt-20] _ = x[actChangeHeaderLabel-20]
_ = x[actChangeQuery-21] _ = x[actChangeMulti-21]
_ = x[actClearScreen-22] _ = x[actChangePreviewLabel-22]
_ = x[actClearQuery-23] _ = x[actChangePrompt-23]
_ = x[actClearSelection-24] _ = x[actChangeQuery-24]
_ = x[actClose-25] _ = x[actChangeNth-25]
_ = x[actDeleteChar-26] _ = x[actClearScreen-26]
_ = x[actDeleteCharEof-27] _ = x[actClearQuery-27]
_ = x[actEndOfLine-28] _ = x[actClearSelection-28]
_ = x[actFatal-29] _ = x[actClose-29]
_ = x[actForwardChar-30] _ = x[actDeleteChar-30]
_ = x[actForwardWord-31] _ = x[actDeleteCharEof-31]
_ = x[actKillLine-32] _ = x[actEndOfLine-32]
_ = x[actKillWord-33] _ = x[actFatal-33]
_ = x[actUnixLineDiscard-34] _ = x[actForwardChar-34]
_ = x[actUnixWordRubout-35] _ = x[actForwardWord-35]
_ = x[actYank-36] _ = x[actKillLine-36]
_ = x[actBackwardKillWord-37] _ = x[actKillWord-37]
_ = x[actSelectAll-38] _ = x[actUnixLineDiscard-38]
_ = x[actDeselectAll-39] _ = x[actUnixWordRubout-39]
_ = x[actToggle-40] _ = x[actYank-40]
_ = x[actToggleSearch-41] _ = x[actBackwardKillWord-41]
_ = x[actToggleAll-42] _ = x[actSelectAll-42]
_ = x[actToggleDown-43] _ = x[actDeselectAll-43]
_ = x[actToggleUp-44] _ = x[actToggle-44]
_ = x[actToggleIn-45] _ = x[actToggleSearch-45]
_ = x[actToggleOut-46] _ = x[actToggleAll-46]
_ = x[actToggleTrack-47] _ = x[actToggleDown-47]
_ = x[actToggleTrackCurrent-48] _ = x[actToggleUp-48]
_ = x[actToggleHeader-49] _ = x[actToggleIn-49]
_ = x[actToggleWrap-50] _ = x[actToggleOut-50]
_ = x[actTrackCurrent-51] _ = x[actToggleTrack-51]
_ = x[actUntrackCurrent-52] _ = x[actToggleTrackCurrent-52]
_ = x[actDown-53] _ = x[actToggleHeader-53]
_ = x[actUp-54] _ = x[actToggleWrap-54]
_ = x[actPageUp-55] _ = x[actToggleMultiLine-55]
_ = x[actPageDown-56] _ = x[actToggleHscroll-56]
_ = x[actPosition-57] _ = x[actTrackCurrent-57]
_ = x[actHalfPageUp-58] _ = x[actUntrackCurrent-58]
_ = x[actHalfPageDown-59] _ = x[actDown-59]
_ = x[actOffsetUp-60] _ = x[actUp-60]
_ = x[actOffsetDown-61] _ = x[actPageUp-61]
_ = x[actOffsetMiddle-62] _ = x[actPageDown-62]
_ = x[actJump-63] _ = x[actPosition-63]
_ = x[actJumpAccept-64] _ = x[actHalfPageUp-64]
_ = x[actPrintQuery-65] _ = x[actHalfPageDown-65]
_ = x[actRefreshPreview-66] _ = x[actOffsetUp-66]
_ = x[actReplaceQuery-67] _ = x[actOffsetDown-67]
_ = x[actToggleSort-68] _ = x[actOffsetMiddle-68]
_ = x[actShowPreview-69] _ = x[actJump-69]
_ = x[actHidePreview-70] _ = x[actJumpAccept-70]
_ = x[actTogglePreview-71] _ = x[actPrintQuery-71]
_ = x[actTogglePreviewWrap-72] _ = x[actRefreshPreview-72]
_ = x[actTransform-73] _ = x[actReplaceQuery-73]
_ = x[actTransformBorderLabel-74] _ = x[actToggleSort-74]
_ = x[actTransformHeader-75] _ = x[actShowPreview-75]
_ = x[actTransformPreviewLabel-76] _ = x[actHidePreview-76]
_ = x[actTransformPrompt-77] _ = x[actTogglePreview-77]
_ = x[actTransformQuery-78] _ = x[actTogglePreviewWrap-78]
_ = x[actPreview-79] _ = x[actTransform-79]
_ = x[actChangePreview-80] _ = x[actTransformBorderLabel-80]
_ = x[actChangePreviewWindow-81] _ = x[actTransformListLabel-81]
_ = x[actPreviewTop-82] _ = x[actTransformInputLabel-82]
_ = x[actPreviewBottom-83] _ = x[actTransformHeader-83]
_ = x[actPreviewUp-84] _ = x[actTransformHeaderLabel-84]
_ = x[actPreviewDown-85] _ = x[actTransformPreviewLabel-85]
_ = x[actPreviewPageUp-86] _ = x[actTransformPrompt-86]
_ = x[actPreviewPageDown-87] _ = x[actTransformQuery-87]
_ = x[actPreviewHalfPageUp-88] _ = x[actPreview-88]
_ = x[actPreviewHalfPageDown-89] _ = x[actChangePreview-89]
_ = x[actPrevHistory-90] _ = x[actChangePreviewWindow-90]
_ = x[actPrevSelected-91] _ = x[actPreviewTop-91]
_ = x[actPrint-92] _ = x[actPreviewBottom-92]
_ = x[actPut-93] _ = x[actPreviewUp-93]
_ = x[actNextHistory-94] _ = x[actPreviewDown-94]
_ = x[actNextSelected-95] _ = x[actPreviewPageUp-95]
_ = x[actExecute-96] _ = x[actPreviewPageDown-96]
_ = x[actExecuteSilent-97] _ = x[actPreviewHalfPageUp-97]
_ = x[actExecuteMulti-98] _ = x[actPreviewHalfPageDown-98]
_ = x[actSigStop-99] _ = x[actPrevHistory-99]
_ = x[actFirst-100] _ = x[actPrevSelected-100]
_ = x[actLast-101] _ = x[actPrint-101]
_ = x[actReload-102] _ = x[actPut-102]
_ = x[actReloadSync-103] _ = x[actNextHistory-103]
_ = x[actDisableSearch-104] _ = x[actNextSelected-104]
_ = x[actEnableSearch-105] _ = x[actExecute-105]
_ = x[actSelect-106] _ = x[actExecuteSilent-106]
_ = x[actDeselect-107] _ = x[actExecuteMulti-107]
_ = x[actUnbind-108] _ = x[actSigStop-108]
_ = x[actRebind-109] _ = x[actFirst-109]
_ = x[actBecome-110] _ = x[actLast-110]
_ = x[actShowHeader-111] _ = x[actReload-111]
_ = x[actHideHeader-112] _ = x[actReloadSync-112]
_ = x[actDisableSearch-113]
_ = x[actEnableSearch-114]
_ = x[actSelect-115]
_ = x[actDeselect-116]
_ = x[actUnbind-117]
_ = x[actRebind-118]
_ = x[actBecome-119]
_ = x[actShowHeader-120]
_ = x[actHideHeader-121]
} }
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader" const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeHeaderLabelactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactChangeNthactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformHeaderLabelactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 690, 705, 722, 729, 734, 743, 754, 765, 778, 793, 804, 817, 832, 839, 852, 865, 882, 897, 910, 924, 938, 954, 974, 986, 1009, 1027, 1051, 1069, 1086, 1096, 1112, 1134, 1147, 1163, 1175, 1189, 1205, 1223, 1243, 1265, 1279, 1294, 1302, 1308, 1322, 1337, 1347, 1363, 1378, 1388, 1396, 1403, 1412, 1425, 1441, 1456, 1465, 1476, 1485, 1494, 1503, 1516, 1529} var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 264, 279, 299, 313, 334, 349, 363, 375, 389, 402, 419, 427, 440, 456, 468, 476, 490, 504, 515, 526, 544, 561, 568, 587, 599, 613, 622, 637, 649, 662, 673, 684, 696, 710, 731, 746, 759, 777, 793, 808, 825, 832, 837, 846, 857, 868, 881, 896, 907, 920, 935, 942, 955, 968, 985, 1000, 1013, 1027, 1041, 1057, 1077, 1089, 1112, 1133, 1155, 1173, 1196, 1220, 1238, 1255, 1265, 1281, 1303, 1316, 1332, 1344, 1358, 1374, 1392, 1412, 1434, 1448, 1463, 1471, 1477, 1491, 1506, 1516, 1532, 1547, 1557, 1565, 1572, 1581, 1594, 1610, 1625, 1634, 1645, 1654, 1663, 1672, 1685, 1698}
func (i actionType) String() string { func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) { if i < 0 || i >= actionType(len(_actionType_index)-1) {

View File

@@ -401,7 +401,7 @@ func debugV2(T []rune, pattern []rune, F []int32, lastIdx int, H []int16, C []in
if i == 0 { if i == 0 {
fmt.Print(" ") fmt.Print(" ")
for j := int(f); j <= lastIdx; j++ { for j := int(f); j <= lastIdx; j++ {
fmt.Printf(" " + string(T[j]) + " ") fmt.Print(" " + string(T[j]) + " ")
} }
fmt.Println() fmt.Println()
} }

View File

@@ -44,7 +44,7 @@ func (s *ansiState) ToString() string {
} }
ret := "" ret := ""
if s.attr&tui.Bold > 0 { if s.attr&tui.Bold > 0 || s.attr&tui.BoldForce > 0 {
ret += "1;" ret += "1;"
} }
if s.attr&tui.Dim > 0 { if s.attr&tui.Dim > 0 {
@@ -98,11 +98,11 @@ func isPrint(c uint8) bool {
return '\x20' <= c && c <= '\x7e' return '\x20' <= c && c <= '\x7e'
} }
func matchOperatingSystemCommand(s string) int { func matchOperatingSystemCommand(s string, start int) int {
// `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)` // `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
// ^ match starting here // ^ match starting here after the first printable character
// //
i := 5 // prefix matched in nextAnsiEscapeSequence() i := start // prefix matched in nextAnsiEscapeSequence()
for ; i < len(s) && isPrint(s[i]); i++ { for ; i < len(s) && isPrint(s[i]); i++ {
} }
if i < len(s) { if i < len(s) {
@@ -156,7 +156,7 @@ func isCtrlSeqStart(c uint8) bool {
// nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to // nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to
// calling FindStringIndex() on the below regex (which was originally used): // calling FindStringIndex() on the below regex (which was originally used):
// //
// "(?:\x1b[\\[()][0-9;:?]*[a-zA-Z@]|\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)" // "(?:\x1b[\\[()][0-9;:?]*[a-zA-Z@]|\x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)"
func nextAnsiEscapeSequence(s string) (int, int) { func nextAnsiEscapeSequence(s string) (int, int) {
// fast check for ANSI escape sequences // fast check for ANSI escape sequences
i := 0 i := 0
@@ -191,12 +191,20 @@ Loop:
} }
} }
// match: `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)` // match: `\x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07)`
if i+5 < len(s) && s[i+1] == ']' && isNumeric(s[i+2]) && if i+5 < len(s) && s[i+1] == ']' {
(s[i+3] == ';' || s[i+3] == ':') && isPrint(s[i+4]) { j := 2
// \x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07)
// ------
for ; i+j < len(s) && isNumeric(s[i+j]); j++ {
}
if j := matchOperatingSystemCommand(s[i:]); j != -1 { // \x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07)
return i, i + j // ---------------
if j > 2 && i+j+1 < len(s) && (s[i+j] == ';' || s[i+j] == ':') && isPrint(s[i+j+1]) {
if k := matchOperatingSystemCommand(s[i:], j+2); k != -1 {
return i, i + k
}
} }
} }
@@ -310,20 +318,15 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
return trimmed, nil, state return trimmed, nil, state
} }
func parseAnsiCode(s string, delimiter byte) (int, byte, string) { func parseAnsiCode(s string) (int, string) {
var remaining string var remaining string
var i int var i int
if delimiter == 0 { // Faster than strings.IndexAny(";:")
// Faster than strings.IndexAny(";:") i = strings.IndexByte(s, ';')
i = strings.IndexByte(s, ';') if i < 0 {
if i < 0 { i = strings.IndexByte(s, ':')
i = strings.IndexByte(s, ':')
}
} else {
i = strings.IndexByte(s, delimiter)
} }
if i >= 0 { if i >= 0 {
delimiter = s[i]
remaining = s[i+1:] remaining = s[i+1:]
s = s[:i] s = s[:i]
} }
@@ -335,14 +338,14 @@ func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
for _, ch := range stringBytes(s) { for _, ch := range stringBytes(s) {
ch -= '0' ch -= '0'
if ch > 9 { if ch > 9 {
return -1, delimiter, remaining return -1, remaining
} }
code = code*10 + int(ch) code = code*10 + int(ch)
} }
return code, delimiter, remaining return code, remaining
} }
return -1, delimiter, remaining return -1, remaining
} }
func interpretCode(ansiCode string, prevState *ansiState) ansiState { func interpretCode(ansiCode string, prevState *ansiState) ansiState {
@@ -378,11 +381,10 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
state256 := 0 state256 := 0
ptr := &state.fg ptr := &state.fg
var delimiter byte
count := 0 count := 0
for len(ansiCode) != 0 { for len(ansiCode) != 0 {
var num int var num int
if num, delimiter, ansiCode = parseAnsiCode(ansiCode, delimiter); num != -1 { if num, ansiCode = parseAnsiCode(ansiCode); num != -1 {
count++ count++
switch state256 { switch state256 {
case 0: case 0:

View File

@@ -335,6 +335,28 @@ func TestExtractColor(t *testing.T) {
assert((*offsets)[0], 0, 6, 2, -1, true) assert((*offsets)[0], 0, 6, 2, -1, true)
assert((*offsets)[1], 6, 11, 200, 100, false) assert((*offsets)[1], 6, 11, 200, 100, false)
}) })
state = nil
var color24 tui.Color = (1 << 24) + (180 << 16) + (190 << 8) + 254
src = "\x1b[1mhello \x1b[22;1;38:2:180:190:254mworld"
check(func(offsets *[]ansiOffset, state *ansiState) {
if len(*offsets) != 2 {
t.Fail()
}
if state.fg != color24 || state.attr != 1 {
t.Fail()
}
assert((*offsets)[0], 0, 6, -1, -1, true)
assert((*offsets)[1], 6, 11, color24, -1, true)
})
src = "\x1b]133;A\x1b\\hello \x1b]133;C\x1b\\world"
check(func(offsets *[]ansiOffset, state *ansiState) {
if len(*offsets) != 1 {
t.Fail()
}
assert((*offsets)[0], 0, 11, color24, -1, true)
})
} }
func TestAnsiCodeStringConversion(t *testing.T) { func TestAnsiCodeStringConversion(t *testing.T) {
@@ -381,7 +403,7 @@ func TestParseAnsiCode(t *testing.T) {
{"-2", "", -1}, {"-2", "", -1},
} }
for _, x := range tests { for _, x := range tests {
n, _, s := parseAnsiCode(x.In, 0) n, s := parseAnsiCode(x.In)
if n != x.N || s != x.Exp { if n != x.N || s != x.Exp {
t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp) t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp)
} }

View File

@@ -172,7 +172,9 @@ func Run(opts *Options) (int, error) {
return chunkList.Push(data) return chunkList.Push(data)
}, eventBox, executor, opts.ReadZero, opts.Filter == nil) }, eventBox, executor, opts.ReadZero, opts.Filter == nil)
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv) readyChan := make(chan bool)
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv, readyChan)
<-readyChan
} }
// Matcher // Matcher
@@ -188,11 +190,14 @@ func Run(opts *Options) (int, error) {
forward = true forward = true
} }
} }
nth := opts.Nth
nthRevision := 0
patternCache := make(map[string]*Pattern) patternCache := make(map[string]*Pattern)
patternBuilder := func(runes []rune) *Pattern { patternBuilder := func(runes []rune) *Pattern {
return BuildPattern(cache, patternCache, return BuildPattern(cache, patternCache,
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos, opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
opts.Filter == nil, opts.Nth, opts.Delimiter, runes) opts.Filter == nil, nth, opts.Delimiter, nthRevision, runes)
} }
inputRevision := revision{} inputRevision := revision{}
snapshotRevision := revision{} snapshotRevision := revision{}
@@ -224,7 +229,7 @@ func Run(opts *Options) (int, error) {
} }
return false return false
}, eventBox, executor, opts.ReadZero, false) }, eventBox, executor, opts.ReadZero, false)
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv) reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv, nil)
} else { } else {
eventBox.Unwatch(EvtReadNew) eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin) eventBox.WaitFor(EvtReadFin)
@@ -299,7 +304,9 @@ func Run(opts *Options) (int, error) {
itemIndex = 0 itemIndex = 0
inputRevision.bumpMajor() inputRevision.bumpMajor()
header = make([]string, 0, opts.HeaderLines) header = make([]string, 0, opts.HeaderLines)
go reader.restart(command, environ) readyChan := make(chan bool)
go reader.restart(command, environ, readyChan)
<-readyChan
} }
exitCode := ExitOk exitCode := ExitOk
@@ -369,6 +376,14 @@ func Run(opts *Options) (int, error) {
command = val.command command = val.command
environ = val.environ environ = val.environ
changed = val.changed changed = val.changed
if val.nth != nil {
// Change nth and clear caches
nth = *val.nth
nthRevision++
patternCache = make(map[string]*Pattern)
cache.Clear()
inputRevision.bumpMinor()
}
if command != nil { if command != nil {
useSnapshot = val.sync useSnapshot = val.sync
} }

View File

@@ -6,10 +6,17 @@ import (
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
) )
type transformed struct {
// Because nth can be changed dynamically by change-nth action, we need to
// keep the revision number at the time of transformation.
revision int
tokens []Token
}
// Item represents each input line. 56 bytes. // Item represents each input line. 56 bytes.
type Item struct { type Item struct {
text util.Chars // 32 = 24 + 1 + 1 + 2 + 4 text util.Chars // 32 = 24 + 1 + 1 + 2 + 4
transformed *[]Token // 8 transformed *transformed // 8
origText *[]byte // 8 origText *[]byte // 8
colors *[]ansiOffset // 8 colors *[]ansiOffset // 8
} }

File diff suppressed because it is too large Load Diff

View File

@@ -9,9 +9,13 @@ import (
) )
func TestDelimiterRegex(t *testing.T) { func TestDelimiterRegex(t *testing.T) {
// Valid regex // Valid regex, but a single character -> string
delim := delimiterRegexp(".") delim := delimiterRegexp(".")
if delim.regex == nil || delim.str != nil { if delim.regex != nil || *delim.str != "." {
t.Error(delim)
}
delim = delimiterRegexp("|")
if delim.regex != nil || *delim.str != "|" {
t.Error(delim) t.Error(delim)
} }
// Broken regex -> string // Broken regex -> string

View File

@@ -60,6 +60,7 @@ type Pattern struct {
cacheKey string cacheKey string
delimiter Delimiter delimiter Delimiter
nth []Range nth []Range
revision int
procFun map[termType]algo.Algo procFun map[termType]algo.Algo
cache *ChunkCache cache *ChunkCache
} }
@@ -72,7 +73,7 @@ func init() {
// BuildPattern builds Pattern object from the given arguments // BuildPattern builds Pattern object from the given arguments
func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool, func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern { withPos bool, cacheable bool, nth []Range, delimiter Delimiter, revision int, runes []rune) *Pattern {
var asString string var asString string
if extended { if extended {
@@ -140,6 +141,7 @@ func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy boo
sortable: sortable, sortable: sortable,
cacheable: cacheable, cacheable: cacheable,
nth: nth, nth: nth,
revision: revision,
delimiter: delimiter, delimiter: delimiter,
cache: cache, cache: cache,
procFun: make(map[termType]algo.Algo)} procFun: make(map[termType]algo.Algo)}
@@ -393,12 +395,15 @@ func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Of
func (p *Pattern) transformInput(item *Item) []Token { func (p *Pattern) transformInput(item *Item) []Token {
if item.transformed != nil { if item.transformed != nil {
return *item.transformed transformed := *item.transformed
if transformed.revision == p.revision {
return transformed.tokens
}
} }
tokens := Tokenize(item.text.ToString(), p.delimiter) tokens := Tokenize(item.text.ToString(), p.delimiter)
ret := Transform(tokens, p.nth) ret := Transform(tokens, p.nth)
item.transformed = &ret item.transformed = &transformed{p.revision, ret}
return ret return ret
} }

View File

@@ -68,7 +68,7 @@ func buildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern { withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
return BuildPattern(NewChunkCache(), make(map[string]*Pattern), return BuildPattern(NewChunkCache(), make(map[string]*Pattern),
fuzzy, fuzzyAlgo, extended, caseMode, normalize, forward, fuzzy, fuzzyAlgo, extended, caseMode, normalize, forward,
withPos, cacheable, nth, delimiter, runes) withPos, cacheable, nth, delimiter, 0, runes)
} }
func TestExact(t *testing.T) { func TestExact(t *testing.T) {
@@ -135,12 +135,12 @@ func TestOrigTextAndTransformed(t *testing.T) {
chunk.items[0] = Item{ chunk.items[0] = Item{
text: util.ToChars([]byte("junegunn")), text: util.ToChars([]byte("junegunn")),
origText: &origBytes, origText: &origBytes,
transformed: &trans} transformed: &transformed{pattern.revision, trans}}
pattern.extended = extended pattern.extended = extended
matches := pattern.matchChunk(&chunk, nil, slab) // No cache matches := pattern.matchChunk(&chunk, nil, slab) // No cache
if !(matches[0].item.text.ToString() == "junegunn" && if !(matches[0].item.text.ToString() == "junegunn" &&
string(*matches[0].item.origText) == "junegunn.choi" && string(*matches[0].item.origText) == "junegunn.choi" &&
reflect.DeepEqual(*matches[0].item.transformed, trans)) { reflect.DeepEqual((*matches[0].item.transformed).tokens, trans)) {
t.Error("Invalid match result", matches) t.Error("Invalid match result", matches)
} }
@@ -148,7 +148,7 @@ func TestOrigTextAndTransformed(t *testing.T) {
if !(match.item.text.ToString() == "junegunn" && if !(match.item.text.ToString() == "junegunn" &&
string(*match.item.origText) == "junegunn.choi" && string(*match.item.origText) == "junegunn.choi" &&
offsets[0][0] == 0 && offsets[0][1] == 5 && offsets[0][0] == 0 && offsets[0][1] == 5 &&
reflect.DeepEqual(*match.item.transformed, trans)) { reflect.DeepEqual((*match.item.transformed).tokens, trans)) {
t.Error("Invalid match result", match, offsets, extended) t.Error("Invalid match result", match, offsets, extended)
} }
if !((*pos)[0] == 4 && (*pos)[1] == 0) { if !((*pos)[0] == 4 && (*pos)[1] == 0) {

View File

@@ -6,8 +6,8 @@ import (
"io" "io"
"io/fs" "io/fs"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -25,16 +25,26 @@ type Reader struct {
event int32 event int32
finChan chan bool finChan chan bool
mutex sync.Mutex mutex sync.Mutex
exec *exec.Cmd
execOut io.ReadCloser
command *string
killed bool killed bool
termFunc func()
command *string
wait bool wait bool
} }
// NewReader returns new Reader object // NewReader returns new Reader object
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, executor *util.Executor, delimNil bool, wait bool) *Reader { func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, executor *util.Executor, delimNil bool, wait bool) *Reader {
return &Reader{pusher, executor, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, nil, false, wait} return &Reader{
pusher,
executor,
eventBox,
delimNil,
int32(EvtReady),
make(chan bool, 1),
sync.Mutex{},
false,
func() { os.Stdin.Close() },
nil,
wait}
} }
func (r *Reader) startEventPoller() { func (r *Reader) startEventPoller() {
@@ -80,19 +90,19 @@ func (r *Reader) fin(success bool) {
func (r *Reader) terminate() { func (r *Reader) terminate() {
r.mutex.Lock() r.mutex.Lock()
r.killed = true r.killed = true
if r.exec != nil && r.exec.Process != nil { if r.termFunc != nil {
r.execOut.Close() r.termFunc()
util.KillCommand(r.exec) r.termFunc = nil
} else {
os.Stdin.Close()
} }
r.mutex.Unlock() r.mutex.Unlock()
} }
func (r *Reader) restart(command commandSpec, environ []string) { func (r *Reader) restart(command commandSpec, environ []string, readyChan chan bool) {
r.event = int32(EvtReady) r.event = int32(EvtReady)
r.startEventPoller() r.startEventPoller()
success := r.readFromCommand(command.command, environ) success := r.readFromCommand(command.command, environ, func() {
readyChan <- true
})
r.fin(success) r.fin(success)
removeFiles(command.tempFiles) removeFiles(command.tempFiles)
} }
@@ -111,21 +121,29 @@ func (r *Reader) readChannel(inputChan chan string) bool {
} }
// ReadSource reads data from the default command or from standard input // ReadSource reads data from the default command or from standard input
func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string, initCmd string, initEnv []string) { func (r *Reader) ReadSource(inputChan chan string, roots []string, opts walkerOpts, ignores []string, initCmd string, initEnv []string, readyChan chan bool) {
r.startEventPoller() r.startEventPoller()
var success bool var success bool
signalReady := func() {
if readyChan != nil {
readyChan <- true
}
}
if inputChan != nil { if inputChan != nil {
signalReady()
success = r.readChannel(inputChan) success = r.readChannel(inputChan)
} else if len(initCmd) > 0 { } else if len(initCmd) > 0 {
success = r.readFromCommand(initCmd, initEnv) success = r.readFromCommand(initCmd, initEnv, signalReady)
} else if util.IsTty(os.Stdin) { } else if util.IsTty(os.Stdin) {
cmd := os.Getenv("FZF_DEFAULT_COMMAND") cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 { if len(cmd) == 0 {
success = r.readFiles(root, opts, ignores) signalReady()
success = r.readFiles(roots, opts, ignores)
} else { } else {
success = r.readFromCommand(cmd, initEnv) success = r.readFromCommand(cmd, initEnv, signalReady)
} }
} else { } else {
signalReady()
success = r.readFromStdin() success = r.readFromStdin()
} }
r.fin(success) r.fin(success)
@@ -248,14 +266,33 @@ func trimPath(path string) string {
return byteString(bytes) return byteString(bytes)
} }
func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool { func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bool {
r.killed = false
conf := fastwalk.Config{ conf := fastwalk.Config{
Follow: opts.follow, Follow: opts.follow,
// Use forward slashes when running a Windows binary under WSL or MSYS // Use forward slashes when running a Windows binary under WSL or MSYS
ToSlash: fastwalk.DefaultToSlash(), ToSlash: fastwalk.DefaultToSlash(),
Sort: fastwalk.SortFilesFirst, Sort: fastwalk.SortFilesFirst,
} }
ignoresBase := []string{}
ignoresFull := []string{}
ignoresSuffix := []string{}
sep := string(os.PathSeparator)
for _, ignore := range ignores {
if strings.ContainsRune(ignore, os.PathSeparator) {
if strings.HasPrefix(ignore, sep) {
ignoresSuffix = append(ignoresSuffix, ignore)
} else {
// 'foo/bar' should match match
// * 'foo/bar'
// * 'baz/foo/bar'
// * but NOT 'bazfoo/bar'
ignoresFull = append(ignoresFull, ignore)
ignoresSuffix = append(ignoresSuffix, sep+ignore)
}
} else {
ignoresBase = append(ignoresBase, ignore)
}
}
fn := func(path string, de os.DirEntry, err error) error { fn := func(path string, de os.DirEntry, err error) error {
if err != nil { if err != nil {
return nil return nil
@@ -268,11 +305,21 @@ func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool
if !opts.hidden && base[0] == '.' && base != ".." { if !opts.hidden && base[0] == '.' && base != ".." {
return filepath.SkipDir return filepath.SkipDir
} }
for _, ignore := range ignores { for _, ignore := range ignoresBase {
if ignore == base { if ignore == base {
return filepath.SkipDir return filepath.SkipDir
} }
} }
for _, ignore := range ignoresFull {
if ignore == path {
return filepath.SkipDir
}
}
for _, ignore := range ignoresSuffix {
if strings.HasSuffix(path, ignore) {
return filepath.SkipDir
}
}
} }
if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher(stringBytes(path)) { if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher(stringBytes(path)) {
atomic.StoreInt32(&r.event, int32(EvtReadNew)) atomic.StoreInt32(&r.event, int32(EvtReadNew))
@@ -285,34 +332,39 @@ func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool
} }
return nil return nil
} }
return fastwalk.Walk(&conf, root, fn) == nil noerr := true
for _, root := range roots {
noerr = noerr && (fastwalk.Walk(&conf, root, fn) == nil)
}
return noerr
} }
func (r *Reader) readFromCommand(command string, environ []string) bool { func (r *Reader) readFromCommand(command string, environ []string, signalReady func()) bool {
r.mutex.Lock() r.mutex.Lock()
r.killed = false r.killed = false
r.termFunc = nil
r.command = &command r.command = &command
r.exec = r.executor.ExecCommand(command, true) exec := r.executor.ExecCommand(command, true)
if environ != nil { if environ != nil {
r.exec.Env = environ exec.Env = environ
} }
execOut, err := exec.StdoutPipe()
var err error if err != nil || exec.Start() != nil {
r.execOut, err = r.exec.StdoutPipe() signalReady()
if err != nil {
r.exec = nil
r.mutex.Unlock() r.mutex.Unlock()
return false return false
} }
err = r.exec.Start() // Function to call to terminate the running command
if err != nil { r.termFunc = func() {
r.exec = nil execOut.Close()
r.mutex.Unlock() util.KillCommand(exec)
return false
} }
signalReady()
r.mutex.Unlock() r.mutex.Unlock()
r.feed(r.execOut)
return r.exec.Wait() == nil r.feed(execOut)
return exec.Wait() == nil
} }

View File

@@ -23,8 +23,12 @@ func TestReadFromCommand(t *testing.T) {
} }
// Normal command // Normal command
reader.fin(reader.readFromCommand(`echo abc&&echo def`, nil)) counter := 0
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" { ready := func() {
counter++
}
reader.fin(reader.readFromCommand(`echo abc&&echo def`, nil, ready))
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" || counter != 1 {
t.Errorf("%s", strs) t.Errorf("%s", strs)
} }
@@ -48,9 +52,9 @@ func TestReadFromCommand(t *testing.T) {
reader.startEventPoller() reader.startEventPoller()
// Failing command // Failing command
reader.fin(reader.readFromCommand(`no-such-command`, nil)) reader.fin(reader.readFromCommand(`no-such-command`, nil, ready))
strs = []string{} strs = []string{}
if len(strs) > 0 { if len(strs) > 0 || counter != 2 {
t.Errorf("%s", strs) t.Errorf("%s", strs)
} }

View File

@@ -104,11 +104,11 @@ func minRank() Result {
return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}} return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
} }
func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, current bool) []colorOffset { func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, attrNth tui.Attr, current bool) []colorOffset {
itemColors := result.item.Colors() itemColors := result.item.Colors()
// No ANSI codes // No ANSI codes
if len(itemColors) == 0 { if len(itemColors) == 0 && len(nthOffsets) == 0 {
var offsets []colorOffset var offsets []colorOffset
for _, off := range matchOffsets { for _, off := range matchOffsets {
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch, match: true}) offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch, match: true})
@@ -118,7 +118,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
// Find max column // Find max column
var maxCol int32 var maxCol int32
for _, off := range matchOffsets { for _, off := range append(matchOffsets, nthOffsets...) {
if off[1] > maxCol { if off[1] > maxCol {
maxCol = off[1] maxCol = off[1]
} }
@@ -129,20 +129,29 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
} }
} }
cols := make([]int, maxCol) type cellInfo struct {
index int
color bool
match bool
nth bool
}
cols := make([]cellInfo, maxCol)
for colorIndex, ansi := range itemColors { for colorIndex, ansi := range itemColors {
for i := ansi.offset[0]; i < ansi.offset[1]; i++ { for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
cols[i] = colorIndex + 1 // 1-based index of itemColors cols[i] = cellInfo{colorIndex, true, false, false}
} }
} }
for _, off := range matchOffsets { for _, off := range matchOffsets {
for i := off[0]; i < off[1]; i++ { for i := off[0]; i < off[1]; i++ {
// Negative of 1-based index of itemColors cols[i].match = true
// - The extra -1 means highlighted }
if cols[i] >= 0 { }
cols[i] = cols[i]*-1 - 1
} for _, off := range nthOffsets {
for i := off[0]; i < off[1]; i++ {
cols[i].nth = true
} }
} }
@@ -152,7 +161,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
// ------------ ---- -- ---- // ------------ ---- -- ----
// ++++++++ ++++++++++ // ++++++++ ++++++++++
// --++++++++-- --++++++++++--- // --++++++++-- --++++++++++---
curr := 0 var curr cellInfo = cellInfo{0, false, false, false}
start := 0 start := 0
ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair { ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
fg := ansi.color.fg fg := ansi.color.fg
@@ -175,12 +184,17 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
} }
var colors []colorOffset var colors []colorOffset
add := func(idx int) { add := func(idx int) {
if curr != 0 && idx > start { if (curr.color || curr.nth || curr.match) && idx > start {
if curr < 0 { if curr.match {
color := colMatch var color tui.ColorPair
if curr.nth {
color = colBase.WithAttr(attrNth).Merge(colMatch)
} else {
color = colBase.Merge(colMatch)
}
var url *url var url *url
if curr < -1 && theme.Colored { if curr.color && theme.Colored {
ansi := itemColors[-curr-2] ansi := itemColors[curr.index]
url = ansi.color.url url = ansi.color.url
origColor := ansiToColorPair(ansi, colMatch) origColor := ansiToColorPair(ansi, colMatch)
// hl or hl+ only sets the foreground color, so colMatch is the // hl or hl+ only sets the foreground color, so colMatch is the
@@ -193,19 +207,32 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
// echo -e "\x1b[42mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline // echo -e "\x1b[42mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
if color.Fg().IsDefault() && origColor.HasBg() { if color.Fg().IsDefault() && origColor.HasBg() {
color = origColor color = origColor
if curr.nth {
color = color.WithAttr(attrNth)
}
} else { } else {
color = origColor.MergeNonDefault(color) color = origColor.MergeNonDefault(color)
} }
} }
colors = append(colors, colorOffset{ colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, color: color, match: true, url: url}) offset: [2]int32{int32(start), int32(idx)}, color: color, match: true, url: url})
} else { } else if curr.color {
ansi := itemColors[curr-1] ansi := itemColors[curr.index]
color := ansiToColorPair(ansi, colBase)
if curr.nth {
color = color.WithAttr(attrNth)
}
colors = append(colors, colorOffset{ colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, offset: [2]int32{int32(start), int32(idx)},
color: ansiToColorPair(ansi, colBase), color: color,
match: false, match: false,
url: ansi.color.url}) url: ansi.color.url})
} else {
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)},
color: colBase.WithAttr(attrNth),
match: false,
url: nil})
} }
} }
} }

View File

@@ -131,7 +131,7 @@ func TestColorOffset(t *testing.T) {
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined) colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined) colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
colors := item.colorOffsets(offsets, tui.Dark256, colBase, colMatch, true) colors := item.colorOffsets(offsets, nil, tui.Dark256, colBase, colMatch, tui.AttrUndefined, true)
assert := func(idx int, b int32, e int32, c tui.ColorPair) { assert := func(idx int, b int32, e int32, c tui.ColorPair) {
o := colors[idx] o := colors[idx]
if o.offset[0] != b || o.offset[1] != e || o.color != c { if o.offset[0] != b || o.offset[1] != e || o.color != c {
@@ -155,20 +155,30 @@ func TestColorOffset(t *testing.T) {
colRegular := tui.NewColorPair(-1, -1, tui.AttrUndefined) colRegular := tui.NewColorPair(-1, -1, tui.AttrUndefined)
colUnderline := tui.NewColorPair(-1, -1, tui.Underline) colUnderline := tui.NewColorPair(-1, -1, tui.Underline)
colors = item.colorOffsets(offsets, tui.Dark256, colRegular, colUnderline, true)
// [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}} nthOffsets := []Offset{{37, 39}, {42, 45}}
// {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}} for _, attr := range []tui.Attr{tui.AttrRegular, tui.StrikeThrough} {
// {[30 32] {3 7 8}} {[32 33] {-1 -1 8}} {[33 35] {4 8 9}} colors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, attr, true)
// {[35 40] {4 8 1}}]
assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined)) // [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}}
assert(1, 5, 15, tui.NewColorPair(1, 5, tui.Underline)) // {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}
assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined)) // {[30 32] {3 7 8}} {[32 33] {-1 -1 8}} {[33 35] {4 8 9}}
assert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold)) // {[35 37] {4 8 1}} {[37 39] {4 8 x|1}} {[39 40] {4 8 x|1}}]
assert(4, 25, 27, tui.NewColorPair(2, 6, tui.Bold|tui.Underline)) assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(5, 27, 30, colUnderline) assert(1, 5, 15, tui.NewColorPair(1, 5, tui.Underline))
assert(6, 30, 32, tui.NewColorPair(3, 7, tui.Underline)) assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(7, 32, 33, colUnderline) assert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))
assert(8, 33, 35, tui.NewColorPair(4, 8, tui.Bold|tui.Underline)) assert(4, 25, 27, tui.NewColorPair(2, 6, tui.Bold|tui.Underline))
assert(9, 35, 40, tui.NewColorPair(4, 8, tui.Bold)) assert(5, 27, 30, colUnderline)
assert(6, 30, 32, tui.NewColorPair(3, 7, tui.Underline))
assert(7, 32, 33, colUnderline)
assert(8, 33, 35, tui.NewColorPair(4, 8, tui.Bold|tui.Underline))
assert(9, 35, 37, tui.NewColorPair(4, 8, tui.Bold))
expected := tui.Bold | attr
if attr == tui.AttrRegular {
expected = tui.AttrRegular
}
assert(10, 37, 39, tui.NewColorPair(4, 8, expected))
assert(11, 39, 40, tui.NewColorPair(4, 8, tui.Bold))
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -507,6 +507,34 @@ func TestParsePlaceholder(t *testing.T) {
} }
} }
func TestExtractPassthroughs(t *testing.T) {
for _, middle := range []string{
"\x1bPtmux;\x1b\x1bbar\x1b\\",
"\x1bPtmux;\x1b\x1bbar\x1bbar\x1b\\",
"\x1b]1337;bar\x1b\\",
"\x1b]1337;bar\x1bbar\x1b\\",
"\x1b]1337;bar\a",
"\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\x1b\\",
"\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\x1b\\\r",
"\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\x1bbar\x1b\\\r",
"\x1b_Gm=1;AAAAAAAAA=\x1b\\",
"\x1b_Gm=1;AAAAAAAAA=\x1b\\\r",
"\x1b_Gm=1;\x1bAAAAAAAAA=\x1b\\\r",
} {
line := "foo" + middle + "baz"
loc := findPassThrough(line)
if loc == nil || line[0:loc[0]] != "foo" || line[loc[1]:] != "baz" {
t.Error("failed to find passthrough")
}
garbage := "\x1bPtmux;\x1b]1337;\x1b_Ga=\x1b]1337;bar\x1b."
line = strings.Repeat("foo"+middle+middle+"baz", 3) + garbage
passthroughs, result := extractPassThroughs(line)
if result != "foobazfoobazfoobaz"+garbage || len(passthroughs) != 6 {
t.Error("failed to extract passthroughs")
}
}
}
/* utilities section */ /* utilities section */
// Item represents one line in fzf UI. Usually it is relative path to files and folders. // Item represents one line in fzf UI. Usually it is relative path to files and folders.

View File

@@ -9,13 +9,18 @@ import (
func runTmux(args []string, opts *Options) (int, error) { func runTmux(args []string, opts *Options) (int, error) {
// Prepare arguments // Prepare arguments
fzf := args[0] fzf, rest := args[0], args[1:]
args = append([]string{"--bind=ctrl-z:ignore"}, args[1:]...) args = []string{"--bind=ctrl-z:ignore"}
if opts.BorderShape == tui.BorderUndefined { if !opts.Tmux.border && opts.BorderShape == tui.BorderUndefined {
args = append(args, "--border") // We append --border option at the end, because `--style=full:STYLE`
// may have changed the default border style.
rest = append(rest, "--border")
}
if opts.Tmux.border && opts.Margin == defaultMargin() {
args = append(args, "--margin=0,1")
} }
argStr := escapeSingleQuote(fzf) argStr := escapeSingleQuote(fzf)
for _, arg := range args { for _, arg := range append(args, rest...) {
argStr += " " + escapeSingleQuote(arg) argStr += " " + escapeSingleQuote(arg)
} }
argStr += ` --no-tmux --no-height` argStr += ` --no-tmux --no-height`
@@ -33,7 +38,10 @@ func runTmux(args []string, opts *Options) (int, error) {
// M Both The mouse position // M Both The mouse position
// W Both The window position on the status line // W Both The window position on the status line
// S -y The line above or below the status line // S -y The line above or below the status line
tmuxArgs := []string{"display-popup", "-E", "-B", "-d", dir} tmuxArgs := []string{"display-popup", "-E", "-d", dir}
if !opts.Tmux.border {
tmuxArgs = append(tmuxArgs, "-B")
}
switch opts.Tmux.position { switch opts.Tmux.position {
case posUp: case posUp:
tmuxArgs = append(tmuxArgs, "-xC", "-y0") tmuxArgs = append(tmuxArgs, "-xC", "-y0")

View File

@@ -18,6 +18,36 @@ type Range struct {
end int end int
} }
func (r Range) IsFull() bool {
return r.begin == rangeEllipsis && r.end == rangeEllipsis
}
func RangesToString(ranges []Range) string {
strs := []string{}
for _, r := range ranges {
s := ""
if r.begin == rangeEllipsis && r.end == rangeEllipsis {
s = ".."
} else if r.begin == r.end {
s = strconv.Itoa(r.begin)
} else {
if r.begin != rangeEllipsis {
s += strconv.Itoa(r.begin)
}
if r.begin != -1 {
s += ".."
if r.end != rangeEllipsis {
s += strconv.Itoa(r.end)
}
}
}
strs = append(strs, s)
}
return strings.Join(strs, ",")
}
// Token contains the tokenized part of the strings and its prefix length // Token contains the tokenized part of the strings and its prefix length
type Token struct { type Token struct {
text *util.Chars text *util.Chars
@@ -41,7 +71,7 @@ func (d Delimiter) String() string {
} }
func newRange(begin int, end int) Range { func newRange(begin int, end int) Range {
if begin == 1 { if begin == 1 && end != 1 {
begin = rangeEllipsis begin = rangeEllipsis
} }
if end == -1 { if end == -1 {
@@ -73,7 +103,7 @@ func ParseRange(str *string) (Range, bool) {
} }
begin, err1 := strconv.Atoi(ns[0]) begin, err1 := strconv.Atoi(ns[0])
end, err2 := strconv.Atoi(ns[1]) end, err2 := strconv.Atoi(ns[1])
if err1 != nil || err2 != nil || begin == 0 || end == 0 { if err1 != nil || err2 != nil || begin == 0 || end == 0 || begin < 0 && end > 0 {
return Range{}, false return Range{}, false
} }
return newRange(begin, end), true return newRange(begin, end), true

View File

@@ -40,6 +40,18 @@ func TestParseRange(t *testing.T) {
t.Errorf("%v", r) t.Errorf("%v", r)
} }
} }
{
i := "1..3..5"
if r, ok := ParseRange(&i); ok {
t.Errorf("%v", r)
}
}
{
i := "-3..3"
if r, ok := ParseRange(&i); ok {
t.Errorf("%v", r)
}
}
} }
func TestTokenize(t *testing.T) { func TestTokenize(t *testing.T) {

View File

@@ -11,6 +11,11 @@ func HasFullscreenRenderer() bool {
var DefaultBorderShape = BorderRounded var DefaultBorderShape = BorderRounded
func (a Attr) Merge(b Attr) Attr { func (a Attr) Merge(b Attr) Attr {
if b&AttrRegular > 0 {
// Only keep bold attribute set by the system
return b | (a & BoldForce)
}
return a | b return a | b
} }
@@ -18,6 +23,7 @@ const (
AttrUndefined = Attr(0) AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 8) AttrRegular = Attr(1 << 8)
AttrClear = Attr(1 << 9) AttrClear = Attr(1 << 9)
BoldForce = Attr(1 << 10)
Bold = Attr(1) Bold = Attr(1)
Dim = Attr(1 << 1) Dim = Attr(1 << 1)
@@ -30,6 +36,7 @@ const (
) )
func (r *FullscreenRenderer) Init() error { return nil } func (r *FullscreenRenderer) Init() error { return nil }
func (r *FullscreenRenderer) DefaultTheme() *ColorTheme { return nil }
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {} func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) Pause(bool) {} func (r *FullscreenRenderer) Pause(bool) {}
func (r *FullscreenRenderer) Resume(bool, bool) {} func (r *FullscreenRenderer) Resume(bool, bool) {}
@@ -48,6 +55,6 @@ func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {} func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window { func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window {
return nil return nil
} }

View File

@@ -73,11 +73,15 @@ func (r *LightRenderer) csi(code string) string {
func (r *LightRenderer) flush() { func (r *LightRenderer) flush() {
if r.queued.Len() > 0 { if r.queued.Len() > 0 {
fmt.Fprint(r.ttyout, "\x1b[?7l\x1b[?25l"+r.queued.String()+"\x1b[?25h\x1b[?7h") r.flushRaw("\x1b[?7l\x1b[?25l" + r.queued.String() + "\x1b[?25h\x1b[?7h")
r.queued.Reset() r.queued.Reset()
} }
} }
func (r *LightRenderer) flushRaw(sequence string) {
fmt.Fprint(r.ttyout, sequence)
}
// Light renderer // Light renderer
type LightRenderer struct { type LightRenderer struct {
closed *util.AtomicBool closed *util.AtomicBool
@@ -112,19 +116,19 @@ type LightRenderer struct {
} }
type LightWindow struct { type LightWindow struct {
renderer *LightRenderer renderer *LightRenderer
colored bool colored bool
preview bool windowType WindowType
border BorderStyle border BorderStyle
top int top int
left int left int
width int width int
height int height int
posx int posx int
posy int posy int
tabstop int tabstop int
fg Color fg Color
bg Color bg Color
} }
func NewLightRenderer(ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) { func NewLightRenderer(ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) {
@@ -170,7 +174,6 @@ func (r *LightRenderer) Init() error {
return err return err
} }
r.updateTerminalSize() r.updateTerminalSize()
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
if r.fullscreen { if r.fullscreen {
r.smcup() r.smcup()
@@ -655,11 +658,13 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
} }
func (r *LightRenderer) smcup() { func (r *LightRenderer) smcup() {
r.csi("?1049h") r.flush()
r.flushRaw("\x1b[?1049h")
} }
func (r *LightRenderer) rmcup() { func (r *LightRenderer) rmcup() {
r.csi("?1049l") r.flush()
r.flushRaw("\x1b[?1049l")
} }
func (r *LightRenderer) Pause(clear bool) { func (r *LightRenderer) Pause(clear bool) {
@@ -774,27 +779,40 @@ func (r *LightRenderer) MaxY() int {
return r.height return r.height
} }
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window { func (r *LightRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window {
width = util.Max(0, width)
height = util.Max(0, height)
w := &LightWindow{ w := &LightWindow{
renderer: r, renderer: r,
colored: r.theme.Colored, colored: r.theme.Colored,
preview: preview, windowType: windowType,
border: borderStyle, border: borderStyle,
top: top, top: top,
left: left, left: left,
width: width, width: width,
height: height, height: height,
tabstop: r.tabstop, tabstop: r.tabstop,
fg: colDefault, fg: colDefault,
bg: colDefault} bg: colDefault}
if preview { switch windowType {
w.fg = r.theme.PreviewFg.Color case WindowBase:
w.bg = r.theme.PreviewBg.Color
} else {
w.fg = r.theme.Fg.Color w.fg = r.theme.Fg.Color
w.bg = r.theme.Bg.Color w.bg = r.theme.Bg.Color
case WindowList:
w.fg = r.theme.ListFg.Color
w.bg = r.theme.ListBg.Color
case WindowInput:
w.fg = r.theme.Input.Color
w.bg = r.theme.InputBg.Color
case WindowHeader:
w.fg = r.theme.Header.Color
w.bg = r.theme.HeaderBg.Color
case WindowPreview:
w.fg = r.theme.PreviewFg.Color
w.bg = r.theme.PreviewBg.Color
} }
if !w.bg.IsDefault() && w.border.shape != BorderNone { if erase && !w.bg.IsDefault() && w.border.shape != BorderNone {
// fzf --color bg:blue --border --padding 1,2
w.Erase() w.Erase()
} }
w.drawBorder(false) w.drawBorder(false)
@@ -810,6 +828,9 @@ func (w *LightWindow) DrawHBorder() {
} }
func (w *LightWindow) drawBorder(onlyHorizontal bool) { func (w *LightWindow) drawBorder(onlyHorizontal bool) {
if w.height == 0 {
return
}
switch w.border.shape { switch w.border.shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble: case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
w.drawBorderAround(onlyHorizontal) w.drawBorderAround(onlyHorizontal)
@@ -839,7 +860,14 @@ func (w *LightWindow) drawBorder(onlyHorizontal bool) {
func (w *LightWindow) drawBorderHorizontal(top, bottom bool) { func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
color := ColBorder color := ColBorder
if w.preview { switch w.windowType {
case WindowList:
color = ColListBorder
case WindowInput:
color = ColInputBorder
case WindowHeader:
color = ColHeaderBorder
case WindowPreview:
color = ColPreviewBorder color = ColPreviewBorder
} }
hw := runeWidth(w.border.top) hw := runeWidth(w.border.top)
@@ -857,7 +885,14 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
func (w *LightWindow) drawBorderVertical(left, right bool) { func (w *LightWindow) drawBorderVertical(left, right bool) {
vw := runeWidth(w.border.left) vw := runeWidth(w.border.left)
color := ColBorder color := ColBorder
if w.preview { switch w.windowType {
case WindowList:
color = ColListBorder
case WindowInput:
color = ColInputBorder
case WindowHeader:
color = ColHeaderBorder
case WindowPreview:
color = ColPreviewBorder color = ColPreviewBorder
} }
for y := 0; y < w.height; y++ { for y := 0; y < w.height; y++ {
@@ -877,7 +912,14 @@ func (w *LightWindow) drawBorderVertical(left, right bool) {
func (w *LightWindow) drawBorderAround(onlyHorizontal bool) { func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
w.Move(0, 0) w.Move(0, 0)
color := ColBorder color := ColBorder
if w.preview { switch w.windowType {
case WindowList:
color = ColListBorder
case WindowInput:
color = ColInputBorder
case WindowHeader:
color = ColHeaderBorder
case WindowPreview:
color = ColPreviewBorder color = ColPreviewBorder
} }
hw := runeWidth(w.border.top) hw := runeWidth(w.border.top)
@@ -929,9 +971,6 @@ func (w *LightWindow) Height() int {
func (w *LightWindow) Refresh() { func (w *LightWindow) Refresh() {
} }
func (w *LightWindow) Close() {
}
func (w *LightWindow) X() int { func (w *LightWindow) X() int {
return w.posx return w.posx
} }
@@ -940,9 +979,16 @@ func (w *LightWindow) Y() int {
return w.posy return w.posy
} }
func (w *LightWindow) EncloseX(x int) bool {
return x >= w.left && x < (w.left+w.width)
}
func (w *LightWindow) EncloseY(y int) bool {
return y >= w.top && y < (w.top+w.height)
}
func (w *LightWindow) Enclose(y int, x int) bool { func (w *LightWindow) Enclose(y int, x int) bool {
return x >= w.left && x < (w.left+w.width) && return w.EncloseX(x) && w.EncloseY(y)
y >= w.top && y < (w.top+w.height)
} }
func (w *LightWindow) Move(y int, x int) { func (w *LightWindow) Move(y int, x int) {
@@ -965,7 +1011,7 @@ func attrCodes(attr Attr) []string {
if (attr & AttrClear) > 0 { if (attr & AttrClear) > 0 {
return codes return codes
} }
if (attr & Bold) > 0 { if (attr&Bold) > 0 || (attr&BoldForce) > 0 {
codes = append(codes, "1") codes = append(codes, "1")
} }
if (attr & Dim) > 0 { if (attr & Dim) > 0 {
@@ -1097,7 +1143,7 @@ func (w *LightWindow) fill(str string, resetCode string) FillReturn {
} }
} }
} }
if w.posx+1 >= w.Width() { if w.posx >= w.Width() {
if w.posy+1 >= w.height { if w.posy+1 >= w.height {
return FillSuspend return FillSuspend
} }

View File

@@ -18,7 +18,7 @@ func IsLightRendererSupported() bool {
return true return true
} }
func (r *LightRenderer) defaultTheme() *ColorTheme { func (r *LightRenderer) DefaultTheme() *ColorTheme {
if strings.Contains(os.Getenv("TERM"), "256") { if strings.Contains(os.Getenv("TERM"), "256") {
return Dark256 return Dark256
} }

View File

@@ -39,7 +39,7 @@ func IsLightRendererSupported() bool {
return canSetVt100 return canSetVt100
} }
func (r *LightRenderer) defaultTheme() *ColorTheme { func (r *LightRenderer) DefaultTheme() *ColorTheme {
// the getenv check is borrowed from here: https://github.com/gdamore/tcell/commit/0c473b86d82f68226a142e96cc5a34c5a29b3690#diff-b008fcd5e6934bf31bc3d33bf49f47d8R178: // the getenv check is borrowed from here: https://github.com/gdamore/tcell/commit/0c473b86d82f68226a142e96cc5a34c5a29b3690#diff-b008fcd5e6934bf31bc3d33bf49f47d8R178:
if !IsLightRendererSupported() || os.Getenv("ConEmuPID") != "" || os.Getenv("TCELL_TRUECOLOR") == "disable" { if !IsLightRendererSupported() || os.Getenv("ConEmuPID") != "" || os.Getenv("TCELL_TRUECOLOR") == "disable" {
return Default16 return Default16

View File

@@ -40,7 +40,7 @@ type Attr int32
type TcellWindow struct { type TcellWindow struct {
color bool color bool
preview bool windowType WindowType
top int top int
left int left int
width int width int
@@ -97,6 +97,7 @@ const (
AttrUndefined = Attr(0) AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 7) AttrRegular = Attr(1 << 7)
AttrClear = Attr(1 << 8) AttrClear = Attr(1 << 8)
BoldForce = Attr(1 << 10)
) )
func (r *FullscreenRenderer) PassThrough(str string) { func (r *FullscreenRenderer) PassThrough(str string) {
@@ -106,8 +107,12 @@ func (r *FullscreenRenderer) PassThrough(str string) {
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {} func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) defaultTheme() *ColorTheme { func (r *FullscreenRenderer) DefaultTheme() *ColorTheme {
if _screen.Colors() >= 256 { s, e := r.getScreen()
if e != nil {
return Default16
}
if s.Colors() >= 256 {
return Dark256 return Dark256
} }
return Default16 return Default16
@@ -137,6 +142,11 @@ func (c Color) Style() tcell.Color {
} }
func (a Attr) Merge(b Attr) Attr { func (a Attr) Merge(b Attr) Attr {
if b&AttrRegular > 0 {
// Only keep bold attribute set by the system
return b | (a & BoldForce)
}
return a | b return a | b
} }
@@ -148,8 +158,19 @@ var (
_initialResize bool = true _initialResize bool = true
) )
func (r *FullscreenRenderer) getScreen() (tcell.Screen, error) {
if _screen == nil {
s, e := tcell.NewScreen()
if e != nil {
return nil, e
}
_screen = s
}
return _screen, nil
}
func (r *FullscreenRenderer) initScreen() error { func (r *FullscreenRenderer) initScreen() error {
s, e := tcell.NewScreen() s, e := r.getScreen()
if e != nil { if e != nil {
return e return e
} }
@@ -161,7 +182,6 @@ func (r *FullscreenRenderer) initScreen() error {
} else { } else {
s.DisableMouse() s.DisableMouse()
} }
_screen = s
return nil return nil
} }
@@ -174,7 +194,6 @@ func (r *FullscreenRenderer) Init() error {
if err := r.initScreen(); err != nil { if err := r.initScreen(); err != nil {
return err return err
} }
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
return nil return nil
} }
@@ -537,14 +556,23 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
_screen.Show() _screen.Show()
} }
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window { func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window {
normal := ColNormal width = util.Max(0, width)
if preview { height = util.Max(0, height)
normal := ColBorder
switch windowType {
case WindowList:
normal = ColNormal
case WindowHeader:
normal = ColHeader
case WindowInput:
normal = ColInput
case WindowPreview:
normal = ColPreview normal = ColPreview
} }
w := &TcellWindow{ w := &TcellWindow{
color: r.theme.Colored, color: r.theme.Colored,
preview: preview, windowType: windowType,
top: top, top: top,
left: left, left: left,
width: width, width: width,
@@ -555,10 +583,6 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
return w return w
} }
func (w *TcellWindow) Close() {
// TODO
}
func fill(x, y, w, h int, n ColorPair, r rune) { func fill(x, y, w, h int, n ColorPair, r rune) {
for ly := 0; ly <= h; ly++ { for ly := 0; ly <= h; ly++ {
for lx := 0; lx <= w; lx++ { for lx := 0; lx <= w; lx++ {
@@ -568,11 +592,7 @@ func fill(x, y, w, h int, n ColorPair, r rune) {
} }
func (w *TcellWindow) Erase() { func (w *TcellWindow) Erase() {
if w.borderStyle.shape.HasLeft() { fill(w.left, w.top, w.width-1, w.height-1, w.normal, ' ')
fill(w.left-1, w.top, w.width, w.height-1, w.normal, ' ')
} else {
fill(w.left, w.top, w.width-1, w.height-1, w.normal, ' ')
}
w.drawBorder(false) w.drawBorder(false)
} }
@@ -581,9 +601,16 @@ func (w *TcellWindow) EraseMaybe() bool {
return true return true
} }
func (w *TcellWindow) EncloseX(x int) bool {
return x >= w.left && x < (w.left+w.width)
}
func (w *TcellWindow) EncloseY(y int) bool {
return y >= w.top && y < (w.top+w.height)
}
func (w *TcellWindow) Enclose(y int, x int) bool { func (w *TcellWindow) Enclose(y int, x int) bool {
return x >= w.left && x < (w.left+w.width) && return w.EncloseX(x) && w.EncloseY(y)
y >= w.top && y < (w.top+w.height)
} }
func (w *TcellWindow) Move(y int, x int) { func (w *TcellWindow) Move(y int, x int) {
@@ -673,7 +700,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
} }
style = style. style = style.
Blink(a&Attr(tcell.AttrBlink) != 0). Blink(a&Attr(tcell.AttrBlink) != 0).
Bold(a&Attr(tcell.AttrBold) != 0). Bold(a&Attr(tcell.AttrBold) != 0 || a&BoldForce != 0).
Dim(a&Attr(tcell.AttrDim) != 0). Dim(a&Attr(tcell.AttrDim) != 0).
Reverse(a&Attr(tcell.AttrReverse) != 0). Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0). Underline(a&Attr(tcell.AttrUnderline) != 0).
@@ -760,6 +787,9 @@ func (w *TcellWindow) DrawHBorder() {
} }
func (w *TcellWindow) drawBorder(onlyHorizontal bool) { func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
if w.height == 0 {
return
}
shape := w.borderStyle.shape shape := w.borderStyle.shape
if shape == BorderNone { if shape == BorderNone {
return return
@@ -772,10 +802,17 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
var style tcell.Style var style tcell.Style
if w.color { if w.color {
if w.preview { switch w.windowType {
style = ColPreviewBorder.style() case WindowBase:
} else {
style = ColBorder.style() style = ColBorder.style()
case WindowList:
style = ColListBorder.style()
case WindowHeader:
style = ColHeaderBorder.style()
case WindowInput:
style = ColInputBorder.style()
case WindowPreview:
style = ColPreviewBorder.style()
} }
} else { } else {
style = w.normal.style() style = w.normal.style()

View File

@@ -15,7 +15,7 @@ func TtyIn() (*os.File, error) {
return os.Stdin, nil return os.Stdin, nil
} }
// TtyIn on Windows returns nil // TtyOut on Windows returns nil
func TtyOut() (*os.File, error) { func TtyOut() (*os.File, error) {
return nil, nil return nil, nil
} }

View File

@@ -205,10 +205,24 @@ type ColorAttr struct {
Attr Attr Attr Attr
} }
func (a ColorAttr) IsColorDefined() bool {
return a.Color != colUndefined
}
func NewColorAttr() ColorAttr { func NewColorAttr() ColorAttr {
return ColorAttr{Color: colUndefined, Attr: AttrUndefined} return ColorAttr{Color: colUndefined, Attr: AttrUndefined}
} }
func (a ColorAttr) Merge(other ColorAttr) ColorAttr {
if other.Color != colUndefined {
a.Color = other.Color
}
if other.Attr != AttrUndefined {
a.Attr = a.Attr.Merge(other.Attr)
}
return a
}
const ( const (
colUndefined Color = -2 colUndefined Color = -2
colDefault Color = -1 colDefault Color = -1
@@ -303,6 +317,9 @@ type ColorTheme struct {
Disabled ColorAttr Disabled ColorAttr
Fg ColorAttr Fg ColorAttr
Bg ColorAttr Bg ColorAttr
ListFg ColorAttr
ListBg ColorAttr
Nth ColorAttr
SelectedFg ColorAttr SelectedFg ColorAttr
SelectedBg ColorAttr SelectedBg ColorAttr
SelectedMatch ColorAttr SelectedMatch ColorAttr
@@ -311,6 +328,9 @@ type ColorTheme struct {
DarkBg ColorAttr DarkBg ColorAttr
Gutter ColorAttr Gutter ColorAttr
Prompt ColorAttr Prompt ColorAttr
InputBg ColorAttr
InputBorder ColorAttr
InputLabel ColorAttr
Match ColorAttr Match ColorAttr
Current ColorAttr Current ColorAttr
CurrentMatch ColorAttr CurrentMatch ColorAttr
@@ -319,13 +339,19 @@ type ColorTheme struct {
Cursor ColorAttr Cursor ColorAttr
Marker ColorAttr Marker ColorAttr
Header ColorAttr Header ColorAttr
HeaderBg ColorAttr
HeaderBorder ColorAttr
HeaderLabel ColorAttr
Separator ColorAttr Separator ColorAttr
Scrollbar ColorAttr Scrollbar ColorAttr
Border ColorAttr Border ColorAttr
PreviewBorder ColorAttr PreviewBorder ColorAttr
PreviewLabel ColorAttr
PreviewScrollbar ColorAttr PreviewScrollbar ColorAttr
BorderLabel ColorAttr BorderLabel ColorAttr
PreviewLabel ColorAttr ListLabel ColorAttr
ListBorder ColorAttr
GapLine ColorAttr
} }
type Event struct { type Event struct {
@@ -348,6 +374,7 @@ type BorderShape int
const ( const (
BorderUndefined BorderShape = iota BorderUndefined BorderShape = iota
BorderLine
BorderNone BorderNone
BorderRounded BorderRounded
BorderSharp BorderSharp
@@ -365,7 +392,7 @@ const (
func (s BorderShape) HasLeft() bool { func (s BorderShape) HasLeft() bool {
switch s { switch s {
case BorderNone, BorderRight, BorderTop, BorderBottom, BorderHorizontal: // No Left case BorderNone, BorderLine, BorderRight, BorderTop, BorderBottom, BorderHorizontal: // No Left
return false return false
} }
return true return true
@@ -373,7 +400,7 @@ func (s BorderShape) HasLeft() bool {
func (s BorderShape) HasRight() bool { func (s BorderShape) HasRight() bool {
switch s { switch s {
case BorderNone, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right case BorderNone, BorderLine, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
return false return false
} }
return true return true
@@ -381,12 +408,24 @@ func (s BorderShape) HasRight() bool {
func (s BorderShape) HasTop() bool { func (s BorderShape) HasTop() bool {
switch s { switch s {
case BorderNone, BorderLeft, BorderRight, BorderBottom, BorderVertical: // No top case BorderNone, BorderLine, BorderLeft, BorderRight, BorderBottom, BorderVertical: // No top
return false return false
} }
return true return true
} }
func (s BorderShape) HasBottom() bool {
switch s {
case BorderNone, BorderLine, BorderLeft, BorderRight, BorderTop, BorderVertical: // No bottom
return false
}
return true
}
func (s BorderShape) Visible() bool {
return s != BorderNone
}
type BorderStyle struct { type BorderStyle struct {
shape BorderShape shape BorderShape
top rune top rune
@@ -402,6 +441,18 @@ type BorderStyle struct {
type BorderCharacter int type BorderCharacter int
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle { func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
if shape == BorderNone {
return BorderStyle{
shape: shape,
top: ' ',
bottom: ' ',
left: ' ',
right: ' ',
topLeft: ' ',
topRight: ' ',
bottomLeft: ' ',
bottomRight: ' '}
}
if !unicode { if !unicode {
return BorderStyle{ return BorderStyle{
shape: shape, shape: shape,
@@ -498,19 +549,6 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
} }
} }
func MakeTransparentBorder() BorderStyle {
return BorderStyle{
shape: BorderRounded,
top: ' ',
bottom: ' ',
left: ' ',
right: ' ',
topLeft: ' ',
topRight: ' ',
bottomLeft: ' ',
bottomRight: ' '}
}
type TermSize struct { type TermSize struct {
Lines int Lines int
Columns int Columns int
@@ -518,7 +556,18 @@ type TermSize struct {
PxHeight int PxHeight int
} }
type WindowType int
const (
WindowBase WindowType = iota
WindowList
WindowPreview
WindowInput
WindowHeader
)
type Renderer interface { type Renderer interface {
DefaultTheme() *ColorTheme
Init() error Init() error
Resize(maxHeightFunc func(int) int) Resize(maxHeightFunc func(int) int)
Pause(clear bool) Pause(clear bool)
@@ -539,7 +588,7 @@ type Renderer interface {
Size() TermSize Size() TermSize
NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window
} }
type Window interface { type Window interface {
@@ -552,10 +601,11 @@ type Window interface {
DrawHBorder() DrawHBorder()
Refresh() Refresh()
FinishFill() FinishFill()
Close()
X() int X() int
Y() int Y() int
EncloseX(x int) bool
EncloseY(y int) bool
Enclose(y int, x int) bool Enclose(y int, x int) bool
Move(y int, x int) Move(y int, x int)
@@ -612,8 +662,11 @@ var (
ColSpinner ColorPair ColSpinner ColorPair
ColInfo ColorPair ColInfo ColorPair
ColHeader ColorPair ColHeader ColorPair
ColHeaderBorder ColorPair
ColHeaderLabel ColorPair
ColSeparator ColorPair ColSeparator ColorPair
ColScrollbar ColorPair ColScrollbar ColorPair
ColGapLine ColorPair
ColBorder ColorPair ColBorder ColorPair
ColPreview ColorPair ColPreview ColorPair
ColPreviewBorder ColorPair ColPreviewBorder ColorPair
@@ -621,6 +674,10 @@ var (
ColPreviewLabel ColorPair ColPreviewLabel ColorPair
ColPreviewScrollbar ColorPair ColPreviewScrollbar ColorPair
ColPreviewSpinner ColorPair ColPreviewSpinner ColorPair
ColListBorder ColorPair
ColListLabel ColorPair
ColInputBorder ColorPair
ColInputLabel ColorPair
) )
func EmptyTheme() *ColorTheme { func EmptyTheme() *ColorTheme {
@@ -629,6 +686,8 @@ func EmptyTheme() *ColorTheme {
Input: ColorAttr{colUndefined, AttrUndefined}, Input: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colUndefined, AttrUndefined}, Fg: ColorAttr{colUndefined, AttrUndefined},
Bg: ColorAttr{colUndefined, AttrUndefined}, Bg: ColorAttr{colUndefined, AttrUndefined},
ListFg: ColorAttr{colUndefined, AttrUndefined},
ListBg: ColorAttr{colUndefined, AttrUndefined},
SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined},
SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined},
SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
@@ -644,6 +703,8 @@ func EmptyTheme() *ColorTheme {
Header: ColorAttr{colUndefined, AttrUndefined}, Header: ColorAttr{colUndefined, AttrUndefined},
Border: ColorAttr{colUndefined, AttrUndefined}, Border: ColorAttr{colUndefined, AttrUndefined},
BorderLabel: ColorAttr{colUndefined, AttrUndefined}, BorderLabel: ColorAttr{colUndefined, AttrUndefined},
ListLabel: ColorAttr{colUndefined, AttrUndefined},
ListBorder: ColorAttr{colUndefined, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined}, Disabled: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined}, PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined}, PreviewBg: ColorAttr{colUndefined, AttrUndefined},
@@ -653,6 +714,14 @@ func EmptyTheme() *ColorTheme {
PreviewLabel: ColorAttr{colUndefined, AttrUndefined}, PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined},
InputBg: ColorAttr{colUndefined, AttrUndefined},
InputBorder: ColorAttr{colUndefined, AttrUndefined},
InputLabel: ColorAttr{colUndefined, AttrUndefined},
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
GapLine: ColorAttr{colUndefined, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined},
} }
} }
@@ -662,6 +731,8 @@ func NoColorTheme() *ColorTheme {
Input: ColorAttr{colDefault, AttrUndefined}, Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined}, Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined},
ListFg: ColorAttr{colDefault, AttrUndefined},
ListBg: ColorAttr{colDefault, AttrUndefined},
SelectedFg: ColorAttr{colDefault, AttrUndefined}, SelectedFg: ColorAttr{colDefault, AttrUndefined},
SelectedBg: ColorAttr{colDefault, AttrUndefined}, SelectedBg: ColorAttr{colDefault, AttrUndefined},
SelectedMatch: ColorAttr{colDefault, AttrUndefined}, SelectedMatch: ColorAttr{colDefault, AttrUndefined},
@@ -684,8 +755,18 @@ func NoColorTheme() *ColorTheme {
PreviewBorder: ColorAttr{colDefault, AttrUndefined}, PreviewBorder: ColorAttr{colDefault, AttrUndefined},
PreviewScrollbar: ColorAttr{colDefault, AttrUndefined}, PreviewScrollbar: ColorAttr{colDefault, AttrUndefined},
PreviewLabel: ColorAttr{colDefault, AttrUndefined}, PreviewLabel: ColorAttr{colDefault, AttrUndefined},
ListLabel: ColorAttr{colDefault, AttrUndefined},
ListBorder: ColorAttr{colDefault, AttrUndefined},
Separator: ColorAttr{colDefault, AttrUndefined}, Separator: ColorAttr{colDefault, AttrUndefined},
Scrollbar: ColorAttr{colDefault, AttrUndefined}, Scrollbar: ColorAttr{colDefault, AttrUndefined},
InputBg: ColorAttr{colDefault, AttrUndefined},
InputBorder: ColorAttr{colDefault, AttrUndefined},
InputLabel: ColorAttr{colDefault, AttrUndefined},
HeaderBg: ColorAttr{colDefault, AttrUndefined},
HeaderBorder: ColorAttr{colDefault, AttrUndefined},
HeaderLabel: ColorAttr{colDefault, AttrUndefined},
GapLine: ColorAttr{colDefault, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined},
} }
} }
@@ -695,6 +776,8 @@ func init() {
Input: ColorAttr{colDefault, AttrUndefined}, Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined}, Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined},
ListFg: ColorAttr{colUndefined, AttrUndefined},
ListBg: ColorAttr{colUndefined, AttrUndefined},
SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined},
SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined},
SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
@@ -717,14 +800,23 @@ func init() {
PreviewBorder: ColorAttr{colUndefined, AttrUndefined}, PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined}, PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
PreviewLabel: ColorAttr{colUndefined, AttrUndefined}, PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
ListLabel: ColorAttr{colUndefined, AttrUndefined},
ListBorder: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined},
InputBg: ColorAttr{colUndefined, AttrUndefined},
InputBorder: ColorAttr{colUndefined, AttrUndefined},
InputLabel: ColorAttr{colUndefined, AttrUndefined},
GapLine: ColorAttr{colUndefined, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined},
} }
Dark256 = &ColorTheme{ Dark256 = &ColorTheme{
Colored: true, Colored: true,
Input: ColorAttr{colDefault, AttrUndefined}, Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined}, Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined},
ListFg: ColorAttr{colUndefined, AttrUndefined},
ListBg: ColorAttr{colUndefined, AttrUndefined},
SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined},
SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined},
SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
@@ -747,14 +839,23 @@ func init() {
PreviewBorder: ColorAttr{colUndefined, AttrUndefined}, PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined}, PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
PreviewLabel: ColorAttr{colUndefined, AttrUndefined}, PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
ListLabel: ColorAttr{colUndefined, AttrUndefined},
ListBorder: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined},
InputBg: ColorAttr{colUndefined, AttrUndefined},
InputBorder: ColorAttr{colUndefined, AttrUndefined},
InputLabel: ColorAttr{colUndefined, AttrUndefined},
GapLine: ColorAttr{colUndefined, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined},
} }
Light256 = &ColorTheme{ Light256 = &ColorTheme{
Colored: true, Colored: true,
Input: ColorAttr{colDefault, AttrUndefined}, Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined}, Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined},
ListFg: ColorAttr{colUndefined, AttrUndefined},
ListBg: ColorAttr{colUndefined, AttrUndefined},
SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined},
SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined},
SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
@@ -777,12 +878,22 @@ func init() {
PreviewBorder: ColorAttr{colUndefined, AttrUndefined}, PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined}, PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
PreviewLabel: ColorAttr{colUndefined, AttrUndefined}, PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
ListLabel: ColorAttr{colUndefined, AttrUndefined},
ListBorder: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined},
InputBg: ColorAttr{colUndefined, AttrUndefined},
InputBorder: ColorAttr{colUndefined, AttrUndefined},
InputLabel: ColorAttr{colUndefined, AttrUndefined},
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
GapLine: ColorAttr{colUndefined, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined},
} }
} }
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) { func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInputWindow bool, hasHeaderWindow bool) {
if forceBlack { if forceBlack {
theme.Bg = ColorAttr{colBlack, AttrUndefined} theme.Bg = ColorAttr{colBlack, AttrUndefined}
} }
@@ -803,7 +914,9 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg) theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
theme.Prompt = o(baseTheme.Prompt, theme.Prompt) theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
theme.Match = o(baseTheme.Match, theme.Match) theme.Match = o(baseTheme.Match, theme.Match)
theme.Current = o(baseTheme.Current, theme.Current) // Inherit from 'fg', so that we don't have to write 'current-fg:dim'
// e.g. fzf --delimiter / --nth -1 --color fg:dim,nth:regular
theme.Current = theme.Fg.Merge(o(baseTheme.Current, theme.Current))
theme.CurrentMatch = o(baseTheme.CurrentMatch, theme.CurrentMatch) theme.CurrentMatch = o(baseTheme.CurrentMatch, theme.CurrentMatch)
theme.Spinner = o(baseTheme.Spinner, theme.Spinner) theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
theme.Info = o(baseTheme.Info, theme.Info) theme.Info = o(baseTheme.Info, theme.Info)
@@ -813,9 +926,15 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
theme.Border = o(baseTheme.Border, theme.Border) theme.Border = o(baseTheme.Border, theme.Border)
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel) theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
undefined := NewColorAttr()
scrollbarDefined := theme.Scrollbar != undefined
previewBorderDefined := theme.PreviewBorder != undefined
// These colors are not defined in the base themes // These colors are not defined in the base themes
theme.SelectedFg = o(theme.Fg, theme.SelectedFg) theme.ListFg = o(theme.Fg, theme.ListFg)
theme.SelectedBg = o(theme.Bg, theme.SelectedBg) theme.ListBg = o(theme.Bg, theme.ListBg)
theme.SelectedFg = o(theme.ListFg, theme.SelectedFg)
theme.SelectedBg = o(theme.ListBg, theme.SelectedBg)
theme.SelectedMatch = o(theme.Match, theme.SelectedMatch) theme.SelectedMatch = o(theme.Match, theme.SelectedMatch)
theme.Disabled = o(theme.Input, theme.Disabled) theme.Disabled = o(theme.Input, theme.Disabled)
theme.Gutter = o(theme.DarkBg, theme.Gutter) theme.Gutter = o(theme.DarkBg, theme.Gutter)
@@ -823,9 +942,38 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
theme.PreviewBg = o(theme.Bg, theme.PreviewBg) theme.PreviewBg = o(theme.Bg, theme.PreviewBg)
theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel) theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel)
theme.PreviewBorder = o(theme.Border, theme.PreviewBorder) theme.PreviewBorder = o(theme.Border, theme.PreviewBorder)
theme.Separator = o(theme.Border, theme.Separator) theme.ListLabel = o(theme.BorderLabel, theme.ListLabel)
theme.Scrollbar = o(theme.Border, theme.Scrollbar) theme.ListBorder = o(theme.Border, theme.ListBorder)
theme.PreviewScrollbar = o(theme.PreviewBorder, theme.PreviewScrollbar) theme.Separator = o(theme.ListBorder, theme.Separator)
theme.Scrollbar = o(theme.ListBorder, theme.Scrollbar)
theme.GapLine = o(theme.ListBorder, theme.GapLine)
/*
--color list-border:green
--color scrollbar:red
--color scrollbar:red,list-border:green
--color scrollbar:red,preview-border:green
*/
if scrollbarDefined && !previewBorderDefined {
theme.PreviewScrollbar = o(theme.Scrollbar, theme.PreviewScrollbar)
} else {
theme.PreviewScrollbar = o(theme.PreviewBorder, theme.PreviewScrollbar)
}
if hasInputWindow {
theme.InputBg = o(theme.Bg, theme.InputBg)
} else {
// We shouldn't use input-bg if there's no separate input window
// e.g. fzf --color 'list-bg:green,input-bg:red' --no-input-border
theme.InputBg = o(theme.Bg, theme.ListBg)
}
theme.InputBorder = o(theme.Border, theme.InputBorder)
theme.InputLabel = o(theme.BorderLabel, theme.InputLabel)
if hasHeaderWindow {
theme.HeaderBg = o(theme.Bg, theme.HeaderBg)
} else {
theme.HeaderBg = o(theme.Bg, theme.ListBg)
}
theme.HeaderBorder = o(theme.Border, theme.HeaderBorder)
theme.HeaderLabel = o(theme.BorderLabel, theme.HeaderLabel)
initPalette(theme) initPalette(theme)
} }
@@ -837,19 +985,19 @@ func initPalette(theme *ColorTheme) {
} }
return ColorPair{fg.Color, bg.Color, fg.Attr} return ColorPair{fg.Color, bg.Color, fg.Attr}
} }
blank := theme.Fg blank := theme.ListFg
blank.Attr = AttrRegular blank.Attr = AttrRegular
ColPrompt = pair(theme.Prompt, theme.Bg) ColPrompt = pair(theme.Prompt, theme.InputBg)
ColNormal = pair(theme.Fg, theme.Bg) ColNormal = pair(theme.ListFg, theme.ListBg)
ColSelected = pair(theme.SelectedFg, theme.SelectedBg) ColSelected = pair(theme.SelectedFg, theme.SelectedBg)
ColInput = pair(theme.Input, theme.Bg) ColInput = pair(theme.Input, theme.InputBg)
ColDisabled = pair(theme.Disabled, theme.Bg) ColDisabled = pair(theme.Disabled, theme.ListBg)
ColMatch = pair(theme.Match, theme.Bg) ColMatch = pair(theme.Match, theme.ListBg)
ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg) ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg)
ColCursor = pair(theme.Cursor, theme.Gutter) ColCursor = pair(theme.Cursor, theme.Gutter)
ColCursorEmpty = pair(blank, theme.Gutter) ColCursorEmpty = pair(blank, theme.Gutter)
if theme.SelectedBg.Color != theme.Bg.Color { if theme.SelectedBg.Color != theme.ListBg.Color {
ColMarker = pair(theme.Marker, theme.SelectedBg) ColMarker = pair(theme.Marker, theme.SelectedBg)
} else { } else {
ColMarker = pair(theme.Marker, theme.Gutter) ColMarker = pair(theme.Marker, theme.Gutter)
@@ -860,11 +1008,11 @@ func initPalette(theme *ColorTheme) {
ColCurrentCursorEmpty = pair(blank, theme.DarkBg) ColCurrentCursorEmpty = pair(blank, theme.DarkBg)
ColCurrentMarker = pair(theme.Marker, theme.DarkBg) ColCurrentMarker = pair(theme.Marker, theme.DarkBg)
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg) ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.Bg) ColSpinner = pair(theme.Spinner, theme.InputBg)
ColInfo = pair(theme.Info, theme.Bg) ColInfo = pair(theme.Info, theme.InputBg)
ColHeader = pair(theme.Header, theme.Bg) ColSeparator = pair(theme.Separator, theme.InputBg)
ColSeparator = pair(theme.Separator, theme.Bg) ColScrollbar = pair(theme.Scrollbar, theme.ListBg)
ColScrollbar = pair(theme.Scrollbar, theme.Bg) ColGapLine = pair(theme.GapLine, theme.ListBg)
ColBorder = pair(theme.Border, theme.Bg) ColBorder = pair(theme.Border, theme.Bg)
ColBorderLabel = pair(theme.BorderLabel, theme.Bg) ColBorderLabel = pair(theme.BorderLabel, theme.Bg)
ColPreviewLabel = pair(theme.PreviewLabel, theme.PreviewBg) ColPreviewLabel = pair(theme.PreviewLabel, theme.PreviewBg)
@@ -872,6 +1020,13 @@ func initPalette(theme *ColorTheme) {
ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg) ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg)
ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg) ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)
ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg) ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg)
ColListLabel = pair(theme.ListLabel, theme.ListBg)
ColListBorder = pair(theme.ListBorder, theme.ListBg)
ColInputBorder = pair(theme.InputBorder, theme.InputBg)
ColInputLabel = pair(theme.InputLabel, theme.InputBg)
ColHeader = pair(theme.Header, theme.HeaderBg)
ColHeaderBorder = pair(theme.HeaderBorder, theme.HeaderBg)
ColHeaderLabel = pair(theme.HeaderLabel, theme.HeaderBg)
} }
func runeWidth(r rune) int { func runeWidth(r rune) int {

View File

@@ -306,5 +306,5 @@ func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWi
} }
} }
return wrapped, false return wrapped, overflow
} }

View File

@@ -1442,10 +1442,14 @@ class TestGoFZF < TestBase
writelines(['=' * 10_000 + '0123456789']) writelines(['=' * 10_000 + '0123456789'])
[0, 3, 6].each do |off| [0, 3, 6].each do |off|
tmux.prepare tmux.prepare
tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 < #{tempname}", :Enter tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 --bind space:toggle-hscroll < #{tempname}", :Enter
tmux.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + '··') } tmux.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + '··') }
tmux.send_keys '9' tmux.send_keys '9'
tmux.until { |lines| assert lines[-3]&.end_with?('789') } tmux.until { |lines| assert lines[-3]&.end_with?('789') }
tmux.send_keys :Space
tmux.until { |lines| assert lines[-3]&.end_with?('=··') }
tmux.send_keys :Space
tmux.until { |lines| assert lines[-3]&.end_with?('789') }
tmux.send_keys :Enter tmux.send_keys :Enter
end end
end end
@@ -2133,7 +2137,11 @@ class TestGoFZF < TestBase
end end
def test_keep_right def test_keep_right
tmux.send_keys "seq 10000 | #{FZF} --read0 --keep-right --no-multi-line", :Enter tmux.send_keys "seq 10000 | #{FZF} --read0 --keep-right --no-multi-line --bind space:toggle-multi-line", :Enter
tmux.until { |lines| assert lines.any_include?('9999␊10000') }
tmux.send_keys :Space
tmux.until { |lines| assert lines.any_include?('> 1') }
tmux.send_keys :Space
tmux.until { |lines| assert lines.any_include?('9999␊10000') } tmux.until { |lines| assert lines.any_include?('9999␊10000') }
end end
@@ -2638,7 +2646,7 @@ class TestGoFZF < TestBase
end end
def test_change_preview_window def test_change_preview_window
tmux.send_keys "seq 1000 | #{FZF} --preview 'echo [[{}]]' --preview-window border-none --bind '" \ tmux.send_keys "seq 1000 | #{FZF} --preview 'echo [[{}]]' --no-preview-border --bind '" \
'a:change-preview(echo __{}__),' \ 'a:change-preview(echo __{}__),' \
'b:change-preview-window(down)+change-preview(echo =={}==)+change-preview-window(up),' \ 'b:change-preview-window(down)+change-preview(echo =={}==)+change-preview-window(up),' \
'c:change-preview(),d:change-preview-window(hidden),' \ 'c:change-preview(),d:change-preview-window(hidden),' \
@@ -2814,13 +2822,13 @@ class TestGoFZF < TestBase
tmux.send_keys "seq 3 | fzf --height ~100% --border=vertical --preview 'seq {}' --preview-window left,5,border-right --padding 1 --exit-0 --header $'hello\\nworld' --header-lines=2", :Enter tmux.send_keys "seq 3 | fzf --height ~100% --border=vertical --preview 'seq {}' --preview-window left,5,border-right --padding 1 --exit-0 --header $'hello\\nworld' --header-lines=2", :Enter
expected = <<~OUTPUT expected = <<~OUTPUT
1 > 3 1 > 3
2 2 2 2
3 1 3 1
hello hello
world world
1/1 1/1
> >
OUTPUT OUTPUT
tmux.until { assert_block(expected, _1) } tmux.until { assert_block(expected, _1) }
@@ -3073,6 +3081,21 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_includes lines, '/1/1/' } tmux.until { |lines| assert_includes lines, '/1/1/' }
end end
def test_alternative_preview_window_opts
tmux.send_keys "seq 10 | #{FZF} --preview-window '~5,2,+0,<100000(~0,+100,wrap,noinfo)' --preview 'seq 1000'", :Enter
tmux.until { |lines| assert_equal 10, lines.item_count }
tmux.until do |lines|
assert_equal ['╭────╮', '│ 10 │', '│ 0 │', '│ 10 │', '│ 1 │'], lines.take(5).map(&:strip)
end
end
def test_preview_window_width_exception
tmux.send_keys "seq 10 | #{FZF} --scrollbar --preview-window border-left --border --preview 'seq 1000'", :Enter
tmux.until do |lines|
assert lines[1]&.end_with?(' 1/1000││')
end
end
def test_become def test_become
tmux.send_keys "seq 100 | #{FZF} --bind 'enter:become:seq {} | #{FZF}'", :Enter tmux.send_keys "seq 100 | #{FZF} --bind 'enter:become:seq {} | #{FZF}'", :Enter
tmux.until { |lines| assert_equal 100, lines.item_count } tmux.until { |lines| assert_equal 100, lines.item_count }
@@ -3400,32 +3423,363 @@ class TestGoFZF < TestBase
> >
100/100 100/100
> 1 > 1
2 2
3 3
4 4
BLOCK BLOCK
tmux.until { assert_block(block, _1) } tmux.until { assert_block(block, _1) }
end end
def test_gap_2 def test_gap_2
tmux.send_keys %(seq 100 | #{FZF} --gap=2 --border --reverse), :Enter tmux.send_keys %(seq 100 | #{FZF} --gap=2 --gap-line xyz --border --reverse), :Enter
block = <<~BLOCK block = <<~BLOCK
> >
100/100 100/100
> 1 > 1
xyzxyzxyzxyzxy
2 2
xyzxyzxyzxyzxy
3 3
BLOCK BLOCK
tmux.until { assert_block(block, _1) } tmux.until { assert_block(block, _1) }
end end
def test_list_border_and_label
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --query 1 --padding 1,2), :Enter
block = <<~BLOCK
11
> 10
3
2
1
19/97
> 1
list
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_input_border_and_label
tmux.send_keys %(seq 100 | #{FZF} --border rounded --input-border bold --input-label input --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2), :Enter
block = <<~BLOCK
11
> 10
3
2
1
input
19/97
> 1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_input_border_and_label_header_first
tmux.send_keys %(seq 100 | #{FZF} --border rounded --input-border bold --input-label input --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 --header-first), :Enter
block = <<~BLOCK
11
> 10
input
19/97
> 1
3
2
1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_list_input_border_and_label
tmux.send_keys %(
seq 100 | #{FZF} --border rounded --list-border double --input-border bold --list-label-pos 2:bottom --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 \
--bind 'start:transform-input-label(echo INPUT)+transform-list-label(echo LIST)' \
--bind 'space:change-input-label( input )+change-list-label( list )'
).strip, :Enter
block = <<~BLOCK
11
> 10
LIST
3
2
1
INPUT
19/97
> 1
BLOCK
tmux.until { assert_block(block, _1) }
tmux.send_keys :Space
block = <<~BLOCK
11
> 10
list
3
2
1
input
19/97
> 1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_list_input_border_and_label_header_first
tmux.send_keys %(
seq 100 | #{FZF} --border rounded --list-border double --input-border bold --list-label-pos 2:bottom --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 \
--bind 'start:transform-input-label(echo INPUT)+transform-list-label(echo LIST)' \
--bind 'space:change-input-label( input )+change-list-label( list )' --header-first
).strip, :Enter
block = <<~BLOCK
11
> 10
LIST
INPUT
19/97
> 1
3
2
1
BLOCK
tmux.until { assert_block(block, _1) }
tmux.send_keys :Space
block = <<~BLOCK
11
> 10
list
input
19/97
> 1
3
2
1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_header_border_and_label
tmux.send_keys %(seq 100 | #{FZF} --border rounded --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2), :Enter
block = <<~BLOCK
12
11
> 10
3
2
1
header
19/97
> 1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_header_border_and_label_header_first
tmux.send_keys %(seq 100 | #{FZF} --border rounded --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --header-first), :Enter
block = <<~BLOCK
12
11
> 10
19/97
> 1
3
2
1
header
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_header_border_and_label_with_list_border
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2), :Enter
block = <<~BLOCK
12
11
> 10
list
3
2
1
header
19/97
> 1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_header_border_and_label_with_list_border_header_first
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --header-first), :Enter
block = <<~BLOCK
12
11
> 10
list
19/97
> 1
3
2
1
header
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_all_borders
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --input-border bold --input-label input --input-label-pos 2:bottom), :Enter
block = <<~BLOCK
12
11
> 10
list
3
2
1
header
19/97
> 1
input
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_all_borders_header_first
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --input-border bold --input-label input --input-label-pos 2:bottom --header-first), :Enter
block = <<~BLOCK
12
11
> 10
list
19/97
> 1
input
3
2
1
header
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_style_full_adaptive_height
tmux.send_keys %(seq 1| #{FZF} --style=full --height=~100% --header-lines=1 --info=default), :Enter
block = <<~BLOCK
1
0/0
>
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_style_full_adaptive_height_double
tmux.send_keys %(seq 1| #{FZF} --style=full:double --border --height=~100% --header-lines=1 --info=default), :Enter
block = <<~BLOCK
1
0/0
>
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_change_nth
input = [
*[''] * 1000,
'foo bar bar bar bar',
'foo foo bar bar bar',
'foo foo foo bar bar',
'foo foo foo foo bar',
*[''] * 1000
]
writelines(input)
nths = '1,2..4,-1,-3..,..2'
tmux.send_keys %(#{FZF} -qfoo -n#{nths} --bind 'space:change-nth(2|3|4|5|),result:transform-prompt:echo "[$FZF_NTH] "' < #{tempname}), :Enter
tmux.until do |lines|
assert lines.any_include?("[#{nths}] foo")
assert_equal 4, lines.match_count
end
tmux.send_keys :Space
tmux.until do |lines|
assert lines.any_include?('[2] foo')
assert_equal 3, lines.match_count
end
tmux.send_keys :Space
tmux.until do |lines|
assert lines.any_include?('[3] foo')
assert_equal 2, lines.match_count
end
tmux.send_keys :Space
tmux.until do |lines|
assert lines.any_include?('[4] foo')
assert_equal 1, lines.match_count
end
tmux.send_keys :Space
tmux.until do |lines|
assert lines.any_include?('[5] foo')
assert_equal 0, lines.match_count
end
tmux.send_keys :Space
tmux.until do |lines|
assert lines.any_include?("[#{nths}] foo")
assert_equal 4, lines.match_count
end
end
end end
module TestShell module TestShell
@@ -3737,6 +4091,23 @@ module CompletionTest
tmux.until { |lines| assert_equal 'unset FZFFOOBAR', lines[-1] } tmux.until { |lines| assert_equal 'unset FZFFOOBAR', lines[-1] }
end end
def test_completion_in_command_sequence
tmux.send_keys 'export FZFFOOBAR=BAZ', :Enter
tmux.prepare
triggers = ['**', '~~', '++', 'ff', '/']
triggers.concat(['&', '[', ';', '`']) if instance_of?(TestZsh)
triggers.each do |trigger|
set_var('FZF_COMPLETION_TRIGGER', trigger)
command = "echo foo; QUX=THUD unset FZFFOOBR#{trigger}"
tmux.send_keys command.sub(/(;|`)$/, '\\\\\1'), :Tab
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :Enter
tmux.until { |lines| assert_equal 'echo foo; QUX=THUD unset FZFFOOBAR', lines[-1] }
end
end
def test_file_completion_unicode def test_file_completion_unicode
FileUtils.mkdir_p('/tmp/fzf-test') FileUtils.mkdir_p('/tmp/fzf-test')
tmux.paste "cd /tmp/fzf-test; echo test3 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2701'; echo test4 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2702'" tmux.paste "cd /tmp/fzf-test; echo test3 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2701'; echo test4 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2702'"