NORMAL ~/wkd/p/insights/help :set skin=modern utf-8

insights.txt

Project analysis: symbols, metrics, tree, fileinfo. — insights.nvim

doc/insights.txt — rendered from the plugin's own vimdoc

*insights.txt*  Project analysis: symbols, metrics, tree, fileinfo.
                                                               *insights.nvim*

CONTENTS *insights-contents*

  1. Introduction ................. |insights-intro|
  2. Requirements ................. |insights-requirements|
  3. Installation ................. |insights-installation|
  4. Commands ..................... |insights-commands|
     4.1 Symbols .................. |insights-symbols|
     4.2 Metrics .................. |insights-metrics|
     4.3 Tree ..................... |insights-tree|
     4.4 File info ................ |insights-fileinfo|
     4.5 Cache .................... |insights-cache|
     4.6 Compress ................. |insights-compress|
     4.7 Imports .................. |insights-imports|
     4.8 Conflicts ................ |insights-conflicts|
     4.9 Unimported ............... |insights-unimported|
     4.10 Devserver ............... |insights-devserver|
     4.11 Smells ................... |insights-smells|
  5. Automatic triggers ........... |insights-autocmds|
  6. Configuration ................ |insights-config|
     6.1 symbols .................. |insights-config-symbols|
     6.2 metrics .................. |insights-config-metrics|
     6.3 tree ..................... |insights-config-tree|
     6.4 fileinfo ................. |insights-config-fileinfo|
     6.5 keymaps .................. |insights-config-keymaps|
     6.6 compress ................. |insights-config-compress|
     6.7 imports .................. |insights-config-imports|
     6.8 conflicts ................ |insights-config-conflicts|
     6.9 unimported ............... |insights-config-unimported|
     6.10 devserver ............... |insights-config-devserver|
  7. Symbol types ................. |insights-symbol-types|
     7.1 Function types ........... |insights-symbol-functions|
     7.2 Lua TS types ............. |insights-symbol-lua-ts|
  8. Pickers ...................... |insights-pickers|
  9. Lua API ...................... |insights-api|
 10. Health check ................. |insights-health|
 11. Troubleshooting .............. |insights-troubleshooting|

1. INTRODUCTION *insights-intro*

insights.nvim is a project-analysis plugin that combines several
previously separate tools into a single unified interface:

  • Symbol index   — ripgrep-based function/method finder across 11 languages,
                     with optional Tree-sitter Lua scanner and persistent cache.
  • Code metrics   — Lua file statistics: lines, comments, annotations, word
                     counts, and ratio analysis per file and folder.
  • Code smells    — magic-number and hardcoded-(unconfigured)-constant scans.
  • Imports        — multi-language import/require usage report, reverse
                     lookup, unused-import detection, dependency graph.
  • File tree      — Async project file tree writer, file counter, and
                     clipboard copy.
  • File info      — Floating window with filesystem metadata (fs.stat)
                     for the current buffer.
  • Compress       — Archive a project directory (tar/zip/PowerShell).
  • Automatic checks — git conflicts, unused imports, and dev servers, each
                     switched off independently.

All subcommands are exposed under a single unified command:
  :Insights <subcommand> [args]
Tab-completion works at every level.

2. REQUIREMENTS *insights-requirements*

  • Neovim ≥ 0.9           (required)
  • lib.nvim                (required, shared notify + cross-platform helpers,
                             ui.kit for the dev-server prompt)
  • rg (ripgrep)            (required for symbol indexing)
  • git                     (optional, for the conflict scan)
  • telescope.nvim          (optional, for telescope symbol picker)
  • fzf-lua                 (optional, for fzf symbol picker)
  • nvim-treesitter         (optional, for Tree-sitter Lua scanner)

On Windows the file tree uses PowerShell; on Unix it uses find + sed.

3. INSTALLATION *insights-installation*

insights.nvim is lazy by design; load it on cmd = "Insights".

                                         *insights-lazy-warning*
Exception: the automatic triggers (|insights-autocmds|) are registered
by setup(), so lazy-loading on cmd means their autocmds never fire —
nothing registers until :Insights is run by hand. If you use
conflicts, unimported, or devserver, load the plugin at startup
(lazy = false) instead. Keep cmd = "Insights" only when all three
are enable = false.

lazy.nvim:
  {
    "StefanBartl/insights.nvim",
    dependencies = { "StefanBartl/lib.nvim" },
    cmd  = "Insights",   -- lazy = false if you use the autocmds
    keys = {
      { "<leader>ps", desc = "Project symbols (telescope)" },
      { "<leader>pS", desc = "Project symbols (fzf)" },
    },
    config = function()
      require("insights").setup()
    end,
  }
packer.nvim:
  use {
    "StefanBartl/insights.nvim",
    requires = { "StefanBartl/lib.nvim" },
    cmd    = "Insights",
    config = function()
      require("insights").setup()
    end,
  }
vim-plug:
  Plug 'StefanBartl/lib.nvim'
  Plug 'StefanBartl/insights.nvim'

  " after plug#end()
  require("insights").setup()

4. COMMANDS *insights-commands*

                                                       *:Insights*
:Insights {subcommand} [args]

  Unified dispatcher. {subcommand} is one of:
  symbols, metrics, smells, tree, count, clipboard, fileinfo, cache, compress,
  imports, conflicts, unimported, devserver.

  Tab-completion is available at every argument position.

  Built via lib.nvim.bindings.usercmd.composer — dispatch and completion are driven
  from one route tree, forwarding to the same handler functions as before
  this migration. An unknown {subcommand} now reports composer's own usage
  block (every registered command, one per line). metrics' flags only
  complete once the leading -- is typed (previously offered alongside
  directory names at every position).

4.1 Symbols *insights-symbols*

                                         *:Insights-symbols*
