DEV Community

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

Posted 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 ^& cat %TEMP%\rg-fzf-r) else (echo ^unbind^(change^)^+^change-prompt^(2. fzf^> ^)^+^enable-search^+^transform-query:echo ^{q^} ^> %TEMP%\rg-fzf-r ^& cat %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'
Enter fullscreen mode Exit fullscreen mode
  • The command above use ripgrep to find the content of the file and preview using bat. Then you can find more specifically by pressing Ctrl + S to switch to fzf mode. Press Ctrl + S 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 - may be builtin - to config keyboard shortcut to fast call function).

$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 _open_path
{
  param (
    [string]$input_path
  )
  if (-not $input_path)
  {
    return
  }
  Write-Output "[ ] cd"
  Write-Output "[*] nvim"
  $choice = Read-Host "Enter your choice"
  if ($input_path -match "^.*:\d+:.*$")
  {
    $input_path = ($input_path -split ":")[0]
  }
  switch ($choice)
  {
    {$_ -eq "" -or $_ -eq " "}
    {
      if (Test-Path -Path $input_path -PathType Leaf)
      {
        $input_path = Split-Path -Path $input_path -Parent
      }
      Set-Location -Path $input_path
    }
    default
    { nvim $input_path
    }
  }
}

function _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 _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 ^& cat %TEMP%\rg-fzf-r) else (echo ^unbind^(change^)^+^change-prompt^(2. fzf^> ^)^+^enable-search^+^transform-query:echo ^{q^} ^> %TEMP%\rg-fzf-r ^& cat %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
{
  _open_path $(_get_path_using_fd)
}

function rgg
{
  _open_path $(_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

Choose what to do


😎 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 (0)