Files
it-know-how/(Neo) Vim/neovim-setup.md
T
2026-04-01 13:35:27 +02:00

31 KiB

Neovim IDE Setup Guide

A comprehensive guide to setting up Neovim as a full-featured IDE for Python, Shell scripts, and more using Lua configuration.

Table of Contents

  1. Prerequisites
  2. Directory Structure
  3. Configuration Files
  4. Installing Language Servers
  5. Keybindings Reference
  6. First Launch

Prerequisites

Install Neovim (v0.9+)

Ubuntu/Debian:

sudo add-apt-repository ppa:neovim-ppa/unstable
sudo apt update
sudo apt install neovim

Fedora:

sudo dnf install neovim

Arch Linux:

sudo pacman -S neovim

macOS:

brew install neovim

Install Required Tools

# Node.js (for LSP servers via Mason)
curl -fsSL https://fnm.vercel.app/install | bash
source ~/.bashrc
fnm install --latest

# Python (for Python LSP and debugging)
pip install python-lsp-server debugpy

# ripgrep (for telescope live grep)
sudo apt install ripgrep  # Ubuntu
sudo dnf install ripgrep  # Fedora
sudo pacman -S ripgrep    # Arch
brew install ripgrep      # macOS

# git (for plugin installation)
sudo apt install git

Directory Structure

Create the following directory structure:

mkdir -p ~/.config/nvim/lua/plugins

Your final structure should look like:

~/.config/nvim/
├── init.lua
└── lua/
    └── plugins/
        ├── init.lua
        ├── lsp.lua
        ├── dap.lua
        ├── treesitter.lua
        ├── telescope.lua
        ├── nvimtree.lua
        ├── cmp.lua
        ├── toggleterm.lua
        └── formatter.lua

Configuration Files

~/.config/nvim/init.lua

Main entry point for Neovim configuration.

-- Leader key
vim.g.mapleader = " "
vim.g.maplocalleader = " "

-- General settings
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.tabstop = 4
vim.opt.shiftwidth = 4
vim.opt.expandtab = true
vim.opt.smartindent = true
vim.opt.wrap = false
vim.opt.scrolloff = 8
vim.opt.signcolumn = "yes"
vim.opt.termguicolors = true
vim.opt.cursorline = true

-- Enable clipboard
vim.opt.clipboard = "unnamedplus"

-- Search settings
vim.opt.ignorecase = true
vim.opt.smartcase = true
vim.opt.hlsearch = true

-- Split settings
vim.opt.splitright = true
vim.opt.splitbelow = true

-- Performance
vim.opt.updatetime = 50
vim.opt.timeoutlen = 300

-- Set colorscheme
vim.cmd.colorscheme "tokyonight"

-- Load plugins
require("plugins")

-- Keybindings
local keymap = vim.keymap.set

-- Better window navigation
keymap("n", "<C-h>", "<C-w>h", { desc = "Navigate left" })
keymap("n", "<C-j>", "<C-w>j", { desc = "Navigate down" })
keymap("n", "<C-k>", "<C-w>k", { desc = "Navigate up" })
keymap("n", "<C-l>", "<C-w>l", { desc = "Navigate right" })

-- Resize windows
keymap("n", "<C-Up>", ":resize -2<CR>", { desc = "Resize window up" })
keymap("n", "<C-Down>", ":resize +2<CR>", { desc = "Resize window down" })
keymap("n", "<C-Left>", ":vertical resize -2<CR>", { desc = "Resize window left" })
keymap("n", "<C-Right>", ":vertical resize +2<CR>", { desc = "Resize window right" })

-- Move text up/down
keymap("n", "<A-j>", ":m .+1<CR>==", { desc = "Move line down" })
keymap("n", "<A-k>", ":m .-2<CR>==", { desc = "Move line up" })
keymap("v", "<A-j>", ":m '>+1<CR>gv=gv", { desc = "Move selection down" })
keymap("v", "<A-k>", ":m '<-2<CR>gv=gv", { desc = "Move selection up" })

-- Stay in indent mode
keymap("v", "<", "<gv", { desc = "Indent left" })
keymap("v", ">", ">gv", { desc = "Indent right" })

