Neovim: Setup, Configuration and Plugins Guide

Neovim: Setup, Configuration and Plugins Guide

Tested on: Neovim 0.10 · Ubuntu 26.04 LTS · Debian 12 · Arch Linux · Fedora 44 · Last updated: June 2026

Neovim is a hyperextensible, Lua-first terminal text editor with built-in LSP support, asynchronous plugin execution, and one of the most active open-source ecosystems in the editor space. Whether you're editing configs over SSH, building a full IDE-replacement for Python or Go, or just tired of reaching for the mouse, this guide gets you from zero to a productive, well-configured Neovim setup.

Practicing this on a live server, or need one to follow along? See our comparison of the best Linux VPS hosting providers for 2026 if you need a Linux box to work on.

Contents
  1. Prerequisites
  2. Install Neovim
  3. Modes and Essential Motions
  4. Configuration Structure
  5. init.lua: Core Settings
  6. Plugin Manager: lazy.nvim
    1. Further Reading

Prerequisites

  • Basic terminal familiarity: you can navigate directories and run commands
  • git installed (required for plugin manager bootstrap)
  • For LSP features: language runtimes installed (node, python3, go as needed)
  • For Telescope live grep: ripgrep, install with sudo apt install ripgrep
  • A terminal with true color support (most modern terminals qualify: Alacritty, Kitty, WezTerm, iTerm2, Windows Terminal)

Install Neovim

The apt version on Ubuntu/Debian is frequently outdated: the PPA or AppImage method gives you a current stable release. Arch users always get the latest.

# Ubuntu / Debian - use the PPA for a current stable build:
sudo add-apt-repository ppa:neovim-ppa/stable
sudo apt update && sudo apt install neovim -y

# Alternative: AppImage works on any x86_64 Linux distro, no PPA needed:
curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.appimage
chmod +x nvim-linux-x86_64.appimage
sudo mv nvim-linux-x86_64.appimage /usr/local/bin/nvim

# Fedora:
sudo dnf install neovim python3-neovim -y

# Arch Linux (always latest stable):
sudo pacman -S neovim

# Verify your version:
nvim --version

Expected output:

NVIM v0.10.4
Build type: Release
LuaJIT 2.1.0-beta3

Set Neovim as your default editor so tools like git commit and sudoedit open it automatically:

echo 'export EDITOR=nvim' >> ~/.bashrc
echo 'export VISUAL=nvim' >> ~/.bashrc
echo 'alias vim=nvim' >> ~/.bashrc
source ~/.bashrc

Modes and Essential Motions

Neovim is a modal editor. Every key does something different depending on which mode you're in. This is what makes it fast, and what confuses beginners. There are four modes you'll use constantly:

  • Normal mode: The default. For navigation and commands. Press Esc from anywhere to return here.
  • Insert mode: For typing text. Enter with i (before cursor), a (after cursor), o (new line below), O (new line above).
  • Visual mode: For selecting text. v = character selection, V = line selection, Ctrl+v = block selection.
  • Command mode: For running editor commands. Enter with :. Examples: :w (save), :q (quit), :s/old/new/g (substitute).

The critical motions to internalize before anything else:

KeyAction
h j k lMove left / down / up / right
w / bJump forward / backward by word
0 / $Start / end of line
gg / GTop / bottom of file
5GJump to line 5
Ctrl+d / Ctrl+uScroll down / up half a page
dd / d$Delete whole line / delete to end of line
yy / pYank (copy) line / paste
u / Ctrl+rUndo / redo
ciwChange inner word (delete word, drop into insert)
%Jump to matching bracket/paren
/patternSearch forward; n / N = next / previous match
:w / :q / :wq / :q!Save / quit / save+quit / force quit

Run the built-in interactive tutorial, it takes about 30 minutes and covers everything above with practice exercises:

:Tutor

Configuration Structure

Neovim reads its config from ~/.config/nvim/. The entry point is init.lua. Splitting your config into modules under lua/ keeps things maintainable as it grows.

# Create the config directory layout:
mkdir -p ~/.config/nvim/lua/plugins

# Recommended structure:
~/.config/nvim/
├── init.lua              ← entry point, sources everything else
└── lua/
    ├── settings.lua      ← vim.opt settings
    ├── keymaps.lua       ← key bindings
    └── plugins/          ← one file per plugin config (optional)
        ├── lsp.lua
        ├── telescope.lua
        └── treesitter.lua

You can start with everything in a single init.lua and split it out later when it gets unwieldy. Both approaches work.

init.lua: Core Settings

This is a production-quality base config. Copy it verbatim and adjust to taste:

-- ~/.config/nvim/init.lua

-- ─── Leader key ──────────────────────────────────────────────────────────────
-- Set BEFORE loading plugins so plugin keymaps inherit it
vim.g.mapleader = " "
vim.g.maplocalleader = " "

-- ─── Editor behaviour ────────────────────────────────────────────────────────
vim.opt.number = true           -- absolute line numbers
vim.opt.relativenumber = true   -- relative numbers for fast jumping
vim.opt.cursorline = true       -- highlight current line
vim.opt.wrap = false            -- no line wrapping
vim.opt.scrolloff = 8           -- keep 8 lines visible above/below cursor
vim.opt.signcolumn = "yes"      -- always show sign column (prevents layout shift)
vim.opt.colorcolumn = "100"     -- visual ruler at column 100
vim.opt.termguicolors = true    -- full 24-bit colour support

