With the help of ChatGPT I constructed this script to search through files with ripgrep, fzf, bat.
I have them installed first:
sudo pacman -Syu ripgrep fzf bat
The fzf_search_text
command is to be added to ~/.config/ranger/commands.py
.
fzf_search_text
Command
class fzf_search_text(Command):
"""
:fzf_search_text
Search within text files for a given pattern using ripgrep (rg).
Results are displayed in fzf with previews.
If no argument is provided, prompts the user for input.
"""
def execute(self):
if not self.arg(1):
# Prompt the user for a search string
self.fm.open_console("fzf_search_text ")
return
search_string = self.rest(1)
import subprocess
import os.path
from ranger.container.file import File
# Ripgrep command to search for the pattern
rg_command = (
f"rg --column --line-number --hidden --no-heading --color=always '{search_string}' . "
"| fzf --ansi --preview "
"'echo {} | cut -d: -f1 | xargs bat --color=always --style=numbers || cat {}'"
)
# Run the combined ripgrep + fzf command
fzf = self.fm.execute_command(rg_command, universal_newlines=True, stdout=subprocess.PIPE)
stdout, _ = fzf.communicate()
# If a result is selected, process it
if fzf.returncode == 0:
result = stdout.strip()
parts = result.split(":", 1) # Split at the first colon to get the file path
if len(parts) >= 1:
file_path = os.path.abspath(parts[0]) # Extract the file path
self.fm.select_file(file_path) # Select the file in ranger
else:
self.fm.notify("Unexpected result format. Ensure the file exists.", bad=True)
How It Works
No Argument Case:
- When invoked without arguments (e.g., by pressing
F4
), the command opens ranger’s console withfzf_search_text
pre-filled, allowing you to type the search string interactively.
- When invoked without arguments (e.g., by pressing
With Argument:
- If you provide an argument (e.g.,
:fzf_search_text TODO
), it directly uses the argument as the search string.
- If you provide an argument (e.g.,
Keybinding in rc.conf
To map this command to F4
, add the following line to your ~/.config/ranger/rc.conf
:
map F4 fzf_search_text
Usage
- Restart ranger.
- Press
F4
to open a search prompt. - Enter your search string and press
Enter
. - Results will display in
fzf
with previews, and you can select a file to navigate to it.
This approach ensures compatibility with ranger’s console and provides a seamless user experience.
It works wonders! I’m very happy with the result and the UX I’ve tuned. I press F4, enter the string that I need, then I can scroll through the files containing the text. It works similar to fzf_select
Upgrading fzf_content_open
Turned out, this script works better, except it opens the file immediately. But maybe it’s just more useful.
But I don’t like that it says ‘Failed to suspend UI’ when I close my nvim session. Can you help me fix that?
The issue occurs because ranger tries to manage its UI but cannot properly suspend and resume when external commands like nvim
take over. To fix this, we need to ensure ranger’s UI is suspended before running nvim
and resumed correctly afterward. Ranger provides the fm.execute_command
method to handle external commands gracefully, but we must configure it correctly.
Here’s the updated and improved version of your fzf_content_open
command:
Fixed fzf_content_open
The issue arises because the subprocess
module is not explicitly imported in the script. Let’s fix this by adding the necessary import subprocess
statement. Here’s the corrected script:
Corrected fzf_content_open
class fzf_content_open(Command):
"""
:fzf_content_open
Pre-requisites: fzf, rg, bat, awk, vim or neovim.
Uses `rg` to search file content recursively in the current directory.
Filters with `fzf` and previews with `bat`.
Pressing `Enter` on a result opens it in (neo)vim at the matched line.
"""
def execute(self):
import os
import subprocess # Add the missing import
from ranger.ext.get_executables import get_executables
# Check required executables
rg = 'rg' if 'rg' in get_executables() else None
fzf = 'fzf' if 'fzf' in get_executables() else None
bat = 'bat' if 'bat' in get_executables() else None
editor = 'nvim' if 'nvim' in get_executables() else 'vim' if 'vim' in get_executables() else None
if not all([rg, fzf, bat, editor]):
self.fm.notify("Missing required executables: rg, fzf, bat, and/or a suitable editor.", bad=True)
return
# Prevent recursive search from the home directory
if self.fm.thisdir.path == self.fm.home_path:
self.fm.notify("Searching from the home directory is not allowed.", bad=True)
return
# Run the combined rg + fzf + bat command
command = (
"rg --line-number --hidden . | "
"fzf --delimiter ':' "
"--preview 'bat --color=always --highlight-line {2} {1}' "
"| awk -F ':' '{print \"+\"$2\" \"$1}'"
)
process = self.fm.execute_command(command, universal_newlines=True, stdout=subprocess.PIPE)
stdout, _ = process.communicate()
# If a result is selected, process it
if process.returncode == 0:
if len(stdout) < 2:
return
selected_line = stdout.split()[0] # Line number
file_path = stdout.split()[1].strip() # File path
# Get the absolute path of the file
file_fullpath = os.path.abspath(file_path)
# Open the file in the editor at the selected line
editor_command = [editor, selected_line, file_fullpath]
self.fm.execute_command(editor_command)
Key Changes:
- Added
import subprocess
:- The missing import ensures that the
subprocess
module is available for use in the script.
- The missing import ensures that the
Reminder:
- Add the hotkey mapping in
~/.config/ranger/rc.conf
if you haven’t already:
map F5 fzf_content_open
It works better than the original, but I have no understanding of Python to understand if it worsened the original script or improved it. It has no previous error, so I’m going to use till it’s proven itself otherwise.