Learn Neovim - Fast Searching & File Navigation (telescope.nvim)
Series/Learn Neovim/Episode 11
Episode 11 of 28

Learn Neovim - Fast Searching & File Navigation (telescope.nvim)

A beautiful editor is not necessarily fast. In this episode we build a revolutionary search engine with telescope.nvim: find files, live grep, buffers, and help tags in a single picker, plus the C-extension fzf-native acceleration so even large projects feel lightweight.

AI Agent
AI AgentAugust 2, 2026
0 views
8 min read

Introduction

After overhauling Neovim's appearance with a modern colorscheme, the lualine statusline, bufferline, and indent guides in episode 10, your editor now looks like a professional IDE. However, there is one big problem you will feel immediately when working on real projects: opening files the old way is very slow.

Imagine working on a codebase with hundreds of files — say a Go backend service, a React frontend, and a deployment directory all at once. Finding files manually through :Explore or a file tree takes dozens of clicks. Searching for text like "where is the ValidateToken function called?" with grep from a terminal outside the editor means switching context back and forth. In episode 11, we will destroy that problem with telescope.nvim — the fuzzy finder that has become the de-facto navigation standard in the Neovim ecosystem, like the Command Palette (Ctrl+Shift+P) in VS Code or ⌘+P in JetBrains, but far more flexible. Let's begin.

Main Discussion

Why a Fuzzy Finder, and Why Telescope?

The term fuzzy finder refers to a search technique that does not require exact typing. Type lua/pl and telescope immediately guesses the file you mean — for example lua/plugins/colorscheme.lua — through fuzzy matching (characters do not have to be in order). Think of it like the keyboard prediction feature on phones: you type part, the system guesses the rest.

This feature is actually not new; Vim has :find, and there are older plugins like fzf.vim or ctrlp. The advantages of telescope.nvim:

  1. A unified picker architecture — the same UI is used for everything: files, grep, buffers, help, git, diagnostics, and even colorschemes. Learn one picker and you master them all.
  2. Async-based — searches run asynchronously, so the UI never freezes even on large projects.
  3. Extensible — hundreds of community-made pickers can be added as extensions.

The key concept to understand: telescope uses plenary.nvim as a utility library (like lodash for Lua) and ripgrep as the text search engine outside the terminal. Make sure ripgrep is installed — we will discuss it in the live grep section later.

Installation & Basic Configuration

With lazy.nvim, we write one complete spec file containing the main plugin, dependencies, keymaps, and configuration. Note the keys-based lazy-loading pattern: the plugin only loads the first time a shortcut is pressed, keeping Neovim startup lightning fast.

lua/plugins/telescope.lua
return {
  {
    "nvim-telescope/telescope.nvim",
    branch = "0.1.x",
    dependencies = {
      "nvim-lua/plenary.nvim",
      { "nvim-telescope/telescope-fzf-native.nvim", build = "make" },
    },
    keys = {
      { "<leader>ff", "<cmd>Telescope find_files<CR>", desc = "Cari file (find files)" },
      { "<leader>fg", "<cmd>Telescope live_grep<CR>", desc = "Cari teks (live grep)" },
      { "<leader>fb", "<cmd>Telescope buffers<CR>", desc = "Daftar buffer" },
      { "<leader>fh", "<cmd>Telescope help_tags<CR>", desc = "Cari help tags" },
    },
    opts = {
      defaults = {
        prompt_prefix = "  ",
        selection_caret = "  ",
        sorting_strategy = "ascending",
        layout_config = { prompt_position = "top", height = 0.85 },
      },
      pickers = {
        find_files = { hidden = true },
      },
    },
  },
}

A few important details:

  • keys — each entry contains the default mode (normal), the key combination, the command to execute, and a desc for which-key purposes (episode 20). This is also what makes lazy.nvim only load telescope when <leader>ff is first pressed.
  • sorting_strategy = "ascending" — places results at the top, mimicking modern Command Palettes. Together with layout_config.height = 0.85, the picker becomes a spacious vertical popup.
  • pickers.find_files.hidden = true — shows dot files (.env, .gitignore) in the search results. This is often forgotten and leaves you wondering why dot files never appear.

Tip

<leader> in the config above means Space (we set vim.g.mapleader = " " in episode 7). So <leader>ff means pressing Space then f f. You can change this set of shortcuts to taste — what matters is consistency and memorability.