:Insights symbols [scope] [type] [ui] [rebuild]

  Build or load the symbol index and open a picker.

  [scope]    cwd      Search the current working directory (default).
             buffer   Search only the current buffer.

  [type]     functions   Function declarations and assignments (default).
             tables      Lua table constructor definitions (Tree-sitter).
             strings     Lua string literals, deduplicated (Tree-sitter).

  [ui]       telescope   Open in telescope.nvim.
             fzf         Open in fzf-lua.
             scratch     Open in a read-only scratch buffer.
             (unset)     Auto-detect: telescope → fzf → scratch.

  rebuild    Force a cache rebuild before opening the picker.
             (Only applies to functions scope; tables/strings are not cached.)

  Arguments can appear in any order. Examples:

    :Insights symbols
    :Insights symbols buffer
    :Insights symbols cwd telescope
    :Insights symbols rebuild
    :Insights symbols fzf rebuild
    :Insights symbols buffer tables
    :Insights symbols cwd strings
    :Insights symbols buffer tables scratch
  Note: tables and strings require nvim-treesitter with the lua
  parser (:TSInstall lua). They work on Lua files only.
  Picker keymaps (telescope / fzf):
    <Enter>   Jump to the symbol definition.
    <C-p>     Toggle preview (telescope only).

  Scratch buffer keymaps:
    q / <Esc>   Close the buffer.
    gf          Follow path:line on the current line.

  Display format in the picker:
    path/to/file.lua:42  [module] my_function
  The label in square brackets is the symbol type (see |insights-symbol-types|).

4.2 Metrics *insights-metrics*

                                         *:Insights-metrics*
:Insights metrics [flags] [dir]

  Analyze the project under [dir] and display a statistics report in a scratch
  buffer. The report is also written to the file configured in
  |insights-config-metrics|.

  [dir]  Directory to analyze. Default: the current working directory.
         Tab-completion suggests directories and flags. Relative paths and ~
         are expanded. Pass an explicit directory when the editor's cwd differs
         from the project you want to measure.

  Report sections (all configurable — see |insights-config-metrics|):
    • Total / Folder / File tables — lines L1=code, L2=comments,
      L3=no-annotations, L4=annotations, L5=blank; words W1-W5; shown as
      counts and/or percentages.
    • Folder ratios — comment%, annotation%, doc%, code%, avg lines/file,
      annotation-to-comment ratio, with deviations from the global averages.
    • Top-N lists — largest files by lines and words; folders by annotation
      ratio.
    • Ratio guidelines — heuristic healthy ranges.
    • Documentation & config files — Markdown / TXT / JSON counts (summary and
      optional per-file detail).

  Flags (override the configured defaults for this invocation):
    --ratios / --no-ratios          toggle the ratio analysis
    --deviations / --no-deviations  toggle deviations in the ratio table
    --lua-only                      Lua files only (no docs)
    --misc-only                     documentation files only (no Lua)
    --no-misc                       skip documentation files
    --misc-detailed                 per-file listing of documentation files
    --no-top                        skip the top-N lists
    --top-files-lines-only          print ONLY the top files by lines
    --top-files-words-only          print ONLY the top files by words
    --percent-only / --numbers-only value display mode
    --reverse / --no-reverse        summary first vs. files first
    --topn=N                        number of items in top-N lists
    --colwidth=N                    table column width
    --file=PATH                     analyze a single file
    --current                       analyze the current buffer

  Directories named .git, node_modules, .cache, debuglog, and docs
  are skipped (matched as whole path segments below the analyzed root).

  Examples:
    :Insights metrics
    :Insights metrics --ratios --deviations ~/projects/app
    :Insights metrics --lua-only --no-top
    :Insights metrics --misc-only --misc-detailed
    :Insights metrics --current

4.3 Tree *insights-tree*

                                             *:Insights-tree*
:Insights tree

  Write the project file tree to the configured output file
  (see |insights-config-tree|). Lists all project files relative to
  cwd, one per line, sorted, excluding configured patterns.

                                            *:Insights-count*
:Insights count

  Count the total number of project files (respects exclude_patterns).

                                        *:Insights-clipboard*
:Insights clipboard

  Copy the content of the previously written tree file to the system
  clipboard. Run |:Insights-tree| first if no tree file exists.

4.4 File info *insights-fileinfo*

                                         *:Insights-fileinfo*
:Insights fileinfo

  Toggle a centered floating window showing filesystem metadata for the
  current buffer:

    Path, Type, Size, Permissions, UID, GID, Accessed, Modified, Changed.

  Calling the command again while the window is open closes it.
  Also available via the keymap configured in |insights-config-fileinfo|
  (default: <leader>fi).

  Keys inside the float:
    q / <Esc>   Close the window.

4.5 Cache *insights-cache*

                                     *:Insights-cache-build*
:Insights cache build

  Rebuild the symbol cache for the current working directory.
  Runs ripgrep over the project and saves the result to the configured
  cache directory (see |insights-config-symbols|).

                                      *:Insights-cache-info*
:Insights cache info

  Print cache statistics: number of indexed symbols, timestamp, CWD,
  file size, and cache file path.

                                     *:Insights-cache-clear*
:Insights cache clear

  Delete the symbol cache for the current working directory.

4.6 Compress *insights-compress*

                                       *:Insights-compress*
