From Zero to Killer Neovim on Fedora 42 (Rust-Edition)

 If you want a vanilla-leaning Neovim that feels stock but has full language features, debugging, tests, Git, fuzzy search, completions, keybinding help, AI, statusline, spell-check, and developer fonts—this is it. We’ll use native LSP, Treesitter, lazy.nvim for plugin management, and a short, readable config. Rust is first-class; Python, TypeScript/CDK, and Bash get the same baseline.


 


1) System prerequisites (Fedora 42)

# Core editor + search tools + build deps
sudo dnf install -y neovim git ripgrep fd-find curl wget unzip cmake gcc-c++ make python3 python3-pip nodejs npm

# Rust toolchain via rustup (recommended)
sudo dnf install -y rustup
rustup toolchain install stable
rustup default stable
rustup component add rustfmt clippy

# rust-analyzer (prefer rustup component if available; else use Fedora package)
rustup component add rust-analyzer || sudo dnf install -y rust-analyzer

# Optional: shells/linters/formatters used later
sudo dnf install -y shellcheck shfmt

Neovim ships with a built-in LSP client, so we’ll not add external LSP frameworks. (Neovim)


2) Developer fonts (Nerd Font + ligatures)

Install a Nerd Font (icons for statusline/git signs/fuzzy UI). JetBrains Mono Nerd Font is a great default.

mkdir -p ~/.local/share/fonts ~/.config/fontconfig/conf.d
cd /tmp
# Download a Nerd Font release zip (choose JetBrainsMono Nerd Font from nerdfonts.com releases)
# Example (update the URL to the latest release if needed):
wget -O JBMNerd.zip "https://github.com/ryanoasis/nerd-fonts/releases/latest/download/JetBrainsMono.zip"
unzip JBMNerd.zip -d ~/.local/share/fonts
fc-cache -fv

For ligatures (JetBrains Mono already supports), set your terminal to use JetBrainsMono Nerd Font.


3) Directory layout

We’ll keep a clean, minimal layout:

~/.config/nvim/
├─ init.lua
└─ lua/
   └─ plugins/
      ├─ core.lua
      ├─ ui.lua
      ├─ lsp.lua
      ├─ cmp.lua
      ├─ treesitter.lua
      ├─ telescope.lua
      ├─ git.lua
      ├─ statusline.lua
      ├─ rust.lua
      ├─ dap.lua
      ├─ test.lua
      ├─ format.lua
      ├─ keys.lua
      └─ ai.lua

4) Bootstrap the plugin manager (lazy.nvim)

lazy.nvim is modern, fast, and dead simple. (GitHub)

~/.config/nvim/init.lua

-- Bootstrap lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    "git","clone","--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", lazypath
  })
end
vim.opt.rtp:prepend(lazypath)

-- Sensible defaults: keep close to vanilla
vim.g.mapleader = " "        -- <Space> as leader
vim.o.number = true
vim.o.relativenumber = true
vim.o.termguicolors = true
vim.o.updatetime = 300
vim.o.signcolumn = "yes"
vim.o.clipboard = "unnamedplus"
vim.o.spell = true
vim.o.spelllang = "en_us"

