README.md

June 26, 2026 · View on GitHub

treegrep

treegrep is a regex pattern matcher that displays results in a tree structure with an interface to jump to matched text.

test release

demo video

examples, integrations, and help.

crates.io | GitHub | AUR | NetBSD

installation

  • cargo: cargo install treegrep
  • releases: Download from releases
  • manual:
    git clone https://github.com/4imothy/treegrep
    cd treegrep
    cargo build --release
    

integrations

neovim
return {
  '4imothy/treegrep',
  build = function()
    require('treegrep').build_tgrep()
  end,
  config = function()
    require('treegrep').setup({
      selection_file = '/tmp/tgrep-select',
      repeat_file = '/tmp/tgrep-repeat',
    })
    vim.keymap.set('n', '<leader>tt', function() require('treegrep').tgrep_with('--menu --live') end)
    vim.keymap.set('n', '<leader>tr', function() require('treegrep').tgrep_with('--repeat --select --live') end)
    vim.keymap.set('n', '<leader>tf', function() require('treegrep').tgrep_with('--files --select --live') end)
  end,
}
helix
  • keybind to run treegrep and open selection
space.t = [
  ':sh rm -f /tmp/tgrep-select',
  ':insert-output tgrep --no-alternate-screen --menu --live --selection-file=/tmp/tgrep-select --repeat-file=/tmp/tgrep-repeat > /dev/tty',
  ':open %sh{ f=$(sed -n 1p /tmp/tgrep-select); l=$(sed -n 2p /tmp/tgrep-select); [ -n "$l" ] && echo "$f:$l" || echo "$f"; }',
  ':redraw',
  ':set-option mouse false',
  ':set-option mouse true',
]
vim
Plug '4imothy/treegrep', {'do': {-> TgrepBuild()}}

let g:tgrep_selection_file = '/tmp/tgrep-select'
let g:tgrep_repeat_file = '/tmp/tgrep-repeat'

nnoremap <leader>tt :call TgrepWith('--menu')<cr>
nnoremap <leader>tr :call TgrepWith('--repeat --select')<cr>
nnoremap <leader>tf :call TgrepWith('--files --select')<cr>
emacs
  • sample setup using use-package with the built-in term
