Flerp
August 17, 2026 · View on GitHub
flerp is a terminal UI for exploring and analyzing local files without leaving the command line. It opens text documents in a full-screen ratatui workspace and gives you live search, content viewing, file statistics, keyword extraction, repeated-line detection, and adjustable display settings.
It is built in Rust and currently supports plain text files, PDFs, and images. PDFs are read in full — every page of text plus the raster images embedded in them, rendered as real pixels in terminals that support a graphics protocol.

What Flerp Does
- Opens a file in an interactive terminal workspace
- Reads every page of a PDF and tracks where each page starts
- Extracts the images embedded in a PDF and displays them as real pixels
- Renders
.pngand.jpgfiles in the terminal - Shows quick structural metrics such as line count, word count, unique words, empty lines, and longest line
- Finds the most frequent keywords in the loaded content
- Searches file content with live updates
- Searches a whole directory tree, PDFs included, and reports the page each match landed on
- Supports case-sensitive, regex, and whole-word search modes
- Lets you jump from search results directly into the viewer
- Highlights repeated lines that appear multiple times
- Persists UI preferences in an XDG config file
- Includes multiple built-in color themes
Supported File Types
.txtand other UTF-8 readable text files.pdf, including page structure and embedded raster images.png,.jpg,.jpeg, rendered in the terminal
Image Rendering
flerp draws images with the terminal's own graphics protocol, so what you see
is the image itself rather than character art. It picks the best available of:
| Protocol | Terminals |
|---|---|
| Kitty graphics | kitty, Ghostty, WezTerm, Konsole |
| Sixel | foot, xterm, WezTerm, Contour, Windows Terminal |
| iTerm2 inline | iTerm2 |
When none of these is available, flerp falls back to Unicode half-blocks and
says so in the Media mode, so it is always clear whether you are looking at
true pixels or an approximation. Pass --graphics kitty (or sixel, iterm2,
halfblocks) to override detection, which is useful under multiplexers that
swallow the capability query.

Above is the fallback; the Media section below shows the same image over a
real graphics protocol.
Images are lifted straight out of the PDF's own data. DCTDecode (JPEG) and
Flate/LZW-compressed raster data are supported across grayscale, RGB, CMYK,
indexed-palette and 1-bit sources, and /SMask transparency is applied so
cut-out logos do not arrive as opaque boxes. JPXDecode (JPEG 2000), CCITT fax
and JBIG2 images are listed with the reason they were skipped rather than
silently dropped.
Interface Overview
The application is organized into various modes, reached with Tab.
Files
Run flerp with no arguments and it opens on the working directory; name a directory and it opens on that one. Either way a tree pane stands to the left of every mode, because which file is loaded is not a per-mode decision:
flerp # the working directory
flerp ./contracts # somewhere else
Folders are read only when you open them, so a directory of a hundred thousand
nested files costs no more to browse than an empty one. Enter opens a folder or
loads a file; h/l and the arrow keys fold; f moves the keyboard between the
tree and the mode beside it. The loaded file is marked ● in the tree, and the
pane's title says Files · f whenever the keyboard is somewhere else.
Search from the tree and committing the query with Enter sweeps every text file
and PDF underneath it. Each row then carries its match count, and a closed folder
carries the total of everything inside it, so you can see where the matches are
before opening anything. Picking a file out of a swept tree lands the viewer on
its first match.

The same thing as it happens — folding the tree open, sweeping it for a word, and dropping into the file that has the most of them:

Dashboard
Quick summary cards, a content preview, and the currently selected search match. Shown at the top of this page.
Search
Search results, search options, and regex validation errors. Matches are highlighted in place and counted per line.

Viewer
Scrollable file content. For PDFs it also reports the current page, the total page count, and how many images sit on the page you are reading.

Analyze
Structural metrics and repeated lines on the left; readability, weighted keywords and repeated phrases on the right.

