{ config, pkgs, ... }: let javaDebug = pkgs.vscode-extensions.vscjava.vscode-java-debug; in { programs.neovim = { enable = true; viAlias = true; vimAlias = true; defaultEditor = true; initLua = '' vim.opt.number = true vim.opt.relativenumber = true vim.opt.tabstop = 2 vim.opt.softtabstop = 2 vim.opt.expandtab = true vim.opt.shiftwidth = 2 vim.opt.smartindent = true vim.opt.wrap = false vim.opt.swapfile = false vim.opt.backup = false vim.opt.undofile = false vim.opt.hlsearch = false vim.opt.incsearch = true vim.opt.termguicolors = true vim.opt.clipboard = "unnamedplus" vim.opt.scrolloff = 999 vim.opt.updatetime = 50 vim.opt.foldmethod = "indent" vim.opt.foldlevel = 99 vim.g.mapleader = " " vim.keymap.set("n", "w", "w", { silent = true }) vim.keymap.set("n", "q", "q", { silent = true }) vim.keymap.set("n", "wq", "wq", { silent = true }) vim.keymap.set("n", "x", "!chmod +x %", { silent = true }) vim.keymap.set("n", "Q", "") vim.keymap.set("n", "o", "o") vim.keymap.set("n", "O", "O") vim.keymap.set("i", "jj", "") local is_dark = vim.o.background ~= "light" require("cyberdream").setup({ transparent = is_dark, variant = is_dark and "dark" or "light", terminal_colors = true, }) vim.cmd.colorscheme("cyberdream") require("nvim-tree").setup({ view = { width = 30 }, renderer = { group_empty = true }, filters = { dotfiles = false }, }) require("bufferline").setup({ options = { offsets = { { filetype = "NvimTree", text = "Files", highlight = "Directory", separator = true, }, }, }, }) require("lualine").setup({ options = { theme = "auto" }, }) require("neocord").setup({ logo = "auto", logo_tooltip = "Neovim", main_image = "language", show_time = true, global_timer = true, }) require("gitsigns").setup({ signs = { add = { text = "+" }, change = { text = "~" }, delete = { text = "_" }, }, }) -- nvim-treesitter no longer exposes a configs module; highlighting is -- started per buffer. Indent is deliberately left alone so treesitter -- doesn't fight the Eclipse-matching tab settings below. vim.api.nvim_create_autocmd("FileType", { pattern = { "java", "xml", "nix", "lua" }, callback = function(args) pcall(vim.treesitter.start, args.buf) end, }) -- Java indentation must match Eclipse's formatter, not the 2-space default. vim.api.nvim_create_autocmd("FileType", { pattern = "java", callback = function() vim.bo.expandtab = false vim.bo.tabstop = 4 vim.bo.shiftwidth = 4 vim.bo.softtabstop = 4 end, }) local cmp = require("cmp") local luasnip = require("luasnip") cmp.setup({ snippet = { expand = function(a) luasnip.lsp_expand(a.body) end }, mapping = cmp.mapping.preset.insert({ [""] = cmp.mapping.complete(), [""] = cmp.mapping.confirm({ select = false }), [""] = cmp.mapping.select_next_item(), [""] = cmp.mapping.select_prev_item(), }), sources = { { name = "nvim_lsp" }, { name = "luasnip" }, { name = "buffer" }, { name = "path" }, }, }) local capabilities = require("cmp_nvim_lsp").default_capabilities() local bundles = vim.fn.glob( "${javaDebug}/share/vscode/extensions/vscjava.vscode-java-debug/server/com.microsoft.java.debug.plugin-*.jar", true, true) if #bundles == 0 then vim.notify("java-debug bundle not found - Java debugging disabled", vim.log.levels.WARN) end local java_home = os.getenv("JAVA_HOME") if not java_home then local javac = vim.fn.exepath("javac") if javac ~= "" then java_home = vim.fn.fnamemodify(vim.fn.resolve(javac), ":h:h") end end if not java_home then vim.notify("No JAVA_HOME and no javac on PATH - jdtls will guess a runtime", vim.log.levels.WARN) end if not java_home then vim.notify("JAVA_HOME unset - jdtls will guess a runtime", vim.log.levels.WARN) end -- Eclipse metadata wins over build files, build files over .git, and the -- file's own directory is the last resort so bare folders still attach. local function find_project_root() local setup = require("jdtls.setup") return setup.find_root({ ".project", ".classpath" }) or setup.find_root({ "pom.xml", "build.gradle", "build.gradle.kts" }) or setup.find_root({ ".git" }) or vim.fn.expand("%:p:h") end local dap_configured = false local function start_jdtls() local jdtls = require("jdtls") local root_dir = find_project_root() local project_name = vim.fn.fnamemodify(root_dir, ":p:h:t") .. "-" .. vim.fn.sha256(root_dir):sub(1, 8) local workspace = vim.fn.stdpath("cache") .. "/jdtls-workspaces/" .. project_name local java_settings = { configuration = { updateBuildConfiguration = "automatic", }, -- Keep a stray pom.xml from overriding the Eclipse project model. -- Flip these on if a course actually requires Maven or Gradle. import = { maven = { enabled = false }, gradle = { enabled = false }, }, project = { outputPath = "bin", referencedLibraries = { "lib/**/*.jar" }, }, signatureHelp = { enabled = true }, eclipse = { downloadSources = true }, } if java_home then java_settings.configuration.runtimes = { { name = "JavaSE-21", path = java_home, default = true }, } end -- If the repo carries an exported Eclipse formatter profile, use it verbatim. local formatter = root_dir .. "/.settings/eclipse-formatter.xml" if vim.uv.fs_stat(formatter) then java_settings.format = { settings = { url = formatter } } end jdtls.start_or_attach({ cmd = { "jdtls", "-data", workspace }, root_dir = root_dir, capabilities = capabilities, settings = { java = java_settings }, init_options = { bundles = bundles }, on_attach = function(_, bufnr) require("jdtls").setup_dap({ hotcodereplace = "auto" }) if not dap_configured then dap_configured = true require("jdtls.dap").setup_dap_main_class_configs() end local map = function(k, f) vim.keymap.set("n", k, f, { buffer = bufnr, silent = true }) end map("gd", vim.lsp.buf.definition) map("gr", vim.lsp.buf.references) map("K", vim.lsp.buf.hover) map("rn", vim.lsp.buf.rename) map("ca", vim.lsp.buf.code_action) map("f", function() vim.lsp.buf.format({ async = true }) end) map("oi", require("jdtls").organize_imports) end, }) end vim.api.nvim_create_autocmd("FileType", { pattern = "java", callback = start_jdtls, }) local dap, dapui = require("dap"), require("dapui") dapui.setup() require("nvim-dap-virtual-text").setup() -- `jtest --debug` suspends the test JVM waiting on :5005; this lets -- dc attach to it directly from the config picker. dap.configurations.java = dap.configurations.java or {} table.insert(dap.configurations.java, { type = "java", request = "attach", name = "Attach to jtest (:5005)", hostName = "127.0.0.1", port = 5005, }) dap.listeners.after.event_initialized["dapui"] = function() dapui.open() end dap.listeners.before.event_terminated["dapui"] = function() dapui.close() end dap.listeners.before.event_exited["dapui"] = function() dapui.close() end vim.keymap.set("n", "db", dap.toggle_breakpoint) vim.keymap.set("n", "dB", function() dap.set_breakpoint(vim.fn.input("Condition: ")) end) vim.keymap.set("n", "dc", dap.continue) vim.keymap.set("n", "dn", dap.step_over) vim.keymap.set("n", "di", dap.step_into) vim.keymap.set("n", "du", dap.step_out) vim.keymap.set("n", "dr", dap.repl.toggle) vim.keymap.set("n", "dq", dap.terminate) vim.keymap.set({ "n", "v" }, "de", function() dapui.eval() end) ''; plugins = with pkgs.vimPlugins; [ { plugin = cyberdream-nvim; } { plugin = nvim-web-devicons; } { plugin = nvim-tree-lua; } { plugin = bufferline-nvim; } { plugin = lualine-nvim; } { plugin = neocord; } { plugin = gitsigns-nvim; } { plugin = nvim-jdtls; } { plugin = nvim-dap; } { plugin = nvim-dap-ui; } { plugin = nvim-nio; } { plugin = nvim-dap-virtual-text; } { plugin = nvim-cmp; } { plugin = cmp-nvim-lsp; } { plugin = cmp-buffer; } { plugin = cmp-path; } { plugin = luasnip; } { plugin = cmp_luasnip; } { plugin = (nvim-treesitter.withPlugins (p: [ p.java p.xml p.nix p.lua p.rust ])); } ]; }; }