(use-package treegrep
  :ensure nil
  :load-path "path/to/treegrep/plugin"
  :preface
  (let ((src (expand-file-name "path/to/treegrep/plugin/treegrep.el")))
    (when (file-newer-than-file-p src (concat src "c"))
      (byte-compile-file src)))
  :custom
  (tgrep-selection-file "/tmp/tgrep-select")
  (tgrep-repeat-file    "/tmp/tgrep-repeat")
  (tgrep-terminal
   (lambda (cmd)
     (when-let ((old (get-buffer "*tgrep*")))
       (when-let ((p (get-buffer-process old)))
         (set-process-query-on-exit-flag p nil)
         (delete-process p))
       (kill-buffer old))
     (let ((buf (get-buffer-create "*tgrep*")))
       (switch-to-buffer buf)
       (term-mode)
       (setq-local scroll-margin 0)
       (setq-local scroll-conservatively 0)
       (setq-local term-suppress-hard-newline t)
       (setq-local show-trailing-whitespace nil)
       (setq-local display-line-numbers nil)
       (setq-local truncate-lines t)
       (let ((term-exec-hook nil))
         (term-exec buf "tgrep" shell-file-name nil (list shell-command-switch cmd)))
       (term-char-mode)
       (let ((proc (get-buffer-process buf)))
         (set-process-query-on-exit-flag proc nil)
         proc))))
  :config
  (require 'term)
  (tgrep-build)
  :bind
  ("C-c t t" . (lambda () (interactive) (delete-other-windows) (tgrep-with "--menu --path=/some/path")))
  ("C-c t r" . (lambda () (interactive) (delete-other-windows) (tgrep-with "--repeat --select")))
  ("C-c t f" . (lambda () (interactive) (delete-other-windows) (tgrep-with "--files --select --path=/some/path"))))

examples

tgrep --regexp \bstruct\s+\w+ --regexp \bimpl\s+\w+ --path src --line-number --context=1 --count
src: 9
├──term.rs: 1
│  ├──-1: 
│  ├──15: pub struct Term<'a> {
│  ╰──+1:     pub height: u16,
├──matcher.rs: 2
│  ├──-1: 
│  ├──41: impl Matcher {
│  ├──+1:     fn new(patterns: &[String], use_pcre2: bool) -> Result<Self, Message> {
│  ├──-1: 
│  ├──114: struct MatchSink<'a> {
│  ╰──+1:     lines: Vec<Line>,
├──match_system.rs: 8
│  ├──-1: 
│  ├──23: pub struct Directory {
│  ├──+1:     pub path: PathBuf,
│  ├──-1: 
│  ├──30: impl Directory {
│  ├──+1:     pub fn new(path: &Path, links: bool) -> Result<Self, Message> {
│  ├──-1: 
│  ├──41: pub struct File {
│  ├──+1:     pub path: PathBuf,
│  ├──-1: 
│  ├──47: impl File {
│  ├──+1:     pub fn from_pathbuf(path: PathBuf, links: bool) -> Result<Self, Message> {
│  ├──-1: #[cfg_attr(test, derive(PartialEq, Debug))]
│  ├──73: pub struct Match {
│  ├──+1:     pub regexp_id: usize,
│  ├──-1: 
│  ├──79: impl Match {
│  ├──+1:     pub fn new(regexp_id: usize, start: usize, end: usize) -> Self {
│  ├──-1: 
│  ├──104: pub struct Line {
│  ├──+1:     pub content: Vec<u8>,
│  ├──-1: 
│  ├──111: impl Line {
│  ╰──+1:     pub fn new(content: Vec<u8>, mut matches: Vec<Match>, line_num: usize) -> Self {
├──errors.rs: 4
│  ├──-1: 
│  ├──14: pub struct Message {
│  ├──+1:     pub mes: String,
│  ├──-1: }
│  ├──17: impl Error for Message {}
│  ├──+1: 
│  ├──-1: 
│  ├──34: impl fmt::Debug for Message {
│  ├──+1:     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
│  ├──-1: 
│  ├──40: impl fmt::Display for Message {
│  ╰──+1:     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
├──style.rs: 1
│  ├──-1: 
│  ├──23: pub struct DisplayRepeater<T>(T, usize);
│  ╰──+1: impl<T: Display> Display for DisplayRepeater<T> {
├──config.rs: 9
│  ├──-1: #[derive(Clone)]
│  ├──28: pub struct KeyBindings {
│  ├──+1:     pub down: Vec<KeyCode>,
│  ├──-1: #[derive(Clone)]
│  ├──52: pub struct Characters {
│  ├──+1:     pub bl: char,
│  ├──-1: #[derive(Clone)]
│  ├──73: pub struct Colors {
│  ├──+1:     pub file: Color,
│  ├──-1: 
│  ├──85: impl args::Color {
│  ├──+1:     fn get(&self) -> Color {
│  ├──-1: #[derive(Clone)]
│  ├──104: pub struct SearchParams {
│  ├──+1:     pub regexps: Vec<String>,
│  ├──-1: #[derive(Clone)]
│  ├──126: pub struct Config {
│  ├──+1:     pub search: SearchParams,
│  ├──-1: 
│  ├──449: struct NonSearchFields {
│  ├──+1:     selection_file: Option<PathBuf>,
│  ├──-1: 
│  ├──467: impl Config {
│  ├──+1:     pub fn get_styling(matches: &ArgMatches) -> (bool, bool) {
│  ├──-1: 
│  ├──770: impl SearchParams {
│  ╰──+1:     fn to_args(&self) -> Vec<OsString> {
├──args.rs: 8
│  ├──-1: 
│  ├──25: impl ValueEnum for OpenStrategy {
│  ├──+1:     fn value_variants<'a>() -> &'a [Self] {
│  ├──-1: 
│  ├──59: impl ValueEnum for CompletionShell {
│  ├──+1:     fn value_variants<'a>() -> &'a [Self] {
│  ├──-1: 
│  ├──85: impl CompletionShell {
│  ├──+1:     pub fn generate(&self, cmd: &mut Command, bin: &str, out: &mut dyn std::io::Write) {
│  ├──-1: #[derive(Clone)]
│  ├──144: pub struct ColorParser;
│  ├──+1: 
│  ├──146: impl clap::builder::TypedValueParser for ColorParser {
│  ├──+1:     type Value = Color;
│  ├──-1: #[derive(Clone)]
│  ├──203: pub struct KeyCodeParser;
│  ├──+1: 
│  ├──205: impl clap::builder::TypedValueParser for KeyCodeParser {
│  ├──+1:     type Value = KeyCode;
│  ├──-1: )]
│  ├──394: pub struct Args {
│  ╰──+1:     #[arg(
├──writer.rs: 19
│  ├──-1: 
│  ├──56: impl HighlightEvent<'_> {
│  ├──+1:     fn priority(&self) -> u8 {
│  ├──-1: 
│  ├──190: pub struct OpenInfo<'a> {
│  ├──+1:     pub path: &'a Path,
│  ├──-1: 
│  ├──203: pub struct WithFilter<'a> {
│  ├──+1:     pub entry: &'a dyn Entry,
│  ├──-1: 
│  ├──208: impl Display for WithFilter<'_> {
│  ├──+1:     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
│  ├──-1: 
│  ├──214: struct PathDisplay {
│  ├──+1:     prefix: Option<Vec<PrefixComponent>>,
│  ├──-1: 
│  ├──225: impl PathDisplay {
│  ├──+1:     fn new(
│  ├──-1: 
│  ├──258: impl Entry for PathDisplay {
│  ├──+1:     fn render(&self, f: &mut fmt::Formatter, filter: &str) -> fmt::Result {
│  ├──-1: 
│  ├──297: impl Display for PathDisplay {
│  ├──+1:     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
│  ├──-1: 
│  ├──355: struct LineDisplay {
│  ├──+1:     prefix: Vec<PrefixComponent>,
│  ├──-1: 
│  ├──366: impl Entry for LineDisplay {
│  ├──+1:     fn render(&self, f: &mut fmt::Formatter, filter: &str) -> fmt::Result {
│  ├──-1: 
│  ├──505: impl Display for LineDisplay {
│  ├──+1:     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
│  ├──-1: 
│  ├──511: struct LongBranchDisplay {
│  ├──+1:     prefix: Vec<PrefixComponent>,
│  ├──-1: 
│  ├──518: impl Entry for LongBranchDisplay {
│  ├──+1:     fn render(&self, f: &mut fmt::Formatter, filter: &str) -> fmt::Result {
│  ├──-1: 
│  ├──564: impl Display for LongBranchDisplay {
│  ├──+1:     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
│  ├──-1: 
│  ├──570: struct OverviewDisplay {
│  ├──+1:     dirs: usize,
│  ├──-1: 
│  ├──579: impl Entry for OverviewDisplay {
│  ├──+1:     fn render(&self, f: &mut fmt::Formatter, filter: &str) -> fmt::Result {
│  ├──-1: 
│  ├──620: impl Display for OverviewDisplay {
│  ├──+1:     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
│  ├──-1: 
│  ├──642: impl Directory {
│  ├──+1:     fn to_lines(
│  ├──-1: 
│  ├──747: impl File {
│  ╰──+1:     fn to_lines(
╰──menu.rs: 9
   ├──-1: 
   ├──59: impl ViewAnchor {
   ├──+1:     fn next(&mut self) {
   ├──-1: 
   ├──69: struct DoubleClick {
   ├──+1:     down_row: u16,
   ├──-1: 
   ├──74: impl DoubleClick {
   ├──+1:     fn new() -> Self {
   ├──-1: 
   ├──101: struct Window {
   ├──+1:     first: isize,
   ├──-1: 
   ├──106: impl Window {
   ├──+1:     fn new() -> Self {
   ├──-1: 
   ├──511: struct CurrentResults {
   ├──+1:     lines: Vec<Box<dyn Entry>>,
   ├──-1: 
   ├──515: impl CurrentResults {
   ├──+1:     fn new(matches: Matches, config: Arc<Config>) -> io::Result<Self> {
   ├──-1: 
   ├──523: pub struct Menu<'a, 'b> {
   ├──+1:     in_menu: bool,
   ├──-1: 
   ├──1729: impl OpenStrategy {
   ╰──+1:     fn from(editor: &str) -> Self {
tgrep Print src/menu.rs --trim --line-number --char-vertical=| --char-horizontal=- --char-top-left=+ --char-top-right=+ --char-bottom-left=+ --char-bottom-right=+ --char-tee=+
menu.rs
+--19: style::{Color, Print, ResetColor, SetBackgroundColor, SetForegroundColor},
+--386: Print(format!(
+--397: Print(format!(
+--409: Print(format!(
+--702: queue!(self.term, Print(WithFilter { entry, filter }))?;
+--705: queue!(self.term, Print(&cfg.chars.ellipsis))?;
+--724: Print(style::style_with(
+--732: queue!(self.term, Print(cfg.chars.selected_indicator.as_str()))?;
+--753: Print(cfg.chars.selected_indicator_clear.as_str()),
+--1108: Print(top),
+--1130: Print(display),
+--1226: Print(line)
+--1246: queue!(self.term, cursor::MoveTo(0, y), Print(msg))?;
+--1461: Print(cfg.chars.selected_indicator_clear.as_str())
tgrep --files --hidden --glob=!.git
treegrep
├──src
│  ├──match_system.rs
│  ├──menu.rs
│  ├──args.rs
│  ├──writer.rs
│  ├──main.rs
│  ├──errors.rs
│  ├──style.rs
│  ├──log.rs
│  ├──config.rs
│  ├──matcher.rs
│  ╰──term.rs
├──doc
│  ├──treegrep.vim9.txt
│  ╰──treegrep.nvim.txt
├──plugin
│  ├──treegrep.el
│  ╰──treegrep.vim
├──.github
│  ╰──workflows
│     ├──update_readme
│     ├──test.yml
│     ├──update_readme.yml
│     ╰──cr.yml
├──tests
│  ├──pool
│  │  ╰──alice_adventures_in_wonderland_by_lewis_carroll.txt
│  ├──targets
│  │  ├──files_long_branch_expr_count_1
│  │  ├──files_with_expr
│  │  ├──menu_custom_keys
│  │  ├──summary_dir
│  │  ├──files_1
│  │  ├──wide_2
│  │  ├──links_4
│  │  ├──links_3
│  │  ├──links_2
│  │  ├──menu_jump_2
│  │  ├──files_long_branch_expr_2
│  │  ├──summary_file
│  │  ├──menu_navigate_1
│  │  ├──glob_exclusion
│  │  ├──no_matches
│  │  ├──files_long_branch_1
│  │  ├──context_b1
│  │  ├──context_a1
│  │  ├──files_long_branch_expr_count_2
│  │  ├──pcre2_lookbehind
│  │  ├──overview_dir
│  │  ├──menu_filter
│  │  ├──wide_1
│  │  ├──files_2
│  │  ├──menu_search_mode
│  │  ├──deep
│  │  ├──context_c1
│  │  ├──links_1
│  │  ├──menu_jump_1
│  │  ├──overview_file
│  │  ├──files_long_branch_expr_1
│  │  ├──overlapping
│  │  ├──menu_navigate_2
│  │  ├──file
│  │  ├──max_depth
│  │  ╰──files_long_branch_2
│  ├──utils.rs
│  ├──tests.rs
│  ├──file_system.rs
│  ╰──tmux.rs
├──benchmarks
│  ├──runner
│  ╰──times
├──lua
│  ╰──treegrep.lua
├──.gitignore
├──README.md
├──LICENSE
├──rustfmt.toml
├──Cargo.toml
╰──Cargo.lock
tgrep --files --branch-each=5 --hidden --glob=!.git
treegrep
├──src
│  ├──match_system.rs, menu.rs, args.rs, writer.rs, main.rs
│  ├──errors.rs, style.rs, log.rs, config.rs, matcher.rs
│  ╰──term.rs
├──doc
│  ╰──treegrep.vim9.txt, treegrep.nvim.txt
├──.github
│  ╰──workflows
│     ╰──update_readme, test.yml, update_readme.yml, cr.yml
├──benchmarks
│  ╰──runner, times
├──lua
│  ╰──treegrep.lua
├──plugin
│  ╰──treegrep.vim, treegrep.el
├──tests
│  ├──pool
│  │  ╰──alice_adventures_in_wonderland_by_lewis_carroll.txt
│  ├──targets
│  │  ├──summary_dir, files_1, wide_2, links_4, links_3
│  │  ├──links_2, menu_jump_2, files_long_branch_expr_2, summary_file, menu_navigate_1
│  │  ├──glob_exclusion, no_matches, files_long_branch_1, context_b1, context_a1
│  │  ├──files_long_branch_expr_count_2, pcre2_lookbehind, overview_dir, menu_filter, wide_1
│  │  ├──files_2, menu_search_mode, deep, context_c1, links_1
│  │  ├──menu_jump_1, overview_file, files_long_branch_expr_1, overlapping, menu_navigate_2
│  │  ├──file, max_depth, files_long_branch_2, menu_custom_keys, files_long_branch_expr_count_1
│  │  ╰──files_with_expr
│  ╰──utils.rs, tests.rs, file_system.rs, tmux.rs
├──rustfmt.toml, Cargo.toml, .gitignore, README.md, LICENSE
╰──Cargo.lock

--help

tgrep 2.2.0

by Timothy Cronin

home page: https://github.com/4imothy/treegrep

regex pattern matcher that displays results in a tree structure with an interface to jump to matched text

tgrep [OPTIONS] <POSITIONAL_REGEXP|--regexp <>|--files|--completions <>|--menu|--repeat> [POSITIONAL_PATH]

arguments:
  [POSITIONAL_REGEXP]
          a regex expression to search for

  [POSITIONAL_PATH]
          the path to search, if not provided, search the current directory

options:
  -e, --regexp <>
          a regex expression to search for (saved for repeat)

  -p, --path <>
          the path to search, if not provided, search the current directory (saved for repeat)

  -s, --select
          results are shown in a selection interface for opening

  -m, --menu
          open a search and selection interface

      --live
          trigger search on every keystroke in the menu

      --live-delay <>
          milliseconds to wait after the last keystroke before triggering a live search

  -f, --files
          if an expression is given, hide matched content, otherwise, show the files that would be searched (saved for repeat)

  -., --hidden
          search hidden files (saved for repeat)

  -n, --line-number
          show the line numbers of matches (saved for repeat)

  -c, --count
          display number of files matched in directory and number of lines matched in a file (saved for repeat)

  -g, --glob <>
          rules match .gitignore globs, but ! has inverted meaning, overrides other ignore logic (saved for repeat)

  -l, --links
          search linked paths (saved for repeat)

  -o, --overview
          conclude results with an overview (saved for repeat)

  -S, --overview-only
          only show the overview, not the results (saved for repeat)

  -d, --max-depth <>
          the max depth to search (saved for repeat)

  -C, --context <>
          number of lines to show before and after each match

  -B, --before-context <>
          number of lines to show before each match (saved for repeat)

  -A, --after-context <>
          number of lines to show after each match (saved for repeat)

      --max-length <>
          set the max length for a matched line (saved for repeat)

      --no-ignore
          disable ignore files (saved for repeat)

      --pcre2
          use PCRE2 instead of the default regex engine (saved for repeat)

      --trim
          trim whitespace at the beginning of lines (saved for repeat)

      --auto-open
          if there is only one match, open it in the configured editor

      --repeat
          repeats the last saved search

      --branch-len <>
          number of characters to show before a match
          
          [default: 3]

      --branch-each <>
          number of files to print on each branch (saved for repeat)
          
          [default: 1]

      --no-color
          disable colors

      --no-bold
          disable bold

      --threads <>
          set the number of threads to use

      --editor <>
          command used to open selections

      --open-like <>
          command line syntax for opening a file at a line
          
          [possible values: vi, hx, code, jed, default]

      --completions <>
          generate completions for given shell
          
          [possible values: bash, elvish, fish, powershell, zsh, nushell]

      --no-mouse
          disable mouse events

      --no-alternate-screen
          disable the terminal alternate screen

      --file-color <>
          black, white, red, green, yellow, blue, magenta, cyan, grey, rgb(_._._), ansi(_)

      --dir-color <>
          black, white, red, green, yellow, blue, magenta, cyan, grey, rgb(_._._), ansi(_)

      --text-color <>
          black, white, red, green, yellow, blue, magenta, cyan, grey, rgb(_._._), ansi(_)

      --branch-color <>
          black, white, red, green, yellow, blue, magenta, cyan, grey, rgb(_._._), ansi(_)

      --line-number-color <>
          black, white, red, green, yellow, blue, magenta, cyan, grey, rgb(_._._), ansi(_)

      --match-colors <>
          black, white, red, green, yellow, blue, magenta, cyan, grey, rgb(_._._), ansi(_)

      --selected-indicator-color <>
          black, white, red, green, yellow, blue, magenta, cyan, grey, rgb(_._._), ansi(_)

      --selected-bg-color <>
          black, white, red, green, yellow, blue, magenta, cyan, grey, rgb(_._._), ansi(_)

      --filter-highlight-color <>
          black, white, red, green, yellow, blue, magenta, cyan, grey, rgb(_._._), ansi(_)

      --char-vertical <>
          vertical branch character
          
          [default: │]

      --char-horizontal <>
          horizontal branch character
          
          [default: ─]

      --char-top-left <>
          top-left corner character
          
          [default: ╭]

      --char-top-right <>
          top-right corner character
          
          [default: ╮]

      --char-bottom-left <>
          bottom-left corner character
          
          [default: ╰]

      --char-bottom-right <>
          bottom-right corner character
          
          [default: ╯]

      --char-tee <>
          tee branch character
          
          [default: ├]

      --ellipsis <>
          folded indicator
          
          [default: ⤵]

      --search-prompt <>
          search mode prompt
          
          [default: "➜ "]

      --search-prompt-inactive <>
          search prompt when not searching
          
          [default: "- "]

      --filter-prompt <>
          filter mode prompt
          
          [default: /]

      --selected-indicator <>
          selected indicator characters
          
          [default: "─❱ "]

      --key-down <>
          move down
          
          [default: down j n]

      --key-up <>
          move up
          
          [default: up k p]

      --key-big-down <>
          big jump down
          
          [default: J N]

      --key-big-up <>
          big jump up
          
          [default: K P]

      --key-down-path <>
          move down to the next path
          
          [default: } ]]

      --key-up-path <>
          move up to the previous path
          
          [default: { []

      --key-down-same-depth <>
          move down to the next path at same depth
          
          [default: ) d]

      --key-up-same-depth <>
          move up to the previous path at same depth
          
          [default: ( u]

      --key-top <>
          move to the top
          
          [default: home g <]

      --key-bottom <>
          move to the bottom
          
          [default: end G >]

      --key-page-down <>
          page down
          
          [default: pagedown f]

      --key-page-up <>
          page up
          
          [default: pageup b]

      --key-cycle-view <>
          cycle cursor position (top/center/bottom)
          
          [default: z l]

      --key-help <>
          show help
          
          [default: h]

      --key-quit <>
          quit
          
          [default: q]

      --key-open <>
          open selection
          
          [default: enter]

      --key-fold <>
          fold/unfold path
          
          [default: tab]

      --key-filter <>
          filter within results
          
          [default: / s]

      --key-search <>
          enter search mode
          
          [default: :]

      --key-submit-search <>
          submit search query
          
          [default: enter]

      --selection-file <>
          file to write selection to

      --repeat-file <>
          file to save and replay regexp searches

  -h, --help
          

  -V, --version
          

arguments are prefixed with the contents of the TREEGREP_DEFAULT_OPTS environment variable