-- Better paste
keymap("v", "p", '"_dP', { desc = "Paste without yanking" })

-- Clear highlights
keymap("n", "<leader>h", ":nohlsearch<CR>", { desc = "Clear search highlights" })

-- Save and quit
keymap("n", "<leader>w", ":w<CR>", { desc = "Save file" })
keymap("n", "<leader>q", ":q<CR>", { desc = "Quit" })
keymap("n", "<leader>Q", ":qa!<CR>", { desc = "Force quit all" })

-- Toggle options
keymap("n", "<leader>tn", ":set nu!<CR>", { desc = "Toggle line numbers" })
keymap("n", "<leader>tr", ":set rnu!<CR>", { desc = "Toggle relative numbers" })

~/.config/nvim/lua/plugins/init.lua

Plugin manager setup using lazy.nvim.

-- 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)

-- Plugin specifications
require("lazy").setup({
  -- Theme
  { "folke/tokyonight.nvim", lazy = false },

  -- Plugin manager
  { "folke/lazy.nvim", version = "*" },

  -- LSP
  { "neovim/nvim-lspconfig" },
  { "williamboman/mason.nvim" },
  { "williamboman/mason-lspconfig.nvim" },

  -- Autocomplete
  { "hrsh7th/nvim-cmp" },
  { "hrsh7th/cmp-nvim-lsp" },
  { "hrsh7th/cmp-buffer" },
  { "hrsh7th/cmp-path" },
  { "hrsh7th/cmp-cmdline" },
  { "L3MON4D3/LuaSnip" },
  { "saadparwaiz1/cmp_luasnip" },

  -- Treesitter (syntax)
  { "nvim-treesitter/nvim-treesitter", build = ":TSUpdate" },
  { "nvim-treesitter/nvim-treesitter-textobjects" },

  -- Fuzzy finder
  { "nvim-telescope/telescope.nvim", dependencies = { "nvim-lua/plenary.nvim" } },

  -- File tree
  { "nvim-tree/nvim-tree.lua" },
  { "nvim-tree/nvim-web-devicons" },

  -- Terminal
  { "akinsho/toggleterm.nvim" },

  -- Debugging
  { "mfussenegger/nvim-dap" },
  { "rcarriga/nvim-dap-ui" },
  { "theHamsta/nvim-dap-virtual-text" },

  -- Formatters/Linters
  { "nvimtools/none-ls.nvim" },
  { "nvim-lua/plenary.nvim" },

  -- Utility
  { "windwp/nvim-autopairs" },
  { "numToStr/Comment.nvim" },
})

~/.config/nvim/lua/plugins/lsp.lua

LSP configuration for Python and Shell scripts.

-- Setup Mason (LSP installer)
require("mason").setup({
  ui = {
    border = "rounded",
    icons = {
      package_installed = "✓",
      package_pending = "➜",
      package_uninstalled = "✗",
    },
  },
  log_level = vim.log.levels.INFO,
  max_concurrent_installers = 4,
})

-- LSP config
local lspconfig = require("lspconfig")
local cmp_nvim_lsp = require("cmp_nvim_lsp")

-- Capabilities for autocomplete
local capabilities = cmp_nvim_lsp.default_capabilities()

-- Diagnostic settings
vim.diagnostic.config({
  virtual_text = true,
  signs = true,
  underline = true,
  update_in_insert = false,
  severity_sort = true,
  float = {
    border = "rounded",
    source = true,
  },
})

-- Signs for diagnostics
local signs = { Error = " ", Warn = " ", Hint = "󰠠 ", Info = " " }
for type, icon in pairs(signs) do
  local hl = "DiagnosticSign" .. type
  vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = "" })
end

-- LSP handlers
vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(vim.lsp.handlers.hover, {
  border = "rounded",
})

vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(vim.lsp.handlers.signature_help, {
  border = "rounded",
})

