Skip to content

fish: Fixes and improvements to CTRL-R#4703

Merged
junegunn merged 1 commit into
junegunn:develfrom
bitraid:fish-hist
Mar 7, 2026
Merged

fish: Fixes and improvements to CTRL-R#4703
junegunn merged 1 commit into
junegunn:develfrom
bitraid:fish-hist

Conversation

@bitraid
Copy link
Copy Markdown
Collaborator

@bitraid bitraid commented Mar 4, 2026

A few more changes to command history:

  • SHIFT-DELETE:
    • Improve performance by only calling history delete once and passing the selected items via command substitution.
  • Preview window:
    • Handle very long lists of selected commands (using temporary files instead of command arguments).
    • Reformat only the highlighted command. Selected commands are only indented and colorized, so that they match how they are inserted by pressing ENTER (instead of ALT-ENTER).
    • Include trailing whitespace characters, so commands that end with \␊ don't lose the trailing newline and cause a following selected command to be shown as a continuation of the previous one.
    • Also bind the check of whether to display the preview to multi, so it will be triggered when selecting the very last command (which is not triggered by focus).
  • Prefix of commands list:
    • Enable changing the timestamp to date/time, or disabling it completely, by pressing ALT-T (uses the new change-with-nth option).
    • Remove the dependence of the external date command for conversion.
    • Set the color to $fish_color_comment, so it is easier distinguished from the actual command.
  • Command insertion:
    • Fix an issue with commands that end with \␊: accept-nth strips the trailing newline, but it is important to keep it when inserting multiple commands. Consider the following example: The commands rm -rf ~/trash \␊ (1) and ls ~/ (2) are selected for insertion. What will be inserted instead, is a single rm -rf command with a line break after ~/trash and the last token being /~ (will wipe the home directory!). For that reason, a become action is bind to ENTER, that reads the selected commands from a temporary file (for not hitting the single argument length limit) created with the s flag, and then having to manually delete the file (since it's not possible for fzf to remove the file). This is unnecessary for most cases though, and it would be much better if accept-nth (and maybe also with-nth) had a flag to keep trailing whitespace characters. Let me know what you think.
  • Update README:
    • Inform about the ability to select multiple commands.
    • Document the key bindings which are unique to fish shell.
    • Add examples of how to change the default prefix through $FZF_CTRL_R_OPTS.

While working on this, I noticed a couple of issues that I think are worth mentioning:

  • The become command may fail to execute and won't return to the shell prompt: Because the command is passed to the shell as a single argument, it is possible to exceed the 128K limit of MAX_ARG_STRLEN. This is not so serious for preview or transform (will return argument list too long error), but for become the shell will be left in a broken state. This doesn't affect the script, but maybe fzf should not attempt to execute the command if its final total length is longer than 128 * 1024 - 1 characters.
  • Scrolling in preview window with enabled wrap/wrap-word, does not happen line by line for wrapped lines. This means that it's not possible to scroll a long line that doesn't completely fit:
echo (string repeat -n 400 'test ') eol | fzf --preview="echo {}" --preview-window=wrap-word --height=8
                                                ╭─────────────────────────────────────────────╮
                                                │ test test test test test test test test     │
                                                │ ↳ test test test test test test test test   │
                                                │ ↳ test test test test test test test test   │
                                                │ ↳ test test test test test test test test   │
▌ test test test test test test test test tes·· │ ↳ test test test test test test test test   │
  1/1 ───────────────────────────────────────── │ ↳ test test test test test test test test   │
>                                               ╰─────────────────────────────────────────────╯

If follow is also set, it scrolls too far down and no part of the line is visible (notice the info shows 2/1).

echo (string repeat -n 400 'test ') eol | fzf --preview="echo {}" --preview-window=wrap-word,follow --height=8
                                                ╭─────────────────────────────────────────────╮
                                                │                                         2/1 │
                                                │                                             │
                                                │                                             │
                                                │                                             │
▌ test test test test test test test test tes·· │                                             │
  1/1 ───────────────────────────────────────── │                                             │
>                                               ╰─────────────────────────────────────────────╯

Scrolling up, will scroll to the beginning of the line, and the ability to scroll again is lost (it's not possible to view the end of the line).

@github-actions github-actions Bot added docs Documentation shell Shell scripts fish Fish shell labels Mar 4, 2026
@junegunn junegunn added the enhancement Enhancement to existing feature label Mar 5, 2026
@bitraid
Copy link
Copy Markdown
Collaborator Author

bitraid commented Mar 5, 2026

I was planning to also add --freeze-left=1 but I forgot. If you agree that this is a good default, let me know and I will update the commit.

EDIT: This will add extra space between the prefix and the command, though. There is also an issue when wrapping is enabled by pressing CTRL-/. Run the following command and press <ctrl-/>:

echo 1\tfoo\n2\t(string repeat -n 100 'bar ') | fzf --freeze-left=1 --no-wrap
▌ 2       bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar
▌ ↳ bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar
▌ ↳ bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar
▌ ↳ bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar
▌ ↳ bar bar bar bar bar bar bar bar bar bar bar bar bar
▌ 1        foo
  2/2 ────────────────────────────────────────────────────────────────────────────────────────
>

The space between 2 and bar has decreased, but it hasn't for 1 and foo. If the focus changes, it also decreases for foo.

@junegunn
Copy link
Copy Markdown
Owner

junegunn commented Mar 5, 2026

Thanks a lot! This looks like a solid improvement. change-with-nth is a nice touch. Let me test it a bit more.

One question: Did you intend to color the first word of a command entry red? Should we append (set_color normal) at the end of --show-time?

  • it is possible to exceed the 128K limit

So I see you appropriately adoped the f flag here.

maybe fzf should not attempt to execute the command

I think it's a good suggestion, though I wonder how we should notify the user when that happens.

  • Scrolling in preview window with enabled wrap/wrap-word, does not happen line by line for wrapped lines.

This was by design to simplify the implementation. Being able to scroll into the middle of a wrapped line brings complexity. For example, we would have to recalculate the display offset whenever the window is resized. Vim has the same limitation, so I thought it would be acceptable for a "preview" window.

If follow is also set, it scrolls too far down and no part of the line is visible (notice the info shows 2/1).

Ah, it's a bug. Thanks. I'll look into it.

The space between 2 and bar has decreased

Another bug! Thanks!

I was planning to also add --freeze-left=1 but I forgot. If you agree that this is a good default, let me know and I will update the commit.

Sounds like a plan.

junegunn added a commit that referenced this pull request Mar 6, 2026
@bitraid
Copy link
Copy Markdown
Collaborator Author

bitraid commented Mar 6, 2026

One question: Did you intend to color the first word of a command entry red? Should we append (set_color normal) at the end of --show-time?

This didn't happen for me - $fish_color_command was successfully applied to the command names, but I was not using the default theme colors. Apparently this only happens with the default theme. I tested many other themes by running fish_config theme choose, and they all seem to work as expected. I don't really know why the command color isn't applied for the default theme, but I'm glad we found out so we can fix it. I will update the commit.

@bitraid bitraid force-pushed the fish-hist branch 2 times, most recently from 02892b8 to 041578a Compare March 6, 2026 10:19
junegunn added a commit that referenced this pull request Mar 6, 2026
Fixes bugs reported in #4703:

* Clamp followOffset return value to avoid going past the end of lines
* Account for t.previewed.filled when determining scrollability
@junegunn
Copy link
Copy Markdown
Owner

junegunn commented Mar 6, 2026

Apparently this only happens with the default theme.

I see, I'm not a fish user, so fortunately I had zero configuration :)

The two commits I pushed to master should fix the issues you reported. Please take a look.

@bitraid
Copy link
Copy Markdown
Collaborator Author

bitraid commented Mar 6, 2026

The two commits I pushed to master should fix the issues you reported. Please take a look.

I tested them and they work great. Thanks!

@junegunn
Copy link
Copy Markdown
Owner

junegunn commented Mar 6, 2026

Test failure seems unrelated. Let me see.

Confirmed unrelated. It can fail on small screens. Let me fix the test case on master.

@junegunn
Copy link
Copy Markdown
Owner

junegunn commented Mar 6, 2026

  • it would be much better if accept-nth (and maybe also with-nth) had a flag to keep trailing whitespace characters.

Hmm, what do you think about making \n an exception?

diff --git a/src/tokenizer.go b/src/tokenizer.go
index 25448f59..a143636c 100644
--- a/src/tokenizer.go
+++ b/src/tokenizer.go
@@ -161,7 +161,7 @@ func awkTokenizer(input string) ([]string, int) {
 	end := 0
 	for idx := 0; idx < len(input); idx++ {
 		r := input[idx]
-		white := r == 9 || r == 32
+		white := unicode.IsSpace(rune(r))
 		switch state {
 		case awkNil:
 			if white {
@@ -218,6 +218,10 @@ func Tokenize(text string, delimiter Delimiter) []Token {
 	return withPrefixLengths(tokens, 0)
 }
 
+func isSpace(r rune) bool {
+	return r != '\n' && unicode.IsSpace(r)
+}
+
 // StripLastDelimiter removes the trailing delimiter and whitespaces
 func StripLastDelimiter(str string, delimiter Delimiter) string {
 	if delimiter.str != nil {
@@ -231,7 +235,7 @@ func StripLastDelimiter(str string, delimiter Delimiter) string {
 			}
 		}
 	}
-	return strings.TrimRightFunc(str, unicode.IsSpace)
+	return strings.TrimRightFunc(str, isSpace)
 }
 
 func GetLastDelimiter(str string, delimiter Delimiter) string {

It won't affect most use cases. \n can only appear in items when --read0 is specified, and maybe it's not right to treat it as an ordinary white space in that case. But I'm not sure.

EDIT: Well, there can be cases where this is not desirable.

# A new line character after the hash used to be stripped
git log -z --format='%H%n%s%n%b' | fzf --reverse --read0 --gap --highlight-line --with-nth '{1} => {2..}'

@bitraid
Copy link
Copy Markdown
Collaborator Author

bitraid commented Mar 6, 2026

Hmm, what do you think about making \n an exception?

That would work for the script. But it couldn't be used for other cases, for example a field containing filenames, because they could have trailing spaces.

It won't affect most use cases. \n can only appear in items when --read0 is specified, and maybe it's not right to treat it as an ordinary white space in that case. But I'm not sure.

EDIT: Well, there can be cases where this is not desirable.

I think that it would make sense to keep it when --print0 is set. Trailing whitespace can always be trimmed afterwards, but it can't be inserted it isn't known it is missing.

@junegunn
Copy link
Copy Markdown
Owner

junegunn commented Mar 7, 2026

Thanks for the input. Let me think a bit more about it.

That said, I think this PR is great and already ready as it is. But since it's mentioning the new feature on the README page, we may delay merging it, or merge it to the devel branch instead, so that they're not exposed to the public before a new release.

@junegunn
Copy link
Copy Markdown
Owner

junegunn commented Mar 7, 2026

How about this approach: when --delimiter is explicitly given, like in this case, only strip the delimiter itself, not trailing whitespace. Only in the default AWK mode (no delimiter) should trailing whitespace be stripped, since whitespace IS the delimiter in that mode. With a custom delimiter, whitespace is a part of the data; stripping it is an assumption we probably shouldn't make.

This actually aligns better with the current man page description of --accept-nth option:

--accept-nth=N[,..] or TEMPLATE
      Define which fields to print on accept. The last delimiter is stripped
      from the output. For advanced transformation, you can provide a template
      containing field index expressions in curly braces. When you use
      a template, the trailing delimiter is stripped from each expression,
      giving you more control over the output. {n} in template evaluates to the
      zero-based ordinal index of the line.

However, while this sounds reasonable, it breaks several contracts:

  1) Failure:
TestCore#test_accept_nth_template [test/test_core.rb:2126]:
--- expected
+++ actual
@@ -1 +1 @@
-["[0] 1st: foo, 3rd: baz, 2nd: bar"]
+["[0] 1st: foo  , 3rd: baz, 2nd: bar"]


  2) Failure:
TestCore#test_accept_nth_regex_delimiter [test/test_core.rb:2108]:
Expected: ["bar,bar,foo  :,:bazfoo"]
  Actual: ["bar,bar,foo  :,:bazfoo  "]

  3) Failure:
TestCore#test_accept_nth_string_delimiter [test/test_core.rb:2099]:
Expected: ["bar,bar,foo  ,bazfoo"]
  Actual: ["bar,bar,foo  ,bazfoo  "]

424 runs, 5063 assertions, 3 failures, 0 errors, 6 skips

I'm not sure how may users rely on the current behavior though.

# Changed behavior
echo 'foo,  bar , baz' | fzf --delimiter , --with-nth '{2} // {1} // {3} // {1}'

# A potential workaround
echo 'foo,  bar , baz' | fzf --delimiter "\s*,\s*" --with-nth '{2} // {1} // {3} // {1}'

@junegunn
Copy link
Copy Markdown
Owner

junegunn commented Mar 7, 2026

I decided to go that route. I think you can now simplify that part by rebasing onto the latest master.

Fixes:
- Commands with trailing newlines
- Very long previews

Improvements:
- SHIFT-DELETE performance
- Bind ALT-T: cycle command prefix (timestamp, date/time, none)
- Set comment color for prefix
@bitraid bitraid changed the base branch from master to devel March 7, 2026 10:32
@bitraid
Copy link
Copy Markdown
Collaborator Author

bitraid commented Mar 7, 2026

Thanks, I made the final change. I also switched the merge branch to devel so it can be merged before the release, and I can start working on a PR for the SHIFT-TAB rewrite (which also modifies key-bindings.fish because common.fish will no longer be used).

@junegunn junegunn merged commit 47f4e0a into junegunn:devel Mar 7, 2026
6 checks passed
@junegunn
Copy link
Copy Markdown
Owner

junegunn commented Mar 7, 2026

Thanks for the great work!

@bitraid bitraid deleted the fish-hist branch March 7, 2026 13:15
junegunn pushed a commit that referenced this pull request Mar 8, 2026
Fixes:
- Commands with trailing newlines
- Very long previews

Improvements:
- SHIFT-DELETE performance
- Bind ALT-T: cycle command prefix (timestamp, date/time, none)
- Set comment color for prefix
junegunn pushed a commit that referenced this pull request Mar 22, 2026
Fixes:
- Commands with trailing newlines
- Very long previews

Improvements:
- SHIFT-DELETE performance
- Bind ALT-T: cycle command prefix (timestamp, date/time, none)
- Set comment color for prefix
junegunn pushed a commit that referenced this pull request Mar 22, 2026
Fixes:
- Commands with trailing newlines
- Very long previews

Improvements:
- SHIFT-DELETE performance
- Bind ALT-T: cycle command prefix (timestamp, date/time, none)
- Set comment color for prefix
junegunn pushed a commit that referenced this pull request Apr 4, 2026
Fixes:
- Commands with trailing newlines
- Very long previews

Improvements:
- SHIFT-DELETE performance
- Bind ALT-T: cycle command prefix (timestamp, date/time, none)
- Set comment color for prefix
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Apr 8, 2026
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [junegunn/fzf](https://github.com/junegunn/fzf) | minor | `v0.70.0` → `v0.71.0` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>junegunn/fzf (junegunn/fzf)</summary>

### [`v0.71.0`](https://github.com/junegunn/fzf/releases/tag/v0.71.0): 0.71.0

[Compare Source](junegunn/fzf@v0.70.0...v0.71.0)

*Release highlights: <https://junegunn.github.io/fzf/releases/0.71.0/>*

- Added `--popup` as a new name for `--tmux` with Zellij support
  - `--popup` starts fzf in a tmux popup or a Zellij floating pane
  - `--tmux` is now an alias for `--popup`
  - Requires tmux 3.3+ or Zellij 0.44+
- Cross-reload item identity with `--id-nth`
  - Added `--id-nth=NTH` to define item identity fields for cross-reload operations
  - When a `reload` is triggered with tracking enabled, fzf searches for the tracked item by its identity fields in the new list.
    - `--track --id-nth ..` tracks by the entire line
    - `--track --id-nth 1` tracks by the first field
    - `--track` without `--id-nth` retains the existing index-based tracking behavior
    - The UI is temporarily blocked (prompt dimmed, input disabled) until the item is found or loading completes.
      - Press `Escape` or `Ctrl-C` to cancel the blocked state without quitting
      - Info line shows `+T*` / `+t*` while searching
  - With `--multi`, selected items are preserved across `reload-sync` by matching their identity fields
- Performance improvements
  - The search performance now scales linearly with the number of CPU cores, as we dropped static partitioning to allow better load balancing across threads.
    ```
    === query: 'linux' ===
      [all]   baseline:    21.95ms  current:    17.47ms  (1.26x)  matches: 179966 (12.79%)
      [1T]    baseline:   179.63ms  current:   180.53ms  (1.00x)  matches: 179966 (12.79%)
      [2T]    baseline:    97.38ms  current:    90.05ms  (1.08x)  matches: 179966 (12.79%)
      [4T]    baseline:    53.83ms  current:    44.77ms  (1.20x)  matches: 179966 (12.79%)
      [8T]    baseline:    41.66ms  current:    22.58ms  (1.84x)  matches: 179966 (12.79%)
    ```
  - Improved the cache structure, reducing memory footprint per entry by 86x.
    - With the reduced per-entry cost, the cache now has broader coverage.
- Shell integration improvements
  - bash: CTRL-R now supports multi-select and `shift-delete` to delete history entries ([#&#8203;4715](junegunn/fzf#4715))
  - fish:
    - Improved command history (CTRL-R) ([#&#8203;4703](junegunn/fzf#4703)) ([@&#8203;bitraid](https://github.com/bitraid))
    - Rewrite completion script (SHIFT-TAB) ([#&#8203;4731](junegunn/fzf#4731)) ([@&#8203;bitraid](https://github.com/bitraid))
    - Increase minimum fish version requirement to 3.4.0 ([#&#8203;4731](junegunn/fzf#4731)) ([@&#8203;bitraid](https://github.com/bitraid))
- `GET /` HTTP endpoint now includes `positions` field in each match entry, providing the indices of matched characters for external highlighting ([#&#8203;4726](junegunn/fzf#4726))
- Allow adaptive height with negative value (`--height=~-HEIGHT`) ([#&#8203;4682](junegunn/fzf#4682))
- Bug fixes
  - `--walker=follow` no longer follows symlinks whose target is an ancestor of the walker root, avoiding severe resource exhaustion when a symlink points outside the tree (e.g. Wine's `z:` → `/`) ([#&#8203;4710](junegunn/fzf#4710))
  - Fixed AWK tokenizer not treating a new line character as whitespace
  - Fixed `--{accept,with}-nth` removing trailing whitespaces with a non-default `--delimiter`
  - Fixed OSC8 hyperlinks being mangled when the URL contains unicode characters ([#&#8203;4707](junegunn/fzf#4707))
  - Fixed `--with-shell` not handling quoted arguments correctly ([#&#8203;4709](junegunn/fzf#4709))
  - Fixed child processes not being terminated on Windows ([#&#8203;4723](junegunn/fzf#4723)) ([@&#8203;pjeby](https://github.com/pjeby))
  - Fixed preview scrollbar not rendered after `toggle-preview`
  - Fixed preview follow/scroll with long wrapped lines
  - Fixed tab width when `--frozen-left` is used
  - Fixed preview mouse events being processed when no preview window exists
  - zsh: Fixed history widget when `sh_glob` option is on ([#&#8203;4714](junegunn/fzf#4714)) ([@&#8203;EvanHahn](https://github.com/EvanHahn))

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDQuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwNC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiLCJhdXRvbWF0aW9uOmJvdC1hdXRob3JlZCIsImRlcGVuZGVuY3ktdHlwZTo6bWlub3IiXX0=-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation enhancement Enhancement to existing feature fish Fish shell shell Shell scripts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants