Skip to content

Repository files navigation

Neovim Config (Minimal)

A flat, minimal Neovim setup focused on Python/Django with LSP, DAP, Treesitter, blink.cmp, and Conform. PyCharm-style UI: Darcula theme, editor tabs, statusline and gutter change stripes. Debugging also covers go, node, typescript and react.

Structure

init.lua                    # Main entry point
lua/
  core/                     # Core configuration
    options.lua
  plugins/                  # Modular plugin configurations
    init.lua                # Plugin loader
    core.lua                # Core plugins (treesitter, telescope, blink, conform)
    ui.lua                  # UI plugins (theme, bufferline, lualine, gitsigns, nvim-tree)
    lsp.lua                 # LSP plugins
    dap.lua                 # Debugging plugins
    tools.lua               # Utility plugins
  keymaps/                  # Categorized keymaps
    init.lua                # Keymap loader
    navigation.lua          # Window/tab navigation
    search.lua              # Telescope and search
    editing.lua             # Formatting and replace
    debug.lua               # Debugging keymaps
    git.lua                 # Git operations
    tools.lua               # Various tool keymaps
  lsp/                      # Enhanced LSP configuration
    init.lua                # Main LSP setup
    servers/                # Server-specific configurations
      python.lua            # pyright + ruff (native `ruff server`)
      lua.lua               # lua_ls
      typescript.lua        # ts_ls
      terraform.lua         # terraformls 0.11 compat shim
  autocmds/                 # Organized autocommands
    init.lua                # Autocmd loader
    filetypes.lua           # Filetype-specific settings
    session.lua             # Session management
    ui.lua                  # UI-related autocommands
  config/                   # Plugin setup bodies (see the rule below)
    blink.lua               # blink.cmp completion
    telescope.lua           # Telescope
    treesitter.lua          # Treesitter
    python_hl.lua           # Python-specific highlights
    conform.lua             # Formatters
    dap.lua                 # DAP adapters and launch configs
    dapui.lua               # DAP UI
    session.lua             # auto-session
queries/
  html_tags/injections.scm  # replaces the bundled query: <script> injects tsx
scripts/
  install.sh                # From-scratch installer (deps, config, dotfiles, plugins)
  brew-export.sh            # Dump installed Homebrew packages to system/Brewfile
  brew-import.sh            # Install everything from system/Brewfile
  mac_packs.sh, macos.sh    # macOS packages and defaults
  display.sh                # External monitor color profile (run by hand)
system/                     # Dotfiles and tool configs, detailed in system/README.md
  zshrc                     # Shell config (linked to ~/.zshrc)
  lazygit.yml               # Lazygit config (delta pager)
  Brewfile                  # Full machine package dump
tests/
  run.sh                    # luacheck + headless feature suite (63 checks)
  features.lua              # The checks: options, UI, LSP, DAP, keymaps, autocmds
  e2e.sh                    # Real-nvim TUI suite driven via tmux (24 checks)

Where a plugin's config goes