-- LSP Keybindings
local function lsp_keymaps(bufnr)
  local opts = { buffer = bufnr, silent = true }

  vim.keymap.set("n", "gd", vim.lsp.buf.definition, opts)
  vim.keymap.set("n", "gD", vim.lsp.buf.declaration, opts)
  vim.keymap.set("n", "gi", vim.lsp.buf.implementation, opts)
  vim.keymap.set("n", "gr", vim.lsp.buf.references, opts)
  vim.keymap.set("n", "K", vim.lsp.buf.hover, opts)
  vim.keymap.set("n", "<leader>rn", vim.lsp.buf.rename, opts)
  vim.keymap.set("n", "<leader>ca", vim.lsp.buf.code_action, opts)
  vim.keymap.set("n", "<leader>e", vim.diagnostic.open_float, opts)
  vim.keymap.set("n", "[d", vim.diagnostic.goto_prev, opts)
  vim.keymap.set("n", "]d", vim.diagnostic.goto_next, opts)
end

-- Python LSP
lspconfig.pyright.setup({
  capabilities = capabilities,
  on_attach = function(client, bufnr)
    lsp_keymaps(bufnr)
    -- Disable virtual text for python if needed
    -- client.server_capabilities.semanticTokensProvider = nil
  end,
  settings = {
    python = {
      analysis = {
        typeCheckingMode = "basic",
        autoSearchPaths = true,
        useLibraryCodeForTypes = true,
      },
    },
  },
})

-- Bash LSP
lspconfig.bashls.setup({
  capabilities = capabilities,
  on_attach = function(client, bufnr)
    lsp_keymaps(bufnr)
  end,
})

-- HTML, CSS, JSON (optional)
lspconfig.html.setup({
  capabilities = capabilities,
  on_attach = lsp_keymaps,
})

lspconfig.jsonls.setup({
  capabilities = capabilities,
  on_attach = lsp_keymaps,
})

-- Lua LSP (for neovim config editing)
lspconfig.lua_ls.setup({
  capabilities = capabilities,
  on_attach = function(client, bufnr)
    lsp_keymaps(bufnr)
  end,
  settings = {
    Lua = {
      runtime = {
        version = "LuaJIT",
      },
      diagnostics = {
        globals = { "vim" },
      },
      workspace = {
        library = vim.api.nvim_get_runtime_file("", true),
        checkThirdParty = false,
      },
      telemetry = {
        enable = false,
      },
    },
  },
})

-- Setup Mason LSP config
require("mason-lspconfig").setup({
  ensure_installed = {
    "pyright",
    "bashls",
    "html",
    "jsonls",
    "lua_ls",
  },
  handlers = {
    function(server_name)
      require("lspconfig")[server_name].setup({
        capabilities = capabilities,
      })
    end,
  },
})

~/.config/nvim/lua/plugins/dap.lua

Debug Adapter Protocol configuration for debugging Python.

local dap = require("dap")
local dapui = require("dapui")

-- Setup DAP UI
dapui.setup({
  icons = { expanded = "▾", collapsed = "▸", current_frame = "▸" },
  mappings = {
    expand = { "<CR>", "<2-LeftMouse>" },
    open = "o",
    remove = "d",
    edit = "e",
    repl = "r",
    toggle = "t",
  },
  expand_lines = vim.fn.has("nvim-0.7") == 1,
  layouts = {
    {
      elements = {
        { id = "scopes", size = 0.25 },
        { id = "breakpoints", size = 0.25 },
        { id = "stacks", size = 0.25 },
        { id = "watches", size = 0.25 },
      },
      size = 0.25,
      position = "right",
    },
    {
      elements = {
        { id = "repl", size = 0.5 },
        { id = "console", size = 0.5 },
      },
      position = "bottom",
      size = 0.25,
    },
  },
  floating = {
    max_height = nil,
    max_width = nil,
    border = "rounded",
    mappings = {
      close = { "q", "<Esc>" },
    },
  },
  windows = { indent = 1 },
})

-- Open DAP UI automatically
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

-- Python debug adapter configuration
 dap.configurations.python = {
  {
    type = "python",
    request = "launch",
    name = "Launch file",
    program = "${file}",
    pythonPath = function()
      return vim.fn.exepath("python3") or "python"
    end,
  },
  {
    type = "python",
    request = "launch",
    name = "Debug current file (integrated terminal)",
    program = "${file}",
    console = "integratedTerminal",
    pythonPath = function()
      return vim.fn.exepath("python3") or "python"
    end,
  },
  {
    type = "python",
    request = "launch",
    name = "Debug with arguments",
    program = "${file}",
    args = function()
      local args = vim.fn.input("Args: ", ""):split(" ")
      return args
    end,
    console = "integratedTerminal",
    pythonPath = function()
      return vim.fn.exepath("python3") or "python"
    end,
  },
}