-- Load plugins from lua/plugins/*.lua
require("lazy").setup("plugins", {
  checker = { enabled = true }, -- background plugin update checker off by default; toggle if you like
})

5) Core plugins (minimal, powerful)

Create ~/.config/nvim/lua/plugins/core.lua:

return {
  -- Keybinding helper popup
  { "folke/which-key.nvim", event = "VeryLazy", opts = {} },

  -- LSP, mason, and autoconfig
  { "neovim/nvim-lspconfig" },
  { "williamboman/mason.nvim", build = ":MasonUpdate", opts = {} },
  { "williamboman/mason-lspconfig.nvim", opts = {} },

  -- Completion
  { "hrsh7th/nvim-cmp" },
  { "hrsh7th/cmp-nvim-lsp" },
  { "hrsh7th/cmp-buffer" },
  { "hrsh7th/cmp-path" },
  { "L3MON4D3/LuaSnip", build = "make install_jsregexp", dependencies = { "saadparwaiz1/cmp_luasnip" } },

  -- Treesitter
  { "nvim-treesitter/nvim-treesitter", build = ":TSUpdate" },

  -- Telescope (fuzzy finder)
  { "nvim-lua/plenary.nvim" },
  { "nvim-telescope/telescope.nvim", tag = "0.1.6" },

  -- Git
  { "lewis6991/gitsigns.nvim", opts = {} },

  -- UI / statusline
  { "nvim-lualine/lualine.nvim" },

  -- Diagnostics drawer (optional, extremely useful)
  { "folke/trouble.nvim", opts = {} },

  -- Terminal for quick cargo/pytest/npm
  { "akinsho/toggleterm.nvim", version = "*", opts = {} },
}
  • lazy.nvim install notes and UI are here. (GitHub)


6) Treesitter (syntax, motions, folding)

lua/plugins/treesitter.lua

return {
  "nvim-treesitter/nvim-treesitter",
  opts = {
    ensure_installed = { "lua", "rust", "python", "tsx", "typescript", "json", "bash", "toml", "yaml", "markdown" },
    highlight = { enable = true },
    indent = { enable = true },
  },
}

7) Completion (nvim-cmp)

lua/plugins/cmp.lua

return {
  "hrsh7th/nvim-cmp",
  event = "InsertEnter",
  dependencies = { "hrsh7th/cmp-nvim-lsp", "hrsh7th/cmp-buffer", "hrsh7th/cmp-path", "L3MON4D3/LuaSnip", "saadparwaiz1/cmp_luasnip" },
  config = function()
    local cmp = require("cmp")
    local luasnip = require("luasnip")
    cmp.setup({
      snippet = { expand = function(args) luasnip.lsp_expand(args.body) end },
      mapping = cmp.mapping.preset.insert({
        ["<C-Space>"] = cmp.mapping.complete(),
        ["<CR>"] = cmp.mapping.confirm({ select = true }),
        ["<Tab>"] = cmp.mapping.select_next_item(),
        ["<S-Tab>"] = cmp.mapping.select_prev_item(),
      }),
      sources = cmp.config.sources({
        { name = "nvim_lsp" }, { name = "luasnip" }, { name = "path" }, { name = "buffer" },
      }),
    })
  end,
}

8) Fuzzy search (Telescope)

lua/plugins/telescope.lua

return {
  "nvim-telescope/telescope.nvim",
  dependencies = { "nvim-lua/plenary.nvim" },
  cmd = "Telescope",
  opts = {
    defaults = {
      mappings = { i = { ["<C-j>"] = "move_selection_next", ["<C-k>"] = "move_selection_previous" } },
    },
  },
}

Usage:

  • :Telescope find_files (respects .gitignore)

  • :Telescope live_grep (uses ripgrep)

  • :Telescope buffers, :Telescope help_tags


9) Git integration

lua/plugins/git.lua

return {
  "lewis6991/gitsigns.nvim",
  event = "BufReadPre",
  opts = {
    signs = { add = { text = "▎" }, change = { text = "▎" }, delete = { text = "契" }, topdelete = { text = "契" }, changedelete = { text = "▎" } },
    on_attach = function(bufnr)
      local gs = package.loaded.gitsigns
      local map = function(m, l, r) vim.keymap.set(m, l, r, { buffer = bufnr }) end
      map("n", "]h", gs.next_hunk)
      map("n", "[h", gs.prev_hunk)
      map("n", "<leader>hs", gs.stage_hunk)
      map("n", "<leader>hr", gs.reset_hunk)
      map("n", "<leader>hb", gs.blame_line)
    end,
  },
}

10) Statusline

lua/plugins/statusline.lua

return {
  "nvim-lualine/lualine.nvim",
  dependencies = { "nvim-tree/nvim-web-devicons" },
  opts = {
    options = { theme = "auto", section_separators = "", component_separators = "" },
    sections = {
      lualine_a = { "mode" },
      lualine_b = { "branch", "diff", "diagnostics" },
      lualine_c = { { "filename", path = 1 } },
      lualine_x = { "encoding", "fileformat", "filetype" },
      lualine_y = { "progress" },
      lualine_z = { "location" },
    },
  },
}

11) Keybinding help

which-key.nvim pops up a cheatsheet when you press <Space>. It’s minimal and perfect for discoverability. Add custom top-level hints in lua/plugins/keys.lua:

return {
  "folke/which-key.nvim",
  opts = function()
    local wk = require("which-key")
    wk.add({
      { "<leader>f", group = "Find (Telescope)" },
      { "<leader>g", group = "Git" },
      { "<leader>l", group = "LSP" },
      { "<leader>t", group = "Test" },
      { "<leader>d", group = "Debug" },
      { "<leader>a", group = "AI" },
    })
  end,
}

12) LSP servers via Mason (one command per language)

lua/plugins/lsp.lua

return {
  "neovim/nvim-lspconfig",
  dependencies = { "williamboman/mason.nvim", "williamboman/mason-lspconfig.nvim" },
  config = function()
    local lspconfig = require("lspconfig")
    local capabilities = require("cmp_nvim_lsp").default_capabilities()

    require("mason").setup()
    require("mason-lspconfig").setup({
      ensure_installed = {
        -- Rust handled by rustaceanvim (below), but mason can still manage tools
        "pyright",                   -- Python
        "tsserver",                  -- TypeScript/JavaScript
        "bashls",                    -- Bash
        "jsonls", "yamlls", "lua_ls"
      },
      automatic_installation = true,
    })

    -- Python
    lspconfig.pyright.setup({ capabilities = capabilities })
    -- TypeScript / JS
    lspconfig.tsserver.setup({ capabilities = capabilities })
    -- Bash
    lspconfig.bashls.setup({ capabilities = capabilities })
    -- Others
    lspconfig.jsonls.setup({ capabilities = capabilities })
    lspconfig.yamlls.setup({ capabilities = capabilities })
    lspconfig.lua_ls.setup({
      capabilities = capabilities,
      settings = { Lua = { diagnostics = { globals = { "vim" } } } },
    })

    -- LSP keymaps (global)
    local map = vim.keymap.set
    map("n","gd",vim.lsp.buf.definition,{desc="LSP: goto definition"})
    map("n","gr",vim.lsp.buf.references,{desc="LSP: references"})
    map("n","K",vim.lsp.buf.hover,{desc="LSP: hover"})
    map("n","<leader>lr",vim.lsp.buf.rename,{desc="LSP: rename"})
    map("n","<leader>la",vim.lsp.buf.code_action,{desc="LSP: code action"})
    map("n","<leader>lf",function() vim.lsp.buf.format({async=true}) end,{desc="LSP: format"})
  end,
}

13) Rust superpowers (rust-analyzer, inlay hints, code actions)

Use rustaceanvim—it auto-wires rust-analyzer with Neovim’s LSP and integrates tools; don’t double-configure rust_analyzer in lspconfig. (GitHub)

lua/plugins/rust.lua

return {
  "mrcjkb/rustaceanvim",
  lazy = false, -- filetype plugin; loads on Rust buffers automatically
  init = function()
    -- Optional rust-analyzer settings
    vim.g.rustaceanvim = {
      server = {
        on_attach = function(_, bufnr)
          local map = function(m, l, r, d) vim.keymap.set(m, l, r, { buffer = bufnr, desc = d }) end
          map("n", "<leader>lc", function() vim.cmd.RustLsp("codeAction") end, "Rust: code action")
          map("n", "<leader>lh", function() vim.cmd.RustLsp("hover actions") end, "Rust: hover actions")
        end,
        settings = {
          ["rust-analyzer"] = {
            cargo = { allFeatures = true },
            checkOnSave = { command = "clippy" },
          },
        },
      },
    }
  end,
}

14) Debugging (nvim-dap + CodeLLDB)

We’ll use nvim-dap with codelldb. The wiki shows canonical config examples and options. (GitHub)

lua/plugins/dap.lua

return {
  { "mfussenegger/nvim-dap" },
  { "rcarriga/nvim-dap-ui", dependencies = { "mfussenegger/nvim-dap" } },
  { "jay-babu/mason-nvim-dap.nvim",
    dependencies = { "williamboman/mason.nvim", "mfussenegger/nvim-dap" },
    opts = { ensure_installed = { "codelldb", "python" }, automatic_installation = true },
    config = function(_, opts)
      require("mason-nvim-dap").setup(opts)
      local dap, dapui = require("dap"), require("dapui")
      dapui.setup()
      dap.listeners.after.event_initialized["dapui_config"] = function() dapui.open() end
      dap.listeners.before.event_terminated["dapui_config"] = function() dapui.close() end
      dap.listeners.before.event_exited["dapui_config"] = function() dapui.close() end
    end
  },
}

Usage:

  • Place breakpoints with F9 (map it if you want); start with :DapContinue.

  • For Rust, debug compiled binaries with codelldb. The dap wiki has a codelldb recipe. (GitHub)


15) Testing (neotest)

Use neotest framework + language adapters. It relies on Treesitter and works across Rust/Python/TS. (GitHub)

lua/plugins/test.lua

return {
  { "nvim-neotest/neotest" },
  { "rouge8/neotest-rust" },       -- Rust (cargo/nextest)
  { "nvim-neotest/neotest-python" },
  { "nvim-neotest/neotest-jest" }, -- or neotest-vitest for TS
  config = function()
    require("neotest").setup({
      adapters = {
        require("neotest-rust")({ args = { "--no-capture" } }), -- example args; customize
        require("neotest-python")({ runner = "pytest" }),
        require("neotest-jest")({ jestCommand = "npm test --" }),
      },
    })
    local map = vim.keymap.set
    map("n","<leader>tt", function() require("neotest").run.run() end, {desc="Test: nearest"})
    map("n","<leader>tf", function() require("neotest").run.run(vim.fn.expand("%")) end, {desc="Test: file"})
    map("n","<leader>ts", function() require("neotest").summary.toggle() end, {desc="Test: summary"})
    map("n","<leader>to", function() require("neotest").output.open({ enter = true }) end, {desc="Test: output"})
  end,
}
  • neotest-rust uses cargo nextest adapter; install with cargo install cargo-nextest if you want nextest. (GitHub)


16) Formatting and linting

Use built-ins where possible:

  • Rust: rustfmt & clippy (already installed via rustup).

  • Python: black (formatter), ruff (linter).

  • TS/JS: prettier (or prettierd), eslint.

  • Bash: shfmt, shellcheck.

A tiny on-save formatter:

lua/plugins/format.lua

return {
  "stevearc/conform.nvim",
  opts = {
    formatters_by_ft = {
      rust = { "rustfmt" },
      python = { "black" },
      javascript = { "prettier" },
      typescript = { "prettier" },
      typescriptreact = { "prettier" },
      json = { "prettier" },
      yaml = { "prettier" },
      bash = { "shfmt" },
      lua = { "stylua" },
    },
    format_on_save = { timeout_ms = 2000, lsp_fallback = true },
  },
}

Install tools:

# Python
pip3 install --user black ruff
# TypeScript/JS
sudo npm i -g prettier eslint typescript typescript-language-server
# Lua (optional)
sudo dnf install -y stylua
# Bash tools installed earlier (shellcheck, shfmt)

17) Diagnostics drawer

Optional but great: Trouble shows a tidy panel for LSP diagnostics, references, and more.

  • Toggle with :Trouble diagnostics

  • Lazy config already added; see core.lua.


18) Built-in spell and quick toggles

  • Spellcheck is on by default in init.lua.

  • Toggle quickly:

vim.keymap.set("n","<leader>us", function() vim.o.spell = not vim.o.spell end, { desc="UI: toggle spell" })

19) AI integration (OpenAI & local/free)

Two solid routes:

  1. OpenAI/ChatGPT inside Neovim: gp.nvim is powerful and minimal-deps. Set OPENAI_API_KEY and go. (GitHub)

  2. Local model with Ollama: avante.nvim can be wired to local providers; community docs show Ollama setups. Results vary by model; still evolving. (GitHub)

lua/plugins/ai.lua

return {
  -- Option A: OpenAI via gp.nvim (great with a ChatGPT sub using API)
  {
    "Robitx/gp.nvim",
    cmd = { "GpChatNew", "GpAppend", "GpRewrite", "GpWhisper" },
    config = function()
      require("gp").setup({
        providers = {
          openai = { -- also supports Azure, Anthropic, Ollama, etc.
            endpoint = "https://api.openai.com/v1/chat/completions",
            secret = os.getenv("OPENAI_API_KEY"),
            model = "gpt-4o-mini", -- pick your paid model
          },
        },
      })
      vim.keymap.set("v","<leader>aa", ":GpAppend<CR>", { desc = "AI: append selection" })
      vim.keymap.set("v","<leader>ar", ":GpRewrite<CR>", { desc = "AI: rewrite selection" })
      vim.keymap.set("n","<leader>ac", ":GpChatNew<CR>", { desc = "AI: new chat" })
    end,
  },

  -- Option B: local models with Ollama (Avante)
  {
    "yetone/avante.nvim",
    enabled = false, -- flip to true if you want this path
    config = function()
      require("avante").setup({
        provider = "openai_compatible", -- point to your local endpoint (e.g., Ollama via an OpenAI shim)
        openai_compatible = { endpoint = "http://localhost:11434/v1", model = "qwen2.5-coder" },
      })
    end,
  },
}

Notes:

  • gp.nvim supports multiple providers, including local (Ollama) paths. (GitHub)

  • avante.nvim is fast-moving; check its README/wiki for provider wiring details. (GitHub)


20) Bash, Python, TypeScript/CDK specifics

  • Bash: bash-language-server + shellcheck + shfmt already configured via mason and conform.

  • Python: pyright + black + ruff, debug with DAP (python adapter is ensured by mason-nvim-dap config).

  • TypeScript/CDK: tsserver via typescript-language-server, format with Prettier, test via neotest-jest or neotest-vitest.

Mason installs/updates LSP servers easily from within Neovim:

:Mason

21) Minimal workflow cheatsheet

  • Find files / grep: <Space> f f:Telescope find_files, <Space> f g:Telescope live_grep

  • Git hunks: ]h / [h, stage/reset hunk <Space> h s / <Space> h r, blame <Space> h b

  • LSP: gd, gr, K, <Space> l r (rename), <Space> l a (code action), <Space> l f (format)

  • Diagnostics: :Trouble diagnostics

  • Debug: :DapContinue, :DapToggleBreakpoint, UI auto-opens

  • Tests: <Space> t t (nearest), <Space> t f (file), <Space> t s (summary)

  • AI: visual select → <Space> a a (append) / <Space> a r (rewrite); chat <Space> a c

  • Terminal: :ToggleTerm (run cargo test, pytest, npm test quickly)


22) Post-install: first launch and health

nvim

Inside Neovim:

:checkhealth
:Lazy
:Mason
  • Use :Lazy to ensure plugins are installed/updated (lazy.nvim docs). (lazy.folke.io)

  • For Rust, open a Rust project file—rustaceanvim auto-activates rust-analyzer; no extra LSP config needed. (GitHub)

  • For DAP, ensure codelldb is installed via :Mason → DAP tab; use the nvim-dap codelldb wiki snippet if you want more control. (GitHub)

  • For neotest adapters, see their READMEs for advanced options (e.g., cargo nextest flags). (GitHub)


23) Troubleshooting

  • Fedora Neovim version: sudo dnf info neovim. If you need the very latest (0.10+), you can build from source (Fedora Magazine article shows steps). (Fedora Magazine)

  • Rust debug troubles: verify codelldb path and see the nvim-dap codelldb wiki. Also consider examples discussed in issues/discussions. (GitHub)

  • AI plugins: gp.nvim requires only curl by default; set OPENAI_API_KEY. For local, follow avante.nvim provider notes and community guides; model quality and “apply edits” UX can vary by model. (GitHub)


24) Why this stack?

  • Vanilla-first: Built-in LSP + Treesitter. No over-the-top distributions or heavy keymap rewrites.

  • Lazy but powerful: lazy.nvim gives simple, declarative plugin specs. (GitHub)

  • Rust-centric: rustaceanvim is the modern Rust plugin; “don’t double-configure rust_analyzer.” (GitHub)

  • Serious tooling: nvim-dap + codelldb for debugging; neotest for unified test UX. (GitHub)


Done

Comments

Popular posts from this blog

Is Docker Still Relevant in 2025? A Practical Guide to Modern Containerization

Going In With Rust: The Interview Prep Guide for the Brave (or the Mad)

How to Set Up and Run a Minecraft Server with Forge and Mods on OCI Free Tier