Compare commits

...

16 Commits

Author SHA1 Message Date
Junegunn Choi f5fbfd848e Let bw theme inherit overridden colors
- Mark derived color slots undefined so fg/bg/list-bg propagate
- Add missing Footer slot (was rendering black)
2026-06-07 18:12:19 +09:00
Junegunn Choi dea72834ed Keep base fg/bg when resolving colors in bw theme
Trailing prompt space and other base-colored segments no longer reset to terminal default
2026-06-07 18:12:19 +09:00
LangLangBart abee152255 test: ALT-C regression tests
discussed in https://github.com/junegunn/fzf/issues/4816
2026-06-05 16:24:27 +09:00
LangLangBart bf114bcc21 test: install nushell in Dockerfile
ref: https://docs.docker.com/reference/dockerfile/#here-documents
2026-06-05 16:24:27 +09:00
Junegunn Choi 838ac7554b make lint 2026-06-02 20:23:23 +09:00
Junegunn Choi ae78a5c56d Allow bare put action in transform output
transform/bg-transform now permit bare `put`, inserting the key that
triggered the action (`a:transform:echo put` puts `a`).
2026-06-02 20:21:04 +09:00
Yi-Yo Chiang 7d647c70c2 [shell][zsh] Don't resolve symlinks in ALT-c (#4816)
This way ALT-c behaves more aligned with `cd`.

Imagine a setup like:
```
/foo -> foo_real
/foo_real/bar
```

Right now if we first `cd foo` (a symlink to `foo_real`), and
then use ALT-c to goto `bar`, then we would end up executing
`cd /foo_real/bar` instead of `cd /foo/bar`. `$PWD = /foo_real/bar`.

For comparison, if we first `cd foo` and then `cd bar`, we end up with
`$PWD = /foo/bar`.

This commit changes the internal logic of `fzf-cd-widget` to first run
`cd <result of FZF_ALT_C_COMMAND>` in a subshell to simulate the
behavior of `cd`, and then insert the target PWD into the shell history.
This way we get behavior consistent with the builtin `cd` command, while
also recording reusable shell history.
2026-05-31 12:38:12 +09:00
Junegunn Choi 6bd17f8f9a Revert "[shell][zsh] Don't resolve symlinks in ALT-c (#4816) (#4817)"
This reverts commit 249a6df4a4.
2026-05-31 12:37:05 +09:00
Junegunn Choi 249a6df4a4 [shell][zsh] Don't resolve symlinks in ALT-c (#4816) (#4817)
This way ALT-c behaves more aligned with `cd`.

Imagine a setup like:
```
/foo -> foo_real
/foo_real/bar
```

Right now if we first `cd foo` (a symlink to `foo_real`), and
then use ALT-c to goto `bar`, then we would end up executing
`cd /foo_real/bar` instead of `cd /foo/bar`. `$PWD = /foo_real/bar`.

For comparison, if we first `cd foo` and then `cd bar`, we end up with
`$PWD = /foo/bar`.

This commit changes the internal logic of `fzf-cd-widget` to first run
`cd <result of FZF_ALT_C_COMMAND>` in a subshell to simulate the
behavior of `cd`, and then insert the target PWD into the shell history.
This way we get behavior consistent with the builtin `cd` command, while
also recording reusable shell history.

Co-authored-by: Yi-Yo Chiang <5255547+silverneko@users.noreply.github.com>