-- DAP keybindings
vim.keymap.set("n", "<leader>db", dap.toggle_breakpoint, { desc = "Toggle breakpoint" })
vim.keymap.set("n", "<leader>dc", dap.continue, { desc = "Continue" })
vim.keymap.set("n", "<leader>ds", dap.step_over, { desc = "Step over" })
vim.keymap.set("n", "<leader>di", dap.step_into, { desc = "Step into" })
vim.keymap.set("n", "<leader>do", dap.step_out, { desc = "Step out" })
vim.keymap.set("n", "<leader>dr", dap.repl.open, { desc = "Open REPL" })
vim.keymap.set("n", "<leader>dl", dap.run_last, { desc = "Run last debug config" })
vim.keymap.set("n", "<leader>du", dapui.toggle, { desc = "Toggle DAP UI" })

~/.config/nvim/lua/plugins/treesitter.lua

Treesitter configuration for syntax highlighting.

require("nvim-treesitter.configs").setup({
  ensure_installed = {
    "python",
    "bash",
    "lua",
    "html",
    "css",
    "javascript",
    "typescript",
    "json",
    "yaml",
    "markdown",
    "markdown_inline",
    "go",
    "rust",
    "c",
    "cpp",
  },
  sync_install = false,
  auto_install = true,
  highlight = {
    enable = true,
    additional_vim_regex_highlighting = false,
  },
  indent = {
    enable = true,
  },
  textobjects = {
    select = {
      enable = true,
      lookahead = true,
      keymaps = {
        ["af"] = "@function.outer",
        ["if"] = "@function.inner",
        ["ac"] = "@class.outer",
        ["ic"] = "@class.inner",
        ["aa"] = "@parameter.outer",
        ["ia"] = "@parameter.inner",
      },
    },
    move = {
      enable = true,
      set_jumps = true,
      goto_next_start = {
        ["]f"] = "@function.outer",
        ["]c"] = "@class.outer",
      },
      goto_next_end = {
        ["]F"] = "@function.outer",
        ["]C"] = "@class.outer",
      },
      goto_previous_start = {
        ["[f"] = "@function.outer",
        ["[c"] = "@class.outer",
      },
      goto_previous_end = {
        ["[F"] = "@function.outer",
        ["[C"] = "@class.outer",
      },
    },
    swap = {
      enable = true,
      swap_next = {
        ["<leader>sp"] = "@parameter.inner",
      },
      swap_previous = {
        ["<leader>sP"] = "@parameter.inner",
      },
    },
  },
})

~/.config/nvim/lua/plugins/telescope.lua

Telescope fuzzy finder configuration.

local telescope = require("telescope")
local actions = require("telescope.actions")

telescope.setup({
  defaults = {
    prompt_prefix = "  ",
    selection_caret = "  ",
    path_display = { "truncate" },
    sorting_strategy = "ascending",
    layout_strategy = "horizontal",
    layout_config = {
      horizontal = {
        prompt_position = "top",
        preview_width = 0.55,
        results_width = 0.8,
      },
      vertical = {
        mirror = false,
      },
      width = 0.87,
      height = 0.80,
      preview_cutoff = 120,
    },
    mappings = {
      i = {
        ["<C-n>"] = actions.cycle_history_next,
        ["<C-p>"] = actions.cycle_history_prev,
        ["<C-j>"] = actions.move_selection_next,
        ["<C-k>"] = actions.move_selection_previous,
        ["<C-c>"] = actions.close,
        ["<Down>"] = actions.move_selection_next,
        ["<Up>"] = actions.move_selection_previous,
        ["<CR>"] = actions.select_default,
        ["<C-x>"] = actions.select_horizontal,
        ["<C-v>"] = actions.select_vertical,
        ["<C-t>"] = actions.select_tab,
        ["<C-u>"] = actions.preview_scrolling_up,
        ["<C-d>"] = actions.preview_scrolling_down,
      },
      n = {
        ["<esc>"] = actions.close,
        ["<CR>"] = actions.select_default,
        ["<C-x>"] = actions.select_horizontal,
        ["<C-v>"] = actions.select_vertical,
        ["<C-t>"] = actions.select_tab,
        ["j"] = actions.move_selection_next,
        ["k"] = actions.move_selection_previous,
        ["H"] = actions.move_to_top,
        ["M"] = actions.move_to_middle,
        ["L"] = actions.move_to_bottom,
        ["gg"] = actions.move_to_top,
        ["G"] = actions.move_to_bottom,
        ["<C-u>"] = actions.preview_scrolling_up,
        ["<C-d>"] = actions.preview_scrolling_down,
      },
    },
  },
  pickers = {
    find_files = {
      theme = "dropdown",
      previewer = false,
    },
    live_grep = {
      theme = "ivy",
    },
    buffers = {
      theme = "dropdown",
      previewer = false,
    },
  },
})