Media
Images found in the file, the renderer in use, and anything that could not be decoded.

Settings
Theme and behavior settings, saved automatically as you change them.

Features
Search
- Live search while typing
- Case-sensitive toggle
- Regex search toggle
- Whole-word matching toggle
- Match counts per result
- Jump-to-line from the selected result
- Recursive search across a directory of text files and PDFs, from the shell (see Searching a whole directory) or from the tree pane, which then shows the match count on every row (see Files)
Analysis
Structural counts:
- Lines, words, characters, stanzas, empty lines
- Unique words, longest line, average word length
- Repeated lines
Content analysis:
- Top keywords, weighted rather than merely counted (see below)
- Repeated phrases, the multi-word runs the text keeps coming back to
- Readability, as a LIX score with its band, sentence count and average sentence length
- Detected language, English or Turkish, or undetermined when neither is clear
How Keywords Are Ranked
Ranking words by raw frequency describes the language, not the document: the,
and, of win every English file and bir, ve, için win every Turkish
one. flerp filters those out with English and Turkish stopword lists, then
weights what is left by tf-idf.
There is no corpus to draw an idf from, so the document supplies its own: each paragraph counts as a separate document, falling back to lines for files with no blank lines. A word spread evenly through every paragraph is scaffolding and scores low however often it repeats; a word that clusters in a few is what those paragraphs are about and scores high. The panel shows the count and the weight side by side, so you can see when the two disagree.
Phrases come from the same stopword lists: a phrase is a run of content words with no stopword or punctuation breaking it, which is the RAKE approach and needs no grammar. A phrase is only reported if it occurs more than once, and a short phrase is dropped when a longer one already accounts for every occurrence of it — listing both "harbour extension" and "harbour extension works" at the same count says the same thing twice.
Both stopword lists always apply, whatever language was detected. Documents mix languages constantly, and filtering by the detected language alone lets the other one's function words to the top. Detection reports what the text is; it does not decide what gets filtered, so a wrong guess costs nothing.
LIX is used for readability rather than Flesch because it needs no syllable counting, which only works for English. Sentences are counted by their terminators, so abbreviations inflate the count slightly.
Paged Documents
- Page count and current page shown while scrolling
[and]step a whole page at a time- Search results report the page a match landed on
- Per-page image counts
UI and Workflow
- Full-screen terminal interface
- Directory tree pane beside every mode, with
fto move between the two - Viewer fills the available height, and
PageUp/PageDownmove by one screenful - Multiple themes
- Optional line numbers
- Optional line wrapping
- Adjustable content preview size
- Adjustable keyword count
- Saved preferences between runs
Installation
From AUR
yay -S flerp
or the latest development version built from the main branch:
yay -S flerp-git
From crates.io
cargo install flerp
From source
git clone https://github.com/Huseynteymurzade28/flerp
cd flerp
cargo install --path .
Running
Open a file
flerp path/to/file.txt
Open a PDF
flerp path/to/document.pdf
Open an image
flerp path/to/image.png
Force a graphics protocol
Detection is automatic, but a multiplexer or SSH session can swallow the capability query. Say what your terminal supports instead:
flerp --graphics kitty path/to/document.pdf
Accepts auto (the default), kitty, sixel, iterm2 and halfblocks.
Start somewhere other than the top
flerp --page 12 report.pdf # open the viewer on page 12
flerp --search "invoice" report.pdf # open with the search already applied
Browse a directory
flerp # the working directory
flerp ./contracts # somewhere else
Opens the TUI with that directory as the tree pane and no file loaded yet. See Files.
Search a directory
flerp --search "invoice" ./contracts
A query alongside a directory prints matches from every text file and PDF under it rather than opening anything, so it pipes into other tools. See Searching a whole directory.
Show help
flerp --help
Show version
flerp --version
Headless Mode
Everything flerp works out about a file can be written to stdout or to disk
instead of drawn in a terminal, so it composes with the rest of your shell.
Passing any of --json, --text or --extract-images skips the TUI entirely,
and so does naming a directory or more than one path, since the TUI holds a
single file and has nowhere to put the rest.
Analysis as JSON
flerp --json report.pdf
One object on stdout: structural stats, the detected language, readability, weighted keywords, repeated phrases, repeated lines, the page table, every embedded image with its dimensions, and any images that could not be decoded along with the reason.
flerp --json report.pdf | jq '.pages | length'
flerp --json report.pdf | jq -r '.keywords[] | "\(.score)\t\(.word)"'
flerp --json report.pdf | jq -r '.phrases[] | "\(.count)\t\(.text)"'
flerp --json report.pdf | jq '.readability.lix'
Plain text
flerp --text report.pdf > report.txt
Writes the extracted text verbatim — every page of a PDF, in page order.
Closing the pipe early (| head) is treated as the reader having had enough,
not as an error.
Extracting embedded images
flerp --extract-images ./out report.pdf
Writes each embedded image as PNG into ./out, named page-003-img-02.png so
the files sort into reading order. PNG keeps the alpha that /SMask
transparency produces. Combine with --json and the written paths are reported
inside the JSON object rather than beside it, so stdout stays parseable.
Searching without the TUI
flerp --search "revenue" --json report.pdf # matches with page numbers
flerp --search "revenue" --text report.pdf # no: --text ignores the search
flerp --search "revenue" --extract-images ./out report.pdf
With --json, matches carry their line number, page number and per-line match
count. Without it, matches print grep-style as file:line:text. The search
modifiers work headlessly too: -i ignore case, -e regex, -w whole word.
Unlike the TUI, headless search never reads your saved settings, so a script
gets the same result on any machine.
Searching a whole directory
Point --search at a directory instead of a file and flerp walks it:
flerp --search "invoice" ./contracts # every text file and every PDF
flerp --search "invoice" ./contracts *.pdf # directories and files together
flerp --search "invoice" --json ./contracts
This is the thing grep cannot do for you. A folder of PDFs is opaque to every
line-oriented tool in the shell, and flerp reads each one's text layer, so a
match inside page 14 of a scanned-in contract prints beside matches from the
plain text files next to it.
Output is grep's, so it drops into the same pipelines and the same editors:
contracts/2025-terms.txt:42:invoice terms, revised
contracts/report.pdf:318:invoice total carried forward
Page numbers do not fit that format, so they come from --json, which groups
matches by file and records what was searched:
flerp --search "invoice" --json ./contracts | jq -r \
'.files[] | .file as $f | .matches[] | "\($f) p\(.page // "-") \(.line)"'
{
"search": { "query": "invoice", "case_sensitive": true, "regex": false, "whole_word": false },
"roots": ["./contracts"],
"files_searched": 31,
"files_matched": 2,
"match_count": 7,
"files": [
{
"file": "contracts/report.pdf",
"kind": "pdf",
"match_count": 5,
"matches": [{ "line_number": 318, "page": 14, "match_count": 1, "line": "invoice total carried forward" }]
}
],
"skipped": []
}
Note that this is a different shape from the single-file object above: one file gets the full analysis, a sweep gets matches. Which one you get depends only on whether you named one file or several.
What gets searched
The walk follows the same rule grep -r does, with one addition:
- PDFs are read, text layer and page structure. That is the point.
- Hidden files and directories are skipped. A walk that descends into
.gitspends its time on object files nobody searched for. - Binaries are skipped, detected the way grep detects them: a NUL byte in the first 8 KiB. Only that prefix is read, so a multi-gigabyte archive costs one short read rather than a failed attempt to load it as text.
- Images are skipped. The text
flerpshows for an image is a description it wrote itself, and matching on that would report hits that are not in the file. Naming one directly reports it underskippedwith that reason. - A file you name by hand is searched anyway, filter or no filter. Naming a file is you saying you want that file.
- Symlinked directories are not followed, so a link pointing at an ancestor cannot send the walk round in a circle.
- Results are sorted by path, so the same directory reports in the same order on every run.
Nothing is dropped silently: a directory the walk was not allowed to read and a
file that failed to open both land in skipped under --json, and on stderr
otherwise, which keeps stdout parseable.
Only searching works across several files. --text has no single text to write
and --extract-images no single document to open, so both say so rather than
picking a file for you.
Embedded images are never decoded during a sweep — searching does not look at them, so a folder of illustrated PDFs costs only its text.
Terminal Requirement
The TUI requires an interactive terminal session and will exit with an error if stdin or stdout is not connected to a real terminal. Headless mode has no such requirement — that is the point of it.
Keyboard Controls
The footer always shows the keys for whatever mode you are in, dropping the less important ones when the terminal is too narrow to hold them all.
Global
qorCtrl-cquit the applicationEsccancel the open prompt, or quit when no prompt is openTabnext mode,Shift-Tabprevious mode/open the search prompt:open the go-to-line prompt; digits only,Enterjumps
Search and Filters
Enterapply the current search or jump to the selected result from the Search tabBackspacedelete the last search characternnext match,Nprevious match — both follow the match into the viewer and wrap around at the endsctoggle case-sensitive searchrtoggle regex modewtoggle whole-word matching
Viewer and Dashboard Preview
Both read the same scroll offset, so these work in either mode.
j/Downscroll down,k/Upscroll upCtrl-dandCtrl-umove by half a screenCtrl-f/PageDownandCtrl-b/PageUpmove by a whole screeng/Homejump to the top,G/Endjump to the bottom[previous page,]next page (paged documents)
h and l are not bound to left and right: l toggles line numbers, and half
of a symmetric pair would be worse than neither.
Media
j/k(orUp/Down) move through the images in the fileEnterjump the viewer to the page the selected image sits on
Search Results and Settings
j/k(orUp/Down) move through search results and settings rowsLeftandRightadjust the selected setting in the Settings tab
Display Options
ltoggle line numbersztoggle line wrapping
Mouse
- Wheel scrolls whatever is under the pointer: the file content, the match list, the image list or the settings rows
- Click a mode in the tab strip to switch to it
- Click a row in the match list, image list or settings list to select it
- Clicks are ignored while a prompt is open, since the prompt covers the panes behind it
Scroll Position
The Viewer draws a scrollbar on the right edge of the content pane, with every search match marked on its track. On a long document that turns "where are my matches" from a scrolling exercise into a glance. Marks never paint over the thumb, so where you are looking stays visible, and a file that already fits on screen gets no scrollbar at all.
Screenshots
Every screenshot in this file is generated from flerp's own render buffer, so what you see is the interface as the program draws it.
Saved Settings
Flerp stores UI preferences in the XDG config directory using directories::ProjectDirs.
Typical Linux path:
~/.config/flerp/settings.toml
Saved settings include:
- theme
- keyword limit
- preview line count
- case sensitivity
- regex mode
- whole-word mode
- line numbers
- line wrapping
Development
Prerequisites
- Rust toolchain
- Cargo
Build
cargo build --release
Test
cargo test
Run from source
cargo run -- path/to/file.txt
Tech Stack
ratatuifor the terminal UI (TUI)crosstermfor terminal input/outputclapfor CLI argument parsingpdf-extractfor PDF text extractionlopdffor reading embedded PDF imagesratatui-imagefor terminal graphics protocolsregexfor advanced searchingserdeandtomlfor persisted settingsdirectoriesfor config directory resolutionimagefor decoding image data
Repository
- Homepage: https://github.com/Huseynteymurzade28/flerp
- Repository: https://github.com/Huseynteymurzade28/flerp
License
Released under the MIT License. See LICENSE for the full text.
Copyright (c) 2025-2026 Huseyn Teymurzade