-- ─── Indentation ─────────────────────────────────────────────────────────────
vim.opt.expandtab = true        -- spaces instead of tabs
vim.opt.shiftwidth = 4
vim.opt.tabstop = 4
vim.opt.softtabstop = 4
vim.opt.autoindent = true
vim.opt.smartindent = true

-- ─── Search ──────────────────────────────────────────────────────────────────
vim.opt.ignorecase = true       -- case-insensitive search...
vim.opt.smartcase = true        -- ...unless you type a capital
vim.opt.hlsearch = true
vim.opt.incsearch = true

-- ─── Files ───────────────────────────────────────────────────────────────────
vim.opt.swapfile = false
vim.opt.backup = false
vim.opt.undofile = true         -- persistent undo across sessions
vim.opt.undodir = vim.fn.stdpath("data") .. "/undodir"

-- ─── Splits ──────────────────────────────────────────────────────────────────
vim.opt.splitbelow = true       -- horizontal splits open below
vim.opt.splitright = true       -- vertical splits open to the right

-- ─── Key mappings ────────────────────────────────────────────────────────────
local map = vim.keymap.set

map("n", "<leader>w", ":w<CR>",          { desc = "Save file" })
map("n", "<leader>q", ":q<CR>",          { desc = "Quit" })
map("n", "<leader>x", ":x<CR>",          { desc = "Save and quit" })
map("n", "<Esc>",     ":nohlsearch<CR>", { desc = "Clear search highlight" })
map("n", "<leader>e", ":Explore<CR>",    { desc = "Open file explorer" })

-- Better window navigation (no need for Ctrl+w prefix):
map("n", "<C-h>", "<C-w>h", { desc = "Move to left split" })
map("n", "<C-j>", "<C-w>j", { desc = "Move to lower split" })
map("n", "<C-k>", "<C-w>k", { desc = "Move to upper split" })
map("n", "<C-l>", "<C-w>l", { desc = "Move to right split" })

-- Move selected lines up/down in visual mode:
map("v", "J", ":m '>+1<CR>gv=gv", { desc = "Move selection down" })
map("v", "K", ":m '<-2<CR>gv=gv", { desc = "Move selection up" })

Plugin Manager: lazy.nvim

lazy.nvim is the standard plugin manager for Neovim. It lazy-loads plugins by default (they load only when needed), supports lock files for reproducible installs, and has a clean UI for managing updates. Bootstrap it at the top of your init.lua, it clones itself on first launch if not present:

-- Add this block AFTER the settings above, in init.lua:

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)

require("lazy").setup({

  -- ── Colorscheme ──────────────────────────────────────────────────────
  {
    "catppuccin/nvim",
    name = "catppuccin",
    priority = 1000,   -- load before everything else
    config = function()
      vim.cmd.colorscheme("catppuccin-mocha")
    end,
  },

  -- ── Status line ──────────────────────────────────────────────────────
  {
    "nvim-lualine/lualine.nvim",
    config = function()
      require("lualine").setup({ options = { theme = "catppuccin" } })
    end,
  },

  -- ── File explorer ─────────────────────────────────────────────────────
  {
    "nvim-tree/nvim-tree.lua",
    config = function()
      require("nvim-tree").setup()
      vim.keymap.set("n", "t", ":NvimTreeToggle", { desc = "Toggle file tree" })
    end,
  },

  -- ── Fuzzy finder ─────────────────────────────────────────────────────
  {
    "nvim-telescope/telescope.nvim",
    dependencies = { "nvim-lua/plenary.nvim" },
    config = function()
      local t = require("telescope.builtin")
      vim.keymap.set("n", "ff", t.find_files,  { desc = "Find files" })
      vim.keymap.set("n", "fg", t.live_grep,   { desc = "Live grep" })
      vim.keymap.set("n", "fb", t.buffers,     { desc = "List buffers" })
      vim.keymap.set("n", "fh", t.help_tags,   { desc = "Search help" })
    end,
  },

  -- ── Syntax highlighting ───────────────────────────────────────────────
  {
    "nvim-treesitter/nvim-treesitter",
    build = ":TSUpdate",
    config = function()
      require("nvim-treesitter.configs").setup({
        ensure_installed = { "lua", "python", "bash", "go", "javascript", "typescript", "json", "yaml", "markdown" },
        highlight = { enable = true },
        indent    = { enable = true },
      })
    end,
  },

  -- ── LSP ──────────────────────────────────────────────────────────────
  { "neovim/nvim-lspconfig" },
  {
    "williamboman/mason.nvim",          -- language server installer
    config = function() require("mason").setup() end,
  },
  {
    "williamboman/mason-lspconfig.nvim", -- bridges mason + lspconfig
    dependencies = { "williamboman/mason.nvim", "neovim/nvim-lspconfig" },
    config = function()
      require("mason-lspconfig").setup({
        ensure_installed = { "pyright", "lua_ls", "bashls", "gopls", "ts_ls" },
        automatic


JM Carreira

JM Carreira

I’ve used Linux since 2008 and it has been my main system since 2021. When an article has a “Tested on” box, I ran the steps on my Lenovo Tiny. More about me and the test machine.

Go up

This site uses cookies for analytics and advertising (Google AdSense). By continuing to browse, you accept our use of cookies. Read our privacy policy