kickstart.nvim/ORGANIZATION.md

12 KiB

Neovim Configuration Organization Guide

Current Structure (What You Have)

~/.config/nvim/
├── init.lua                          # Main entry point (1200 lines - TOO LARGE!)
├── lua/
│   ├── custom/
│   │   └── plugins/
│   │       ├── init.lua             # Common plugins (all filetypes)
│   │       ├── flutter.lua          # Flutter-specific (lazy-loaded)
│   │       ├── python.lua           # Python-specific (lazy-loaded)
│   │       ├── svelte.lua           # Svelte-specific (lazy-loaded)
│   │       └── session.lua          # Session management
│   └── kickstart/
│       ├── health.lua
│       └── plugins/
│           ├── autopairs.lua
│           ├── debug.lua
│           ├── gitsigns.lua
│           ├── indent_line.lua
│           ├── lint.lua
│           └── neo-tree.lua

Problems with Current Setup

  1. init.lua is massive (1200 lines) - Should be split into modules
  2. LSP setup is duplicated - Language-specific LSP code is in init.lua instead of language files
  3. Unclear separation - What belongs in custom/ vs kickstart/?
  4. No clear loading strategy - Which plugins load when?

~/.config/nvim/
├── init.lua                          # THIN entry point (~50 lines)
│
├── lua/
│   ├── config/
│   │   ├── options.lua              # Vim options (set, opt)
│   │   ├── keymaps.lua              # Global keymaps
│   │   ├── autocmds.lua             # Global autocmds
│   │   └── lazy.lua                 # Lazy.nvim bootstrap
│   │
│   ├── plugins/
│   │   ├── core/
│   │   │   ├── ui.lua               # UI plugins (always loaded)
│   │   │   ├── editor.lua           # Editor enhancements (always loaded)
│   │   │   ├── git.lua              # Git tools (always loaded)
│   │   │   └── completion.lua       # Completion engine (always loaded)
│   │   │
│   │   ├── lsp/
│   │   │   ├── init.lua             # LSP infrastructure (mason, lspconfig)
│   │   │   ├── servers.lua          # General LSP servers (lua_ls, etc.)
│   │   │   └── keymaps.lua          # LSP keymaps (shared)
│   │   │
│   │   └── lang/
│   │       ├── python.lua           # Python: LSP + formatters + linters
│   │       ├── flutter.lua          # Flutter: LSP + debugging + tools
│   │       ├── svelte.lua           # Svelte: LSP + formatters
│   │       ├── go.lua               # Future: Go support
│   │       └── rust.lua             # Future: Rust support
│   │
│   └── util/
│       ├── lsp.lua                  # Shared LSP utilities
│       └── init.lua                 # Shared helper functions

How This Works: Lazy-Loading Strategy

1. Core Plugins (Always Loaded)

-- lua/plugins/core/editor.lua
return {
  { 'echasnovski/mini.pairs' },        -- Autopairs
  { 'folke/which-key.nvim' },          -- Keybinding helper
  { 'nvim-telescope/telescope.nvim' }, -- Fuzzy finder
}

2. LSP Infrastructure (Loaded Early)

-- lua/plugins/lsp/init.lua
return {
  'neovim/nvim-lspconfig',
  dependencies = { 'mason.nvim', 'mason-lspconfig.nvim' },
  config = function()
    -- Setup mason, but don't configure language servers here
    require('mason').setup()
    require('mason-lspconfig').setup()
  end,
}

3. Language-Specific (Lazy-Loaded by FileType)