-- Load telescope extensions
pcall(telescope.load_extension, "fzf")

-- Telescope keybindings
local builtin = require("telescope.builtin")
vim.keymap.set("n", "<leader>ff", builtin.find_files, { desc = "Find files" })
vim.keymap.set("n", "<leader>fg", builtin.live_grep, { desc = "Live grep" })
vim.keymap.set("n", "<leader>fb", builtin.buffers, { desc = "Find buffers" })
vim.keymap.set("n", "<leader>fh", builtin.help_tags, { desc = "Help tags" })
vim.keymap.set("n", "<leader>fr", builtin.oldfiles, { desc = "Recent files" })
vim.keymap.set("n", "<leader>fc", builtin.grep_string, { desc = "Grep string under cursor" })
vim.keymap.set("n", "<leader>fd", builtin.diagnostics, { desc = "Find diagnostics" })

~/.config/nvim/lua/plugins/nvimtree.lua

File tree configuration.

require("nvim-tree").setup({
  on_attach = function(bufnr)
    local api = require("nvim-tree.api")

    local function opts(desc)
      return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true }
    end

    vim.keymap.set("n", "<CR>", api.node.open.edit, opts("Open"))
    vim.keymap.set("n", "<Tab>", api.node.open.preview, opts("Open Preview"))
    vim.keymap.set("n", ">", api.node.navigate.sibling.next, opts("Next Sibling"))
    vim.keymap.set("n", "<", api.node.navigate.sibling.prev, opts("Previous Sibling"))
    vim.keymap.set("n", ".", api.node.run.cmd, opts("Run Command"))
    vim.keymap.set("n", "-", api.tree.change_root_to_parent, opts("Up"))
    vim.keymap.set("n", "a", api.fs.create, opts("Create File"))
    vim.keymap.set("n", "bd", api.node.mbx.trash, opts("Trash"))
    vim.keymap.set("n", "bn", api.fs.rename, opts("Rename"))
    vim.keymap.set("n", "bmv", api.fs.rename_basename, opts("Rename: Basename"))
    vim.keymap.set("n", "bt", api.fs.copy.node_to_clipboard, opts("Copy To Clipboard"))
    vim.keymap.set("n", "bp", api.fs.paste_from_clipboard, opts("Paste From Clipboard"))
    vim.keymap.set("n", "q", api.tree.close, opts("Close"))
    vim.keymap.set("n", "R", api.tree.reload, opts("Refresh"))
    vim.keymap.set("n", "f", api.live_filter.start, opts("Filter"))
    vim.keymap.set("n", "F", api.live_filter.clear, opts("Clean Filter"))
    vim.keymap.set("n", "g?", api.tree.toggle_help, opts("Help"))
    vim.keymap.set("n", "W", api.tree.collapse_all, opts("Collapse"))
    vim.keymap.set("n", "E", api.tree.expand_all, opts("Expand All"))
    vim.keymap.set("n", "S", api.node.search.manual, opts("Search"))
    vim.keymap.set("n", "U", api.tree.toggle_custom_filter, opts("Toggle Filter"))
    vim.keymap.set("n", "]", api.node.navigate.diagnostics.next, opts("Next Diagnostic"))
    vim.keymap.set("n", "[", api.node.navigate.diagnostics.prev, opts("Prev Diagnostic"))
  end,
  sort_by = "case_sensitive",
  view = {
    width = 30,
    hide_root_folder = false,
    side = "left",
    number = false,
    relativenumber = false,
    signcolumn = "yes",
  },
  renderer = {
    group_empty = true,
    indent_width = 1,
    indent_markers = {
      enable = true,
      inline_arrows = true,
      icons = {
        corner = "└",
        edge = "│",
        item = "│",
        bottom = "─",
        none = " ",
      },
    },
    icons = {
      webdev_colors = true,
      git_placement = "after",
      padding = " ",
      symlink_arrow = " ➛ ",
      show = {
        file = true,
        folder = true,
        folder_arrow = true,
        git = true,
      },
      glyphs = {
        default = " ",
        symlink = "S",
        bookmark = "B",
        folder = {
          arrow_closed = "▸",
          arrow_open = "▾",
          default = "D",
          open = "D",
          empty = "E",
          empty_open = "E",
          symlink = "S",
          symlink_open = "S",
        },
        git = {
          unstaged = "✗",
          staged = "✓",
          unmerged = "✦",
          renamed = "➜",
          untracked = "★",
          ignored = "◌",
        },
      },
    },
  },
  filters = {
    dotfiles = false,
    git_clean = false,
    no_buffer = false,
    custom = { "node_modules", ".cache", "__pycache__" },
    exclude = {},
  },
  actions = {
    use_system_clipboard = true,
    change_dir = {
      enable = true,
      global = false,
      restrict_above_cwd = false,
    },
    file_popup = {
      open_win_config = {
        col = 1,
        row = 1,
        relative = "cursor",
        border = "shadow",
        style = "minimal",
      },
    },
    open_file = {
      quit_on_open = false,
      resize_window = true,
      window_picker = {
        enable = true,
        chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
        exclude = {
          filetype = { "notify", "packer", "qf", "diff", "fugitive", "Outline" },
          buftype = { "terminal", "help" },
        },
      },
    },
    remove_file = {
      close_buffer = true,
    },
  },
  trash = {
    cmd = "trash",
  },
})