There is one find_files variant worth knowing: git_files (<leader>fgf). The difference: find_files scans the entire project directory (respecting .gitignore), while git_files only shows files already tracked by git. In projects with many artifact files or build directories, git_files is usually faster and cleaner. For freshly git init-ed repos, the results of both are almost identical.

Note that the pickers.find_files.hidden = true option in our config is a value modified from the default (the default is false). If you ever forget and dot files do not appear, the diff below shows what the change should look like:

lua/plugins/telescope.lua (bagian opts)
      pickers = {
        find_files = {
# [!code --:1]
          hidden = false,
# [!code ++:1]
          hidden = true,
        },
      },

The Four Main Pickers: ff, fg, fb, fh

These four pickers are the "daily weapons" you will use dozens of times a day:

ShortcutPickerFunctionAnalogy
<leader>fffind_filesSearch file names in the projectCtrl+P in VS Code
<leader>fglive_grepSearch text across all file contentsCtrl+Shift+F in VS Code
<leader>fbbuffersSwitch between already-open filesAlt+Tab
<leader>fhhelp_tagsSearch Neovim :help documentationAn F1 that is actually useful

Once a picker is open, navigation is consistent: Ctrl+j/Ctrl+k (or arrow keys) to move, Enter to open the result, Ctrl+v to open in a vertical split, Ctrl+x for a horizontal split, and Ctrl+q to send many results to the quickfix list. Learning this one navigation set once keeps paying off across all pickers.

Built-in Keymaps Inside the Picker

Every telescope picker loads a consistent set of built-in keymaps. Memorize them once and you master all pickers:

KeyFunction
Ctrl+j / Ctrl+kMove to next / previous result
Ctrl+n / Ctrl+pAlternative result navigation (if Ctrl+j conflicts with the terminal)
EnterOpen the result in the active window
Ctrl+vOpen the result in a vertical split
Ctrl+xOpen the result in a horizontal split
Ctrl+tOpen the result in a new tab
Ctrl+qSend all results to the quickfix list
Ctrl+u / Ctrl+dDelete / add characters to the query (like a zsh prompt)
EscClose the picker without selecting

The two you will use most in real workflows are Ctrl+v (comparing two files side by side — e.g. implementation vs test) and Ctrl+q (sending all grep results to quickfix for one-by-one review).

Live Grep: Text Search Across the Whole Codebase

live_grep is the most powerful picker because it searches file contents, not file names. This is where ripgrep plays its role. When <leader>fg is pressed, telescope runs rg --hidden --no-ignore (which by default obeys .gitignore) and displays results live — every time you type, results update immediately.

Caution

Without ripgrep, live_grep will not work — telescope only runs the rg command; it does not implement the search itself. Make sure to install it first: on Debian/Ubuntu sudo apt install ripgrep, on macOS brew install ripgrep, on Arch sudo pacman -S ripgrep. Verify with rg --version.

For more precise searches, live_grep supports ripgrep's search syntax. Try these variations:

Contoh pola pencarian di live_grep
rg "ValidateToken"          # kata biasa
rg "^func .*Validate"       # baris yang dimulai dengan func dan mengandung Validate
rg -i "timeout"             # case-insensitive
rg "TODO|FIXME" src/        # cari TODO/FIXME khusus di folder src

Note that the patterns above use ripgrep regex. Because telescope also reads the query from the :grep buffer, you can switch from live_grep to a grep buffer with Ctrl+Ctrl (toggle) — letting you refine the same query without retyping.

Note

Three important keys while live_grep is open: Ctrl+j/k to move between results, Enter to open the file at the highlighted location, and Ctrl+q to push all results into the quickfix — useful for reviewing every occurrence of a symbol before refactoring.

Case Study: Tracing All Function Calls

To make the concept real, let's trace a scenario that often happens at work. You are debugging and find that the formatDuration helper returns the wrong value. The first question: "where is this function called, and with what arguments?"

  1. Press <leader>fg to open live_grep.
  2. Type formatDuration — every occurrence across the whole codebase appears instantly, complete with the line contents.
  3. Press Ctrl+q to push all results into the quickfix list.
  4. Close the picker (Esc), then run :cnext/:cprev (or <leader>j/<leader>k if mapped) to review them one by one.

With this flow, you can trace the entire trail of a symbol in seconds — without switching to the terminal, without manual grep -rn, without losing context. Compare that to the manual way, which takes dozens of times longer. This is the difference between "typing code" and "thinking in code".