-- lua/plugins/lang/python.lua
return {
  -- Mason tools
  {
    'WhoIsSethDaniel/mason-tool-installer.nvim',
    ft = 'python',
    config = function()
      require('mason-tool-installer').setup({
        ensure_installed = { 'pyright', 'ruff' }
      })
    end,
  },
  
  -- LSP setup
  {
    'neovim/nvim-lspconfig',
    ft = 'python',
    config = function()
      -- Start pyright via autocmd
      vim.api.nvim_create_autocmd('FileType', {
        pattern = 'python',
        callback = function(args)
          local capabilities = require('blink.cmp').get_lsp_capabilities()
          vim.lsp.start({
            name = 'pyright',
            cmd = { vim.fn.stdpath('data') .. '/mason/bin/pyright-langserver', '--stdio' },
            root_dir = vim.fs.root(args.buf, { 'pyproject.toml', 'setup.py', '.git' }),
            capabilities = capabilities,
          })
        end,
      })
    end,
  },
  
  -- Formatters
  {
    'stevearc/conform.nvim',
    ft = 'python',
    config = function()
      require('conform').formatters_by_ft.python = { 'ruff_format', 'ruff_organize_imports' }
    end,
  },
}

Migration Plan (Step-by-Step)

Phase 1: Extract Configuration from init.lua

-- NEW init.lua (50 lines instead of 1200!)
require('config.options')   -- Vim settings
require('config.keymaps')   -- Global keymaps
require('config.autocmds')  -- Global autocmds
require('config.lazy')      -- Bootstrap lazy.nvim and load plugins

Phase 2: Organize Plugins by Loading Strategy

Always Loaded (Core):

  • lua/plugins/core/ui.lua - colorscheme, statusline, bufferline
  • lua/plugins/core/editor.lua - telescope, which-key, autopairs, neo-tree
  • lua/plugins/core/git.lua - gitsigns, fugitive
  • lua/plugins/core/completion.lua - blink.cmp

Lazy-Loaded by FileType:

  • lua/plugins/lang/python.lua - ft = 'python'
  • lua/plugins/lang/flutter.lua - ft = { 'dart', 'flutter' }
  • lua/plugins/lang/svelte.lua - ft = 'svelte'

Lazy-Loaded by Command:

  • Debugging tools - cmd = { 'DapContinue', 'DapToggleBreakpoint' }
  • Session management - cmd = { 'SessionSave', 'SessionLoad' }

Phase 3: Fix LSP Loading Issue

Problem: You discovered that language-specific LSP configs in lazy-loaded files don't work because nvim-lspconfig is already loaded by init.lua.

Solution: Two approaches:

Option A: Centralized LSP Setup (Simpler)

-- lua/plugins/lsp/servers.lua
local M = {}

M.setup_python = function()
  -- Python LSP setup
end

M.setup_flutter = function()
  -- Flutter LSP setup  
end

-- Autocmds to trigger setup
vim.api.nvim_create_autocmd('FileType', {
  pattern = 'python',
  once = true,
  callback = M.setup_python,
})

Option B: Per-Language Setup (Cleaner but Complex)

-- lua/plugins/lang/python.lua
return {
  {
    'WhoIsSethDaniel/mason-tool-installer.nvim',
    ft = 'python',
    config = function()
      -- Install tools
      require('mason-tool-installer').setup({ ensure_installed = { 'pyright', 'ruff' }})
      
      -- Setup LSP via autocmd (since lspconfig is already loaded)
      vim.api.nvim_create_autocmd('FileType', {
        pattern = 'python',
        callback = function(args)
          require('util.lsp').start_server('pyright', args.buf, {
            settings = { python = { analysis = { typeCheckingMode = 'basic' }}}
          })
        end,
      })
    end,
  },
}

-- lua/util/lsp.lua (shared utility)
local M = {}

M.start_server = function(name, bufnr, opts)
  local clients = vim.lsp.get_clients({ bufnr = bufnr, name = name })
  if #clients > 0 then return end
  
  local capabilities = require('blink.cmp').get_lsp_capabilities()
  vim.lsp.start(vim.tbl_deep_extend('force', {
    name = name,
    capabilities = capabilities,
  }, opts))
end

return M

Loading Performance Best Practices

1. Use Lazy-Loading Triggers

-- ❌ BAD: Loads immediately on startup
{ 'some/plugin' }