-- Nvim-tree keybindings
vim.keymap.set("n", "<leader>e", ":NvimTreeToggle<CR>", { desc = "Toggle file tree" })
vim.keymap.set("n", "<leader>o", ":NvimTreeFocus<CR>", { desc = "Focus file tree" })

~/.config/nvim/lua/plugins/cmp.lua

Autocomplete configuration.

local cmp = require("cmp")
local luasnip = require("luasnip")

require("luasnip.loaders.from_vscode").lazy_load()

local has_words_before = function()
  local line, col = unpack(vim.api.nvim_win_get_cursor(0))
  return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil
end

cmp.setup({
  snippet = {
    expand = function(args)
      luasnip.lsp_expand(args.body)
    end,
  },
  mapping = cmp.mapping.preset.insert({
    ["<C-b>"] = cmp.mapping.scroll_docs(-4),
    ["<C-f>"] = cmp.mapping.scroll_docs(4),
    ["<C-Space>"] = cmp.mapping.complete(),
    ["<C-e>"] = cmp.mapping.abort(),
    ["<CR>"] = cmp.mapping.confirm({ select = true }),
    ["<Tab>"] = cmp.mapping(function(fallback)
      if cmp.visible() then
        cmp.select_next_item()
      elseif luasnip.expand_or_jumpable() then
        luasnip.expand_or_jump()
      elseif has_words_before() then
        cmp.complete()
      else
        fallback()
      end
    end, { "i", "s" }),
    ["<S-Tab>"] = cmp.mapping(function(fallback)
      if cmp.visible() then
        cmp.select_prev_item()
      elseif luasnip.jumpable(-1) then
        luasnip.jump(-1)
      else
        fallback()
      end
    end, { "i", "s" }),
  }),
  sources = cmp.config.sources({
    { name = "nvim_lsp" },
    { name = "luasnip" },
    { name = "path" },
  }, {
    { name = "buffer", keyword_length = 3 },
  }),
  formatting = {
    fields = { "kind", "abbr", "menu" },
    format = function(entry, vim_item)
      local kind_icons = {
        Text = "󰉿",
        Method = "󰆧",
        Function = "󰊕",
        Constructor = "",
        Field = "󰇽",
        Variable = "󰀡",
        Class = "󰠱",
        Interface = "",
        Module = "",
        Property = "󰜢",
        Unit = "󰑭",
        Value = "󰎠",
        Enum = "",
        Keyword = "󰌋",
        Snippet = "",
        Color = "󰏘",
        File = "󰈙",
        Reference = "󰈇",
        Folder = "󰉋",
        EnumMember = "",
        Constant = "󰏿",
        Struct = "󰙅",
        Event = "",
        Operator = "󰆕",
        TypeParameter = "",
      }
      vim_item.kind = string.format("%s %s", kind_icons[vim_item.kind], vim_item.kind)
      vim_item.menu = ({
        nvim_lsp = "[LSP]",
        luasnip = "[Snip]",
        buffer = "[Buf]",
        path = "[Path]",
      })[entry.source.name]
      return vim_item
    end,
  },
  window = {
    completion = cmp.config.window.bordered(),
    documentation = cmp.config.window.bordered(),
  },
})

