Fzf advanced integration in Powershell
Kevin Nitro

Kevin Nitro @kevinnitro

About: Huhm hi?

Location:
Ho Chi Minh, Vietnam
Joined:
Jun 3, 2023

Fzf advanced integration in Powershell

Publish Date: Jun 5 '24
8 4

🪟 INTRO

If you want to integrate fzf with rg, fd, bat to fuzzy find files, directories or ripgrep the content of a file and preview using bat, but the fzf document only has commands for Linux shell (bash,...), and you want to achieve that on your Windows Machine using Powershell, this post may be for you.



💫 INTRODUCTION FOR THOSE MENTIONED CLI TOOL

  • fzf: 🌸 A command-line fuzzy finder
  • ripgrep (rg): ripgrep recursively searches directories for a regex pattern while respecting your gitignore
  • fd: A simple, fast and user-friendly alternative to 'find'
  • bat: A cat(1) clone with wings.
  • eza (optional): A modern, maintained replacement for ls

NOTE

You can install those tool via scoop or Chocolatey for easy installation and update.


💻 COMMANDS

NOTE

fzf uses CMD for external command, not Powershell or pwsh.

I won't explain the commands in details.

1️⃣ Find files / directories

Preview

  • Find files Find Files
  • Find directories Find directories

Command



  fd --type file --follow --hidden --exclude .git |
    fzf --prompt 'Files> ' `
      --header-first `
      --header 'CTRL-S: Switch between Files/Directories' `
      --bind 'ctrl-s:transform:if not "%FZF_PROMPT%"=="Files> " (echo ^change-prompt^(Files^> ^)^+^reload^(fd --type file^)) else (echo ^change-prompt^(Directory^> ^)^+^reload^(fd --type directory^))' `
      --preview 'if "%FZF_PROMPT%"=="Files> " (bat --color=always {} --style=plain) else (eza -T --colour=always --icons=always {})'


Enter fullscreen mode Exit fullscreen mode
  • The command above pipes the output of fd (find files) to fzf. Press Ctrl + S to change the mode to find directories. The preview pane using bat to preview file's content is on the right side, and eza for directory tree.
  • You can use tree command on Windows instead of eza in the last argument. But it wouldn't be colourful and have files icons.


- eza -T --colour=always --icons=always {}
+ tree /A {}


Enter fullscreen mode Exit fullscreen mode

2️⃣ Ripgrep content + fzf

Preview

  1. Ripgrep Ripgrep
  2. Fzf Fzf

Command



$INITIAL_QUERY = "${*:-}"
$RG_PREFIX = "rg --column --line-number --no-heading --color=always --smart-case"
"" |
fzf --ansi --disabled --query "$INITIAL_QUERY" `
  --bind "start:reload:$RG_PREFIX {q}" `
  --bind "change:reload:sleep 0.1 & $RG_PREFIX {q} || rem" `
  --bind 'ctrl-s:transform:if not "%FZF_PROMPT%" == "1. ripgrep> " (echo ^rebind^(change^)^+^change-prompt^(1. ripgrep^> ^)^+^disable-search^+^transform-query:echo ^{q^} ^> %TEMP%\rg-fzf-f ^& type %TEMP%\rg-fzf-r) else (echo ^unbind^(change^)^+^change-prompt^(2. fzf^> ^)^+^enable-search^+^transform-query:echo ^{q^} ^> %TEMP%\rg-fzf-r ^& type %TEMP%\rg-fzf-f)' `
  --color 'hl:-1:underline,hl+:-1:underline:reverse' `
  --delimiter ':' `
  --prompt '1. ripgrep> ' `
  --preview-label 'Preview' `
  --header 'CTRL-S: Switch between ripgrep/fzf' `
  --header-first `
  --preview 'bat --color=always {1} --highlight-line {2} --style=plain' `
  --preview-window 'up,60%,border-bottom,+{2}+3/3'
```

- The command above use `ripgrep` to find the content of the file and preview using `bat`. Then you can find more specifically by pressing <kbd>Ctrl</kbd> + <kbd>S</kbd> to switch to `fzf` mode. Press <kbd>Ctrl</kbd> + <kbd>S</kbd> again to back to the first step _(`ripgrep`)_.

---

## ⚙️ ADVANCED USE

You can put them all in a function, choose what to do with the output of the command, like `Set-Location`, or open the file / directory in your editor. Just open your `$PROFILE` file using your editor (ex: `nvim $PROFILE`).

