diff --git a/.config/nvim/init.lua b/.config/nvim/init.lua index 4894456..4fdd4f4 100644 --- a/.config/nvim/init.lua +++ b/.config/nvim/init.lua @@ -1,13 +1,7 @@ --- Basic settings -vim.opt.number = true -vim.opt.relativenumber = true -vim.opt.mouse = 'a' -vim.opt.ignorecase = true -vim.opt.smartcase = true -vim.opt.tabstop = 4 -vim.opt.shiftwidth = 4 -vim.opt.expandtab = true -vim.opt.termguicolors = true +vim.g.mapleader = " " +vim.g.maplocalleader = "\\" --- Basic keymaps -vim.g.mapleader = ' ' +require("config.options") +require("config.keymaps") +require("config.autocmds") +require("config.lazy") diff --git a/.config/nvim/lazy-lock.json b/.config/nvim/lazy-lock.json new file mode 100644 index 0000000..c899993 --- /dev/null +++ b/.config/nvim/lazy-lock.json @@ -0,0 +1,18 @@ +{ + "Comment.nvim": { "branch": "master", "commit": "e30b7f2008e52442154b66f7c519bfd2f1e32acb" }, + "blink.cmp": { "branch": "main", "commit": "4b18c32adef2898f95cdef6192cbd5796c1a332d" }, + "catppuccin": { "branch": "main", "commit": "12c004cde3f36cb1d57242f1e6aac46b09a0e5b4" }, + "conform.nvim": { "branch": "master", "commit": "086a40dc7ed8242c03be9f47fbcee68699cc2395" }, + "gitsigns.nvim": { "branch": "main", "commit": "7c4faa3540d0781a28588cafbd4dd187a28ac6e3" }, + "lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" }, + "lazydev.nvim": { "branch": "main", "commit": "5231c62aa83c2f8dc8e7ba957aa77098cda1257d" }, + "lualine.nvim": { "branch": "master", "commit": "47f91c416daef12db467145e16bed5bbfe00add8" }, + "nvim-lint": { "branch": "master", "commit": "606b823a57b027502a9ae00978ebf4f5d5158098" }, + "nvim-lspconfig": { "branch": "master", "commit": "d8bf4c47385340ab2029402201d4d7c9f99f437a" }, + "nvim-treesitter": { "branch": "main", "commit": "5cb05e1b0fa3c469958a2b26f36b3fe930af221c" }, + "nvim-web-devicons": { "branch": "master", "commit": "737cf6c657898d0c697311d79d361288a1343d50" }, + "oil.nvim": { "branch": "master", "commit": "0fcc83805ad11cf714a949c98c605ed717e0b83e" }, + "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" }, + "telescope.nvim": { "branch": "0.1.x", "commit": "a0bbec21143c7bc5f8bb02e0005fa0b982edc026" }, + "which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" } +} diff --git a/.config/nvim/lua/config/autocmds.lua b/.config/nvim/lua/config/autocmds.lua new file mode 100644 index 0000000..d6735e7 --- /dev/null +++ b/.config/nvim/lua/config/autocmds.lua @@ -0,0 +1,9 @@ +local group = vim.api.nvim_create_augroup("user-autocmds", { clear = true }) + +vim.api.nvim_create_autocmd("TextYankPost", { + group = group, + desc = "Highlight on yank", + callback = function() + vim.highlight.on_yank() + end, +}) diff --git a/.config/nvim/lua/config/keymaps.lua b/.config/nvim/lua/config/keymaps.lua new file mode 100644 index 0000000..0fe264f --- /dev/null +++ b/.config/nvim/lua/config/keymaps.lua @@ -0,0 +1,10 @@ +local map = vim.keymap.set + +map("n", "", "nohlsearch", { desc = "Clear search highlight" }) +map("n", "w", "write", { desc = "Write buffer" }) +map("n", "q", "quit", { desc = "Quit window" }) + +map("n", "", "h", { desc = "Go to left window" }) +map("n", "", "j", { desc = "Go to lower window" }) +map("n", "", "k", { desc = "Go to upper window" }) +map("n", "", "l", { desc = "Go to right window" }) diff --git a/.config/nvim/lua/config/lazy.lua b/.config/nvim/lua/config/lazy.lua new file mode 100644 index 0000000..4b234be --- /dev/null +++ b/.config/nvim/lua/config/lazy.lua @@ -0,0 +1,37 @@ +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" + +if not vim.uv.fs_stat(lazypath) then + local repo = "https://github.com/folke/lazy.nvim.git" + local output = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", repo, lazypath }) + if vim.v.shell_error ~= 0 then + vim.api.nvim_echo({ + { "Failed to clone lazy.nvim:\n", "ErrorMsg" }, + { output, "WarningMsg" }, + }, true, {}) + os.exit(1) + end +end + +vim.opt.rtp:prepend(lazypath) + +require("lazy").setup({ + spec = { + { import = "plugins" }, + }, + install = { + colorscheme = { "habamax", "tokyonight" }, + }, + checker = { enabled = false }, + change_detection = { notify = false }, + performance = { + rtp = { + disabled_plugins = { + "gzip", + "tarPlugin", + "tohtml", + "tutor", + "zipPlugin", + }, + }, + }, +}) diff --git a/.config/nvim/lua/config/options.lua b/.config/nvim/lua/config/options.lua new file mode 100644 index 0000000..4f2239f --- /dev/null +++ b/.config/nvim/lua/config/options.lua @@ -0,0 +1,22 @@ +local opt = vim.opt + +opt.number = true +opt.relativenumber = true +opt.mouse = "a" +opt.ignorecase = true +opt.smartcase = true +opt.tabstop = 4 +opt.shiftwidth = 4 +opt.expandtab = true +opt.termguicolors = true +opt.signcolumn = "yes" +opt.updatetime = 250 +opt.timeoutlen = 300 +opt.splitright = true +opt.splitbelow = true +opt.scrolloff = 4 +opt.cursorline = true +opt.wrap = false +opt.undofile = true +opt.clipboard = "unnamedplus" +opt.completeopt = { "menu", "menuone", "noselect" } diff --git a/.config/nvim/lua/plugins/completion.lua b/.config/nvim/lua/plugins/completion.lua new file mode 100644 index 0000000..cf166aa --- /dev/null +++ b/.config/nvim/lua/plugins/completion.lua @@ -0,0 +1,40 @@ +return { + { + "saghen/blink.cmp", + version = "*", + opts = { + keymap = { + preset = "super-tab", + }, + appearance = { + nerd_font_variant = "mono", + }, + completion = { + accept = { + auto_brackets = { + enabled = true, + }, + }, + documentation = { + auto_show = false, + }, + ghost_text = { + enabled = true, + show_with_menu = false, + }, + menu = { + auto_show = false, + }, + }, + signature = { + enabled = true, + }, + sources = { + default = { "lsp", "path", "buffer" }, + }, + fuzzy = { + implementation = "prefer_rust_with_warning", + }, + }, + }, +} diff --git a/.config/nvim/lua/plugins/editor.lua b/.config/nvim/lua/plugins/editor.lua new file mode 100644 index 0000000..a62f024 --- /dev/null +++ b/.config/nvim/lua/plugins/editor.lua @@ -0,0 +1,53 @@ +return { + { "nvim-lua/plenary.nvim", lazy = true }, + + { + "nvim-telescope/telescope.nvim", + branch = "0.1.x", + cmd = "Telescope", + dependencies = { + "nvim-lua/plenary.nvim", + "nvim-tree/nvim-web-devicons", + }, + keys = { + { "ff", "Telescope find_files", desc = "Find files" }, + { "fg", "Telescope live_grep", desc = "Live grep" }, + { "fb", "Telescope buffers", desc = "Buffers" }, + { "fh", "Telescope help_tags", desc = "Help tags" }, + { "gc", "Telescope git_commits", desc = "Git commits" }, + { "gB", "Telescope git_branches", desc = "Git branches" }, + { "gs", "Telescope git_status", desc = "Git status" }, + { "gf", "Telescope git_files", desc = "Git files" }, + }, + opts = { + defaults = { + sorting_strategy = "ascending", + layout_config = { + prompt_position = "top", + }, + }, + }, + }, + + { + "nvim-treesitter/nvim-treesitter", + event = { "BufReadPost", "BufNewFile" }, + build = ":TSUpdate", + main = "nvim-treesitter", + opts = { + ensure_installed = { + "bash", + "lua", + "luadoc", + "markdown", + "markdown_inline", + "nix", + "query", + "vim", + "vimdoc", + }, + highlight = { enable = true }, + indent = { enable = true }, + }, + }, +} diff --git a/.config/nvim/lua/plugins/explorer.lua b/.config/nvim/lua/plugins/explorer.lua new file mode 100644 index 0000000..4af7105 --- /dev/null +++ b/.config/nvim/lua/plugins/explorer.lua @@ -0,0 +1,46 @@ +return { + { + "stevearc/oil.nvim", + cmd = "Oil", + keys = { + { + "-", + "Oil", + desc = "Open parent directory", + }, + { + "e", + function() + require("oil").toggle_float() + end, + desc = "Explorer (Oil)", + }, + }, + dependencies = { + "nvim-tree/nvim-web-devicons", + }, + opts = { + default_file_explorer = true, + columns = { + "icon", + }, + delete_to_trash = false, + skip_confirm_for_simple_edits = true, + view_options = { + show_hidden = true, + }, + float = { + padding = 2, + max_width = 90, + max_height = 0, + border = "rounded", + }, + keymaps = { + [""] = { "actions.select", opts = { vertical = true } }, + [""] = { "actions.select", opts = { horizontal = true } }, + [""] = "actions.preview", + [""] = "actions.close", + }, + }, + }, +} diff --git a/.config/nvim/lua/plugins/format.lua b/.config/nvim/lua/plugins/format.lua new file mode 100644 index 0000000..7cd947f --- /dev/null +++ b/.config/nvim/lua/plugins/format.lua @@ -0,0 +1,36 @@ +return { + { + "stevearc/conform.nvim", + event = { "BufWritePre" }, + cmd = { "ConformInfo" }, + keys = { + { + "cf", + function() + require("conform").format({ async = true, lsp_fallback = true }) + end, + mode = { "n", "v" }, + desc = "Format buffer", + }, + }, + opts = { + notify_on_error = false, + format_on_save = function() + return { + timeout_ms = 1000, + lsp_fallback = true, + } + end, + formatters_by_ft = { + lua = { "stylua" }, + nix = { "alejandra", "nixfmt" }, + sh = { "shfmt" }, + bash = { "shfmt" }, + zsh = { "shfmt" }, + json = { "prettierd", "prettier" }, + yaml = { "prettierd", "prettier" }, + markdown = { "prettierd", "prettier" }, + }, + }, + }, +} diff --git a/.config/nvim/lua/plugins/lint.lua b/.config/nvim/lua/plugins/lint.lua new file mode 100644 index 0000000..0f3e4d4 --- /dev/null +++ b/.config/nvim/lua/plugins/lint.lua @@ -0,0 +1,23 @@ +return { + { + "mfussenegger/nvim-lint", + event = { "BufReadPre", "BufNewFile" }, + config = function() + local lint = require("lint") + + lint.linters_by_ft = { + sh = { "shellcheck" }, + bash = { "shellcheck" }, + zsh = { "shellcheck" }, + } + + local group = vim.api.nvim_create_augroup("user-lint", { clear = true }) + vim.api.nvim_create_autocmd({ "BufEnter", "BufWritePost", "InsertLeave" }, { + group = group, + callback = function() + lint.try_lint() + end, + }) + end, + }, +} diff --git a/.config/nvim/lua/plugins/lsp.lua b/.config/nvim/lua/plugins/lsp.lua new file mode 100644 index 0000000..833b287 --- /dev/null +++ b/.config/nvim/lua/plugins/lsp.lua @@ -0,0 +1,158 @@ +return { + { + "folke/lazydev.nvim", + ft = "lua", + opts = { + library = { + { path = "${3rd}/luv/library", words = { "vim%.uv" } }, + }, + }, + }, + + { + "neovim/nvim-lspconfig", + event = { "BufReadPre", "BufNewFile" }, + config = function() + -- Nix-first workflow: + -- - install language servers with nix profile / flake / devShell + -- - start Neovim from a direnv-loaded shell + -- - Neovim uses server binaries from PATH only + local lspconfig = require("lspconfig") + local util = require("lspconfig.util") + + vim.diagnostic.config({ + severity_sort = true, + virtual_text = { + spacing = 2, + source = "if_many", + }, + float = { border = "rounded" }, + }) + + vim.api.nvim_create_autocmd("LspAttach", { + group = vim.api.nvim_create_augroup("user-lsp-attach", { clear = true }), + callback = function(event) + local map = function(keys, func, desc) + vim.keymap.set("n", keys, func, { buffer = event.buf, desc = desc }) + end + + map("gd", vim.lsp.buf.definition, "Goto definition") + map("gD", vim.lsp.buf.declaration, "Goto declaration") + map("gr", vim.lsp.buf.references, "Goto references") + map("gi", vim.lsp.buf.implementation, "Goto implementation") + map("K", vim.lsp.buf.hover, "Hover") + map("rn", vim.lsp.buf.rename, "Rename") + map("ca", vim.lsp.buf.code_action, "Code action") + map("cd", vim.diagnostic.open_float, "Line diagnostics") + + if vim.lsp.inlay_hint then + map("uh", function() + vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled({ bufnr = event.buf }), { bufnr = event.buf }) + end, "Toggle inlay hints") + end + end, + }) + + local capabilities = vim.lsp.protocol.make_client_capabilities() + + local ok_blink, blink = pcall(require, "blink.cmp") + if ok_blink then + capabilities = blink.get_lsp_capabilities(capabilities) + end + + local missing = {} + + local function setup(server, bin, config) + config = config or {} + config.capabilities = vim.tbl_deep_extend("force", capabilities, config.capabilities or {}) + + if vim.fn.executable(bin) ~= 1 then + table.insert(missing, string.format("%s (%s)", server, bin)) + return + end + + lspconfig[server].setup(config) + end + + setup("lua_ls", "lua-language-server", { + cmd = { "lua-language-server" }, + settings = { + Lua = { + completion = { callSnippet = "Replace" }, + diagnostics = { globals = { "vim" } }, + telemetry = { enable = false }, + workspace = { checkThirdParty = false }, + }, + }, + }) + + setup("nixd", "nixd", { + cmd = { "nixd" }, + }) + + setup("bashls", "bash-language-server", { + cmd = { "bash-language-server", "start" }, + }) + + setup("marksman", "marksman", { + cmd = { "marksman", "server" }, + }) + + setup("ts_ls", "typescript-language-server", { + cmd = { "typescript-language-server", "--stdio" }, + root_dir = util.root_pattern("tsconfig.json", "jsconfig.json", "package.json", ".git"), + single_file_support = false, + }) + + setup("gopls", "gopls", { + cmd = { "gopls" }, + settings = { + gopls = { + gofumpt = true, + analyses = { + unusedparams = true, + }, + staticcheck = true, + }, + }, + }) + + setup("rust_analyzer", "rust-analyzer", { + cmd = { "rust-analyzer" }, + settings = { + ["rust-analyzer"] = { + cargo = { + allFeatures = true, + }, + checkOnSave = { + command = "clippy", + }, + }, + }, + }) + + setup("pyright", "pyright-langserver", { + cmd = { "pyright-langserver", "--stdio" }, + settings = { + python = { + analysis = { + autoSearchPaths = true, + useLibraryCodeForTypes = true, + diagnosticMode = "workspace", + }, + }, + }, + }) + + if #missing > 0 and #vim.api.nvim_list_uis() > 0 then + vim.schedule(function() + vim.notify( + "LSP not on PATH (install with Nix):\n- " .. table.concat(missing, "\n- "), + vim.log.levels.INFO, + { title = "nvim-lspconfig" } + ) + end) + end + end, + }, +} diff --git a/.config/nvim/lua/plugins/statusline.lua b/.config/nvim/lua/plugins/statusline.lua new file mode 100644 index 0000000..33cff8b --- /dev/null +++ b/.config/nvim/lua/plugins/statusline.lua @@ -0,0 +1,41 @@ +return { + { + "nvim-lualine/lualine.nvim", + event = "VeryLazy", + dependencies = { + "nvim-tree/nvim-web-devicons", + }, + opts = { + options = { + theme = "catppuccin", + globalstatus = true, + section_separators = "", + component_separators = "│", + disabled_filetypes = { + statusline = { "oil" }, + }, + }, + sections = { + lualine_a = { "mode" }, + lualine_b = { "branch", "diff", "diagnostics" }, + lualine_c = { + { + "filename", + path = 1, + }, + }, + lualine_x = { + { + "lsp_status", + ignore_lsp = { "copilot" }, + }, + "encoding", + "filetype", + }, + lualine_y = { "progress" }, + lualine_z = { "location" }, + }, + extensions = { "quickfix", "lazy" }, + }, + }, +} diff --git a/.config/nvim/lua/plugins/ui.lua b/.config/nvim/lua/plugins/ui.lua new file mode 100644 index 0000000..e0c2bb0 --- /dev/null +++ b/.config/nvim/lua/plugins/ui.lua @@ -0,0 +1,58 @@ +return { + { + "catppuccin/nvim", + name = "catppuccin", + lazy = false, + priority = 1000, + opts = { + flavour = "mocha", + integrations = { + gitsigns = true, + telescope = true, + which_key = true, + cmp = true, + treesitter = true, + native_lsp = { + enabled = true, + }, + }, + }, + config = function(_, opts) + require("catppuccin").setup(opts) + vim.cmd.colorscheme("catppuccin") + end, + }, + + { "nvim-tree/nvim-web-devicons", lazy = true }, + + { + "folke/which-key.nvim", + event = "VeryLazy", + opts = {}, + }, + + { + "lewis6991/gitsigns.nvim", + event = { "BufReadPre", "BufNewFile" }, + opts = { + on_attach = function(buffer) + local gs = package.loaded.gitsigns + local map = function(mode, lhs, rhs, desc) + vim.keymap.set(mode, lhs, rhs, { buffer = buffer, desc = desc }) + end + + map("n", "]h", gs.next_hunk, "Next hunk") + map("n", "[h", gs.prev_hunk, "Previous hunk") + map("n", "gp", gs.preview_hunk, "Preview hunk") + map("n", "gr", gs.reset_hunk, "Reset hunk") + map("n", "gb", gs.blame_line, "Blame line") + end, + }, + }, + + { + "numToStr/Comment.nvim", + event = "VeryLazy", + opts = {}, + }, +}