-- Command line completion
cmp.setup.cmdline(":", {
  mapping = cmp.mapping.preset.cmdline(),
  sources = cmp.config.sources({
    { name = "path" },
  }, {
    { name = "cmdline" },
  }),
})

cmp.setup.cmdline("/", {
  mapping = cmp.mapping.preset.cmdline(),
  sources = {
    { name = "buffer" },
  },
})

~/.config/nvim/lua/plugins/toggleterm.lua

Integrated terminal configuration.

require("toggleterm").setup({
  size = function(term)
    if term.direction == "horizontal" then
      return 15
    elseif term.direction == "vertical" then
      return vim.o.columns * 0.4
    end
  end,
  open_mapping = [[<c-\>]],
  hide_numbers = true,
  shade_filetypes = {},
  shade_terminals = true,
  shading_factor = 2,
  start_in_insert = true,
  insert_mappings = true,
  terminal_mappings = true,
  persist_size = true,
  direction = "float",
  close_on_exit = true,
  shell = vim.o.shell,
  float_opts = {
    border = "curved",
    winblend = 0,
    highlights = {
      border = "Normal",
      background = "Normal",
    },
  },
})

-- Terminal keybindings
local Terminal = require("toggleterm.terminal").Terminal

local lazygit = Terminal:new({
  cmd = "lazygit",
  hidden = true,
  direction = "float",
  float_opts = {
    border = "curved",
  },
})

function _lazygit_toggle()
  lazygit:toggle()
end

vim.keymap.set("n", "<leader>gg", "<cmd>lua _lazygit_toggle()<CR>", { desc = "Toggle lazygit" })
vim.keymap.set("n", "<leader>tf", "<cmd>ToggleTerm direction=float<CR>", { desc = "Toggle float terminal" })
vim.keymap.set("n", "<leader>th", "<cmd>ToggleTerm direction=horizontal<CR>", { desc = "Toggle horizontal terminal" })
vim.keymap.set("n", "<leader>tv", "<cmd>ToggleTerm direction=vertical<CR>", { desc = "Toggle vertical terminal" })

~/.config/nvim/lua/plugins/formatter.lua

Formatter and linter configuration using none-ls.

local null_ls = require("null-ls")
local formatting = null_ls.builtins.formatting
local diagnostics = null_ls.builtins.diagnostics

null_ls.setup({
  debug = false,
  sources = {
    -- Python
    formatting.black,
    formatting.isort,
    diagnostics.pylint,
    diagnostics.mypy,

    -- Shell
    formatting.shfmt,
    diagnostics.shellcheck,

    -- Lua
    formatting.stylua,

    -- JavaScript/TypeScript
    formatting.prettier,
    formatting.eslint_d,

    -- JSON/YAML
    formatting.jsonlint,
    formatting.yamllint,
  },
  on_attach = function(client, bufnr)
    if client.supports_method("textDocument/formatting") then
      vim.keymap.set("n", "<leader>f", function()
        vim.lsp.buf.format({ async = false })
      end, { buffer = bufnr, desc = "Format buffer" })
    end
  end,
})

