DEV Community

Cover image for Fzf advanced integration in Powershell
Kevin Nitro
Kevin Nitro

Posted on • Edited on

Fzf advanced integration in Powershell

🪟 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.

Top comments (5)

Collapse
 
geert_theys_1c1302d0a0e8a profile image
Geert Theys

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!

Collapse
 
kevinnitro profile image
Kevin Nitro

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

Collapse
 
ntgnguyen profile image
OreoNoCake

🔥

Collapse
 
kevinnitro profile image
Kevin Nitro

❤️‍🔥