Take my config as reference _(require [`PsReadLine`](https://github.com/PowerShell/PSReadLine) - may be builtin - to config keyboard shortcut to fast call function)_.

```powershell
$env:FZF_DEFAULT_OPTS=@"
--layout=reverse
--cycle
--scroll-off=5
--border
--preview-window=right,60%,border-left
--bind ctrl-u:preview-half-page-up
--bind ctrl-d:preview-half-page-down
--bind ctrl-f:preview-page-down
--bind ctrl-b:preview-page-up
--bind ctrl-g:preview-top
--bind ctrl-h:preview-bottom
--bind alt-w:toggle-preview-wrap
--bind ctrl-e:toggle-preview
"@

function _fzf_open_path
{
  param (
    [Parameter(Mandatory=$true)]
    [string]$input_path
  )
  if ($input_path -match "^.*:\d+:.*$")
  {
    $input_path = ($input_path -split ":")[0]
  }
  if (-not (Test-Path $input_path))
  {
    return
  }
  $cmds = @{
    'bat' = { bat $input_path }
    'cat' = { Get-Content $input_path }
    'cd' = {
      if (Test-Path $input_path -PathType Leaf)
      {
        $input_path = Split-Path $input_path -Parent
      }
      Set-Location $input_path
    }
    'nvim' = { nvim $input_path }
    'remove' = { Remove-Item -Recurse -Force $input_path }
    'echo' = { Write-Output $input_path }
  }
  $cmd = $cmds.Keys | fzf --prompt 'Select command> '
  & $cmds[$cmd]
}

function _fzf_get_path_using_fd
{
  $input_path = fd --type file --follow --hidden --exclude .git |
    fzf --prompt 'Files> ' `
      --header-first `
      --header 'CTRL-S: Switch between Files/Directories' `
      --bind 'ctrl-s:transform:if not "%FZF_PROMPT%"=="Files> " (echo ^change-prompt^(Files^> ^)^+^reload^(fd --type file^)) else (echo ^change-prompt^(Directory^> ^)^+^reload^(fd --type directory^))' `
      --preview 'if "%FZF_PROMPT%"=="Files> " (bat --color=always {} --style=plain) else (eza -T --colour=always --icons=always {})'
  return $input_path
}

function _fzf_get_path_using_rg
{
  $INITIAL_QUERY = "${*:-}"
  $RG_PREFIX = "rg --column --line-number --no-heading --color=always --smart-case"
  $input_path = "" |
    fzf --ansi --disabled --query "$INITIAL_QUERY" `
      --bind "start:reload:$RG_PREFIX {q}" `
      --bind "change:reload:sleep 0.1 & $RG_PREFIX {q} || rem" `
      --bind 'ctrl-s:transform:if not "%FZF_PROMPT%" == "1. ripgrep> " (echo ^rebind^(change^)^+^change-prompt^(1. ripgrep^> ^)^+^disable-search^+^transform-query:echo ^{q^} ^> %TEMP%\rg-fzf-f ^& type %TEMP%\rg-fzf-r) else (echo ^unbind^(change^)^+^change-prompt^(2. fzf^> ^)^+^enable-search^+^transform-query:echo ^{q^} ^> %TEMP%\rg-fzf-r ^& type %TEMP%\rg-fzf-f)' `
      --color 'hl:-1:underline,hl+:-1:underline:reverse' `
      --delimiter ':' `
      --prompt '1. ripgrep> ' `
      --preview-label 'Preview' `
      --header 'CTRL-S: Switch between ripgrep/fzf' `
      --header-first `
      --preview 'bat --color=always {1} --highlight-line {2} --style=plain' `
      --preview-window 'up,60%,border-bottom,+{2}+3/3'
  return $input_path
}

function fdg
{
  _fzf_open_path $(_fzf_get_path_using_fd)
}

function rgg
{
  _fzf_open_path $(_fzf_get_path_using_rg)
}


# SET KEYBOARD SHORTCUTS TO CALL FUNCTION

Set-PSReadLineKeyHandler -Key "Ctrl+f" -ScriptBlock {
  [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
  [Microsoft.PowerShell.PSConsoleReadLine]::Insert("fdg")
  [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
}

Set-PSReadLineKeyHandler -Key "Ctrl+g" -ScriptBlock {
  [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
  [Microsoft.PowerShell.PSConsoleReadLine]::Insert("rgg")
  [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
}


Enter fullscreen mode Exit fullscreen mode

Select command

NOTE

  • Due to become action on Windows doesn't work well as we expected to echo out the {1}, we can only handle in the if check in _fzf_open_path
  • Also because echo command in Windows cmd is weird, there will be some backslash when switching mode in _fzf_get_path_using_rg. Just delete them and use normally

😎 LAST WORDS

  • You can read more on document of those tool for further use (custom fzf layout, color schemes,...)
  • Please change the command according to your demands.
  • Read more about the amazing tool PSFzf, a PowerShell wrapper around the fuzzy finder fzf.
  • By the way you can come visit my windows dotfiles and grab some stuff you like 😁.
  • My English is not good and this is the first time I write a post in English. If there are any mistakes, please forgive me.
  • Thank you.

Comments 4 total

  • OreoNoCake
    OreoNoCakeJun 28, 2024

    🔥

  • Geert Theys
    Geert TheysJul 9, 2024

    Oh man I'm a long time linux users currently making windows and cli feel like linux. I'm getting quite proficient with it and have a nice starter dotfiles now. But I am learning loads from your dotfiles!

    • Kevin Nitro
      Kevin NitroJul 9, 2024

      I hope you will be happy on Windows. I have started to use Linux along with Windows recently 😁

Add comment