Skip to main content

NvimTree line count

Taming AI Code Bloat: Adding Line Counts to Nvim-Tree

Tools like GitHub Copilot and modern LLMs have dramatically accelerated development velocity. With tab completions, automated boilerplate generation, and rapid prototyping, writing code has never been faster. However, this velocity introduces a subtle software architecture anti-pattern: silent file bloat.

Before AI assistance, writing 2,000 lines of code in a single file took deliberate effort and plenty of typing fatigue, giving developers time to pause and say, "This file is getting out of hand; I need to split this into smaller modules." Today, an AI can generate hundreds of lines in seconds. Without visual feedback in our project navigation, a once-compact module can swell to 1,500 or 2,000 lines before anyone notices during a pull request review.

To tackle this architectural drift directly at the source, I added a custom decorator to nvim-tree.lua that displays the line count directly next to every filename in the file tree.

Why Visual File Metrics Matter

File trees usually display git status, file icons, and bookmarks. While useful, none of these indicators answer the fundamental architectural question: "Which parts of my codebase are becoming monoliths?"

By placing line counts right beside file names:

  • Bloated files stand out immediately: A file with (1840) lines catches the eye far quicker than one with (65) lines.
  • Refactoring becomes proactive: Instead of waiting for a file to become unmaintainable, I identify candidates for decomposition while simply navigating my project.
  • AI generation stays grounded: It serves as a continuous reminder to prompt AI tools for modular separation rather than appending more functions to existing files.

Building the Custom Line Count Decorator

nvim-tree.lua provides an extensible decorator API (require("nvim-tree.api").Decorator). By subclassing this base class, we can append custom annotations and icons after any tree node.

Here is the implementation of the decorator in lua/configs/nvim-tree/decorators/line_count.lua:

local api = require("nvim-tree.api")

local LineCountDecorator = api.Decorator:extend()

function LineCountDecorator:new()
  self.enabled = true
  self.highlight_range = "none"
  self.icon_placement = "after" -- places the annotation after the filename
end

local function count_lines(path)
  -- Use loaded buffer line count if available to avoid disk I/O
  local bufnr = vim.fn.bufnr(path)
  if bufnr ~= -1 and vim.api.nvim_buf_is_loaded(bufnr) then
    return vim.api.nvim_buf_line_count(bufnr)
  end

  -- Fall back to reading the file from disk safely
  local ok, lines = pcall(vim.fn.readfile, path)
  return ok and #lines or nil
end

function LineCountDecorator:icons(node)
  if node.type ~= "file" then
    return nil
  end

  local count = count_lines(node.absolute_path)
  if not count then
    return nil
  end

  return { { str = "(" .. count .. ")", hl = { "Comment" } } }
end

return LineCountDecorator

How It Works

  1. Inheritance: We inherit from api.Decorator:extend() and enable it with self.icon_placement = "after" so the badge appears immediately after the filename.
  2. Buffer Awareness: count_lines() first checks if the file is currently loaded in a Neovim buffer using vim.api.nvim_buf_line_count(). This keeps the count updated in real time as you edit, without waiting for disk writes.
  3. Safe Fallback: If the file is not loaded in memory, it safely reads the file lines via pcall(vim.fn.readfile, path).
  4. Subtle Styling: The count is wrapped in parentheses and highlighted using the standard Comment highlight group so that it provides vital context without overpowering the tree view.

Registering the Decorator in Nvim-Tree

To activate the decorator, import it and insert it into the renderer.decorators array inside your Nvim-Tree configuration (lua/configs/nvim-tree/init.lua):

local M = {}

local LineCountDecorator = require("configs.nvim-tree.decorators.line_count")

M.opts = {
  filters = { dotfiles = false },
  disable_netrw = true,
  hijack_netrw = true,
  git = { enable = true },
  view = {
    width = 30,
    side = "left",
  },
  renderer = {
    highlight_git = true,
    icons = {
      show = {
        file = true,
        folder = true,
        folder_arrow = true,
        git = true,
      },
    },
    decorators = {
      "Git",
      "Open",
      "Hidden",
      "Modified",
      "Bookmark",
      "Diagnostics",
      "Copied",
      LineCountDecorator,
      "Cut",
    },
  },
  actions = {
    open_file = {
      quit_on_open = false,
      resize_window = true,
    },
  },
}

function M.setup()
  require("nvim-tree").setup(M.opts)
end

return M

The Result

When expanding directories in Nvim-Tree, every file entry clearly indicates its size:

▾ controllers/
  ├── auth_controller.lua (92)
  ├── dashboard_controller.lua (1420)
  └── user_controller.lua (115)

At a glance, dashboard_controller.lua immediately signals a code smell. Before running a git commit, I can promptly plan its decomposition into smaller services or helper modules.

Conclusion

AI tooling provides immense power, but our editor environment must evolve to keep our codebases structured and clean. Adding custom line count decorators to nvim-tree.lua is a lightweight, low-overhead enhancement that restores architectural awareness right where we navigate code every day.

Popular posts from this blog

Undefined global vim

Defining vim as global outside of Neovim When developing plugins for Neovim, particularly in Lua, developers often encounter the "Undefined global vim" warning. This warning can be a nuisance and disrupt the development workflow. However, there is a straightforward solution to this problem by configuring the Lua Language Server Protocol (LSP) to recognize 'vim' as a global variable. Getting "Undefined global vim" warning when developing Neovim plugin While developing Neovim plugins using Lua, the Lua language server might not recognize the 'vim' namespace by default. This leads to warnings about 'vim' being an undefined global variable. These warnings are not just annoying but can also clutter the development environment with unnecessary alerts, potentially hiding other important warnings or errors. Defining vim as global in Lua LSP configuration to get rid of the warning To resolve the "Undefined global vi...

LazyGit AI Commit Message

Having AI‑generated commit messages directly integrated into LazyGit If you use LazyGit every day, you already know how it turns Git from a chore into something you can actually enjoy. But there is one part of the workflow that still tends to feel a bit tedious: writing good commit messages. In this post, I show how to plug OpenAI models directly into LazyGit using a tiny one‑file BASH script, so you can get AI‑generated commit messages based on your actual diffs, without waiting for external tools to catch up with the new OpenAI Responses API . The result is a minimal, focused tool you can drop into your setup today: lgaicm . It behaves like a mini aichat that does exactly one thing: generate commit messages from Git diffs, optimized for LazyGit. Why AI‑generated commit messages in LazyGit? Commit messages matter. They are the stor...

CopilotChat GlobFile Configuration

CopilotChat GlobFile Configuration Want to feed multiple files into GitHub Copilot Chat from Neovim without listing each one manually? Let's add a tiny feature that does exactly that: a file glob that includes full file contents . In this post, we'll walk through what CopilotChat.nvim offers out of the box, why the missing piece matters, and how to implement a custom #file_glob:<pattern> function to include the contents of all files matching a glob. Using Copilot Chat with Neovim CopilotChat.nvim brings GitHub Copilot's chat right into your editing flow. No context switching, no browser hopping — just type your prompt in a Neovim buffer and let the AI help you refactor code, write tests, or explain tricky functions. You can open the chat (for example) with a command like :CopilotChat , then provide extra context using built-in functions. That “extra context” is where the magic really happens. Built-in functio...