-- ✅ GOOD: Loads only when needed
{ 'some/plugin', ft = 'python' }           -- When opening .py files
{ 'some/plugin', cmd = 'SomeCommand' }     -- When running :SomeCommand
{ 'some/plugin', keys = '<leader>x' }      -- When pressing <leader>x
{ 'some/plugin', event = 'VeryLazy' }      -- After startup (low priority)

2. Profile Your Startup Time

# Measure startup time
nvim --startuptime startup.log

# Find slow plugins
grep "sourcing" startup.log | sort -k2 -n

3. Use :Lazy to Monitor Loading

  • Green plugins = loaded
  • Gray plugins = not loaded yet (lazy)
  • See what triggered loading

~/.config/nvim/
├── init.lua                    # ~50 lines: require config modules
│
├── lua/
│   ├── config/
│   │   ├── options.lua        # set.number, opt.clipboard, etc.
│   │   ├── keymaps.lua        # Global keymaps only
│   │   ├── autocmds.lua       # Global autocmds only
│   │   └── lazy.lua           # Bootstrap lazy.nvim
│   │
│   ├── plugins/
│   │   ├── core/
│   │   │   ├── ui.lua         # Colorscheme, statusline, etc.
│   │   │   ├── editor.lua     # Telescope, which-key, autopairs
│   │   │   ├── git.lua        # Gitsigns, fugitive
│   │   │   └── completion.lua # blink.cmp
│   │   │
│   │   ├── lsp/
│   │   │   ├── init.lua       # Mason, lspconfig setup
│   │   │   ├── servers.lua    # lua_ls and other general servers
│   │   │   └── keymaps.lua    # LSP keymaps (on_attach)
│   │   │
│   │   └── lang/
│   │       ├── python.lua     # ft = 'python'
│   │       ├── flutter.lua    # ft = 'dart'
│   │       └── svelte.lua     # ft = 'svelte'
│   │
│   └── util/
│       ├── lsp.lua            # Shared LSP helpers
│       └── init.lua           # Shared utility functions
│
├── ORGANIZATION.md             # This file
├── MIGRATION.md                # Step-by-step migration guide
└── README.md                   # Your custom README

Key Principles

  1. Thin init.lua - Just require modules, no logic
  2. Separate concerns - Options, keymaps, autocmds, plugins
  3. Lazy-load everything possible - Use ft, cmd, keys, event
  4. Language files are independent - Each lang/ file is self-contained
  5. Share common code - Use util/ for helpers
  6. Profile regularly - Use :Lazy profile and --startuptime

Learning Resources

Understanding Neovim Configuration

  • :help lua-guide - Official Lua guide
  • :help options - All vim options
  • :help api - Lua API reference

Lazy-Loading

  • :help lazy.nvim - Lazy.nvim documentation
  • :Lazy profile - See what's slow
  • :Lazy - Interactive plugin manager

LSP

  • :help lsp - LSP overview
  • :help vim.lsp.start() - Start LSP servers
  • :LspInfo - See active LSP clients

Performance

  • nvim --startuptime startup.log - Measure startup
  • :profile start profile.log | profile func * | profile file * - Profile runtime

Next Steps

  1. Commit current working state (You're doing this now)
  2. Read through this guide to understand the concepts
  3. Try the migration in a branch (don't break your working config!)
  4. Migrate incrementally:
    • Step 1: Extract options/keymaps from init.lua
    • Step 2: Move plugins to core/ structure
    • Step 3: Refactor language-specific configs
  5. Test after each step - Make sure everything still works
  6. Profile before and after - Measure improvements

Questions to Consider

  1. Do you need kickstart/ folder anymore?

    • If you understand the code, merge it into your own structure
  2. How many languages will you support?

    • 3-5 languages: Current structure is fine
    • 10+ languages: Consider more sophisticated loading
  3. Do you want to share your config?

    • Yes: Document everything, make it modular
    • No: Optimize for your own workflow
  4. How often will you add new languages?

    • Frequently: Build a template system
    • Rarely: Current per-file approach works

Remember: This is YOUR config. Start with what works, refactor when you understand WHY you're refactoring. Don't cargo-cult someone else's structure!