:Insights compress [path] [outdir]

  Compress a project directory using the configured engine. Both arguments
  are optional.

  [path]    Directory to compress. Default: current working directory.
            Tab-completion suggests directories.
  [outdir]  Output directory override (overrides compress.outdir for this
            invocation). Tab-completion suggests directories.

  Two files are always created in the output directory:
    compressed/<name>.tar.gz   — archive  (engine=tar)
    compressed/<name>.zip      — archive  (engine=zip or powershell)
    compressed/file-list.txt   — all archived paths, one per line
  Output directory resolution:
    compress.outdir = "" (default)  →  <path>/compressed/
    compress.outdir = "/some/dir"   →  /some/dir/<name>-compressed/

  Available engines:
    auto        tar on Unix, Compress-Archive on Windows (default)
    tar         find + tar → .tar.gz  (requires tar + find)
    zip         find + zip → .zip     (requires zip + find)
    powershell  Get-ChildItem + Compress-Archive → .zip  (Windows)

  Runs asynchronously. A notification is shown on completion (or failure).
  Requires compress.enable = true (default).

  Examples:
    :Insights compress
    :Insights compress /home/user/myproject
    :Insights compress . ~/backups

4.7 Imports *insights-imports*

                                         *:Insights-imports*
:Insights imports [filter/lang...] [telescope|fzf|graph]

  Scan source files under the current working directory for import/require
  statements across six languages — Lua, Python, JavaScript/TypeScript, Go,
  Rust, and C/C++ — and display a report in a scratch buffer (default) or a
  telescope/fzf picker over the occurrence list. The scratch report is also
  written to the file configured in |insights-config-imports|.

  [filter]  Zero or more module filters. A module matches a filter when it
            equals the filter or begins with filter./filter:: (prefix on
            the module hierarchy). lib matches lib, lib.nvim,
            lib.usrcmds but not mylib. A filter that names a configured
            group (see |insights-config-imports|) expands to that group's
            prefix list. Multiple filters are OR-combined.
  [lang]    A language id/alias (lua, python/py, javascript/js/ts,
            go, rust/rs, c/cpp) scopes the report to just that
            language. Multiple language tokens are OR-combined; combined with
            module filters, both conditions apply (AND).
  [ui]      telescope or fzf opens a picker over the occurrence list
            instead of the scratch buffer (still writes output_file).
            graph renders the same filtered data as a Graphviz dependency
            graph PNG instead — see |insights-imports-graph| below.
  Tab-completion suggests configured group names, language ids, and the
  picker tokens.

  How modules are detected: Lua uses Tree-sitter by default — only genuine
  require("…") calls in the syntax tree are counted, so the word require
  inside comments or string literals is ignored — with a ripgrep line-scan
  fallback when the Lua parser is unavailable (selectable via imports.engine,
  see |insights-config-imports|). The other five languages use a regex/text
  scan of each file's source (there is no Tree-sitter query for them yet);
  this still resolves grouped/multi-line syntax correctly (Go import ( … )
  blocks, Python from x import ( … ), nested Rust use a::{ … }). The
  backend used per language is shown in the report header.

  Report sections:
    Count        Each module with its occurrence count, sorted descending,
                 tagged with its language ([lua], [py], [js], [go],
                 [rs], [c]). A module with no matching local source file
                 is tagged (extern) — e.g. vim, react, fmt.
    Occurrences  Every call as `path:line  [lang]  module  imported-name
                 (.field)`, sorted by language, then module, then file, line.

  External/local classification per language:
    Lua         no matching .lua file under lua/<path> or <path> (cwd)
    Python      relative imports (from . import x) are always local;
                absolute ones need a matching <path>.py or <path>/__init__.py
    JS/TS       relative/absolute specifiers (./x, /x) are local; bare
                specifiers (react, @scope/pkg) are npm packages
    Go          local iff the import path matches (or is a subpackage of) the
                project's own module path in go.mod; without go.mod,
                everything is reported external
    Rust        crate::…/self::…/super::… are local; everything else is
                an external crate
    C/C++       #include <...> (system) vs. #include "..." (local) — the
                include form itself decides, no filesystem lookup

  Scratch buffer keymaps:
    q / <Esc>   Close the buffer.
    gf          Follow path:line on the current line (the import site).
    gd          Go to the definition behind a Lua import on the current line.
    gp          Preview that definition in a floating window.

  Go to definition (gd/gp) is currently Lua-only: it resolves the required
  module to the file that defines it — without executing require(...) — and
  reveals the definition of the accessed field. On an Occurrence line the jump
  lands on the field's definition (e.g. …notify  notify (.create) opens
  function M.create); on a Count line it opens the module file. Field
  location is Tree-sitter-accurate with a regex fallback. Module resolution
  searches project-local lua/ paths first, then the Neovim loader cache,
  package.path, and the runtimepath. Placing the cursor on a non-Lua entry
  and pressing gd/gp just notifies that it isn't supported yet. The view,
  float border, and keys are configured under |insights-config-imports|.

  The scan runs asynchronously and does not block the editor: file discovery
  uses rg --files-with-matches (or a plain glob when ripgrep is unavailable),
  then reads + parses matched files in scheduled chunks. The report opens when
  the scan completes.

  Examples:
    :Insights imports
    :Insights imports python
    :Insights imports js fzf
    :Insights imports lib
    :Insights imports insights
    :Insights imports lib foo.bar
                                         *:Insights-imports-reverse*
:Insights imports reverse {module}

  Given a module, list every file that imports it — the reverse of the main
  report. {module} matches by the same exact/prefix rule as the main
  filters. Opens a scratch buffer listing path:line  [lang]  imported-name
  per occurrence, grouped implicitly by sorting on file then line.

  Example:
    :Insights imports reverse insights.config
                                         *insights-imports-graph*