-- Formatter keybindings
vim.keymap.set("n", "<leader>f", vim.lsp.buf.format, { desc = "Format buffer" })

Installing Language Servers

After launching Neovim for the first time with this configuration:

  1. Open Neovim: nvim
  2. Wait for lazy.nvim to install all plugins (this may take a few minutes)
  3. After plugins are installed, install language servers:
    • Press <leader>ms to open Mason (or run :Mason)
    • Install the following:
      • pyright (Python)
      • bash-language-server (Shell/Bash)
      • lua-language-server (Lua)
      • html-lsp (HTML)
      • json-lsp (JSON)
      • prettier (Formatting)
      • shellcheck (Shell linting)
      • shfmt (Shell formatting)
      • black (Python formatting)
      • mypy (Python type checking)

Keybindings Reference

General

Keybinding Action
<Space> Leader key
<leader>h Clear search highlights
<leader>w Save file
<leader>q Quit
<leader>tn Toggle line numbers
<leader>tr Toggle relative numbers

Window Navigation

Keybinding Action
<C-h> Navigate left
<C-j> Navigate down
<C-k> Navigate up
<C-l> Navigate right
<C-Up> Resize window up
<C-Down> Resize window down
<C-Left> Resize window left
<C-Right> Resize window right

Telescope (Fuzzy Finder)

Keybinding Action
<leader>ff Find files
<leader>fg Live grep
<leader>fb Find buffers
<leader>fh Help tags
<leader>fr Recent files
<leader>fc Grep string under cursor
<leader>fd Find diagnostics

NvimTree (File Tree)

Keybinding Action
<leader>e Toggle file tree
<leader>o Focus file tree

LSP

Keybinding Action
gd Go to definition
gD Go to declaration
gi Go to implementation
gr Go to references
K Hover
<leader>rn Rename
<leader>ca Code actions
<leader>e Show diagnostics
[d Previous diagnostic
]d Next diagnostic

Debugging (DAP)

Keybinding Action
<leader>db Toggle breakpoint
<leader>dc Continue
<leader>ds Step over
<leader>di Step into
<leader>do Step out
<leader>dr Open REPL
<leader>dl Run last debug config
<leader>du Toggle DAP UI

Terminal

Keybinding Action
<C-\> Toggle terminal
<leader>tf Toggle float terminal
<leader>th Toggle horizontal terminal
<leader>tv Toggle vertical terminal
<leader>gg Toggle lazygit

Treesitter (Textobjects)

Keybinding Action
af Select function (outer)
if Select function (inner)
ac Select class (outer)
ic Select class (inner)
aa Select parameter (outer)
ia Select parameter (inner)

First Launch

  1. Launch Neovim:

    nvim
    
  2. Wait for plugin installation:

    • Lazy.nvim will automatically install all plugins
    • You'll see progress in the statusline
  3. Install language servers:

    • Run :Mason or press <leader>ms
    • Install the servers listed above
  4. Verify installation:

    • For Python: Open a .py file and run :LspInfo
    • Run :checkhealth to see overall status
  5. Optional: Install additional tools:

    # For Python debugging
    pip install debugpy
    
    # For shell linting
    pip install shellcheck-py
    
    # For lazygit (optional but recommended)
    brew install lazygit  # macOS
    sudo apt install lazygit  # Ubuntu
    

Troubleshooting

LSP not starting

  • Run :LspInfo to check LSP status
  • Make sure language servers are installed via Mason

Treesitter highlighting not working

  • Run :TSUpdate to update/install parsers
  • Check :TSInstallInfo for available parsers

Telescope not working

  • Ensure ripgrep is installed
  • Check telescope log with :Telescope log

DAP not connecting

  • Ensure debugpy is installed: pip install debugpy
  • Check : DapLog for debug logs

Performance issues

  • Check startup time with :StartupTime
  • Disable unused plugins in lua/plugins/init.lua

This guide provides a complete Neovim IDE setup. Modify the configuration files to suit your preferences!