One rule, so a plugin is never configured in two places at once:

  • lua/plugins/*.lua holds the lazy spec (repo, lazy trigger, opts) and any config/init body up to about five lines.
  • lua/config/<plugin>.lua holds anything longer, exposing M.setup(), and the spec just calls it.
  • Autocmds that are not part of a plugin's own setup live in lua/autocmds/.

Ignoring this is how the *.dbout autocmd ended up registered twice.

Plugins

  • xiantang/darcula-dark.nvim (PyCharm Darcula theme)
  • akinsho/bufferline.nvim (editor tabs)
  • nvim-lualine/lualine.nvim (statusline)
  • lewis6991/gitsigns.nvim (gutter change stripes)
  • lazy.nvim
  • nvim-lspconfig, mason.nvim, mason-lspconfig.nvim
  • blink.cmp (completion, loads on InsertEnter/CmdlineEnter)
  • telescope.nvim (plenary.nvim, loads on demand)
  • nvim-treesitter
    • nvim-treesitter-context (scope context)
    • nvim-treesitter-textobjects (af/if/ac/ic, master branch)
    • indent-blankline.nvim (indent guides)
    • nvim-puppeteer (Python f-string auto-conversion)
  • nvim-tree (nvim-web-devicons)
  • conform.nvim
  • nvim-dap + nvim-dap-ui (nvim-nio) + mason-nvim-dap
  • auto-session
  • kulala.nvim (HTTP client, pure Lua, no luarocks)
  • vim-dadbod + vim-dadbod-ui (SQL client)
  • markdown-preview.nvim (random free port)
  • github/copilot.vim (bundled language server, no npx)
  • vim-maximizer
  • f-person/git-blame.nvim (inline blame, 1s delay off the cursor path)

LSP

  • Servers ensured: pyright, ruff (native ruff server), lua_ls, ts_ls
  • mason-lspconfig v2 auto-enables every installed server, so automatic_enable in lua/lsp/init.lua is an explicit allowlist. Add a server there to use it.
  • lua/lsp/servers/*.lua must be required before mason-lspconfig.setup(): vim.lsp.enable() starts clients immediately, and a vim.lsp.config() call after that is ignored for the running client
  • Loads deferred on the first buffer, then re-fires nvim.lsp.enable so the file opened from the command line also attaches
  • Buffer-local LSP keymaps via LspAttach: gd, gr, gi, K, <leader>rn, <leader>ca. Global: <leader>gd (vsplit), <leader>gr (telescope)
  • terraformls: default on_attach disabled on nvim 0.11 (uses a 0.12-only API)

Treesitter

  • Context showing function/class scope at top of window
  • Textobjects: af/if (function), ac/ic (class)
  • Incremental selection: gnn, grn, grc, grm
  • Indent guides (indent-blankline)
  • Language parsers: lua, python, javascript, typescript, tsx, html, http, css, json, markdown, bash, vim, go, rust, ruby, toml, yaml, requirements, dockerfile, make, tmux
  • queries/html_tags/injections.scm replaces the bundled query so a bare <script> injects tsx (JSX highlights in plain .html). It has to live under html_tags/, not html/: html_tags is a base lang for html, so a file under queries/html/ can only add rules, never remove the javascript one

Keymaps

Leader is <Space>. Every map carries a desc, so :Telescope keymaps lists them with their descriptions. Debug keymaps have their own section further down.

Motions

Key Does Note
H / L go to line start / line end ^ and $ without the symbol keys
W / B word back / word forward deliberately swapped from vim's defaults
<leader>j / <leader>k down / up one screen line gj/gk, moves inside a wrapped line

Files, windows, tabs, buffers

Key Does Note
<leader>ww save
<leader>wq save and quit
<leader>qq quit and throw away changes :q!
<leader>ee file explorer on/off nvim-tree
<leader>ef reveal the current file in the explorer
<leader>sv / <leader>sh split vertical / horizontal
<leader>se equalize splits, in every tab
<leader>sm maximize the split, press again to restore vim-maximizer
<leader>to / <leader>tn / <leader>tp new / next / previous tab
<leader>bn / <leader>bp / <leader>bd next / previous / close buffer the tabs bufferline draws on top. Not on <Tab>: in a terminal that is the same byte as <C-i>, and it would kill jumplist-forward

Search

Key Does Note
<leader>ff find files includes gitignored files (no_ignore)
<leader>fg grep the whole project as you type e.g. type def index to land on the Django view
<leader>fb pick an open buffer
<leader>fo symbols of the current file column widths follow the window width

LSP

Key Does Note
gd / gr / gi / K definition / references / implementations / hover buffer-local, appear only after a server attaches
<leader>rn / <leader>ca rename / code action buffer-local
<leader>gd definition in a vertical split splits only after a result comes back, so a miss leaves no empty window
<leader>gr references in telescope

Diagnostics

Key Does Note
<leader>e float with the diagnostic under the cursor
<leader>E the same float, focusable enter it to yank the message
[d / ]d previous / next diagnostic
<leader>gp / <leader>gn same as [d / ]d

Editing

Key Does Note
<leader>f format the buffer (normal) or the selection (visual) conform.nvim, no LSP fallback
<leader>S replace the word under the cursor everywhere in the file fills :%s/\<word\>/word/gI and parks the cursor on the replacement: type the new text and press Enter
<leader>S (visual) same, using the selection

Git and tools

Key Does Note
<leader>gb inline git blame on/off
<leader>mp / <leader>mP markdown preview start / stop
<leader>rr run the HTTP request under the cursor kulala, in .http files
<leader>db database UI on/off dadbod-ui. Queries run with its own buffer-local <leader>S

Debugging

Works in python, go, javascript, typescript and both react filetypes. Any other filetype has no configuration, and <leader>dc says so instead of starting.

Breakpoint keymaps

Key Does Example
<leader>bb breakpoint on the current line, press again to remove
<leader>bc breakpoint that only stops when a condition holds asks for it, answer i == 3 to stop on the 4th pass of a loop
<leader>bl logpoint: prints instead of stopping asks for the message, answer double {value} and every call prints in the REPL with value filled in
<leader>ba put every breakpoint in the quickfix window :cclose to close it again
<leader>br delete every breakpoint

Session keymaps

Key Does Note
<leader>dc start, or continue when stopped the first press shows the numbered config menu for the filetype
<leader>dj step over
<leader>dk step into steps into the function being called on the current line
<leader>do step out back to the caller
<leader>dl run the last config again no menu
<leader>dt terminate the session UI closes with it
<leader>dd disconnect and close the UI
<leader>du show / hide the UI the session keeps running

Python only:

Key Does Note
<leader>df debug the pytest test under the cursor reads LSP symbols, so TestClass::test_method is resolved for a method
<leader>dp picker with every test in the file plus a "Manual" entry to type any pytest target

Commands, once the plugin has loaded: :DapEval (evaluate expressions in a scratch window), :DapToggleRepl, :DapPause, :DapRestartFrame, :DapShowLog.

A full run, start to finish

With a file that has a double() helper called inside a loop:

  1. <leader>bc on the loop line, answer i == 3
  2. <leader>bl on the first line of double, answer double {value}
  3. <leader>ba to confirm both are registered, :cclose
  4. <leader>dc, pick 1 (Launch file); the UI opens and execution stops on the loop only when i is 3
  5. read i and total in the Scopes panel
  6. <leader>dk steps into double, <leader>do comes back, <leader>dj moves one line
  7. <leader>dc runs to the end; the logpoint lines are waiting in the REPL
  8. <leader>dt ends it, <leader>dl runs the same config again without asking

The UI

Left column: scopes, watches, breakpoints. Right column: REPL and console. Both scale with the terminal width. It opens and closes with the session.

Inside any panel: <CR> expands, e edits a value, d removes an entry, r sends the entry to the REPL, o opens, t toggles. The watches panel is a prompt buffer: press i and type an expression such as total * 10. The REPL takes expressions the same way.

Adapters and configurations

  • mason-nvim-dap ensures debugpy (python), delve (go) and js-debug-adapter (node, typescript, react). They install on the first debug session, not at install time
  • Python: Launch file, Django runserver, Pytest file. The adapter uses mason's debugpy when installed, otherwise the python of the active venv
  • Go: Launch file, Launch package, Test package, Attach to process. delve gets a 20s initialize timeout because it compiles the program before answering
  • Node, typescript and react: Launch file, Attach to process, Attach to node port 9229, Launch Chrome on dev server (asks for the URL, default http://localhost:5173), Attach to Chrome port 9222. Launching a .ts file needs no ts-node or tsx: node 22.18+ strips the types itself
  • The same js-debug-adapter serves node and chrome. React components only stop on the chrome configs; the node ones cannot reach browser code
  • Known upstream noise: terminating a python session while it sits on a breakpoint makes debugpy SIGKILL the debuggee, and its adapter then exits 1, so nvim-dap warns. Letting the program finish, or terminating it while it runs, exits clean. delve and js-debug never do this

Sessions

  • Auto-restores sessions on startup (auto-session defaults)
  • NvimTree opens only if no session was restored

Formatting

  • <leader>f uses conform.nvim (no LSP fallback)
  • Formatters: Python ruff_format, Lua stylua, C clang_format, JS/TS/JSON/CSS/HTML/YAML/Markdown prettier (reads .prettierrc.json)
  • All of them come from mason; a formatter that is not installed is a silent no-op, so check with :ConformInfo if <leader>f seems to do nothing

Performance

  • Startup ~43ms: telescope and treesitter load on first use, not at boot. blink.cmp is the exception: lua/lsp/init.lua requires it so its plugin file registers LSP capabilities before any server starts, so it loads on the first buffer, not on InsertEnter
  • Unused providers disabled (python3, ruby, perl, node)
  • git-blame virtual text delayed 1s so it stays off the cursor path
  • No lazyredraw (Neovim marks it unsupported; it causes stutter)

Tests

./tests/run.sh    # luacheck, then 63 headless feature checks; nonzero on failure
./tests/e2e.sh    # real nvim TUI in tmux: real keystrokes, rendered screen,
                  # full debug session; requires tmux

Install

From scratch on a new machine (macOS or Ubuntu/Debian):

curl -fsSL https://raw.githubusercontent.com/iklobato/iklobato-nvim-config-python/main/scripts/install.sh | bash

Or from a local checkout:

git clone https://github.com/iklobato/iklobato-nvim-config-python.git ~/.config/nvim
~/.config/nvim/scripts/install.sh

Pass --no-dotfiles to install Neovim only and leave ~/.zshrc and lazygit untouched. Anything it replaces (~/.config/nvim, ~/.zshrc, lazygit config) is moved to a timestamped .bak_<date> first.

What it sets up:

  • Dependencies: Neovim 0.11+, git, Node 22+, ripgrep, python3, git-delta (lazygit pager), Meslo LG Nerd Font
  • Config: this repo at ~/.config/nvim
  • Dotfiles: system/zshrc~/.zshrc and system/lazygit.yml → the platform lazygit path, plus oh-my-zsh and the zsh-syntax-highlighting plugin the zshrc expects
  • Plugins: Lazy! sync headless, then mason installs pyright, ruff, lua_ls, ts_ls, stylua and debugpy. delve and js-debug-adapter are not installed here: mason-nvim-dap pulls them the first time you open a debug session

system/Brewfile is not installed by the script: it's a full machine dump. Use ./scripts/brew-import.sh if you want it.

After install, set your terminal font to "MesloLGS Nerd Font" so icons render.

Requirements

  • macOS or Ubuntu/Debian (other systems: install deps manually, then run the script)
  • Neovim 0.11+
  • Python 3 (for LSP and DAP)
  • Node.js 22.18+ and ripgrep (for Telescope, LSP servers, Copilot, and the node/typescript debugger)
  • Go (only to debug Go: mason builds delve with the local toolchain)
  • tmux (only for tests/e2e.sh)

About

My NVIM python configuration

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages