Telescope, Treesitter and essential coding plugins for Neovim

Telescope, Treesitter and essential coding plugins for Neovim

·6 min read·Updated on February 6, 2026

Beyond the LSP

A well-configured LSP isn't enough to turn Neovim into a full development environment. You need a layer of plugins handling search, code manipulation and Git.

Telescope: search everything

Telescope is a fuzzy finder that searches files, text, buffers, help — everything — from one unified interface. It's the most-used plugin in a typical config.

By default its performance degrades on a large project. The telescope-fzf-native extension, a C-compiled binary replacing the default search algorithm, changes that completely.

Keybindings

local builtin = require("telescope.builtin")

vim.keymap.set("n", "<leader>ff", builtin.find_files, { desc = "Find files" })
vim.keymap.set("n", "<leader>fg", builtin.live_grep, { desc = "Live grep" })
vim.keymap.set("n", "<leader>fb", builtin.buffers, { desc = "Buffers" })
vim.keymap.set("n", "<leader>fh", builtin.help_tags, { desc = "Help tags" })
vim.keymap.set("n", "<leader>fr", builtin.oldfiles, { desc = "Recent files" })
vim.keymap.set("n", "<leader>fw", builtin.grep_string, { desc = "Grep word under cursor" })
vim.keymap.set("n", "<leader>/", builtin.current_buffer_fuzzy_find, { desc = "Fuzzy find in buffer" })

<leader>ff to open a file, <leader>fg to sweep the project with a text search. And <leader>/ to search the current buffer, far better than vanilla /.

Configuration and filters

require("telescope").setup({
  defaults = {
    path_display = { "truncate" },
    file_ignore_patterns = {
      "node_modules/",
      ".git/",
      ".next/",
      "dist/",
      "%.lock",
      "%.min%.js",
      "%.min%.css",
    },
    mappings = {
      i = {
        ["<C-j>"] = require("telescope.actions").move_selection_next,
        ["<C-k>"] = require("telescope.actions").move_selection_previous,
        ["<C-q>"] = require("telescope.actions").send_selected_to_qflist
          + require("telescope.actions").open_qflist,
      },
    },
  },
})

The details that matter:

  • Ignore patterns: without blacklisting node_modules, .git, .next, dist, lock files and minified assets, a search on a Next.js project returns tens of thousands of useless results. Note that %.lock and %.min.js are Lua regex, not globs.
  • Path display truncate: makes long paths readable.
  • C-j/C-k in insert mode: navigate results without leaving insert mode.
  • C-q: sends results to the quickfix list, very useful for a multi-file search-and-replace.

The preview uses Treesitter for syntax highlighting, not approximate regex.

Treesitter: more than syntax highlighting

Treesitter is an incremental parser that understands code structure: where functions start and end, where comments are, how blocks nest. Real AST parsing, not regex.

require("nvim-treesitter.configs").setup({
  ensure_installed = {
    "bash", "c", "css", "dockerfile", "go", "html",
    "javascript", "json", "lua", "markdown", "markdown_inline",
    "python", "rust", "tsx", "typescript", "vim", "vimdoc", "yaml",
  },
  auto_install = true,
  highlight = { enable = true },
  indent = { enable = true },
})

Eighteen parsers covering most day-to-day languages, plus auto_install = true so the rest install themselves when needed.

Two complements built on top:

  • nvim-ts-autotag automatically closes HTML/JSX tags, and updates the closing tag when you edit the opening one. Essential in React.
  • tailwind-tools.nvim previews colours inline, conceals long class lists for readability, and sorts class order automatically.

Editing essentials

nvim-autopairs

{
  "windwp/nvim-autopairs",
  event = "InsertEnter",
  config = function()
    local npairs = require("nvim-autopairs")
    npairs.setup({
      check_ts = true,
    })
    -- nvim-cmp integration
    local cmp_autopairs = require("nvim-autopairs.completion.cmp")
    require("cmp").event:on("confirm_done", cmp_autopairs.on_confirm_done())
  end,
}

Automatic closing of parentheses, brackets, braces and quotes. check_ts = true asks Treesitter before closing: no orphan parenthesis in a comment, no broken quote inside a string. The cmp integration also closes pairs when you accept a completion.

Comment.nvim

{
  "numToStr/Comment.nvim",
  opts = {},
}

gcc toggles a line, gc plus a motion covers more (gcap for a paragraph, gc3j for three lines). The plugin picks the comment symbol based on the file's language.

nvim-surround

{
  "kylechui/nvim-surround",
  version = "*",
  event = "VeryLazy",
  opts = {},
}
  • ys{motion}{char} — add a wrapper. ysiw" surrounds the word under the cursor with quotes.
  • cs{old}{new} — change. cs"' swaps double quotes for single ones.
  • ds{char} — delete. ds( removes the parentheses around an expression.

todo-comments.nvim

{
  "folke/todo-comments.nvim",
  event = "VimEnter",
  dependencies = { "nvim-lua/plenary.nvim" },
  opts = { signs = false },
}

Highlights TODO, FIXME and BUG in distinct colours so they stay visible in the code.

Formatting with Conform.nvim

{
  "stevearc/conform.nvim",
  event = "BufWritePre",
  config = function()
    require("conform").setup({
      formatters_by_ft = {
        javascript = { "prettier" },
        typescript = { "prettier" },
        typescriptreact = { "prettier" },
        javascriptreact = { "prettier" },
        css = { "prettier" },
        html = { "prettier" },
        json = { "prettier" },
        yaml = { "prettier" },
        markdown = { "prettier" },
        lua = { "stylua" },
        python = { "ruff_format" },
      },
      format_on_save = {
        timeout_ms = 3000,
        lsp_format = "fallback",
      },
    })

    vim.keymap.set({ "n", "v" }, "<leader>cf", function()
      require("conform").format({ async = true, lsp_format = "fallback" })
    end, { desc = "Format file or selection" })
  end,
}

Prettier for everything web, Stylua for Lua (including the Neovim config itself), ruff_format for Python. Format-on-save has a 3-second timeout and falls back to the LSP if the dedicated formatter fails. <leader>cf for manual formatting.

The binaries are installed automatically by Mason via mason-conform.

Gitsigns: Git in the gutter

Gitsigns shows Git changes line by line in the sign column.

{
  "lewis6991/gitsigns.nvim",
  opts = {
    on_attach = function(bufnr)
      local gitsigns = require("gitsigns")
      local map = function(mode, l, r, opts)
        opts = opts or {}
        opts.buffer = bufnr
        vim.keymap.set(mode, l, r, opts)
      end

      -- Navigate between changes
      map("n", "]h", gitsigns.next_hunk, { desc = "Next hunk" })
      map("n", "[h", gitsigns.prev_hunk, { desc = "Previous hunk" })

      -- Hunk actions
      map("n", "<leader>hs", gitsigns.stage_hunk, { desc = "Stage hunk" })
      map("n", "<leader>hr", gitsigns.reset_hunk, { desc = "Reset hunk" })
      map("n", "<leader>hp", gitsigns.preview_hunk, { desc = "Preview hunk" })
      map("n", "<leader>hb", gitsigns.blame_line, { desc = "Blame line" })
    end,
  },
}

The gutter symbols indicate added, modified or deleted. The keybindings let you navigate hunks (]h / [h), stage or reset them individually, preview the diff before committing, or blame a line — all without leaving Neovim.

Wrapping up

Telescope to find anything instantly, Treesitter to understand code deeply, Conform to format without thinking about it, Gitsigns to see the code's history in the gutter. No flashy plugins: each does one thing and does it well — exactly the Unix philosophy that runs through Neovim.

ShareLinkedInXBluesky

Related articles