diff --git a/flake.nix b/flake.nix index e5c6068..87e94a4 100644 --- a/flake.nix +++ b/flake.nix @@ -47,6 +47,8 @@ zulu #java11 fish fzf + perl536 + perl536Packages.CPAN ]; neovim-augmented = recursiveMerge [ pkgs.neovim-unwrapped diff --git a/lua/config/lazy.lua b/lua/config/lazy.lua index fb32ae0..ea2f472 100644 --- a/lua/config/lazy.lua +++ b/lua/config/lazy.lua @@ -8,58 +8,44 @@ end vim.opt.rtp:prepend(vim.env.LAZY or lazypath) require("lazy").setup({ - spec = { - -- add LazyVim and import its plugins - { "LazyVim/LazyVim", import = "lazyvim.plugins" }, - { - "folke/tokyonight.nvim", - opts = { - transparent = true, - styles = { - sidebars = "transparent", - floats = "transparent", - }, - }, - }, - { - "telescope.nvim", - dependencies = { - "nvim-telescope/telescope-fzf-native.nvim", - build = "make", - config = function() - require("telescope").load_extension("fzf") - end, - }, - }, - { import = "lazyvim.plugins.extras.lang.json" }, - { import = "lazyvim.plugins.extras.ui.mini-animate" }, - { import = "lazyvim.plugins.extras.lang.terraform" }, - { import = "plugins" }, - }, - defaults = { - -- By default, only LazyVim plugins will be lazy-loaded. Your custom plugins will load during startup. - -- If you know what you're doing, you can set this to `true` to have all your custom plugins lazy-loaded by default. - lazy = false, - -- It's recommended to leave version=false for now, since a lot the plugin that support versioning, - -- have outdated releases, which may break your Neovim install. - version = false, -- always use the latest git commit - -- version = "*", -- try installing the latest stable version for plugins that support semver - }, - install = { colorscheme = { "tokyonight", "habamax" } }, - checker = { enabled = true }, -- automatically check for plugin updates - performance = { - rtp = { - -- disable some rtp plugins - disabled_plugins = { - "gzip", - -- "matchit", - -- "matchparen", - -- "netrwPlugin", - "tarPlugin", - "tohtml", - "tutor", - "zipPlugin", - }, - }, - }, + spec = { + -- add LazyVim and import its plugins + { "LazyVim/LazyVim", import = "lazyvim.plugins" }, + { import = "lazyvim.plugins.extras.ui.mini-animate" }, + { import = "lazyvim.plugins.extras.lang.python" }, + { import = "lazyvim.plugins.extras.lang.json" }, + { import = "lazyvim.plugins.extras.lang.java" }, + { import = "lazyvim.plugins.extras.lang.go" }, + { import = "lazyvim.plugins.extras.lang.docker" }, + { import = "lazyvim.plugins.extras.lang.json" }, + { import = "lazyvim.plugins.extras.lang.terraform" }, + { import = "lazyvim.plugins.extras.lang.yaml" }, + { import = "plugins" }, + }, + defaults = { + -- By default, only LazyVim plugins will be lazy-loaded. Your custom plugins will load during startup. + -- If you know what you're doing, you can set this to `true` to have all your custom plugins lazy-loaded by default. + lazy = false, + -- It's recommended to leave version=false for now, since a lot the plugin that support versioning, + -- have outdated releases, which may break your Neovim install. + version = false, -- always use the latest git commit + -- version = "*", -- try installing the latest stable version for plugins that support semver + }, + install = { colorscheme = { "tokyonight", "habamax" } }, + checker = { enabled = true }, -- automatically check for plugin updates + performance = { + rtp = { + -- disable some rtp plugins + disabled_plugins = { + "gzip", + -- "matchit", + -- "matchparen", + -- "netrwPlugin", + "tarPlugin", + "tohtml", + "tutor", + "zipPlugin", + }, + }, + }, }) diff --git a/lua/plugins/mason.lua b/lua/plugins/mason.lua index b6fbd81..4d9f6d5 100644 --- a/lua/plugins/mason.lua +++ b/lua/plugins/mason.lua @@ -1,6 +1,16 @@ return { - "williamboman/mason.nvim", - opts = function(_, opts) - table.insert(opts.ensure_installed, "prettierd") - end, + "williamboman/mason.nvim", + opts = function(_, opts) + opts.ensure_installed = opts.ensure_installed or {} + vim.list_extend(opts.ensure_installed, { + "prettierd", + "gomodifytags", + "impl", + "gofumpt", + "goimports-reviser", + "delve", + "java-test", + "java-debug-adapter", + }) + end, } diff --git a/lua/plugins/neotest-go.lua b/lua/plugins/neotest-go.lua new file mode 100644 index 0000000..a8a4b3d --- /dev/null +++ b/lua/plugins/neotest-go.lua @@ -0,0 +1,3 @@ +return { + "nvim-neotest/neotest-go", +} diff --git a/lua/plugins/neotest-python.lua b/lua/plugins/neotest-python.lua new file mode 100644 index 0000000..cc33d41 --- /dev/null +++ b/lua/plugins/neotest-python.lua @@ -0,0 +1,3 @@ +return { + "nvim-neotest/neotest-python", +} diff --git a/lua/plugins/neotest.lua b/lua/plugins/neotest.lua new file mode 100644 index 0000000..6a550ef --- /dev/null +++ b/lua/plugins/neotest.lua @@ -0,0 +1,20 @@ +return { + "nvim-neotest/neotest", + optional = true, + dependencies = { + "nvim-neotest/neotest-go", + }, + opts = { + adapters = { + ["neotest-python"] = { + -- Here you can specify the settings for the adapter, i.e. + -- runner = "pytest", + -- python = ".venv/bin/python", + }, + ["neotest-go"] = { + -- Here we can set options for neotest-go, e.g. + -- args = { "-tags=integration" } + }, + }, + }, +} diff --git a/lua/plugins/null-ls.lua b/lua/plugins/null-ls.lua index ef25e20..2599382 100644 --- a/lua/plugins/null-ls.lua +++ b/lua/plugins/null-ls.lua @@ -1,13 +1,18 @@ return { - "jose-elias-alvarez/null-ls.nvim", - opts = function(_, opts) - if type(opts.sources) == "table" then - local null_ls = require("null-ls") - vim.list_extend(opts.sources, { - null_ls.builtins.formatting.prettierd, - null_ls.builtins.formatting.terraform_fmt, - null_ls.builtins.diagnostics.terraform_validate, - }) - end - end, + "jose-elias-alvarez/null-ls.nvim", + opts = function(_, opts) + if type(opts.sources) == "table" then + local null_ls = require("null-ls") + vim.list_extend(opts.sources, { + null_ls.builtins.diagnostics.hadolint, + null_ls.builtins.formatting.prettierd, + null_ls.builtins.formatting.terraform_fmt, + null_ls.builtins.diagnostics.terraform_validate, + null_ls.builtins.code_actions.gomodifytags, + null_ls.builtins.code_actions.impl, + null_ls.builtins.formatting.gofumpt, + null_ls.builtins.formatting.goimports_reviser, + }) + end + end, } diff --git a/lua/plugins/nvim-dap-go.lua b/lua/plugins/nvim-dap-go.lua new file mode 100644 index 0000000..0aa846d --- /dev/null +++ b/lua/plugins/nvim-dap-go.lua @@ -0,0 +1,4 @@ +return { + "leoluz/nvim-dap-go", + config = true, +} diff --git a/lua/plugins/nvim-dap-python.lua b/lua/plugins/nvim-dap-python.lua new file mode 100644 index 0000000..111f574 --- /dev/null +++ b/lua/plugins/nvim-dap-python.lua @@ -0,0 +1,12 @@ +return { + "mfussenegger/nvim-dap-python", + -- stylua: ignore + keys = { + { "dPt", function() require('dap-python').test_method() end, desc = "Debug Method" }, + { "dPc", function() require('dap-python').test_class() end, desc = "Debug Class" }, + }, + config = function() + local path = require("mason-registry").get_package("debugpy"):get_install_path() + require("dap-python").setup(path .. "/venv/bin/python") + end, +} diff --git a/lua/plugins/nvim-dap.lua b/lua/plugins/nvim-dap.lua new file mode 100644 index 0000000..c16ceb8 --- /dev/null +++ b/lua/plugins/nvim-dap.lua @@ -0,0 +1,4 @@ +return { + "mfussenegger/nvim-dap", + optional = true, +} diff --git a/lua/plugins/nvim-jdtls.lua b/lua/plugins/nvim-jdtls.lua new file mode 100644 index 0000000..c51e6b5 --- /dev/null +++ b/lua/plugins/nvim-jdtls.lua @@ -0,0 +1,163 @@ +return { + "mfussenegger/nvim-jdtls", + ft = java_filetypes, + opts = function() + return { + -- How to find the root dir for a given filename. The default comes from + -- lspconfig which provides a function specifically for java projects. + root_dir = require("lspconfig.server_configurations.jdtls").default_config.root_dir, + + -- How to find the project name for a given root dir. + project_name = function(root_dir) + return root_dir and vim.fs.basename(root_dir) + end, + + -- Where are the config and workspace dirs for a project? + jdtls_config_dir = function(project_name) + return vim.fn.stdpath("cache") .. "/jdtls/" .. project_name .. "/config" + end, + jdtls_workspace_dir = function(project_name) + return vim.fn.stdpath("cache") .. "/jdtls/" .. project_name .. "/workspace" + end, + + -- How to run jdtls. This can be overridden to a full java command-line + -- if the Python wrapper script doesn't suffice. + cmd = { "jdtls" }, + full_cmd = function(opts) + local fname = vim.api.nvim_buf_get_name(0) + local root_dir = opts.root_dir(fname) + local project_name = opts.project_name(root_dir) + local cmd = vim.deepcopy(opts.cmd) + if project_name then + vim.list_extend(cmd, { + "-configuration", + opts.jdtls_config_dir(project_name), + "-data", + opts.jdtls_workspace_dir(project_name), + }) + end + return cmd + end, + + -- These depend on nvim-dap, but can additionally be disabled by setting false here. + dap = { hotcodereplace = "auto", config_overrides = {} }, + test = true, + } + end, + config = function() + local opts = Util.opts("nvim-jdtls") or {} + + -- Find the extra bundles that should be passed on the jdtls command-line + -- if nvim-dap is enabled with java debug/test. + local mason_registry = require("mason-registry") + local bundles = {} ---@type string[] + if opts.dap and Util.has("nvim-dap") and mason_registry.is_installed("java-debug-adapter") then + local java_dbg_pkg = mason_registry.get_package("java-debug-adapter") + local java_dbg_path = java_dbg_pkg:get_install_path() + local jar_patterns = { + java_dbg_path .. "/extension/server/com.microsoft.java.debug.plugin-*.jar", + } + -- java-test also depends on java-debug-adapter. + if opts.test and mason_registry.is_installed("java-test") then + local java_test_pkg = mason_registry.get_package("java-test") + local java_test_path = java_test_pkg:get_install_path() + vim.list_extend(jar_patterns, { + java_test_path .. "/extension/server/*.jar", + }) + end + for _, jar_pattern in ipairs(jar_patterns) do + for _, bundle in ipairs(vim.split(vim.fn.glob(jar_pattern), "\n")) do + table.insert(bundles, bundle) + end + end + end + + local function attach_jdtls() + local fname = vim.api.nvim_buf_get_name(0) + + -- Configuration can be augmented and overridden by opts.jdtls + local config = extend_or_override({ + cmd = opts.full_cmd(opts), + root_dir = opts.root_dir(fname), + init_options = { + bundles = bundles, + }, + -- enable CMP capabilities + capabilities = require("cmp_nvim_lsp").default_capabilities(), + }, opts.jdtls) + + -- Existing server will be reused if the root_dir matches. + require("jdtls").start_or_attach(config) + -- not need to require("jdtls.setup").add_commands(), start automatically adds commands + end + + -- Attach the jdtls for each java buffer. HOWEVER, this plugin loads + -- depending on filetype, so this autocmd doesn't run for the first file. + -- For that, we call directly below. + vim.api.nvim_create_autocmd("FileType", { + pattern = java_filetypes, + callback = attach_jdtls, + }) + + -- Setup keymap and dap after the lsp is fully attached. + -- https://github.com/mfussenegger/nvim-jdtls#nvim-dap-configuration + -- https://neovim.io/doc/user/lsp.html#LspAttach + vim.api.nvim_create_autocmd("LspAttach", { + callback = function(args) + local client = vim.lsp.get_client_by_id(args.data.client_id) + if client and client.name == "jdtls" then + local wk = require("which-key") + wk.register({ + ["cx"] = { name = "+extract" }, + ["cxv"] = { require("jdtls").extract_variable_all, "Extract Variable" }, + ["cxc"] = { require("jdtls").extract_constant, "Extract Constant" }, + ["gs"] = { require("jdtls").super_implementation, "Goto Super" }, + ["gS"] = { require("jdtls.tests").goto_subjects, "Goto Subjects" }, + ["co"] = { require("jdtls").organize_imports, "Organize Imports" }, + }, { mode = "n", buffer = args.buf }) + wk.register({ + ["c"] = { name = "+code" }, + ["cx"] = { name = "+extract" }, + ["cxm"] = { + [[lua require('jdtls').extract_method(true)]], + "Extract Method", + }, + ["cxv"] = { + [[lua require('jdtls').extract_variable_all(true)]], + "Extract Variable", + }, + ["cxc"] = { + [[lua require('jdtls').extract_constant(true)]], + "Extract Constant", + }, + }, { mode = "v", buffer = args.buf }) + + if opts.dap and Util.has("nvim-dap") and mason_registry.is_installed("java-debug-adapter") then + -- custom init for Java debugger + require("jdtls").setup_dap(opts.dap) + require("jdtls.dap").setup_dap_main_class_configs() + + -- Java Test require Java debugger to work + if opts.test and mason_registry.is_installed("java-test") then + -- custom keymaps for Java test runner (not yet compatible with neotest) + wk.register({ + ["t"] = { name = "+test" }, + ["tt"] = { require("jdtls.dap").test_class, "Run All Test" }, + ["tr"] = { require("jdtls.dap").test_nearest_method, "Run Nearest Test" }, + ["tT"] = { require("jdtls.dap").pick_test, "Run Test" }, + }, { mode = "n", buffer = args.buf }) + end + end + + -- User can set additional keymaps in opts.on_attach + if opts.on_attach then + opts.on_attach(args) + end + end + end, + }) + + -- Avoid race condition by calling attach the first time, since the autocmd won't fire. + attach_jdtls() + end, +} diff --git a/lua/plugins/nvim-lspconfig.lua b/lua/plugins/nvim-lspconfig.lua index b1c713f..814a7b8 100644 --- a/lua/plugins/nvim-lspconfig.lua +++ b/lua/plugins/nvim-lspconfig.lua @@ -1,8 +1,136 @@ return { - "neovim/nvim-lspconfig", - opts = { - servers = { - terraformls = {}, - }, - }, + "neovim/nvim-lspconfig", + opts = { + servers = { + jdtls = {}, + dockerls = {}, + docker_compose_language_service = {}, + terraformls = {}, + pyright = {}, + ruff_lsp = {}, + yamlls = { + -- Have to add this for yamlls to understand that we support line folding + capabilities = { + textDocument = { + foldingRange = { + dynamicRegistration = false, + lineFoldingOnly = true, + }, + }, + }, + -- lazy-load schemastore when needed + on_new_config = function(new_config) + new_config.settings.yaml.schemas = new_config.settings.yaml.schemas or {} + vim.list_extend(new_config.settings.yaml.schemas, require("schemastore").yaml.schemas()) + end, + settings = { + redhat = { telemetry = { enabled = false } }, + yaml = { + keyOrdering = false, + format = { + enable = true, + }, + validate = true, + schemaStore = { + -- Must disable built-in schemaStore support to use + -- schemas from SchemaStore.nvim plugin + enable = false, + -- Avoid TypeError: Cannot read properties of undefined (reading 'length') + url = "", + }, + }, + }, + }, + jsonls = { + -- lazy-load schemastore when needed + on_new_config = function(new_config) + new_config.settings.json.schemas = new_config.settings.json.schemas or {} + vim.list_extend(new_config.settings.json.schemas, require("schemastore").json.schemas()) + end, + settings = { + json = { + format = { + enable = true, + }, + validate = { enable = true }, + }, + }, + }, + gopls = { + keys = { + -- Workaround for the lack of a DAP strategy in neotest-go: https://github.com/nvim-neotest/neotest-go/issues/12 + { "td", "lua require('dap-go').debug_test()", desc = "Debug Nearest (Go)" }, + }, + settings = { + gopls = { + gofumpt = true, + codelenses = { + gc_details = false, + generate = true, + regenerate_cgo = true, + run_govulncheck = true, + test = true, + tidy = true, + upgrade_dependency = true, + vendor = true, + }, + hints = { + assignVariableTypes = true, + compositeLiteralFields = true, + compositeLiteralTypes = true, + constantValues = true, + functionTypeParameters = true, + parameterNames = true, + rangeVariableTypes = true, + }, + analyses = { + fieldalignment = true, + nilness = true, + unusedparams = true, + unusedwrite = true, + useany = true, + }, + usePlaceholders = true, + completeUnimported = true, + staticcheck = true, + directoryFilters = { "-.git", "-.vscode", "-.idea", "-.vscode-test", "-node_modules" }, + semanticTokens = true, + }, + }, + }, + }, + setup = { + gopls = function(_, opts) + -- workaround for gopls not supporting semanticTokensProvider + -- https://github.com/golang/go/issues/54531#issuecomment-1464982242 + require("lazyvim.util").on_attach(function(client, _) + if client.name == "gopls" then + if not client.server_capabilities.semanticTokensProvider then + local semantic = client.config.capabilities.textDocument.semanticTokens + client.server_capabilities.semanticTokensProvider = { + full = true, + legend = { + tokenTypes = semantic.tokenTypes, + tokenModifiers = semantic.tokenModifiers, + }, + range = true, + } + end + end + end) + -- end workaround + end, + jdtls = function() + return true -- avoid duplicate servers + end, + ruff_lsp = function() + require("lazyvim.util").on_attach(function(client, _) + if client.name == "ruff_lsp" then + -- Disable hover in favor of Pyright + client.server_capabilities.hoverProvider = false + end + end) + end, + }, + }, } diff --git a/lua/plugins/nvim-treesitter.lua b/lua/plugins/nvim-treesitter.lua index 5cb2905..ee76d59 100644 --- a/lua/plugins/nvim-treesitter.lua +++ b/lua/plugins/nvim-treesitter.lua @@ -1,11 +1,25 @@ return { - "nvim-treesitter/nvim-treesitter", - opts = function(_, opts) - if type(opts.ensure_installed) == "table" then - vim.list_extend(opts.ensure_installed, { - "terraform", - "hcl", - }) - end - end, + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { + "java", + "go", + "gomod", + "gowork", + "gosum", + "dockerfile", + "terraform", + "hcl", + "json", + "json5", + "jsonc", + "ninja", + "python", + "rst", + "toml", + "yaml", + }) + end + end, } diff --git a/lua/plugins/schemastore.lua b/lua/plugins/schemastore.lua new file mode 100644 index 0000000..9ca9103 --- /dev/null +++ b/lua/plugins/schemastore.lua @@ -0,0 +1,4 @@ +return { + "b0o/SchemaStore.nvim", + version = false, -- last release is way too old +} diff --git a/lua/plugins/telescope.lua b/lua/plugins/telescope.lua new file mode 100644 index 0000000..d73c3f1 --- /dev/null +++ b/lua/plugins/telescope.lua @@ -0,0 +1,10 @@ +return { + "telescope.nvim", + dependencies = { + "nvim-telescope/telescope-fzf-native.nvim", + build = "make", + config = function() + require("telescope").load_extension("fzf") + end, + }, +} diff --git a/lua/plugins/tokyonight.lua b/lua/plugins/tokyonight.lua new file mode 100644 index 0000000..2302514 --- /dev/null +++ b/lua/plugins/tokyonight.lua @@ -0,0 +1,10 @@ +return { + "folke/tokyonight.nvim", + opts = { + transparent = true, + styles = { + sidebars = "transparent", + floats = "transparent", + }, + }, +} diff --git a/lua/plugins/venv-selector.lua b/lua/plugins/venv-selector.lua new file mode 100644 index 0000000..ad353fc --- /dev/null +++ b/lua/plugins/venv-selector.lua @@ -0,0 +1,13 @@ +return { + "linux-cultist/venv-selector.nvim", + cmd = "VenvSelect", + opts = { + name = { + "venv", + ".venv", + "env", + ".env", + }, + }, + keys = { { "cv", ":VenvSelect", desc = "Select VirtualEnv" } }, +} diff --git a/lua/plugins/which-key.lua b/lua/plugins/which-key.lua new file mode 100644 index 0000000..1e0d478 --- /dev/null +++ b/lua/plugins/which-key.lua @@ -0,0 +1 @@ +return { "folke/which-key.nvim" }