:Insights imports graph [filter/lang...]

  Render the same (filtered) import data as a Graphviz dependency graph
  instead of a text report — every entry is already an edge (filename
  imports module), just never drawn as one until this. Nodes: importing
  files (filled blue); external modules only appear when
  imports.graph.include_external is true (default false — a real project
  imports far more external modules than it has source files, and drawing
  them turns the graph into noise instead of showing project structure).

  Needs Graphviz (the CLI named by imports.graph.layout, default "dot")
  on PATH — reading a dependency graph out of source text needs a real
  layout engine, no pure-Lua substitute exists; reported as a clear error
  rather than a silent no-op when missing. Rendered to
  imports.graph.outdir/{project}-imports.png and shown inline through
  images.nvim (https://github.com/StefanBartl/images.nvim) if installed,
  else just reported as a file path to open manually.

  Deliberately scoped to the dependency graph only, the one place in
  insights.nvim where the data is already graph-shaped. Call-tree and
  symbol-distribution graphs don't exist as data anywhere else in this
  plugin (symbols is a flat, uncorrelated list) — building that analysis
  from scratch would be a separate, much larger feature.

  Example:
    :Insights imports graph
    :Insights imports python graph
                                         *:Insights-imports-unused*
:Insights imports unused [filter/lang...]

  List bound import names that never appear again in their file — a crude
  textual check (whole-word count of the bound identifier across the file),
  not a reference analysis. False positives are possible: re-exports via
  string, reflection, and shadowed names all look "unused" to this heuristic.
  Blank/wildcard bindings (Go _, a bare *) are always skipped. Accepts the
  same language/module filters as the main report.

  Example:
    :Insights imports unused python

4.8 Conflicts *insights-conflicts*

                                         *:Insights-conflicts*
:Insights conflicts

  Ask git for files in the unmerged state (git diff --diff-filter=U), put
  them in the |quickfix| list, and open it with |:copen|.

  Does nothing outside a git repository or when the repo has no conflicts.
  Also runs automatically on VimEnter — see |insights-autocmds|.

  Configure under conflicts (|insights-config-conflicts|).

4.9 Unimported *insights-unimported*

                                         *:Insights-unimported*
:Insights unimported

  Report component tags used in the current buffer that have no matching
  import or local definition.

  A tag whose name starts with an uppercase letter (<Card />) is treated as
  a component reference; lowercase tags are HTML elements and ignored. A name
  counts as bound when it is imported (import Card …, import { Card } …)
  or declared locally (const/let/var/function/class).

  This is a textual check, not a type-checker: it never reads other files, so
  it cannot tell whether an import actually resolves. Names that are
  deliberately never imported (globals, framework injections) belong in
  unimported.ignore.

  Runs automatically on BufWritePost for the configured filetypes — see
  |insights-autocmds|. Configure under unimported
  (|insights-config-unimported|).

4.10 Devserver *insights-devserver*

                                         *:Insights-devserver*
:Insights devserver [list|kill]

  list   List the dev servers tracked in this session, with their pid and
         whether they will be killed on exit (default).
  kill   Kill every tracked dev server now, regardless of the answer given
         to the prompt.

  Tracking itself is automatic — see |insights-autocmds|. Configure
  under devserver (|insights-config-devserver|).

4.11 Smells *insights-smells*

                                         *:Insights-smells*
:Insights smells [--magic-numbers-only|--constants-only] [dir]

  Two scans over a project's Lua source, distinct from |insights-metrics|'s
  size/ratio analysis — candidates, not verdicts, for both. [dir] defaults to
  the current working directory; tab-completion suggests directories.

  Magic numbers   A number written straight into a call with no name to hold
                  a config key against: vim.defer_fn(fn, 3000),
                  vim.wait(500), timer:start(N, N), timeout = N,
                  vim.o.columns * 0.N, vim.o.lines * 0.N. A
                  defer/wait/timer value of 50 or under is "get off the
                  current tick", not a preference, and is never flagged.
  Hardcoded       A module-level local NAME = VALUE whose name describes
  constants       behaviour (timeout, delay, limit, width, count, …) and
                  whose value is SCREAMING_CASE or a plain integer other
                  than 0/1, but which never made it into the project's own
                  config surface (any file under config//@types/, or
                  named *defaults*/config/init.lua).

  --magic-numbers-only   Skip the hardcoded-constants scan.
  --constants-only       Skip the magic-numbers scan.

  Opens the report in a scratch buffer. Has no setup() config of its own —
  each run is scoped by its flags/directory argument only.

  Example:
    :Insights smells
    :Insights smells --magic-numbers-only

5. AUTOMATIC TRIGGERS *insights-autocmds*

Most of the plugin only acts when asked. Three features also run on their own.
Each registers its autocmds in setup() and is switched off with its enable
key; a disabled feature registers nothing.

  conflicts    VimEnter                      quickfix unresolved conflicts
  unimported   BufWritePost                  check component imports
  devserver    TermOpen, TermRequest,        detect and kill dev servers
               VimLeavePre

Both conflicts and unimported are silent when they find nothing, so a
clean project produces no messages at startup or on write.

                                     *insights-devserver-tracking*

Dev-server tracking

When a terminal's command matches one of devserver.patterns (npm run dev,
astro dev, vite, …), a prompt (lib.nvim's ui.kit confirm dialog) asks
once whether that server should be killed when Neovim exits. Answering yes
kills its process tree on VimLeavePre; answering no — or pressing <Esc> —
leaves it alone. Each terminal is asked about once; the answer holds for that
terminal's lifetime.

Only terminals started by this Neovim instance are tracked. The kill targets
that terminal's recorded pid: its process tree via taskkill /T on Windows,
its process group via kill -TERM -<pid> elsewhere. A server running in
another shell or a tmux pane is never touched — the plugin only kills
processes it can account for, rather than sweeping the machine for everything
matching a name.

A command typed into an already-open shell is only detected if the program
sets the terminal title (OSC 0/2), which most dev servers do. Starting the
server as the terminal's own command (:terminal npm run dev) always works.

To skip the prompt and always kill matching servers:
  devserver = { prompt = false, kill_on_exit = true }

6. CONFIGURATION *insights-config*

Call setup() once during Neovim startup:

  require("insights").setup({
    -- options here (see below)
  })
All keys are optional. Unset keys use the defaults shown below.

6.1 symbols *insights-config-symbols*

  symbols = {
    enable        = true,
    default_scope = "cwd",   -- "cwd" | "buffer"

    languages = {
      lua        = true,
      python     = true,
      javascript = true,
      typescript = true,
      go         = true,
      rust       = true,
      c          = true,
      cpp        = true,
      java       = true,
      ruby       = true,
      php        = true,
    },

    -- When true, Lua symbols are scanned via Tree-sitter (more precise
    -- names for complex patterns); all other languages still use rg.
    use_treesitter_for_lua = false,

    indexing = {
      exclude_patterns = {
        ".git/", "node_modules/", ".cache/",
        "build/", "dist/", "target/",
      },
      max_file_size_kb = 1024,   -- 0 = no limit
      follow_symlinks  = false,
    },

    cache = {
      enabled     = true,
      dir         = vim.fn.stdpath("cache") .. "/insights/symbols",
      ttl_seconds = 3600,   -- seconds before cache is considered stale; 0 = never
    },

    progress_style = "auto",  -- indicator while a cwd index is built
  },
default_scope
  Scope used when :Insights symbols is called without an explicit
  scope argument. Either "cwd" or "buffer".

use_treesitter_for_lua
  When true, Lua files are scanned with Tree-sitter's AST (via bufadd /
  bufload). This produces more precise names for all Lua definition
  patterns but is slower than regex for large projects. Requires
  nvim-treesitter with the lua parser installed.