Close #4816
2026-05-31 12:34:58 +09:00
Junegunn Choi a50619388d [install] Fix empty-shell detection in install script
${#shells} is the string length, not the shell count.

Thanks to @matheus-pacifico for the report.

Close #4813
2026-05-28 23:02:34 +09:00
Copilot 5ef8dea36e Prevent nushell source contamination in install shell loop (#4812) 2026-05-28 10:26:43 +09:00
Junegunn Choi 845752f305 Update README 2026-05-25 22:28:11 +09:00
Junegunn Choi 9a61a1457d Bump action versions for Node.js 24 support 2026-05-25 21:11:25 +09:00
Junegunn Choi dfcacb443d Allow manual dispatch of Winget workflow 2026-05-25 21:09:25 +09:00
Junegunn Choi 5412f39b84 Use PAT in release workflow
Releases created with the default GITHUB_TOKEN do not trigger other
workflows (anti-recursion). Winget workflow therefore did not fire
on v0.73.1. Switch to RELEASE_PAT (registered in the `release`
environment) so the release is authored by the user.
2026-05-25 21:08:48 +09:00
Junegunn Choi 07c5cd4185 Fix typo in CHANGELOG 2026-05-25 14:39:05 +09:00
16 changed files with 166 additions and 69 deletions
+4 -4
View File
@@ -19,11 +19,11 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
environment: release environment: release
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-go@v5 - uses: actions/setup-go@v6
with: with:
go-version: stable go-version: stable
@@ -60,7 +60,7 @@ jobs:
| tee tmp/release-note | tee tmp/release-note
- name: Run goreleaser - name: Run goreleaser
uses: goreleaser/goreleaser-action@v6 uses: goreleaser/goreleaser-action@v7
with: with:
version: latest version: latest
args: >- args: >-
@@ -68,7 +68,7 @@ jobs:
&& 'release --clean --release-notes tmp/release-note' && 'release --clean --release-notes tmp/release-note'
|| 'release --snapshot --clean --skip=publish' }} || 'release --snapshot --clean --skip=publish' }}
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.RELEASE_PAT }}
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }} MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }} MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
+7
View File
@@ -2,6 +2,12 @@ name: Publish to Winget
on: on:
release: release:
types: [released] types: [released]
workflow_dispatch:
inputs:
release-tag:
description: 'Release tag to submit (e.g. v0.73.1)'
required: true
type: string
jobs: jobs:
publish: publish:
@@ -10,5 +16,6 @@ jobs:
- uses: vedantmgoyal2009/winget-releaser@4ffc7888bffd451b357355dc214d43bb9f23917e # v2 - uses: vedantmgoyal2009/winget-releaser@4ffc7888bffd451b357355dc214d43bb9f23917e # v2
with: with:
identifier: junegunn.fzf identifier: junegunn.fzf
release-tag: ${{ inputs.release-tag || github.event.release.tag_name }}
installers-regex: '-windows_(armv7|arm64|amd64)\.zip$' installers-regex: '-windows_(armv7|arm64|amd64)\.zip$'
token: ${{ secrets.WINGET_TOKEN }} token: ${{ secrets.WINGET_TOKEN }}
+1 -1
View File
@@ -4,7 +4,7 @@ CHANGELOG
0.73.1 0.73.1
------ ------
- Bug fixes - Bug fixes
- Skip `$FZF_CURRENT_ITEM` export when the item contains a NUL byte; `exec(2)` rejects the env, breaking preview and other child commands (#2395) - Skip `$FZF_CURRENT_ITEM` export when the item contains a NUL byte; `exec(2)` rejects the env, breaking preview and other child commands (#4806)
- Fixed O(n^2) HTTP body accumulation in `--listen`; a single ~390 KB request could block the single-threaded server for ~8 s (Michal Majchrowicz, Marcin Wyczechowski, AFINE Team) - Fixed O(n^2) HTTP body accumulation in `--listen`; a single ~390 KB request could block the single-threaded server for ~8 s (Michal Majchrowicz, Marcin Wyczechowski, AFINE Team)
0.73.0 0.73.0
+11 -1
View File
@@ -1,5 +1,15 @@
FROM rubylang/ruby:3.4.1-noble FROM rubylang/ruby:3.4.1-noble
RUN apt-get update -y && apt install -y git make golang zsh fish tmux RUN apt-get update && apt-get install -y git make golang zsh fish tmux
# https://www.nushell.sh/book/installation.html
RUN <<EOF
set -ex
apt-get install -y wget gnupg
wget -qO- https://apt.fury.io/nushell/gpg.key | gpg --dearmor -o /etc/apt/keyrings/fury-nushell.gpg
echo "deb [signed-by=/etc/apt/keyrings/fury-nushell.gpg] https://apt.fury.io/nushell/ /" | tee /etc/apt/sources.list.d/fury-nushell.list
apt-get update
apt-get install -y nushell
EOF
RUN gem install --no-document -v 5.22.3 minitest RUN gem install --no-document -v 5.22.3 minitest
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
RUN echo '. ~/.bashrc' >> ~/.bash_profile RUN echo '. ~/.bashrc' >> ~/.bash_profile
+9 -9
View File
@@ -25,22 +25,22 @@
--- ---
fzf is a general-purpose command-line fuzzy finder. fzf is a general-purpose command-line fuzzy finder and an interactive terminal toolkit.
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-preview.png" width=640> <img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-preview.png" width=640>
It's an interactive filter program for any kind of list; files, command Whether you're selecting files, browsing command history, previewing data,
history, processes, hostnames, bookmarks, git commits, etc. It implements navigating complex datasets with fuzzy matching, or creating custom menus and
a "fuzzy" matching algorithm, so you can quickly type in patterns with omitted workflows, fzf provides the building blocks to turn shell scripts into rich
characters and still get the results you want. terminal applications.
Highlights Highlights
---------- ----------
- **Portable** -- Distributed as a single binary for easy installation - **Portable** // Distributed as a single binary for easy installation
- **Fast** -- Optimized to process millions of items instantly - **Fast** // Optimized to process millions of items in milliseconds
- **Versatile** -- Fully customizable through an event-action binding mechanism - **Programmable** // Event-driven architecture for building custom terminal interfaces and workflows
- **All-inclusive** -- Comes with integrations for Bash, Zsh, Fish, Nushell, Vim, and Neovim - **Batteries-included** // Comes with integrations for Bash, Zsh, Fish, Nushell, Vim, and Neovim
Table of Contents Table of Contents
----------------- -----------------
+4 -4
View File
@@ -227,13 +227,13 @@ fi
for s in $shells; do for s in $shells; do
bin=$s bin=$s
[[ "$s" = nushell ]] && bin=nu [[ $s == nushell ]] && bin=nu
if ! command -v "$bin" > /dev/null; then if ! command -v "$bin" > /dev/null; then
shells=${shells/$s/} shells=${shells/$s/}
fi fi
done done
if [[ ${#shells} -lt 3 ]]; then if [[ -z ${shells// /} ]]; then
echo "No shell configuration to be updated." echo "No shell configuration to be updated."
exit 0 exit 0
fi fi
@@ -252,10 +252,10 @@ fi
echo echo
for shell in $shells; do for shell in $shells; do
[[ $shell == nushell ]] && continue
fzf_completion="source \"$fzf_base/shell/completion.${shell}\"" fzf_completion="source \"$fzf_base/shell/completion.${shell}\""
fzf_key_bindings="source \"$fzf_base/shell/key-bindings.${shell}\"" fzf_key_bindings="source \"$fzf_base/shell/key-bindings.${shell}\""
[[ $shell == fish ]] && continue [[ $shell == fish ]] && continue
[[ $shell == nushell ]] && continue
src=${prefix_expand}.${shell} src=${prefix_expand}.${shell}
echo -n "Generate $src ... " echo -n "Generate $src ... "
@@ -442,7 +442,7 @@ if [[ $shells =~ fish ]]; then
fi fi
fi fi
if [[ "$shells" =~ nushell ]]; then if [[ $shells =~ nushell ]]; then
if [[ $key_bindings -eq 1 || $auto_completion -eq 1 ]]; then if [[ $key_bindings -eq 1 || $auto_completion -eq 1 ]]; then
echo "Setting up Nushell integration ..." echo "Setting up Nushell integration ..."
nushell_autoload_dir=$(nu -c '$nu.user-autoload-dirs | first') nushell_autoload_dir=$(nu -c '$nu.user-autoload-dirs | first')
+7 -1
View File
@@ -110,8 +110,14 @@ fzf-cd-widget() {
zle redisplay zle redisplay
return 0 return 0
fi fi
# Use subshell expansion to get the absolute PWD of the target dir.
# This allows the recorded shell history to be reused even from a different
# working directory.
# If failed, fallback to the unexpanded path to surface the error to the user.
# NOTE: Don't use the `:a` modifier as it resolves symlinks like `pwd -P`.
dir=$(builtin cd >/dev/null -- "${dir}" && echo "${PWD}" || echo "${dir}")
zle push-line # Clear buffer. Auto-restored on next prompt. zle push-line # Clear buffer. Auto-restored on next prompt.
BUFFER="builtin cd -- ${(q)dir:a}" BUFFER="builtin cd -- ${(q)dir}"
zle accept-line zle accept-line
local ret=$? local ret=$?
unset dir # ensure this doesn't end up appearing in prompt expansion unset dir # ensure this doesn't end up appearing in prompt expansion
+3 -5
View File
@@ -10,7 +10,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode"
"github.com/junegunn/fzf/src/algo" "github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/tui" "github.com/junegunn/fzf/src/tui"
@@ -1734,10 +1733,10 @@ Loop:
return masked return masked
} }
func parseSingleActionList(str string) ([]*action, error) { func parseSingleActionList(str string, putAllowed bool) ([]*action, error) {
// We prepend a colon to satisfy argActionRegexp and remove it later // We prepend a colon to satisfy argActionRegexp and remove it later
masked := maskActionContents(":" + str)[1:] masked := maskActionContents(":" + str)[1:]
return parseActionList(masked, str, []*action{}, false) return parseActionList(masked, str, []*action{}, putAllowed)
} }
func parseActionList(masked string, original string, prevActions []*action, putAllowed bool) ([]*action, error) { func parseActionList(masked string, original string, prevActions []*action, putAllowed bool) ([]*action, error) {
@@ -2043,8 +2042,7 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) error {
} }
key = firstKey(keys) key = firstKey(keys)
} }
putAllowed := key.Type == tui.Rune && unicode.IsGraphic(key.Char) keymap[key], err = parseActionList(pair[1], origPairStr[len(pair[0])+1:], keymap[key], key.Printable())
keymap[key], err = parseActionList(pair[1], origPairStr[len(pair[0])+1:], keymap[key], putAllowed)
if err != nil { if err != nil {
return err return err
} }
+2 -2
View File
@@ -572,7 +572,7 @@ func TestValidateSign(t *testing.T) {
} }
func TestParseSingleActionList(t *testing.T) { func TestParseSingleActionList(t *testing.T) {
actions, _ := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down") actions, _ := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down", false)
if len(actions) != 4 { if len(actions) != 4 {
t.Errorf("Invalid number of actions parsed:%d", len(actions)) t.Errorf("Invalid number of actions parsed:%d", len(actions))
} }
@@ -588,7 +588,7 @@ func TestParseSingleActionList(t *testing.T) {
} }
func TestParseSingleActionListError(t *testing.T) { func TestParseSingleActionListError(t *testing.T) {
_, err := parseSingleActionList("change-query(foobar)baz") _, err := parseSingleActionList("change-query(foobar)baz", false)
if err == nil { if err == nil {
t.Errorf("Failed to detect error") t.Errorf("Failed to detect error")
} }
+4 -1
View File
@@ -198,7 +198,10 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
start := 0 start := 0
ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair { ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
if !theme.Colored { if !theme.Colored {
return tui.NewColorPair(-1, -1, ansi.color.attr).MergeAttr(base) // Ignore ANSI colors but keep the attributes. Retain the base
// colors (e.g. an overridden input-bg or list-bg) instead of
// resetting to the terminal default.
return tui.NewColorPair(base.Fg(), base.Bg(), ansi.color.attr).MergeAttr(base)
} }
// fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular // fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular
if base.ShouldStripColors() { if base.ShouldStripColors() {
+1 -1
View File
@@ -240,7 +240,7 @@ Loop:
} }
body = body[:contentLength] body = body[:contentLength]
actions, err := parseSingleActionList(strings.Trim(string(body), "\r\n")) actions, err := parseSingleActionList(strings.Trim(string(body), "\r\n"), false)
if err != nil { if err != nil {
return bad(err.Error()) return bad(err.Error())
} }
+2 -1
View File
@@ -6973,7 +6973,8 @@ func (t *Terminal) Loop() error {
}) })
case actTransform, actBgTransform: case actTransform, actBgTransform:
capture(false, func(body string) { capture(false, func(body string) {
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil { // Allow 'put' if the triggering key is a printable character
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n"), event.Printable()); err == nil {
// NOTE: We're not properly passing the return value here // NOTE: We're not properly passing the return value here
doActions(actions) doActions(actions)
} }
+50 -38
View File
@@ -4,6 +4,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
"github.com/rivo/uniseg" "github.com/rivo/uniseg"
@@ -252,6 +253,12 @@ func (e Event) Comparable() Event {
return Event{e.Type, e.Char, nil} return Event{e.Type, e.Char, nil}
} }
// Printable returns true if the event is a printable character that can be
// inserted into the query (e.g. via the 'put' action).
func (e Event) Printable() bool {
return e.Type == Rune && unicode.IsGraphic(e.Char)
}
func (e Event) KeyName() string { func (e Event) KeyName() string {
if me := e.MouseEvent; me != nil { if me := e.MouseEvent; me != nil {
return me.Name() return me.Name()
@@ -995,51 +1002,56 @@ func init() {
undefined := ColorAttr{colUndefined, AttrUndefined} undefined := ColorAttr{colUndefined, AttrUndefined}
NoColorTheme = &ColorTheme{ NoColorTheme = &ColorTheme{
Colored: false, Colored: false,
Input: defaultColor, // Root colors. Everything else is left undefined so that overriding a
Fg: defaultColor, // root (e.g. --color bw,bg:blue) propagates to the derived colors,
Bg: defaultColor, // just like in the colored base themes.
ListFg: defaultColor, Input: defaultColor,
ListBg: defaultColor, Fg: defaultColor,
Bg: defaultColor,
DarkBg: defaultColor,
Prompt: defaultColor,
Match: defaultColor,
Spinner: defaultColor,
Info: defaultColor,
Pointer: defaultColor,
Marker: defaultColor,
Header: defaultColor,
Footer: defaultColor,
BorderLabel: defaultColor,
// Derived colors. Left undefined so they inherit from a root.
ListFg: undefined,
ListBg: undefined,
AltBg: undefined, AltBg: undefined,
SelectedFg: defaultColor, SelectedFg: undefined,
SelectedBg: defaultColor, SelectedBg: undefined,
SelectedMatch: defaultColor, SelectedMatch: undefined,
DarkBg: defaultColor,
Prompt: defaultColor,
Match: defaultColor,
Current: undefined, Current: undefined,
CurrentMatch: undefined, CurrentMatch: undefined,
Spinner: defaultColor,
Info: defaultColor,
Pointer: defaultColor,
Marker: defaultColor,
Header: defaultColor,
Border: undefined, Border: undefined,
BorderLabel: defaultColor,
Ghost: undefined, Ghost: undefined,
Disabled: defaultColor, Disabled: undefined,
PreviewFg: defaultColor, PreviewFg: undefined,
PreviewBg: defaultColor, PreviewBg: undefined,
Gutter: undefined, Gutter: undefined,
AltGutter: undefined, AltGutter: undefined,
PreviewBorder: defaultColor, PreviewBorder: undefined,
PreviewScrollbar: defaultColor, PreviewScrollbar: undefined,
PreviewLabel: defaultColor, PreviewLabel: undefined,
ListLabel: defaultColor, ListLabel: undefined,
ListBorder: defaultColor, ListBorder: undefined,
Separator: defaultColor, Separator: undefined,
Scrollbar: defaultColor, Scrollbar: undefined,
InputBg: defaultColor, InputBg: undefined,
InputBorder: defaultColor, InputBorder: undefined,
InputLabel: defaultColor, InputLabel: undefined,
HeaderBg: defaultColor, HeaderBg: undefined,
HeaderBorder: defaultColor, HeaderBorder: undefined,
HeaderLabel: defaultColor, HeaderLabel: undefined,
FooterBg: defaultColor, FooterBg: undefined,
FooterBorder: defaultColor, FooterBorder: undefined,
FooterLabel: defaultColor, FooterLabel: undefined,
GapLine: defaultColor, GapLine: undefined,
Nth: undefined, Nth: undefined,
Nomatch: undefined, Nomatch: undefined,
} }
+2 -1
View File
@@ -136,6 +136,7 @@ class Tmux
rescue Minitest::Assertion rescue Minitest::Assertion
retries += 1 retries += 1
raise if retries > 5 raise if retries > 5
retry retry
end end
send_keys 'clear', :Enter send_keys 'clear', :Enter
@@ -295,7 +296,7 @@ class Tmux
if @shell == :nushell if @shell == :nushell
message = "Prepare[#{tries}]" message = "Prepare[#{tries}]"
send_keys 'C-u', 'C-l' send_keys 'C-u', 'C-l'
sleep 0.2 sleep(0.2)
send_keys ' ', 'C-u', :Enter, message send_keys ' ', 'C-u', :Enter, message
self.until { |lines| lines[-1] == message } self.until { |lines| lines[-1] == message }
else else
+18
View File
@@ -971,6 +971,24 @@ class TestCore < TestInteractive
tmux.until { |lines| assert_includes lines[1], ' aabravo/aabravo' } tmux.until { |lines| assert_includes lines[1], ' aabravo/aabravo' }
end end
def test_transform_put
tmux.send_keys %(seq 1000 | #{FZF} --bind 'a:transform:echo put'), :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.send_keys :a
tmux.until { |lines| assert_equal '> a', lines.last }
tmux.send_keys :b
tmux.until { |lines| assert_equal '> ab', lines.last }
end
# The async callback runs in a later iteration, but 'put' must still insert
# the key that triggered the bg-transform (snapshot of the scheduling event).
def test_bg_transform_put
tmux.send_keys %(seq 1000 | #{FZF} --bind 'a:bg-transform:sleep 0.5; echo put'), :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.send_keys 'ab'
tmux.until { |lines| assert_equal '> ba', lines.last }
end
def test_accept_non_empty def test_accept_non_empty
tmux.send_keys %(seq 1000 | #{fzf('--print-query --bind enter:accept-non-empty')}), :Enter tmux.send_keys %(seq 1000 | #{fzf('--print-query --bind enter:accept-non-empty')}), :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count } tmux.until { |lines| assert_equal 1000, lines.match_count }
+41
View File
@@ -100,6 +100,47 @@ module TestShell
tmux.until { |lines| assert_equal '/tmp', lines[-1] } tmux.until { |lines| assert_equal '/tmp', lines[-1] }
end end
def test_alt_c_symlink
base = '/tmp/fzf-test-alt-c-symlink'
FileUtils.rm_rf(base)
FileUtils.mkdir_p("#{base}/real/subdir")
FileUtils.ln_s("#{base}/real", "#{base}/link")
tmux.prepare
tmux.send_keys "cd #{base}/link", :Enter
tmux.prepare
tmux.send_keys :Escape, :c
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
tmux.send_keys 'subdir'
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :Enter
tmux.prepare
tmux.send_keys :pwd, :Enter
tmux.until { |lines| assert_equal "#{base}/link/subdir", lines[-1] }
ensure
FileUtils.rm_rf(base)
end
def test_alt_c_absolute_cmd
base = '/tmp/fzf-test-alt-c-absolute'
FileUtils.rm_rf(base)
FileUtils.mkdir_p(base)
set_var('FZF_ALT_C_COMMAND', "echo #{base}")
tmux.prepare
tmux.send_keys 'cd /tmp', :Enter
tmux.prepare
tmux.send_keys :Escape, :c
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :Enter
tmux.prepare
tmux.send_keys :pwd, :Enter
tmux.until { |lines| assert_equal base, lines[-1] }
ensure
FileUtils.rm_rf(base)
end
def test_ctrl_r def test_ctrl_r
tmux.prepare tmux.prepare
tmux.send_keys 'echo 1st', :Enter tmux.send_keys 'echo 1st', :Enter