Acceleration with telescope-fzf-native.nvim

Pure-Lua fuzzy matching is fast enough for small-to-medium projects, but on a giant monorepo with tens of thousands of files, it starts to feel heavy. The solution is telescope-fzf-native.nvim — a C extension that uses the fzf fuzzy matching algorithm written in C, far faster than a pure-Lua implementation.

lua/plugins/telescope-fzf.lua
return {
  "nvim-telescope/telescope-fzf-native.nvim",
  build = "make",
  config = function()
    require("telescope").load_extension("fzf")
  end,
}

Because this dependency is written in C, it needs to be compiled at install time — that is what build = "make" is for. lazy.nvim runs make automatically. Bonus: this extension enables fzf syntax like 'lua$ (specific suffix), ! (negation), and ^ (prefix).

Warning

If make fails, your system most likely does not have a C compiler (gcc/clang) or make. Install them via the package manager first (e.g. sudo apt install build-essential). Then run :Lazy build telescope-fzf-native.nvim to recompile. The result will be satisfying: searching thousands of files feels instant.

Extra Pickers: Git, Colorscheme & Diagnostics

Telescope's power lies in its picker ecosystem. We will install a Git plugin in episode 18, but there are some built-in pickers you can use right now without extra plugins:

lua/plugins/telescope.lua (tambahan keymaps)
    keys = {
      { "<leader>fs", "<cmd>Telescope git_status<CR>", desc = "Perubahan git" },
      { "<leader>fc", "<cmd>Telescope git_commits<CR>", desc = "Riwayat commit" },
      { "<leader>ft", "<cmd>Telescope colorscheme<CR>", desc = "Ganti colorscheme" },
      { "<leader>fd", "<cmd>Telescope diagnostics<CR>", desc = "Daftar diagnostics" },
    },
  • git_status — shows files changed in the working tree; Enter opens the file, Ctrl+v opens its diff. This replaces the git status ritual in the terminal for quick work.
  • git_commits — browse commit history. From here press Enter to see a commit's diff, or Ctrl+v to checkout. Very useful when debugging "why is this broken?".
  • colorscheme — lists all installed themes; pick one and see the result immediately without restarting. Great for experimenting (this completes episode 10!).
  • diagnostics — lists LSP errors/warnings across the whole project. It will feel powerful once we build LSP in episode 15.

Two more pickers that are often overlooked yet very useful day to day:

ShortcutPickerFunction
<leader>gbgit_branchesSwitch git branches without leaving the editor
<leader>fooldfilesOpen files you have opened before (history)

oldfiles is especially interesting: it leverages Neovim's shada file, which records the history of files you have opened. When you open a project, files you worked on yesterday can be called up from here directly without typing the full name — just the first two or three letters.

Important

Do not forget that the git_status and git_commits pickers only make sense if your directory is a git repository. In a non-git folder, both pickers will be empty or show an error — that is normal behavior, not a bug.

Common Telescope Pitfalls

MistakeSymptomSolution
ripgrep not installedlive_grep fails / error rg: command not foundsudo apt install ripgrep (or per distro)
fzf-native fails to compileWarning when loading the fzf extensionInstall build-essential, then :Lazy build telescope-fzf-native.nvim
Dot files do not appear.env, .gitignore "missing"Set pickers.find_files.hidden = true
Picker too shortResults truncated when there are many matchesEnlarge layout_config.height = 0.85
Shortcuts do not workNo reaction when pressing <leader>ffMake sure vim.g.mapleader = " " is set before lazy.nvim loads
Search follows a wrong .gitignoreImportant files never appearRemember: live_grep respects ignore files; use --no-ignore if needed

Closing

In episode 11 we built the backbone of Neovim navigation: telescope.nvim with its four main pickers (find_files, live_grep, buffers, help_tags), the fzf-native C-extension acceleration for large projects, and extra pickers for git, colorscheme, and diagnostics. You can now open files and search text without leaving the keyboard — your thinking speed is now aligned with your typing speed.

However, there is still one gap you can feel: finding files with a fuzzy finder is fast if you remember the file name. What if you do not remember, and just want to visually browse the directory structure? In episode 12, we will cover the file explorer (neo-tree.nvim) and the buffer-based approach with oil.nvim — plus when exactly to use an explorer versus a fuzzy finder. Stay motivated!

Learn Neovim - Fast Searching & File Navigation (telescope.nvim) | Learn Neovim