indexing.exclude_patterns
  Glob patterns passed to rg --glob '!<pattern>' to skip files and
  directories. Use node_modules/ (no leading */) for any occurrence of
  that directory; use */node_modules/* to match only at the root level.

cache.ttl_seconds
  How long a cached index is considered fresh. The cache is also
  invalidated automatically when source files are modified (mtime check).
  Set to 0 to disable TTL-based expiry (mtime check still applies).

progress_style
  Progress indicator while a cwd index is built. One of "auto" (default),
  "notify", "statusline", "fidget", "float", "kit".

  Building the index runs one rg pass per enabled language pattern, each
  scanning the whole tree — on a large project that is seconds of no output.
  The indicator counts the passes and shows the running symbol total.

  Requires lib.nvim, which provides it via lib.nvim.progress. Without
  lib.nvim installed the option is silently a no-op.

  Note that the build is synchronous: it returns symbols to its caller rather
  than taking a callback, and that public API is unchanged. The indicator can
  still update live because insights.scan.rg waits on each rg via
  vim.wait, which drains scheduled callbacks. That specifically — not libuv
  timers, which keep ticking either way — is what a progress handle needs:
  lib.nvim.progress schedules both its delay guard and its statusline
  redraw. Under a plain vim.fn.systemlist a handle never becomes visible.

  Cancelling is not offered: "float"/"kit" would close the indicator
  without stopping the build.

6.2 metrics *insights-config-metrics*

  metrics = {
    enable             = true,
    output_file        = vim.fn.stdpath("state") .. "/insights/metrics.md",

    analyze_lua        = true,    -- analyze Lua source files
    analyze_misc       = true,    -- analyze Markdown / TXT / JSON files

    show_file_tables   = true,    -- detailed per-file table (L1-L5 / W1-W5)
    show_folder_tables = true,    -- per-folder aggregate table
    show_total_summary = true,    -- grand-total row
    show_ratios        = true,    -- folder ratio analysis
    show_deviations    = true,    -- deviations from the global averages
    show_top_lists     = true,    -- top-N files by lines/words
    show_misc_detailed = true,    -- per-file listing for misc files

    percent_mode       = "both",  -- "both" | "percent" | "numbers"
    reverse_order      = true,    -- summary first (vs. files first)
    top_n              = 50,       -- items in top-N lists
    col_width          = 7,        -- data column width in tables
    exclude_type_files = true,     -- exclude @types files from ratio analysis
  },
output_file
  Path where the report is written (in addition to the scratch buffer). The
  directory is created automatically. Set to "" to skip writing a file.
  Ending it in .pdf writes a PDF via pdfport.nvim
  (github.com/StefanBartl/pdfport.nvim, optional dependency, needs pandoc +
  a PDF engine) instead of plain text.

analyze_lua / analyze_misc
  Which file groups to analyze. analyze_misc covers Markdown (*.md),
  text/help (*.txt), and JSON (*.json) files.

show_file_tables / show_folder_tables / show_total_summary
  Toggle the three Lua detail tables (per file, per folder, grand total).

show_ratios / show_deviations
  show_ratios adds the folder ratio table and the annotation-ratio ranking;
  show_deviations adds per-folder deviation columns against the global
  averages.

show_top_lists
  Add the "Top N Files by Lines/Words" lists (uses top_n).

show_misc_detailed
  When true, list every documentation file individually (in addition to the
  per-type summary).

percent_mode
  How table cells display values: "both" (e.g. 120 (60.0%)), "percent",
  or "numbers".

reverse_order
  When true, the total summary and ratios come before the folder/file
  tables; when false, files come first.

top_n / col_width
  Number of items in top-N lists, and the width of data columns in the tables.

exclude_type_files
  When true, @types files are excluded from ratio analysis (they would
  otherwise skew annotation ratios) but still counted in totals.

All of these can be overridden per invocation with the flags documented under
|:Insights-metrics|.

6.3 tree *insights-config-tree*

  tree = {
    enable           = true,
    exclude_patterns = { "*/.git/*", "*/node_modules/*", "*/.cache/*" },
    outdir           = vim.fn.stdpath("state") .. "/insights/tree",
    outfile_fmt      = "%s-tree.txt",
  },
outfile_fmt
  Printf-style format string for the output filename. %s is replaced with
  the project name (tail of the current working directory). Example:
  for /home/user/myproject with outfile_fmt = "%s-tree.txt" the output
  is myproject-tree.txt inside outdir.

6.4 fileinfo *insights-config-fileinfo*

  fileinfo = {
    enable = true,
    keymap = "<leader>fi",   -- false to disable the keymap
  },

6.5 keymaps *insights-config-keymaps*

  keymaps = {
    symbols_telescope = "<leader>ps",   -- false to disable
    symbols_fzf       = "<leader>pS",   -- false to disable
  },
commands
  Set to false to skip registering the :Insights user command
  entirely (use the Lua API instead). Default: true.

6.6 compress *insights-config-compress*

  compress = {
    enable = true,
    engine = "auto",   -- "auto"|"tar"|"zip"|"powershell"
    outdir = "",       -- "" = compressed/ next to source
  },
enable
  Set to false to hide the compress subcommand and skip compress-related
  health checks.

engine                                   *insights-compress-engine*
  Compression backend. LuaLS completes the valid string values:

    "auto"        OS detection: tar on Unix, powershell on Windows. Default.
    "tar"         find + tar → .tar.gz  (Unix/macOS, requires tar + find)
    "zip"         find + zip → .zip     (Unix/macOS, requires zip + find)
    "powershell"  PowerShell Compress-Archive → .zip  (Windows)

outdir
  Base directory for output. Two behaviours:
    ""          (default) — place compressed/ adjacent to the source dir.
    "/path"     — create <outdir>/<name>-compressed/ for each project.

  The command's second argument overrides this for a single invocation.

6.7 imports *insights-config-imports*

  imports = {
    enable      = true,
    progress_style = "auto", -- indicator for the async cwd scan + `unused` re-read
    engine      = "auto",   -- "auto" | "treesitter" | "ripgrep" — Lua only
    output_file = vim.fn.stdpath("state") .. "/insights/imports.md",
    languages = {
      lua = true, python = true, javascript = true,
      go = true, rust = true, c = true,
    },
    groups = {
      lib = { "lib", "lib.nvim", "lib.usrcmds" },
    },
    classify_external = true,
    definition = {
      view    = "edit",       -- "edit" | "float"
      border  = "rounded",
      keymaps = { jump = "gd", preview = "gp" },
    },
    graph = {
      include_external = false,
      outdir = vim.fn.stdpath("cache") .. "/insights/graph",
      layout = "dot",         -- Graphviz layout engine on PATH
    },
  },
enable
  Set to false to hide the imports subcommand.

engine
  Detection backend for Lua require() calls only — the other five languages
  always use a regex/text scan (no Tree-sitter query implemented for them):
    "auto"        (default) Tree-sitter when the Lua parser is available,
                  otherwise the ripgrep line scan.
    "treesitter"  Force the AST scan. Falls back to ripgrep (with a warning)
                  if the Lua parser is missing.
    "ripgrep"     Force the line scan. Faster and dependency-light, but the
                  word require in comments/strings is matched too.
  The Tree-sitter backend requires nvim-treesitter with the lua parser
  (:TSInstall lua).

output_file
  Path where the report is written (in addition to the scratch buffer). The
  directory is created automatically. Set to "" to skip writing a file.

languages
  Which languages |:Insights-imports| scans; set an entry to false to skip
  it entirely (e.g. a Python-only project could set everything but python
  to false). A bare language id/alias used as a filter argument scopes a
  single run to that language without changing this table.

groups
  Named filter groups. Each name maps to a list of module prefixes. Passing
  the group name as a filter to |:Insights-imports| expands to those
  prefixes. Group names are offered in tab-completion.

classify_external
  When true, modules with no matching local source file are tagged
  (extern) in the count table. Resolution is per-language — see
  |insights-imports| for the exact rule each language uses.

definition
  Controls "go to definition" from the imports report (gd / gp),
  currently Lua-only.
    view     "edit" jumps in the current window; "float" opens a preview
             window. Affects gd; gp always uses a float.
    border   Border style for the floating preview ("rounded", "single", …).
    keymaps  Buffer-local keys in the report: jump (default "gd") and
             preview (default "gp"). Set either to false to disable it.

graph
  Controls |insights-imports-graph| (:Insights imports graph).
    include_external  Draw external modules as graph nodes too. Default
                      false — usually far more noise than signal.
    outdir            Directory the rendered PNG is written to.
    layout             Graphviz layout engine on PATH: "dot" (default,
                      hierarchical), "neato"/"fdp"/"sfdp" (force-directed),
                      "twopi"/"circo" (radial/circular).

6.8 conflicts *insights-config-conflicts*

  conflicts = {
    enable      = true,
    events      = { "VimEnter" },
    git_cmd     = "git",
    diff_filter = "U",
    open_qf     = true,
    notify      = true,
  },
enable
  false disables the feature entirely: no autocmd is registered and
  :Insights conflicts reports that it is disabled.

events
  Autocmd events that trigger the scan. Set to {} to never scan
  automatically and use the command only.

git_cmd
  The git executable.

diff_filter
  A git status --porcelain code to match, in either the index or the
  worktree column. "U" (the default) means "unmerged" and matches the full
  conflict code set (UU, AA, DD, AU, UD, UA, DU), not just paths containing
  the literal letter U.

open_qf
  false fills the quickfix list without opening the window.

notify
  false suppresses the notification listing the conflicting files. The
  quickfix list is still populated.

6.9 unimported *insights-config-unimported*

  unimported = {
    enable    = true,
    events    = { "BufWritePost" },
    filetypes = { "astro", "javascriptreact", "typescriptreact",
                  "vue", "svelte" },
    ignore    = {},
  },
enable
  false disables the feature entirely: no autocmd is registered and
  :Insights unimported reports that it is disabled.

events
  Autocmd events that trigger the check. Set to {} for command-only use.

filetypes
  Filetypes the check applies to. Buffers of any other filetype are skipped,
  including by the autocmd.

ignore
  Component names never reported, for names that are legitimately never
  imported (globals, framework injections):
    ignore = { "Fragment", "Astro" }

6.10 devserver *insights-config-devserver*

  devserver = {
    enable       = true,
    prompt       = true,
    kill_on_exit = true,
    patterns     = {
      "astro dev", "npm run dev", "pnpm dev", "yarn dev", "bun dev",
      "vite", "next dev", "nuxt dev", "ng serve", "rails server",
    },
  },
enable
  false disables the feature entirely: no terminal is watched, nothing is
  killed, and :Insights devserver reports that it is disabled.

prompt
  true (default) asks — once per terminal — whether the detected server
  should be killed on exit, via lib.nvim's ui.kit confirm dialog.
  false skips the dialog and applies kill_on_exit to every match.

kill_on_exit
  The answer used when prompt = false. Ignored when prompt = true, where
  the user's answer decides. prompt = false, kill_on_exit = false therefore
  disables killing while still tracking matches for
  :Insights devserver list.

patterns
  Plain substrings (not Lua patterns), matched case-insensitively against the
  terminal's command. Replace the list to track a different set of servers:
    patterns = { "npm run dev", "cargo watch" }
  See |insights-devserver-tracking| for what is and is not detected.

7. SYMBOL TYPES *insights-symbol-types*

The picker displays a type label for each symbol in square brackets.

7.1 Function types *insights-symbol-functions*

Symbols of the default functions type:
  local      local function foo()
  global     function foo()  /  top-level def foo(): in Python
  module     function M.foo()  /  M.foo = function()
  method     receiver method in Go, class method in Python/Java/Ruby
  anonymous  const foo = () =>  /  foo = function()
  exported   export function foo() in JavaScript/TypeScript
  unknown    pattern matched but type could not be inferred

7.2 Lua TS types *insights-symbol-lua-ts*

Lua-specific types, produced by the Tree-sitter scanner
(|:Insights-symbols| with tables or strings):
  table      table constructor: local t = {}  /  state.win = {}  /  { field = {} }
  string     unique string literal: "require path", event name, magic value

8. PICKERS *insights-pickers*

:Insights symbols auto-selects a picker in this order:
  1. telescope.nvim  (if installed)
  2. fzf-lua         (if installed)
  3. scratch buffer  (always available)

Force a specific picker by passing its name as an argument:

  :Insights symbols telescope
  :Insights symbols fzf
  :Insights symbols scratch
                                        *insights-picker-telescope*

Telescope picker

  Displays symbols as path:line  [type] name.
  Default actions:
    <Enter>    edit file at symbol line
    <C-p>      toggle file preview

                                             *insights-picker-fzf*

fzf-lua picker

  Same display format. Default action:
    <Enter>    edit file at symbol line

                                          *insights-picker-scratch*

Scratch buffer

  Read-only buffer, one symbol per line in the format:
    lua/insights/init.lua:14  [module] M.setup
  Keymaps:
    q / <Esc>    close buffer
    gf           follow path:line under cursor

9. LUA API *insights-api*

After calling setup(), the following functions are available:

                                             *insights.get_symbols()*
require("insights").get_symbols([scope [, force_rebuild]])

  Returns (entries, message) where entries is a list of symbol tables
  and message is a status string.

  scope         "cwd" | "buffer" | nil (uses default_scope)
  force_rebuild boolean, force a cache rebuild

  Each entry has:
    {
      filename  = "lua/foo/bar.lua",
      lnum      = 42,
      col       = 0,
      name      = "my_function",
      func_type = "module",
      language  = "lua",
      signature = "my_function(a, b)",
      text      = "function M.my_function(a, b)",
    }
                                            *insights.get_tables()*
require("insights").get_tables([scope])

  Scan Lua table definitions via Tree-sitter.
  Returns (entries, message).

  scope  "buffer" (default) | "cwd"

  Each entry has filename, lnum, col, name, func_type = "table".
  Requires nvim-treesitter with the lua parser.

                                           *insights.get_strings()*
require("insights").get_strings([scope])

  Scan unique Lua string literals via Tree-sitter.
  Returns (entries, message).

  scope  "buffer" (default) | "cwd"

  Each entry has filename, lnum, col, name, func_type = "string".
  Requires nvim-treesitter with the lua parser.

                                             *insights.run_metrics()*
require("insights").run_metrics()

  Run the Lua metrics analysis for the current project and open the report
  in a scratch buffer.

                                             *insights.run_imports()*
require("insights").run_imports([filters], [ui])

  Scan import/require usage for the current project (Lua, Python, JS/TS, Go,
  Rust, C/C++) and open the report.

  filters  optional list of module prefixes / language ids / group names to
             filter by, e.g. { "insights" }, { "lib" }, or { "python" }.
  ui       optional "telescope" or "fzf" to open a picker over the
             occurrence list instead of the scratch buffer.

                                             *insights.run_imports_reverse()*
require("insights").run_imports_reverse(module)

  List every file that imports module and open the report in a scratch
  buffer.

                                             *insights.run_imports_unused()*
require("insights").run_imports_unused([filters])

  List bound import names that never appear again in their file (heuristic,
  see |insights-imports|) and open the report in a scratch buffer.

  filters  optional list of module prefixes / language ids / group names.

                                              *insights.write_tree()*
require("insights").write_tree([callback])

  Write the project file tree.
  callback receives (success, message, outpath).

                                           *insights.show_fileinfo()*
require("insights").show_fileinfo()

  Toggle the file info float for the current buffer.

                                           *insights.run_conflicts()*
require("insights").run_conflicts()

  Scan for unresolved merge conflicts and populate the quickfix list.
  Returns the number of conflicting files (0 outside a git repository).

                                        *insights.check_unimported()*
require("insights").check_unimported([bufnr])

  Returns a list of component names used in bufnr (default: the current
  buffer) with no matching import or local definition, and notifies about
  them. The buffer's filetype is not checked — pass any buffer.

  Use require("insights.unimported").check_buf(bufnr) for the same
  list without the notification.

                                             *insights.devservers()*
require("insights").devservers()

  Dev servers tracked in this session, keyed by terminal channel:
    {
      [3] = {
        pid          = 12345,
        cmd          = "npm run dev",
        kill_on_exit = true,
      },
    }

10. HEALTH CHECK *insights-health*

  :checkhealth insights
Reports:

  Neovim version      Requires ≥ 0.9; notes vim.system availability (0.10+).
  External tools      rg (required); PowerShell (Windows) or find/sed (Unix)
                      for the file tree.
  Optional pickers    telescope.nvim, fzf-lua.
  Optional PDF export pdfport.nvim, only relevant when metrics.output_file
                      ends in .pdf.
  Tree-sitter         nvim-treesitter installation status.
  Configuration       Active language list, scope, cache settings.
  Automatic triggers  conflicts / unimported / devserver status, plus the
                      tools they need (git, taskkill or kill).
  Compress            Engine availability (tar/find/zip/powershell), outdir writability.
  Hover contribution  Whether insights registers into hover.nvim, and the
                      import-index freshness (cold/stale/warm) it reads from.
  Cache               Symbol count, last-indexed timestamp, file path.
  Declared tools      Cross-check against docs/install.json
  (lib.nvim.deps)     (:Lib deps show insights.nvim).

11. TROUBLESHOOTING *insights-troubleshooting*

No symbols found

  • Is rg in your PATH? Check with :checkhealth insights.
  • Is the current working directory correct? (:pwd)
  • Are the target languages enabled in symbols.languages?
  • Run :Insights cache build to force a fresh index.

Symbols are stale

  • The cache is invalidated automatically when source files change (mtime).
  • To force a rebuild: :Insights symbols rebuild or
    :Insights cache build.
  • Lower symbols.cache.ttl_seconds or set it to 0 to disable TTL.

Tree command fails

  • On Unix, find and sed must be available.
  • On Windows, PowerShell 5.1+ is required.
  • Check tree.outdir is writable.

File tree clipboard empty

  • Run :Insights tree first to generate the tree file.
  • The clipboard write uses setreg("+", …); ensure the + register is
    available (Neovim has clipboard support built in).

Tree-sitter Lua scanner shows no results

  • nvim-treesitter must be installed with the lua parser:
    :TSInstall lua
  • The scanner loads each file with bufadd/bufload; very large
    projects may be slow — use the rg scanner for those.
  • This applies to all three TS-based types: functions, tables, strings.

Tables or strings scan returns nothing

  • Verify the current buffer (or cwd) contains Lua files.
  • For buffer scope, the buffer must have filetype=lua.
  • The tables scanner finds {} constructor assignments only; bare
    require("mod") calls are not matched as tables.
  • The strings scanner deduplicates by content, so repeated literals
    appear once regardless of how many times they occur.

Picker not opening

  • If you specified telescope or fzf, verify the plugin is installed.
  • Fall back to :Insights symbols scratch which has no dependencies.

Compress fails

  • Run :checkhealth insights to see which engine tools are
    available on your system.
  • tar engine: tar and find must be in PATH.
  • zip engine: zip and find must be in PATH.
  • powershell engine: PowerShell 5.1+ required (Compress-Archive).
  • Verify the output directory (or its parent) is writable.
  • Paths with spaces: the PowerShell engine uses single-quoted strings
    which handle spaces, but if problems occur use a simpler outdir.

Dev server was never detected

  • Is the command in devserver.patterns? Check the exact command with
    :echo b:term_title or :lua =vim.api.nvim_get_chan_info(vim.b.terminal_job_id).argv
    and add a substring of it to the list.
  • A command typed into an already-running shell is only seen if the program
    sets the terminal title. Start it as the terminal's command instead:
    :terminal npm run dev.
  • A server started outside Neovim (another shell, tmux pane) is never
    detected. This is by design — see |insights-devserver-tracking|.

Dev server was not killed on exit

  • Confirm it was tracked and approved: :Insights devserver list.
    kill on exit: false means the prompt was answered "no" or cancelled.
  • :checkhealth insights shows whether taskkill (Windows) or
    kill (Unix) is available.
  • The process must be a child of the terminal Neovim started. A server that
    daemonises itself away from that process tree outlives the kill.

Dev-server prompt never appears

  • devserver.prompt = false applies kill_on_exit silently by design.
  • The prompt fires once per terminal; it does not re-ask for a terminal
    already answered.
  • It needs lib.nvim's ui.kit; :checkhealth insights reports a
    missing or outdated lib.nvim.

Unimported reports a component that is imported

  • The check is textual and single-file: it looks for an import line or a
    local declaration binding that exact name. An import generated at build
    time or injected globally is invisible to it — add such names to
    unimported.ignore.

Conflicts reports files that are not conflicted

  • Only stdout from git diff --diff-filter=U is parsed, so warnings on
    stderr are not treated as file names. If the list still looks wrong, run
    git diff --name-only --diff-filter=U yourself to compare.

keys

j / k
next / previous line
gg / G
first / last line
⏎
open the line under the cursor
/
search the plugins
:
command line — Tab completes
:help x
vimdoc of a plugin (:e x = plugin page)
:ls · :log · :stack
plugin list · activity stream · dependency graph
:colo x
colorscheme
:set skin=
modern | tui
?
this help
esc
close