| 1 | -- smooth editing, LSP, Treesitter, Telescope, formatting, Git, terminals, and a few QoL boosts. |
| 2 | |
| 3 | --------------------------------------------------------------- |
| 4 | -- 0) Bootstrap lazy.nvim (plugin manager) |
| 5 | --------------------------------------------------------------- |
| 6 | local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" |
| 7 | if not vim.loop.fs_stat(lazypath) then |
| 8 | vim.fn.system({ |
| 9 | "git", "clone", "--filter=blob:none", |
| 10 | "https://github.com/folke/lazy.nvim.git", "--branch=stable", lazypath, |
| 11 | }) |
| 12 | end |
| 13 | vim.opt.rtp:prepend(lazypath) |
| 14 | |
| 15 | --------------------------------------------------------------- |
| 16 | -- 1) Core settings (Wayland-safe clipboard, sensible defaults) |
| 17 | --------------------------------------------------------------- |
| 18 | vim.g.mapleader = " " |
| 19 | vim.g.maplocalleader = "," |
| 20 | |
| 21 | local opt = vim.opt |
| 22 | opt.number = true |
| 23 | opt.relativenumber = true |
| 24 | opt.signcolumn = "yes" |
| 25 | opt.termguicolors = true |
| 26 | opt.cursorline = true |
| 27 | opt.wrap = false |
| 28 | opt.scrolloff = 5 |
| 29 | opt.sidescrolloff = 8 |
| 30 | opt.expandtab = true |
| 31 | opt.shiftwidth = 2 |
| 32 | opt.tabstop = 2 |
| 33 | opt.smartindent = true |
| 34 | opt.ignorecase = true |
| 35 | opt.smartcase = true |
| 36 | opt.incsearch = true |
| 37 | opt.splitbelow = true |
| 38 | opt.splitright = true |
| 39 | opt.updatetime = 300 |
| 40 | opt.timeoutlen = 400 |
| 41 | opt.undofile = true |
| 42 | opt.clipboard = "unnamedplus" -- uses wl-clipboard on Wayland if installed |
| 43 | |
| 44 | --------------------------------------------------------------- |
| 45 | -- 2) Plugins via lazy.nvim |
| 46 | --------------------------------------------------------------- |
| 47 | require("lazy").setup({ |
| 48 | -- UI + UX --------------------------------------------------- |
| 49 | { "folke/tokyonight.nvim", lazy = false, priority = 1000, opts = { style = "night" } }, |
| 50 | { "nvim-lualine/lualine.nvim", dependencies = { "nvim-tree/nvim-web-devicons" }, |
| 51 | opts = { options = { theme = "auto", globalstatus = true } } }, |
| 52 | { "folke/which-key.nvim", event = "VeryLazy", opts = {} }, |
| 53 | { "stevearc/dressing.nvim", event = "VeryLazy", opts = {} }, |
| 54 | { "rcarriga/nvim-notify", opts = { timeout = 2000 } }, |
| 55 | -- Removed noice.nvim for faster command execution |
| 56 | { "lukas-reineke/indent-blankline.nvim", main = "ibl", opts = {} }, |
| 57 | { "numToStr/Comment.nvim", opts = {} }, |
| 58 | { "kylechui/nvim-surround", event = "VeryLazy", opts = {} }, |
| 59 | |
| 60 | -- Files, search, projects ---------------------------------- |
| 61 | { "nvim-telescope/telescope.nvim", dependencies = { "nvim-lua/plenary.nvim" } }, |
| 62 | { "nvim-telescope/telescope-fzf-native.nvim", build = "make", cond = function() return vim.fn.executable("make") == 1 end }, |
| 63 | { "ahmedkhalf/project.nvim", |
| 64 | config = function() |
| 65 | require("project_nvim").setup({ |
| 66 | detection_methods = { "pattern", "lsp" }, |
| 67 | patterns = { ".git", "pyproject.toml", "package.json", "Makefile" } |
| 68 | }) |
| 69 | end |
| 70 | }, |
| 71 | { "stevearc/oil.nvim", opts = { view_options = { show_hidden = true } } }, |
| 72 | |
| 73 | -- Git ------------------------------------------------------- |
| 74 | { "lewis6991/gitsigns.nvim", opts = {} }, |
| 75 | { "kdheepak/lazygit.nvim", cmd = { "LazyGit" } }, |
| 76 | |
| 77 | -- Terminal & task runner ----------------------------------- |
| 78 | { "akinsho/toggleterm.nvim", version = "*", opts = { open_mapping = [[<C-`>]], direction = "float" } }, |
| 79 | { "stevearc/overseer.nvim", opts = {} }, |
| 80 | |
| 81 | -- Treesitter ------------------------------------------------ |
| 82 | { "nvim-treesitter/nvim-treesitter", build = ":TSUpdate", |
| 83 | config = function() |
| 84 | require("nvim-treesitter.configs").setup({ |
| 85 | ensure_installed = { |
| 86 | "bash", "c", "cpp", "lua", "python", "rust", "json", "yaml", "toml", |
| 87 | "html", "css", "javascript", "typescript", "markdown", "markdown_inline", |
| 88 | "make", "fish" |
| 89 | }, |
| 90 | highlight = { enable = true }, |
| 91 | indent = { enable = true }, |
| 92 | }) |
| 93 | end |
| 94 | }, |
| 95 | |
| 96 | -- LSP, format, lint ---------------------------------------- |
| 97 | { "williamboman/mason.nvim", opts = { ui = { border = "rounded" } } }, |
| 98 | { "williamboman/mason-lspconfig.nvim" }, |
| 99 | { "neovim/nvim-lspconfig" }, |
| 100 | { "stevearc/conform.nvim", opts = { |
| 101 | notify_on_error = false, |
| 102 | format_on_save = function(buf) |
| 103 | -- Disable on big files |
| 104 | local ok, stats = pcall(vim.loop.fs_stat, vim.api.nvim_buf_get_name(buf)) |
| 105 | if ok and stats and stats.size > 512 * 1024 then return end |
| 106 | return { timeout_ms = 2000, lsp_fallback = true } |
| 107 | end, |
| 108 | formatters_by_ft = { |
| 109 | lua = { "stylua" }, |
| 110 | python = { "ruff_format", "black" }, |
| 111 | sh = { "shfmt" }, bash = { "shfmt" }, zsh = { "shfmt" }, fish = { "fish_indent" }, |
| 112 | c = { "clang_format" }, cpp = { "clang_format" }, |
| 113 | javascript = { "prettier" }, typescript = { "prettier" }, |
| 114 | json = { "jq", "prettier" }, yaml = { "prettier" }, toml = { "taplo" }, |
| 115 | html = { "prettier" }, css = { "prettier" }, markdown = { "prettier" }, |
| 116 | }, |
| 117 | } |
| 118 | }, |
| 119 | |
| 120 | -- Debugging (optional, light defaults) --------------------- |
| 121 | { "mfussenegger/nvim-dap" }, |
| 122 | { "rcarriga/nvim-dap-ui", dependencies = { "mfussenegger/nvim-dap", "nvim-neotest/nvim-nio" } }, |
| 123 | }, { |
| 124 | install = { colorscheme = { "tokyonight" } }, |
| 125 | change_detection = { notify = false }, |
| 126 | }) |
| 127 | |
| 128 | --------------------------------------------------------------- |
| 129 | -- 3) Post-plugin config (LSP, Telescope, keymaps, etc.) |
| 130 | --------------------------------------------------------------- |
| 131 | -- Colorscheme |
| 132 | vim.cmd.colorscheme("tokyonight") |
| 133 | |
| 134 | -- Lualine |
| 135 | require("lualine").setup({}) |
| 136 | |
| 137 | -- Telescope |
| 138 | local telescope = require("telescope") |
| 139 | telescope.setup({ |
| 140 | defaults = { |
| 141 | mappings = { |
| 142 | i = { |
| 143 | ["<C-j>"] = "move_selection_next", |
| 144 | ["<C-k>"] = "move_selection_previous" |
| 145 | } |
| 146 | } |
| 147 | } |
| 148 | }) |
| 149 | pcall(telescope.load_extension, "fzf") |
| 150 | pcall(telescope.load_extension, "projects") |
| 151 | |
| 152 | -- Oil: simple file manager toggle |
| 153 | vim.keymap.set("n", "-", function() require("oil").toggle_float() end, { desc = "Oil file manager" }) |
| 154 | |
| 155 | -- ToggleTerm: float terminal with <C-`> |
| 156 | -- (Already mapped via opts. Add extra terminals if desired.) |
| 157 | |
| 158 | -- Overseer: tasks (build, run, test) |
| 159 | vim.keymap.set("n", "<leader>tt", ":OverseerToggle<CR>", { desc = "Toggle task list" }) |
| 160 | vim.keymap.set("n", "<leader>tr", ":OverseerRun<CR>", { desc = "Run a task" }) |
| 161 | |
| 162 | -- Gitsigns |
| 163 | require("gitsigns").setup() |
| 164 | |
| 165 | -- Mason + LSPConfig |
| 166 | require("mason").setup() |
| 167 | local mason_lspconfig = require("mason-lspconfig") |
| 168 | |
| 169 | -- Ensure these servers are installed |
| 170 | mason_lspconfig.setup({ |
| 171 | ensure_installed = { |
| 172 | "pyright", "lua_ls", "clangd", "rust_analyzer", "bashls", |
| 173 | "jsonls", "yamlls", "html", "cssls", "marksman", "taplo" |
| 174 | } |
| 175 | }) |
| 176 | |
| 177 | local lspconfig = require("lspconfig") |
| 178 | local capabilities = vim.lsp.protocol.make_client_capabilities() |
| 179 | |
| 180 | -- Pretty borders |
| 181 | local handlers = { |
| 182 | ["textDocument/hover"] = vim.lsp.with(vim.lsp.handlers.hover, { border = "rounded" }), |
| 183 | ["textDocument/signatureHelp"] = vim.lsp.with(vim.lsp.handlers.signature_help, { border = "rounded" }), |
| 184 | } |
| 185 | |
| 186 | -- on_attach: buffer-local mappings |
| 187 | local on_attach = function(_, bufnr) |
| 188 | local nmap = function(keys, func, desc) |
| 189 | vim.keymap.set("n", keys, func, { buffer = bufnr, desc = desc }) |
| 190 | end |
| 191 | nmap("gd", vim.lsp.buf.definition, "Goto Definition") |
| 192 | nmap("gr", vim.lsp.buf.references, "References") |
| 193 | nmap("gD", vim.lsp.buf.declaration, "Goto Declaration") |
| 194 | nmap("gi", vim.lsp.buf.implementation, "Goto Implementation") |
| 195 | nmap("K", vim.lsp.buf.hover, "Hover") |
| 196 | nmap("<leader>rn", vim.lsp.buf.rename, "Rename symbol") |
| 197 | nmap("<leader>ca", vim.lsp.buf.code_action, "Code Action") |
| 198 | nmap("<leader>fd", function() vim.lsp.buf.format({ async = true }) end, "Format buffer") |
| 199 | end |
| 200 | |
| 201 | -- Manual server setup (more compatible) |
| 202 | local servers = { |
| 203 | pyright = {}, |
| 204 | lua_ls = { |
| 205 | settings = { |
| 206 | Lua = { |
| 207 | diagnostics = { globals = { "vim" } }, |
| 208 | workspace = { checkThirdParty = false } |
| 209 | } |
| 210 | } |
| 211 | }, |
| 212 | clangd = {}, |
| 213 | rust_analyzer = {}, |
| 214 | bashls = {}, |
| 215 | jsonls = {}, |
| 216 | yamlls = {}, |
| 217 | html = {}, |
| 218 | cssls = {}, |
| 219 | marksman = {}, |
| 220 | taplo = {}, |
| 221 | } |
| 222 | |
| 223 | -- Setup each server |
| 224 | for server, config in pairs(servers) do |
| 225 | config.capabilities = capabilities |
| 226 | config.on_attach = on_attach |
| 227 | config.handlers = handlers |
| 228 | lspconfig[server].setup(config) |
| 229 | end |
| 230 | |
| 231 | -- DAP minimal sugar |
| 232 | local dap_ok, dapui = pcall(require, "dapui") |
| 233 | if dap_ok then |
| 234 | dapui.setup() |
| 235 | local dap = require("dap") |
| 236 | dap.listeners.after.event_initialized["dapui_config"] = function() dapui.open() end |
| 237 | dap.listeners.before.event_terminated["dapui_config"] = function() dapui.close() end |
| 238 | dap.listeners.before.event_exited["dapui_config"] = function() dapui.close() end |
| 239 | end |
| 240 | |
| 241 | --------------------------------------------------------------- |
| 242 | -- 4) Keymaps you'll actually use (and remember) |
| 243 | --------------------------------------------------------------- |
| 244 | local map = vim.keymap.set |
| 245 | -- Save, quit |
| 246 | map({"n","i","v"}, "<C-s>", function() vim.cmd("silent w") end, { desc = "Save" }) |
| 247 | map("n", "<leader>q", ":q<CR>", { desc = "Quit" }) |
| 248 | |
| 249 | -- Windows and tabs |
| 250 | map("n", "<leader>sv", ":vsplit<CR>", { desc = "Split vertical" }) |
| 251 | map("n", "<leader>sh", ":split<CR>", { desc = "Split horizontal" }) |
| 252 | map("n", "<leader>to", ":tabnew<CR>", { desc = "New tab" }) |
| 253 | |
| 254 | -- Telescope |
| 255 | map("n", "<leader>ff", function() require("telescope.builtin").find_files() end, { desc = "Find files" }) |
| 256 | map("n", "<leader>fg", function() require("telescope.builtin").live_grep() end, { desc = "Live grep" }) |
| 257 | map("n", "<leader>fb", function() require("telescope.builtin").buffers() end, { desc = "Buffers" }) |
| 258 | map("n", "<leader>fh", function() require("telescope.builtin").help_tags() end, { desc = "Help tags" }) |
| 259 | map("n", "<leader>fp", function() require("telescope").extensions.projects.projects() end, { desc = "Projects" }) |
| 260 | |
| 261 | -- Move lines (visual) |
| 262 | map("v", "J", ":m '>+1<CR>gv=gv") |
| 263 | map("v", "K", ":m '<-2<CR>gv=gv") |
| 264 | |
| 265 | -- Clear search |
| 266 | map("n", "<Esc>", ":noh<CR>", { silent = true }) |
| 267 | |
| 268 | -- Format |
| 269 | map("n", "<leader>f", function() require("conform").format({ lsp_fallback = true }) end, { desc = "Format file" }) |
| 270 | |
| 271 | --------------------------------------------------------------- |
| 272 | -- 5) Small QoL autocommands |
| 273 | --------------------------------------------------------------- |
| 274 | -- Restore cursor to last position |
| 275 | vim.api.nvim_create_autocmd("BufReadPost", { |
| 276 | callback = function() |
| 277 | local mark = vim.api.nvim_buf_get_mark(0, '"') |
| 278 | local lcount = vim.api.nvim_buf_line_count(0) |
| 279 | if mark[1] > 0 and mark[1] <= lcount then |
| 280 | pcall(vim.api.nvim_win_set_cursor, 0, mark) |
| 281 | end |
| 282 | end |
| 283 | }) |
| 284 | |
| 285 | -- Highlight on yank |
| 286 | vim.api.nvim_create_autocmd("TextYankPost", { |
| 287 | callback = function() vim.highlight.on_yank({ higroup = "IncSearch", timeout = 120 }) end |
| 288 | }) |
| 289 | |
| 290 | -- Wayland clipboard fix hint (if clipboard missing) |
| 291 | if vim.env.WAYLAND_DISPLAY and vim.o.clipboard ~= "unnamedplus" then |
| 292 | vim.notify("Tip: install 'wl-clipboard' and set clipboard=unnamedplus for system clipboard.", vim.log.levels.INFO) |
| 293 | end |
| 294 | |
| 295 | --------------------------------------------------------------- |
| 296 | -- 6) Add language tools via Mason: :Mason |
| 297 | -- Choose servers/formatters: pyright, ruff, lua_ls, clangd, rust_analyzer, bashls, |
| 298 | -- jsonls, yamlls, html, cssls, tsserver/ts_ls, marksman, taplo, fortls, etc. |
| 299 | -- Then enjoy: <leader>ff (files), <leader>fg (grep), <C-`> (terminal), - (Oil), |
| 300 | -- <leader>f (format), gd/gr/K, and :OverseerRun for project tasks. |
| 301 | --------------------------------------------------------------- |