Fzf advanced integration in Powershell
Tags: powershell, fzf, ripgrep, bat
Published: 2024-06-05 12:46:17

## 🪟 INTRO

If you want to integrate [`fzf`](https://github.com/junegunn/fzf) with [`rg`](https://github.com/BurntSushi/ripgrep), [`fd`](https://github.com/sharkdp/fd), [`bat`](https://github.com/sharkdp/bat) to fuzzy find files, directories or ripgrep the content of a file and preview using `bat`, but the [fzf document](https://github.com/junegunn/fzf/blob/master/ADVANCED.md) 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`](https://github.com/junegunn/fzf): 🌸 A command-line fuzzy finder
- [`ripgrep`](https://github.com/BurntSushi/ripgrep) _(rg)_: ripgrep recursively searches directories for a regex pattern while respecting your gitignore
- [`fd`](https://github.com/sharkdp/fd): A simple, fast and user-friendly alternative to 'find'
- [`bat`](https://github.com/sharkdp/bat): A cat(1) clone with wings.
- [`eza`](https://github.com/eza-community/eza) _(optional)_: A modern, maintained replacement for ls

> NOTE
>
> You can install those tool via [`scoop`](https://scoop.sh/) or [`Chocolatey`](https://chocolatey.org/) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ra37fsbdexu17og04a7i.png)
- Find directories
  ![Find directories](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m95mxhhgkfmemifq63gc.png)

#### Command

```powershell
  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 {})'
```

- The command above pipes the output of `fd` _(find files)_ to `fzf`. Press <kbd>Ctrl</kbd> + <kbd>S</kbd> 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.
```diff
- eza -T --colour=always --icons=always {}
+ tree /A {}
```

### 2️⃣ Ripgrep content + fzf

#### Preview

1. Ripgrep
   ![Ripgrep](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kajoe1a49rcvnojjmopq.png)
2. Fzf
   ![Fzf](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vpyo3jw9ro7pktkm0szj.png)

#### Command

```powershell
$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 _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 ^& 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
{
  _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()
}
```

![Choose what to do](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rwx0vmt0le5fvijoujfl.png)

---

## 😎 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`](https://github.com/kelleyma49/PSFzf), a PowerShell wrapper around the fuzzy finder fzf.
- By the way you can come visit my [windows dotfiles](https://github.com/KevinNitroG/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.