diff --git a/README.md b/README.md new file mode 100644 index 0000000..f0211c6 --- /dev/null +++ b/README.md @@ -0,0 +1,223 @@ +# Modern Neovim Configuration + +A modern, fully-featured Neovim configuration optimized for Java, TypeScript, Golang, Python, and Bash development. + +## Features + +### Core +- **lazy.nvim** - Modern, fast plugin manager +- **mason.nvim** - Easy LSP/DAP/formatter installer +- **Treesitter** - Advanced syntax highlighting +- **Telescope** - Fuzzy finder and navigation +- **Which-key** - Keybinding helper + +### Language Support +- **Java** - Spring Boot & Gradle with nvim-jdtls +- **TypeScript/JavaScript** - React & Vite with ts_ls +- **Golang** - Full LSP support with gopls and vim-go +- **Python** - Pyright LSP + Jupyter notebooks (molten-nvim) +- **Bash** - Shell scripting with bashls + +### Development Tools +- **LSP** - Language servers for all supported languages +- **DAP** - Debugging for Go, Python, and Java +- **Linting** - nvim-lint with language-specific linters +- **Formatting** - conform.nvim with auto-format on save +- **Git Integration** - Gitsigns and Lazygit +- **File Explorer** - nvim-tree with VS Code-like sidebar +- **Terminal** - toggleterm with bottom horizontal layout (VS Code style) + +## Installation + +1. Backup your existing config: +```bash +mv ~/.config/nvim ~/.config/nvim.backup +``` + +2. Clone this repository: +```bash +git clone ~/.config/nvim +``` + +3. Start Neovim: +```bash +nvim +``` + +4. lazy.nvim will automatically install all plugins. Once complete, run: +``` +:Mason +``` + +All LSP servers, formatters, and linters will be automatically installed by mason-tool-installer. + +## Key Bindings + +Leader key: `Space` + +### General +- `e` - Toggle file explorer +- `w` - Save file +- `q` - Quit +- `c` - Close buffer +- `h` - Clear search highlight +- `f` - Format file +- `Ctrl+p` - Find files (Telescope) +- `Ctrl+f` - Search text (Telescope) +- `Ctrl+\` - Toggle terminal + +### Window Navigation +- `Ctrl+h/j/k/l` - Navigate between windows +- `Shift+h/l` - Navigate between buffers +- `Shift+arrows` - Resize windows + +### LSP +- `gd` - Go to definition +- `gD` - Go to declaration +- `gr` - References +- `gi` - Go to implementation +- `K` - Show hover documentation +- `la` - Code action +- `lr` - Rename +- `lf` - Format +- `ld` - Show diagnostics +- `lj/k` - Next/previous diagnostic + +### Debugging (DAP) +- `db` - Toggle breakpoint +- `dB` - Conditional breakpoint +- `dc` - Continue +- `di` - Step into +- `do` - Step over +- `dO` - Step out +- `du` - Toggle DAP UI +- `dt` - Terminate debug session + +### Java-specific (Spring Boot) +- `jo` - Organize imports +- `jv` - Extract variable +- `jc` - Extract constant +- `jm` - Extract method +- `jt` - Test class +- `jn` - Test nearest method + +### Jupyter Notebooks (Python) +- `mi` - Initialize Molten +- `me` - Evaluate selection +- `ml` - Evaluate line +- `mr` - Re-evaluate cell +- `mo` - Show output +- `mh` - Hide output + +### Git +- `gg` - Lazygit +- `gj/k` - Next/previous hunk +- `gl` - Blame line +- `gp` - Preview hunk +- `gs` - Stage hunk +- `gr` - Reset hunk + +### Search (Telescope) +- `sf` - Find files +- `st` - Live grep (search text) +- `sb` - Git branches +- `sc` - Colorschemes +- `sh` - Help tags +- `sr` - Recent files +- `sk` - Keymaps + +### Terminal +- `tt` - Toggle terminal +- `tg` - Lazygit +- `tn` - Node REPL +- `th` - Htop + +## Language Setup Notes + +### Java (Spring Boot) +- Requires Java 17+ installed +- Set `JAVA_HOME` environment variable +- Gradle/Maven projects auto-detected +- Run tests: `jt` or `jn` + +### TypeScript/React +- Install Node.js and npm +- Works with Vite, Create React App, and Next.js +- ESLint/Prettier auto-formatting on save + +### Golang +- Install Go 1.21+ +- Auto-formatting with gofumpt and goimports +- Run tests with vim-go: `:GoTest` +- Debug with DAP: `dc` + +### Python (Jupyter) +- Install Python 3.8+ +- Jupyter support via molten-nvim +- Initialize kernel: `mi` +- Evaluate cells: `me` or `ml` + +### Bash +- Shell script linting with shellcheck +- Formatting with shfmt +- Full LSP support + +## Customization + +All configuration files are in `~/.config/nvim/lua/user/`: + +- `options.lua` - General Neovim settings +- `keymaps.lua` - Custom keybindings +- `lazy.lua` - Plugin specifications +- `lsp/` - LSP configuration +- `conform.lua` - Formatter configuration +- `lint.lua` - Linter configuration +- `dap.lua` - Debugger configuration + +## Troubleshooting + +### LSP not working +1. Check installed servers: `:Mason` +2. Check LSP status: `:LspInfo` +3. Restart LSP: `:LspRestart` + +### Formatters not working +1. Check Mason: `:Mason` +2. Check conform: `:ConformInfo` +3. Manually format: `f` or `:Format` + +### Java not working +1. Verify JAVA_HOME: `echo $JAVA_HOME` +2. Check jdtls: `:LspInfo` in a .java file +3. Restart Neovim + +### Jupyter notebooks not working +1. Install Python plugin: `:UpdateRemotePlugins` +2. Initialize molten: `mi` +3. Select kernel: `:MoltenInit python3` + +## Requirements + +- Neovim >= 0.9.0 +- Git +- Node.js >= 18 (for TypeScript/JavaScript) +- Python >= 3.8 (for Python development) +- Go >= 1.21 (for Golang development) +- Java >= 17 (for Java development) +- A Nerd Font (for icons) +- ripgrep (for Telescope text search) +- fd (for Telescope file search) + +## Credits + +This configuration is built with these excellent plugins: +- [lazy.nvim](https://github.com/folke/lazy.nvim) +- [mason.nvim](https://github.com/williamboman/mason.nvim) +- [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) +- [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) +- [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim) +- [nvim-dap](https://github.com/mfussenegger/nvim-dap) +- [nvim-jdtls](https://github.com/mfussenegger/nvim-jdtls) +- [molten-nvim](https://github.com/benlubas/molten-nvim) + +And many more! diff --git a/ftplugin/java.lua b/ftplugin/java.lua new file mode 100644 index 0000000..9b59e25 --- /dev/null +++ b/ftplugin/java.lua @@ -0,0 +1,148 @@ +-- Java-specific configuration using nvim-jdtls +local status_ok, jdtls = pcall(require, "jdtls") +if not status_ok then + return +end + +-- Determine OS +local home = os.getenv("HOME") +local jdtls_path = vim.fn.stdpath("data") .. "/mason/packages/jdtls" +local workspace_path = home .. "/.local/share/nvim/jdtls-workspace/" +local project_name = vim.fn.fnamemodify(vim.fn.getcwd(), ":p:h:t") +local workspace_dir = workspace_path .. project_name + +-- Find Java installation +local function get_java_executable() + local java_home = os.getenv("JAVA_HOME") + if java_home then + return java_home .. "/bin/java" + end + return "java" +end + +-- JDTLS configuration +local config = { + cmd = { + get_java_executable(), + "-Declipse.application=org.eclipse.jdt.ls.core.id1", + "-Dosgi.bundles.defaultStartLevel=4", + "-Declipse.product=org.eclipse.jdt.ls.core.product", + "-Dlog.protocol=true", + "-Dlog.level=ALL", + "-Xmx1g", + "--add-modules=ALL-SYSTEM", + "--add-opens", "java.base/java.util=ALL-UNNAMED", + "--add-opens", "java.base/java.lang=ALL-UNNAMED", + "-jar", vim.fn.glob(jdtls_path .. "/plugins/org.eclipse.equinox.launcher_*.jar"), + "-configuration", jdtls_path .. "/config_linux", + "-data", workspace_dir, + }, + + root_dir = require("jdtls.setup").find_root({ ".git", "mvnw", "gradlew", "pom.xml", "build.gradle" }), + + settings = { + java = { + eclipse = { + downloadSources = true, + }, + configuration = { + updateBuildConfiguration = "interactive", + runtimes = { + { + name = "JavaSE-17", + path = os.getenv("JAVA_HOME") or "/usr/lib/jvm/java-17-openjdk", + }, + }, + }, + maven = { + downloadSources = true, + }, + implementationsCodeLens = { + enabled = true, + }, + referencesCodeLens = { + enabled = true, + }, + references = { + includeDecompiledSources = true, + }, + format = { + enabled = true, + settings = { + url = vim.fn.stdpath("config") .. "/lang-servers/intellij-java-google-style.xml", + profile = "GoogleStyle", + }, + }, + signatureHelp = { enabled = true }, + contentProvider = { preferred = "fernflower" }, + completion = { + favoriteStaticMembers = { + "org.hamcrest.MatcherAssert.assertThat", + "org.hamcrest.Matchers.*", + "org.hamcrest.CoreMatchers.*", + "org.junit.jupiter.api.Assertions.*", + "java.util.Objects.requireNonNull", + "java.util.Objects.requireNonNullElse", + "org.mockito.Mockito.*", + }, + importOrder = { + "java", + "javax", + "com", + "org", + }, + }, + sources = { + organizeImports = { + starThreshold = 9999, + staticStarThreshold = 9999, + }, + }, + codeGeneration = { + toString = { + template = "${object.className}{${member.name()}=${member.value}, ${otherMembers}}", + }, + useBlocks = true, + }, + }, + }, + + flags = { + allow_incremental_sync = true, + }, + + init_options = { + bundles = {}, + }, + + on_attach = function(client, bufnr) + -- Regular LSP on_attach + require("user.lsp.handlers").on_attach(client, bufnr) + + -- Java-specific keymaps + local opts = { noremap = true, silent = true, buffer = bufnr } + vim.keymap.set("n", "jo", "lua require'jdtls'.organize_imports()", opts) + vim.keymap.set("n", "jv", "lua require('jdtls').extract_variable()", opts) + vim.keymap.set("v", "jv", "lua require('jdtls').extract_variable(true)", opts) + vim.keymap.set("n", "jc", "lua require('jdtls').extract_constant()", opts) + vim.keymap.set("v", "jc", "lua require('jdtls').extract_constant(true)", opts) + vim.keymap.set("v", "jm", "lua require('jdtls').extract_method(true)", opts) + vim.keymap.set("n", "jt", "lua require'jdtls'.test_class()", opts) + vim.keymap.set("n", "jn", "lua require'jdtls'.test_nearest_method()", opts) + + -- Enable codelens + if client.server_capabilities.codeLensProvider then + vim.api.nvim_create_autocmd({ "BufEnter", "CursorHold", "InsertLeave" }, { + buffer = bufnr, + callback = function() + vim.lsp.codelens.refresh() + end, + }) + end + end, + + capabilities = require("user.lsp.handlers").capabilities, +} + +-- Start jdtls +jdtls.start_or_attach(config) diff --git a/init.lua b/init.lua index a120351..73a82d2 100644 --- a/init.lua +++ b/init.lua @@ -1,18 +1,9 @@ +-- Core settings require("user.options") require("user.keymaps") -require("user.plugins") + +-- Plugin manager (lazy.nvim) +require("user.lazy") + +-- Theme require("user.colorscheme") -require("user.cmp") -require("user.lsp") -require("user.telescope") -require("user.treesitter") -require("user.autopairs") -require("user.comment") -require("user.gitsigns") -require("user.nvim-tree") -require("user.bufferline") -require("user.lualine") -require("user.toggleterm") -require("user.impatient") -require("user.golang") -require("user.coc") diff --git a/lua/user/conform.lua b/lua/user/conform.lua new file mode 100644 index 0000000..e2807aa --- /dev/null +++ b/lua/user/conform.lua @@ -0,0 +1,62 @@ +local status_ok, conform = pcall(require, "conform") +if not status_ok then + return +end + +conform.setup({ + formatters_by_ft = { + -- TypeScript/JavaScript + typescript = { "prettier" }, + javascript = { "prettier" }, + typescriptreact = { "prettier" }, + javascriptreact = { "prettier" }, + + -- Web + html = { "prettier" }, + css = { "prettier" }, + scss = { "prettier" }, + json = { "prettier" }, + yaml = { "prettier" }, + markdown = { "prettier" }, + + -- Lua + lua = { "stylua" }, + + -- Go + go = { "goimports", "gofumpt" }, + + -- Python + python = { "isort", "black" }, + + -- Java + java = { "google-java-format" }, + + -- Bash + sh = { "shfmt" }, + bash = { "shfmt" }, + }, + + format_on_save = { + timeout_ms = 500, + lsp_fallback = true, + }, + + formatters = { + shfmt = { + prepend_args = { "-i", "2", "-ci" }, + }, + }, +}) + +-- Format command +vim.api.nvim_create_user_command("Format", function(args) + local range = nil + if args.count ~= -1 then + local end_line = vim.api.nvim_buf_get_lines(0, args.line2 - 1, args.line2, true)[1] + range = { + start = { args.line1, 0 }, + ["end"] = { args.line2, end_line:len() }, + } + end + require("conform").format({ async = true, lsp_fallback = true, range = range }) +end, { range = true }) diff --git a/lua/user/dap.lua b/lua/user/dap.lua new file mode 100644 index 0000000..f259f79 --- /dev/null +++ b/lua/user/dap.lua @@ -0,0 +1,138 @@ +local status_ok, dap = pcall(require, "dap") +if not status_ok then + return +end + +local status_ok_ui, dapui = pcall(require, "dapui") +if not status_ok_ui then + return +end + +local status_ok_virtual_text, dap_virtual_text = pcall(require, "nvim-dap-virtual-text") +if not status_ok_virtual_text then + return +end + +-- Setup DAP UI +dapui.setup({ + icons = { expanded = "▾", collapsed = "▸", current_frame = "▸" }, + mappings = { + expand = { "", "<2-LeftMouse>" }, + open = "o", + remove = "d", + edit = "e", + repl = "r", + toggle = "t", + }, + element_mappings = {}, + expand_lines = vim.fn.has("nvim-0.7") == 1, + layouts = { + { + elements = { + { id = "scopes", size = 0.25 }, + "breakpoints", + "stacks", + "watches", + }, + size = 40, + position = "left", + }, + { + elements = { + "repl", + "console", + }, + size = 0.25, + position = "bottom", + }, + }, + controls = { + enabled = true, + element = "repl", + icons = { + pause = "", + play = "", + step_into = "", + step_over = "", + step_out = "", + step_back = "", + run_last = "↻", + terminate = "□", + }, + }, + floating = { + max_height = nil, + max_width = nil, + border = "rounded", + mappings = { + close = { "q", "" }, + }, + }, + windows = { indent = 1 }, + render = { + max_type_length = nil, + max_value_lines = 100, + }, +}) + +-- Setup virtual text +dap_virtual_text.setup({ + enabled = true, + enabled_commands = true, + highlight_changed_variables = true, + highlight_new_as_changed = false, + show_stop_reason = true, + commented = false, + only_first_definition = true, + all_references = false, + filter_references_pattern = "db", "lua require'dap'.toggle_breakpoint()", opts) +vim.keymap.set("n", "dB", "lua require'dap'.set_breakpoint(vim.fn.input('Breakpoint condition: '))", opts) +vim.keymap.set("n", "dc", "lua require'dap'.continue()", opts) +vim.keymap.set("n", "di", "lua require'dap'.step_into()", opts) +vim.keymap.set("n", "do", "lua require'dap'.step_over()", opts) +vim.keymap.set("n", "dO", "lua require'dap'.step_out()", opts) +vim.keymap.set("n", "dr", "lua require'dap'.repl.toggle()", opts) +vim.keymap.set("n", "dl", "lua require'dap'.run_last()", opts) +vim.keymap.set("n", "du", "lua require'dapui'.toggle()", opts) +vim.keymap.set("n", "dt", "lua require'dap'.terminate()", opts) diff --git a/lua/user/keymaps.lua b/lua/user/keymaps.lua index 30c395d..c9b4128 100644 --- a/lua/user/keymaps.lua +++ b/lua/user/keymaps.lua @@ -5,6 +5,10 @@ local term_opts = { silent = true } -- Shorten function name local keymap = vim.api.nvim_set_keymap +-- Set leader key +vim.g.mapleader = " " +vim.g.maplocalleader = " " + -- Modes -- normal_mode = "n", -- insert_mode = "i", @@ -36,12 +40,13 @@ keymap("n", "", ":vertical resize -2", opts) keymap("n", "", ":vertical resize +2", opts) -- NVimTree -keymap("n", "e", ":NVimTreeToggle", opts) +keymap("n", "e", ":NvimTreeToggle", opts) -- Telescope -keymap("n", "", ":Telescope find_files", opts) +keymap("n", "", ":Telescope find_files", opts) +keymap("n", "", ":Telescope live_grep", opts) --- LSP Null LS Format +-- LSP Format keymap("n", "f", ":Format", opts) -- Move text up and down (Option-jk) diff --git a/lua/user/lazy.lua b/lua/user/lazy.lua new file mode 100644 index 0000000..4d320be --- /dev/null +++ b/lua/user/lazy.lua @@ -0,0 +1,218 @@ +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) + +require("lazy").setup({ + -- Essential + "nvim-lua/plenary.nvim", + "nvim-tree/nvim-web-devicons", + + -- Color Scheme + "sainnhe/sonokai", + + -- File Explorer + { + "nvim-tree/nvim-tree.lua", + dependencies = { "nvim-tree/nvim-web-devicons" }, + config = function() + require("user.nvim-tree") + end, + }, + + -- Fuzzy Finder + { + "nvim-telescope/telescope.nvim", + dependencies = { "nvim-lua/plenary.nvim" }, + config = function() + require("user.telescope") + end, + }, + + -- LSP + { + "neovim/nvim-lspconfig", + dependencies = { + "williamboman/mason.nvim", + "williamboman/mason-lspconfig.nvim", + "WhoIsSethDaniel/mason-tool-installer.nvim", + }, + config = function() + require("user.lsp") + end, + }, + + -- Java Support + { + "mfussenegger/nvim-jdtls", + ft = "java", + }, + + -- Completion + { + "hrsh7th/nvim-cmp", + event = "InsertEnter", + dependencies = { + "hrsh7th/cmp-nvim-lsp", + "hrsh7th/cmp-buffer", + "hrsh7th/cmp-path", + "hrsh7th/cmp-cmdline", + "saadparwaiz1/cmp_luasnip", + "L3MON4D3/LuaSnip", + "rafamadriz/friendly-snippets", + }, + config = function() + require("user.cmp") + end, + }, + + -- Treesitter + { + "nvim-treesitter/nvim-treesitter", + build = ":TSUpdate", + event = { "BufReadPost", "BufNewFile" }, + config = function() + require("user.treesitter") + end, + }, + "JoosepAlviste/nvim-ts-context-commentstring", + + -- Formatting and Linting + { + "stevearc/conform.nvim", + event = { "BufWritePre" }, + cmd = { "ConformInfo" }, + config = function() + require("user.conform") + end, + }, + { + "mfussenegger/nvim-lint", + event = { "BufReadPre", "BufNewFile" }, + config = function() + require("user.lint") + end, + }, + + -- Debugging + { + "mfussenegger/nvim-dap", + dependencies = { + "rcarriga/nvim-dap-ui", + "nvim-neotest/nvim-nio", + "theHamsta/nvim-dap-virtual-text", + "leoluz/nvim-dap-go", + "mfussenegger/nvim-dap-python", + }, + config = function() + require("user.dap") + end, + }, + + -- Jupyter Notebooks + { + "benlubas/molten-nvim", + version = "^1.0.0", + build = ":UpdateRemotePlugins", + init = function() + vim.g.molten_auto_open_output = false + vim.g.molten_image_provider = "none" -- Disabled image support (requires luarocks) + vim.g.molten_output_win_max_height = 20 + vim.g.molten_wrap_output = true + vim.g.molten_virt_text_output = true + vim.g.molten_virt_lines_off_by_1 = true + end, + }, + + -- Golang + { + "fatih/vim-go", + ft = "go", + build = ":GoUpdateBinaries", + }, + + -- Terminal + { + "akinsho/toggleterm.nvim", + version = "*", + config = function() + require("user.toggleterm") + end, + }, + + -- UI Enhancements + { + "nvim-lualine/lualine.nvim", + dependencies = { "nvim-tree/nvim-web-devicons" }, + config = function() + require("user.lualine") + end, + }, + { + "akinsho/bufferline.nvim", + version = "*", + dependencies = "nvim-tree/nvim-web-devicons", + config = function() + require("user.bufferline") + end, + }, + + -- Git Integration + { + "lewis6991/gitsigns.nvim", + config = function() + require("user.gitsigns") + end, + }, + + -- Utilities + { + "windwp/nvim-autopairs", + event = "InsertEnter", + config = function() + require("user.autopairs") + end, + }, + { + "numToStr/Comment.nvim", + config = function() + require("user.comment") + end, + }, + { + "folke/which-key.nvim", + event = "VeryLazy", + config = function() + require("user.whichkey") + end, + }, + + -- AI Assistance + { + "github/copilot.vim", + event = "InsertEnter", + }, +}, { + ui = { + border = "rounded", + }, + performance = { + rtp = { + disabled_plugins = { + "gzip", + "tarPlugin", + "tohtml", + "tutor", + "zipPlugin", + }, + }, + }, +}) diff --git a/lua/user/lint.lua b/lua/user/lint.lua new file mode 100644 index 0000000..7c46740 --- /dev/null +++ b/lua/user/lint.lua @@ -0,0 +1,26 @@ +local status_ok, lint = pcall(require, "lint") +if not status_ok then + return +end + +lint.linters_by_ft = { + typescript = { "eslint_d" }, + javascript = { "eslint_d" }, + typescriptreact = { "eslint_d" }, + javascriptreact = { "eslint_d" }, + go = { "golangcilint" }, + python = { "pylint" }, + sh = { "shellcheck" }, + bash = { "shellcheck" }, + java = { "checkstyle" }, +} + +-- Auto-lint on save and text change +local lint_augroup = vim.api.nvim_create_augroup("lint", { clear = true }) + +vim.api.nvim_create_autocmd({ "BufEnter", "BufWritePost", "InsertLeave" }, { + group = lint_augroup, + callback = function() + lint.try_lint() + end, +}) diff --git a/lua/user/lsp/init.lua b/lua/user/lsp/init.lua index 9da8242..5c5501c 100644 --- a/lua/user/lsp/init.lua +++ b/lua/user/lsp/init.lua @@ -3,6 +3,5 @@ if not status_ok then return end -require("user.lsp.lsp-installer") require("user.lsp.handlers").setup() -require("user.lsp.null-ls") +require("user.lsp.mason") diff --git a/lua/user/lsp/mason.lua b/lua/user/lsp/mason.lua new file mode 100644 index 0000000..2846679 --- /dev/null +++ b/lua/user/lsp/mason.lua @@ -0,0 +1,118 @@ +local status_ok, mason = pcall(require, "mason") +if not status_ok then + return +end + +local status_ok_config, mason_lspconfig = pcall(require, "mason-lspconfig") +if not status_ok_config then + return +end + +local status_ok_installer, mason_tool_installer = pcall(require, "mason-tool-installer") +if not status_ok_installer then + return +end + +-- Setup mason +mason.setup({ + ui = { + border = "rounded", + icons = { + package_installed = "✓", + package_pending = "➜", + package_uninstalled = "✗", + }, + }, +}) + +-- LSP servers for your tech stack +local servers = { + "jdtls", -- Java + "ts_ls", -- TypeScript/JavaScript + "gopls", -- Golang + "pyright", -- Python + "bashls", -- Bash + "lua_ls", -- Lua (for Neovim config) + "html", -- HTML + "cssls", -- CSS + "jsonls", -- JSON + "vimls", -- Vimscript +} + +mason_lspconfig.setup({ + ensure_installed = servers, + automatic_installation = true, +}) + +-- Additional tools (formatters, linters, debuggers) +mason_tool_installer.setup({ + ensure_installed = { + -- Formatters + "prettier", -- TypeScript/JavaScript/CSS/HTML + "stylua", -- Lua + "gofumpt", -- Go + "goimports", -- Go imports + "black", -- Python + "isort", -- Python imports + "google-java-format", -- Java + "shfmt", -- Shell/Bash + + -- Linters + "eslint_d", -- TypeScript/JavaScript + "golangci-lint", -- Go + "pylint", -- Python + "shellcheck", -- Bash + "checkstyle", -- Java + + -- Debuggers + "delve", -- Go + "debugpy", -- Python + "java-debug-adapter", -- Java + }, + auto_update = false, + run_on_start = true, +}) + +-- Setup handlers for automatic LSP configuration +mason_lspconfig.setup_handlers({ + -- Default handler for all servers + function(server_name) + local opts = { + on_attach = require("user.lsp.handlers").on_attach, + capabilities = require("user.lsp.handlers").capabilities, + } + + -- Server-specific settings + if server_name == "lua_ls" then + local lua_opts = require("user.lsp.settings.lua_ls") + opts = vim.tbl_deep_extend("force", lua_opts, opts) + end + + if server_name == "ts_ls" then + local ts_opts = require("user.lsp.settings.ts_ls") + opts = vim.tbl_deep_extend("force", ts_opts, opts) + end + + if server_name == "jsonls" then + local json_opts = require("user.lsp.settings.jsonls") + opts = vim.tbl_deep_extend("force", json_opts, opts) + end + + if server_name == "gopls" then + local go_opts = require("user.lsp.settings.gopls") + opts = vim.tbl_deep_extend("force", go_opts, opts) + end + + if server_name == "pyright" then + local python_opts = require("user.lsp.settings.pyright") + opts = vim.tbl_deep_extend("force", python_opts, opts) + end + + -- Skip jdtls as it needs special handling (will be setup in ftplugin/java.lua) + if server_name == "jdtls" then + return + end + + require("lspconfig")[server_name].setup(opts) + end, +}) diff --git a/lua/user/lsp/settings/gopls.lua b/lua/user/lsp/settings/gopls.lua new file mode 100644 index 0000000..9425aa7 --- /dev/null +++ b/lua/user/lsp/settings/gopls.lua @@ -0,0 +1,38 @@ +return { + 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, + }, + }, +} diff --git a/lua/user/lsp/settings/lua_ls.lua b/lua/user/lsp/settings/lua_ls.lua new file mode 100644 index 0000000..0d720ce --- /dev/null +++ b/lua/user/lsp/settings/lua_ls.lua @@ -0,0 +1,25 @@ +return { + settings = { + Lua = { + runtime = { + version = "LuaJIT", + }, + diagnostics = { + globals = { "vim" }, + }, + workspace = { + library = { + [vim.fn.expand("$VIMRUNTIME/lua")] = true, + [vim.fn.stdpath("config") .. "/lua"] = true, + }, + checkThirdParty = false, + }, + telemetry = { + enable = false, + }, + format = { + enable = false, + }, + }, + }, +} diff --git a/lua/user/lsp/settings/pyright.lua b/lua/user/lsp/settings/pyright.lua new file mode 100644 index 0000000..25d63b1 --- /dev/null +++ b/lua/user/lsp/settings/pyright.lua @@ -0,0 +1,20 @@ +return { + settings = { + python = { + analysis = { + typeCheckingMode = "basic", + diagnosticMode = "workspace", + inlayHints = { + variableTypes = true, + functionReturnTypes = true, + }, + autoSearchPaths = true, + useLibraryCodeForTypes = true, + diagnosticSeverityOverrides = { + reportUnusedVariable = "warning", + reportUnusedImport = "warning", + }, + }, + }, + }, +} diff --git a/lua/user/lsp/settings/ts_ls.lua b/lua/user/lsp/settings/ts_ls.lua new file mode 100644 index 0000000..87ee24f --- /dev/null +++ b/lua/user/lsp/settings/ts_ls.lua @@ -0,0 +1,26 @@ +return { + settings = { + typescript = { + inlayHints = { + includeInlayParameterNameHints = "all", + includeInlayParameterNameHintsWhenArgumentMatchesName = false, + includeInlayFunctionParameterTypeHints = true, + includeInlayVariableTypeHints = true, + includeInlayPropertyDeclarationTypeHints = true, + includeInlayFunctionLikeReturnTypeHints = true, + includeInlayEnumMemberValueHints = true, + }, + }, + javascript = { + inlayHints = { + includeInlayParameterNameHints = "all", + includeInlayParameterNameHintsWhenArgumentMatchesName = false, + includeInlayFunctionParameterTypeHints = true, + includeInlayVariableTypeHints = true, + includeInlayPropertyDeclarationTypeHints = true, + includeInlayFunctionLikeReturnTypeHints = true, + includeInlayEnumMemberValueHints = true, + }, + }, + }, +} diff --git a/lua/user/molten.lua b/lua/user/molten.lua new file mode 100644 index 0000000..ded40d0 --- /dev/null +++ b/lua/user/molten.lua @@ -0,0 +1,30 @@ +-- Molten (Jupyter notebook support) keymaps +local opts = { noremap = true, silent = true } + +-- Initialize Molten in a buffer +vim.keymap.set("n", "mi", ":MoltenInit", opts) + +-- Evaluate +vim.keymap.set("n", "me", ":MoltenEvaluateOperator", opts) +vim.keymap.set("n", "ml", ":MoltenEvaluateLine", opts) +vim.keymap.set("v", "me", ":MoltenEvaluateVisualgv", opts) +vim.keymap.set("n", "mr", ":MoltenReevaluateCell", opts) + +-- Show/hide output +vim.keymap.set("n", "mo", ":MoltenShowOutput", opts) +vim.keymap.set("n", "mh", ":MoltenHideOutput", opts) + +-- Delete cell +vim.keymap.set("n", "md", ":MoltenDelete", opts) + +-- Interrupt/restart kernel +vim.keymap.set("n", "mx", ":MoltenInterrupt", opts) + +-- Auto-commands for Python files +vim.api.nvim_create_autocmd("FileType", { + pattern = "python", + callback = function() + -- Automatically initialize molten for Python files if desired + -- vim.cmd("MoltenInit python3") + end, +}) diff --git a/lua/user/nvim-tree.lua b/lua/user/nvim-tree.lua index cf0518d..56ed90d 100644 --- a/lua/user/nvim-tree.lua +++ b/lua/user/nvim-tree.lua @@ -3,26 +3,118 @@ if not status_ok then return end --- disable netrw at the very start of your init.lua +-- Disable netrw vim.g.loaded_netrw = 1 vim.g.loaded_netrwPlugin = 1 -nvim_tree.setup {} +nvim_tree.setup({ + disable_netrw = true, + hijack_netrw = true, + hijack_cursor = false, + update_cwd = true, + sync_root_with_cwd = true, + respect_buf_cwd = true, -local function open_nvim_tree(data) + diagnostics = { + enable = true, + show_on_dirs = true, + icons = { + hint = "", + info = "", + warning = "", + error = "", + }, + }, - -- buffer is a real file on the disk - local real_file = vim.fn.filereadable(data.file) == 1 + update_focused_file = { + enable = true, + update_cwd = true, + ignore_list = {}, + }, + + system_open = { + cmd = nil, + args = {}, + }, + + filters = { + dotfiles = false, + custom = { ".git", "node_modules", ".cache", "__pycache__" }, + }, + + git = { + enable = true, + ignore = false, + timeout = 500, + }, - -- buffer is a [No Name] + view = { + width = 35, + side = "left", + number = false, + relativenumber = false, + }, + + renderer = { + highlight_git = true, + root_folder_label = ":~:s?$?/..?", + indent_markers = { + enable = true, + }, + icons = { + show = { + file = true, + folder = true, + folder_arrow = true, + git = true, + }, + glyphs = { + default = "", + symlink = "", + git = { + unstaged = "✗", + staged = "✓", + unmerged = "", + renamed = "➜", + untracked = "★", + deleted = "", + ignored = "◌", + }, + folder = { + arrow_open = "", + arrow_closed = "", + default = "", + open = "", + empty = "", + empty_open = "", + symlink = "", + symlink_open = "", + }, + }, + }, + }, + + actions = { + open_file = { + quit_on_open = false, + resize_window = true, + window_picker = { + enable = true, + }, + }, + }, +}) + +-- Auto-open nvim-tree on startup +local function open_nvim_tree(data) + local real_file = vim.fn.filereadable(data.file) == 1 local no_name = data.file == "" and vim.bo[data.buf].buftype == "" if not real_file and not no_name then return end - -- open the tree, find the file but don't focus it - require("nvim-tree.api").tree.toggle({ focus = false, find_file = true, }) + require("nvim-tree.api").tree.toggle({ focus = false, find_file = true }) end vim.api.nvim_create_autocmd({ "VimEnter" }, { callback = open_nvim_tree }) diff --git a/lua/user/toggleterm.lua b/lua/user/toggleterm.lua index 373eb6a..7452670 100644 --- a/lua/user/toggleterm.lua +++ b/lua/user/toggleterm.lua @@ -4,25 +4,32 @@ if not status_ok then end toggleterm.setup({ - size = 20, + 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 = [[]], 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", + persist_mode = true, + direction = "horizontal", -- VS Code style bottom terminal close_on_exit = true, shell = vim.o.shell, + auto_scroll = true, float_opts = { border = "curved", winblend = 0, - highlights = { - border = "Normal", - background = "Normal", - }, + }, + winbar = { + enabled = false, }, }) diff --git a/lua/user/treesitter.lua b/lua/user/treesitter.lua index f349496..bcedf0e 100644 --- a/lua/user/treesitter.lua +++ b/lua/user/treesitter.lua @@ -3,14 +3,60 @@ if not status_ok then return end -configs.setup { - ensure_installed = "all", -- one of "all", "maintained" (parsers with maintainers), or a list of languages - sync_install = false, -- install languages synchronously (only applied to `ensure_installed`) +configs.setup({ + -- Install parsers for your tech stack + ensure_installed = { + "java", + "typescript", + "tsx", + "javascript", + "go", + "python", + "bash", + "lua", + "vim", + "vimdoc", + "html", + "css", + "json", + "yaml", + "markdown", + "markdown_inline", + "regex", + "comment", + }, + + sync_install = false, auto_install = true, - ignore_install = { "phpdoc" }, -- list of parsers to ignore installing + ignore_install = { "phpdoc" }, + highlight = { - enable = true, -- false will disable the whole extension - disable = { "" }, -- list of language that will be disabled - additional_vim_regex_highlighting = true, + enable = true, + disable = {}, + additional_vim_regex_highlighting = false, + }, + + indent = { + enable = true, + disable = { "yaml", "python" }, + }, + + autopairs = { + enable = true, + }, + + context_commentstring = { + enable = true, + enable_autocmd = false, + }, + + incremental_selection = { + enable = true, + keymaps = { + init_selection = "", + node_incremental = "", + scope_incremental = false, + node_decremental = "", + }, }, -} +}) diff --git a/lua/user/whichkey.lua b/lua/user/whichkey.lua new file mode 100644 index 0000000..620d14e --- /dev/null +++ b/lua/user/whichkey.lua @@ -0,0 +1,153 @@ +local status_ok, which_key = pcall(require, "which-key") +if not status_ok then + return +end + +which_key.setup({ + plugins = { + marks = true, + registers = true, + spelling = { + enabled = true, + suggestions = 20, + }, + presets = { + operators = false, + motions = false, + text_objects = false, + windows = true, + nav = true, + z = true, + g = true, + }, + }, + icons = { + breadcrumb = "»", + separator = "➜", + group = "+", + }, + window = { + border = "rounded", + position = "bottom", + margin = { 1, 0, 1, 0 }, + padding = { 2, 2, 2, 2 }, + winblend = 0, + }, + layout = { + height = { min = 4, max = 25 }, + width = { min = 20, max = 50 }, + spacing = 3, + align = "left", + }, +}) + +-- Leader key mappings +local mappings = { + ["e"] = { ":NvimTreeToggle", "Explorer" }, + ["w"] = { ":w", "Save" }, + ["q"] = { ":q", "Quit" }, + ["c"] = { ":Bdelete!", "Close Buffer" }, + ["h"] = { ":nohlsearch", "No Highlight" }, + ["f"] = { ":Format", "Format" }, + + d = { + name = "Debug", + b = { "lua require'dap'.toggle_breakpoint()", "Toggle Breakpoint" }, + B = { "lua require'dap'.set_breakpoint(vim.fn.input('Breakpoint condition: '))", "Conditional Breakpoint" }, + c = { "lua require'dap'.continue()", "Continue" }, + i = { "lua require'dap'.step_into()", "Step Into" }, + o = { "lua require'dap'.step_over()", "Step Over" }, + O = { "lua require'dap'.step_out()", "Step Out" }, + r = { "lua require'dap'.repl.toggle()", "REPL" }, + l = { "lua require'dap'.run_last()", "Run Last" }, + u = { "lua require'dapui'.toggle()", "Toggle UI" }, + t = { "lua require'dap'.terminate()", "Terminate" }, + }, + + g = { + name = "Git", + g = { ":lua _LAZYGIT_TOGGLE()", "Lazygit" }, + j = { ":lua require 'gitsigns'.next_hunk()", "Next Hunk" }, + k = { ":lua require 'gitsigns'.prev_hunk()", "Prev Hunk" }, + l = { ":lua require 'gitsigns'.blame_line()", "Blame" }, + p = { ":lua require 'gitsigns'.preview_hunk()", "Preview Hunk" }, + r = { ":lua require 'gitsigns'.reset_hunk()", "Reset Hunk" }, + R = { ":lua require 'gitsigns'.reset_buffer()", "Reset Buffer" }, + s = { ":lua require 'gitsigns'.stage_hunk()", "Stage Hunk" }, + u = { ":lua require 'gitsigns'.undo_stage_hunk()", "Undo Stage Hunk" }, + o = { ":Telescope git_status", "Open changed file" }, + b = { ":Telescope git_branches", "Checkout branch" }, + c = { ":Telescope git_commits", "Checkout commit" }, + d = { ":Gitsigns diffthis HEAD", "Diff" }, + }, + + j = { + name = "Java", + o = { "lua require'jdtls'.organize_imports()", "Organize Imports" }, + v = { "lua require('jdtls').extract_variable()", "Extract Variable" }, + c = { "lua require('jdtls').extract_constant()", "Extract Constant" }, + m = { "lua require('jdtls').extract_method(true)", "Extract Method" }, + t = { "lua require'jdtls'.test_class()", "Test Class" }, + n = { "lua require'jdtls'.test_nearest_method()", "Test Nearest Method" }, + }, + + l = { + name = "LSP", + a = { "lua vim.lsp.buf.code_action()", "Code Action" }, + d = { "Telescope diagnostics bufnr=0", "Document Diagnostics" }, + w = { "Telescope diagnostics", "Workspace Diagnostics" }, + f = { ":Format", "Format" }, + i = { "LspInfo", "Info" }, + I = { "Mason", "Mason Info" }, + j = { "lua vim.diagnostic.goto_next()", "Next Diagnostic" }, + k = { "lua vim.diagnostic.goto_prev()", "Prev Diagnostic" }, + l = { "lua vim.lsp.codelens.run()", "CodeLens Action" }, + q = { "lua vim.diagnostic.setloclist()", "Quickfix" }, + r = { "lua vim.lsp.buf.rename()", "Rename" }, + s = { "Telescope lsp_document_symbols", "Document Symbols" }, + S = { "Telescope lsp_dynamic_workspace_symbols", "Workspace Symbols" }, + }, + + m = { + name = "Molten (Jupyter)", + i = { ":MoltenInit", "Initialize" }, + e = { ":MoltenEvaluateOperator", "Evaluate" }, + l = { ":MoltenEvaluateLine", "Evaluate Line" }, + r = { ":MoltenReevaluateCell", "Re-evaluate Cell" }, + o = { ":MoltenShowOutput", "Show Output" }, + h = { ":MoltenHideOutput", "Hide Output" }, + d = { ":MoltenDelete", "Delete Cell" }, + x = { ":MoltenInterrupt", "Interrupt" }, + }, + + s = { + name = "Search", + b = { ":Telescope git_branches", "Checkout branch" }, + c = { ":Telescope colorscheme", "Colorscheme" }, + f = { ":Telescope find_files", "Find File" }, + h = { ":Telescope help_tags", "Find Help" }, + M = { ":Telescope man_pages", "Man Pages" }, + r = { ":Telescope oldfiles", "Open Recent File" }, + R = { ":Telescope registers", "Registers" }, + k = { ":Telescope keymaps", "Keymaps" }, + C = { ":Telescope commands", "Commands" }, + t = { ":Telescope live_grep", "Text" }, + }, + + t = { + name = "Terminal", + t = { ":lua _DEFAULT_TOGGLE()", "Toggle Terminal" }, + g = { ":lua _LAZYGIT_TOGGLE()", "Lazygit" }, + n = { ":lua _NODE_TOGGLE()", "Node" }, + h = { ":lua _HTOP_TOGGLE()", "Htop" }, + }, +} + +which_key.register(mappings, { + mode = "n", + prefix = "", + buffer = nil, + silent = true, + noremap = true, + nowait = true, +})