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

2 hours ago 1

 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

Read Entire Article