--- Comprehensive LSP, formatting, and linting configuration --- Optimized for tech stack: --- - Cloudflare Workers/Pages --- - Golang (Cosmos SDK) --- - TypeScript/React/Vite --- - Protobufs --- - Bun/ES Modules/WASM --- @module plugins.astrolsp return { -- ============================================================================ -- AstroLSP - Core LSP Configuration -- ============================================================================ { "AstroNvim/astrolsp", ---@type AstroLSPOpts opts = { -- Enable LSP features features = { autoformat = true, codelens = true, inlay_hints = true, -- Enable inlay hints for TS/Go lsp_handlers = true, semantic_tokens = true, }, -- LSP UI defaults defaults = { hover = { border = "rounded", silent = true }, -- Rounded border for hover windows signature_help = { border = "rounded", silent = true }, -- Enable signature help }, -- LSP client flags flags = { exit_timeout = 5000, -- Time to wait for LSP server to exit before force kill debounce_text_changes = 150, -- Debounce didChange notifications }, -- LSP Formatting Configuration formatting = { format_on_save = { enabled = true, -- Allow specific filetypes (prefer conform.nvim for JS/TS) allow_filetypes = { "go", "proto", "toml", "lua", }, }, -- Disable LSP formatting for these (use conform.nvim/oxfmt instead) disabled = { "ts_ls", -- Use oxfmt via conform "tsserver", -- Old name, keeping for compatibility "vtsls", -- Use oxfmt via conform "html", -- Use conform "cssls", -- Use conform "tailwindcss", -- Doesn't format }, timeout_ms = 5000, -- Increase timeout for large Go files filter = function(client) -- Prefer conform.nvim for JS/TS formatting if vim.bo.filetype == "typescript" or vim.bo.filetype == "javascript" or vim.bo.filetype == "typescriptreact" or vim.bo.filetype == "javascriptreact" then return false end -- Use gopls for Go formatting if vim.bo.filetype == "go" then return client.name == "gopls" end return true end, }, -- Enable file operations for better refactoring file_operations = { timeout = 10000, operations = { willCreate = true, didCreate = true, willRename = true, didRename = true, willDelete = true, didDelete = true, }, }, -- Language server configurations config = { -- Golang configuration optimized for Cosmos SDK gopls = { settings = { gopls = { -- Code analysis analyses = { unusedparams = true, shadow = true, nilness = true, unusedwrite = true, useany = true, unusedvariable = true, }, -- Staticcheck integration staticcheck = true, -- Inlay hints hints = { assignVariableTypes = true, compositeLiteralFields = true, compositeLiteralTypes = true, constantValues = true, functionTypeParameters = true, parameterNames = true, rangeVariableTypes = true, }, -- Code completion usePlaceholders = true, completeUnimported = true, -- Build flags for Cosmos SDK buildFlags = { "-tags=ledger,test" }, -- Workspace settings directoryFilters = { "-**/node_modules", "-**/.git", "-**/.vscode", "-**/.idea", "-**/.devbox", "-**/dist", "-**/build", }, -- Semantic tokens semanticTokens = true, -- Code lenses codelenses = { gc_details = true, generate = true, regenerate_cgo = true, test = true, tidy = true, upgrade_dependency = true, vendor = true, }, }, }, }, -- TypeScript configuration for Vite/React/Cloudflare ts_ls = { settings = { typescript = { inlayHints = { includeInlayParameterNameHints = "all", includeInlayParameterNameHintsWhenArgumentMatchesName = true, includeInlayFunctionParameterTypeHints = true, includeInlayVariableTypeHints = true, includeInlayVariableTypeHintsWhenTypeMatchesName = true, includeInlayPropertyDeclarationTypeHints = true, includeInlayFunctionLikeReturnTypeHints = true, includeInlayEnumMemberValueHints = true, }, suggest = { completeFunctionCalls = true, }, -- Cloudflare Workers types preferences = { importModuleSpecifier = "relative", importModuleSpecifierEnding = "minimal", }, }, javascript = { inlayHints = { includeInlayParameterNameHints = "all", includeInlayParameterNameHintsWhenArgumentMatchesName = true, includeInlayFunctionParameterTypeHints = true, includeInlayVariableTypeHints = true, includeInlayPropertyDeclarationTypeHints = true, includeInlayFunctionLikeReturnTypeHints = true, includeInlayEnumMemberValueHints = true, }, suggest = { completeFunctionCalls = true, }, }, }, }, -- Volar for Vue (also helpful for some React scenarios) volar = { filetypes = { "vue", "typescript", "javascript", "typescriptreact", "javascriptreact" }, }, -- Tailwind CSS for React/shadcn components tailwindcss = { root_dir = function(fname) local util = require "lspconfig.util" return util.root_pattern("tailwind.config.js", "tailwind.config.ts", "postcss.config.js")(fname) end, settings = { tailwindCSS = { experimental = { classRegex = { -- Support for cn() and clsx() utilities { "cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]" }, { "clsx\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]" }, { "cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]" }, }, }, validate = true, lint = { cssConflict = "warning", invalidApply = "error", invalidScreen = "error", invalidVariant = "error", invalidConfigPath = "error", invalidTailwindDirective = "error", recommendedVariantOrder = "warning", }, }, }, }, -- CSS LSP cssls = {}, -- HTML for React/templates html = { filetypes = { "html", "typescriptreact", "javascriptreact" }, settings = { html = { format = { templating = true, wrapLineLength = 100, wrapAttributes = "auto", }, hover = { documentation = true, references = true, }, }, }, }, -- JSON/JSONC for config files jsonls = { settings = { json = { schemas = { -- Cloudflare wrangler.toml { fileMatch = { "wrangler.toml", "wrangler.json" }, url = "https://raw.githubusercontent.com/cloudflare/wrangler/main/wrangler-schema.json", }, -- Package.json { fileMatch = { "package.json" }, url = "https://json.schemastore.org/package.json", }, -- tsconfig.json { fileMatch = { "tsconfig.json", "tsconfig.*.json" }, url = "https://json.schemastore.org/tsconfig.json", }, }, validate = { enable = true }, }, }, }, -- YAML for configs yamlls = { settings = { yaml = { schemaStore = { enable = true, url = "https://www.schemastore.org/api/json/catalog.json", }, schemas = { ["https://json.schemastore.org/github-workflow.json"] = ".github/workflows/*.yml", ["https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json"] = "docker-compose*.yml", }, validate = true, hover = true, completion = true, }, }, }, -- Lua for Neovim config lua_ls = { settings = { Lua = { hint = { enable = true, arrayIndex = "Auto", setType = true, }, }, }, }, }, -- Capabilities configuration capabilities = { textDocument = { completion = { completionItem = { documentationFormat = { "markdown", "plaintext" }, snippetSupport = true, preselectSupport = true, insertReplaceSupport = true, labelDetailsSupport = true, deprecatedSupport = true, commitCharactersSupport = true, tagSupport = { valueSet = { 1 } }, resolveSupport = { properties = { "documentation", "detail", "additionalTextEdits", }, }, }, }, }, }, -- LSP-specific keymappings (keeping existing bindings) mappings = { n = { -- LSP Code Actions ["a"] = { function() vim.lsp.buf.code_action() end, desc = "LSP Code Action", }, -- LSP Hover ["h"] = { function() vim.lsp.buf.hover() end, desc = "LSP Hover", }, -- LSP Rename (using inc-rename plugin) ["r"] = { function() return ":IncRename " .. vim.fn.expand "" end, expr = true, desc = "Rename current symbol", cond = "textDocument/rename", }, -- LSP Diagnostics (using snacks.picker) ["d"] = { function() require("snacks").picker.diagnostics() end, desc = "Find LSP diagnostics", }, -- LSP Symbols (using snacks.picker) ["s"] = { function() require("snacks").picker.lsp_symbols() end, desc = "Find LSP symbols", }, -- Navigate diagnostics K = { function() vim.diagnostic.goto_prev() end, desc = "Previous Diagnostic", }, J = { function() vim.diagnostic.goto_next() end, desc = "Next Diagnostic", }, }, }, -- Autocmds for LSP-specific behavior autocmds = { -- Disable inlay hints in insert mode for cleaner editing no_insert_inlay_hints = { cond = vim.lsp.inlay_hint and "textDocument/inlayHint" or false, { event = "InsertEnter", desc = "Disable inlay hints on insert", callback = function(args) local filter = { bufnr = args.buf } if vim.lsp.inlay_hint.is_enabled(filter) then vim.lsp.inlay_hint.enable(false, filter) vim.api.nvim_create_autocmd("InsertLeave", { buffer = args.buf, once = true, callback = function() vim.lsp.inlay_hint.enable(true, filter) end, }) end end, }, }, }, }, -- ============================================================================ -- Formatting - Conform.nvim -- ============================================================================ { "stevearc/conform.nvim", event = "BufWritePre", cmd = { "ConformInfo" }, opts = { formatters_by_ft = { typescript = { "oxfmt" }, typescriptreact = { "oxfmt" }, javascript = { "oxfmt" }, javascriptreact = { "oxfmt" }, json = { "oxfmt" }, jsonc = { "oxfmt" }, css = { "oxfmt" }, scss = { "oxfmt" }, html = { "oxfmt" }, markdown = { "oxfmt" }, yaml = { "oxfmt" }, lua = { "stylua" }, rust = { "rustfmt" }, toml = { "taplo" }, }, format_on_save = function(bufnr) -- Disable with a global or buffer-local variable if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then return end return { timeout_ms = 500, lsp_fallback = true, } end, formatters = { -- oxfmt uses default configuration }, }, config = function(_, opts) require("conform").setup(opts) -- Command to toggle format on save vim.api.nvim_create_user_command("FormatToggle", function(args) if args.bang then -- Toggle buffer-local setting vim.b.disable_autoformat = not vim.b.disable_autoformat else -- Toggle global setting vim.g.disable_autoformat = not vim.g.disable_autoformat end local status = vim.g.disable_autoformat and "disabled" or "enabled" vim.notify("Format on save " .. status, vim.log.levels.INFO) end, { desc = "Toggle format on save", bang = true, }) end, }, -- ============================================================================ -- Linting - nvim-lint -- ============================================================================ { "mfussenegger/nvim-lint", event = { "BufReadPre", "BufNewFile" }, config = function() local lint = require "lint" -- Configure linters per filetype lint.linters_by_ft = { typescript = { "oxlint" }, typescriptreact = { "oxlint" }, javascript = { "oxlint" }, javascriptreact = { "oxlint" }, css = { "stylelint" }, scss = { "stylelint" }, markdown = { "markdownlint" }, } -- Auto-lint on save and text changed local lint_augroup = vim.api.nvim_create_augroup("lint", { clear = true }) vim.api.nvim_create_autocmd({ "BufEnter", "BufWritePost", "InsertLeave", "TextChanged" }, { group = lint_augroup, callback = function() -- Only lint if file exists on disk if vim.fn.filereadable(vim.api.nvim_buf_get_name(0)) == 1 then lint.try_lint() end end, }) end, }, -- ============================================================================ -- Treesitter Configuration -- ============================================================================ { "nvim-treesitter/nvim-treesitter", optional = true, opts = function(_, opts) if opts.ensure_installed ~= "all" then opts.ensure_installed = require("astrocore").list_insert_unique(opts.ensure_installed, { "typescript", "tsx", "javascript", "jsdoc", "json", "jsonc", "css", "scss", "html", "markdown", "markdown_inline", "regex", "bash", "vim", "lua", "proto", -- Protobuf syntax highlighting }) end end, }, -- Treesitter context for React component awareness { "nvim-treesitter/nvim-treesitter-context", event = { "BufReadPost", "BufNewFile" }, opts = { enable = true, max_lines = 3, patterns = { default = { "class", "function", "method", "for", "while", "if", "switch", "case", "interface", "struct", "enum", }, typescript = { "class_declaration", "abstract_class_declaration", "function_declaration", "method_definition", "arrow_function", "function_expression", "lexical_declaration", "variable_declaration", }, typescriptreact = { "class_declaration", "function_declaration", "method_definition", "arrow_function", "function_expression", "jsx_element", "jsx_self_closing_element", }, }, }, }, -- ============================================================================ -- TypeScript Tooling -- ============================================================================ { "pmizio/typescript-tools.nvim", ft = { "typescript", "typescriptreact", "javascript", "javascriptreact" }, dependencies = { "nvim-lua/plenary.nvim", "neovim/nvim-lspconfig" }, opts = { settings = { separate_diagnostic_server = true, publish_diagnostic_on = "insert_leave", expose_as_code_action = "all", tsserver_file_preferences = { includeInlayParameterNameHints = "all", includeInlayParameterNameHintsWhenArgumentMatchesName = true, includeInlayFunctionParameterTypeHints = true, includeInlayVariableTypeHints = true, includeInlayVariableTypeHintsWhenTypeMatchesName = true, includeInlayPropertyDeclarationTypeHints = true, includeInlayFunctionLikeReturnTypeHints = true, includeInlayEnumMemberValueHints = true, }, tsserver_format_options = { allowIncompleteCompletions = false, allowRenameOfImportPath = true, }, }, }, }, -- Auto-close and auto-rename JSX tags { "windwp/nvim-ts-autotag", event = { "BufReadPost", "BufNewFile" }, ft = { "html", "javascript", "typescript", "javascriptreact", "typescriptreact", "xml", "vue", "svelte", }, opts = { autotag = { enable = true, enable_rename = true, enable_close = true, enable_close_on_slash = true, }, }, }, -- ============================================================================ -- Mason Configuration -- ============================================================================ { "williamboman/mason-lspconfig.nvim", optional = true, opts = function(_, opts) opts.ensure_installed = require("astrocore").list_insert_unique(opts.ensure_installed, { -- TypeScript/JavaScript "ts_ls", -- tsserver was renamed to ts_ls "tailwindcss", "html", -- Styling "cssls", -- Config files "jsonls", "yamlls", -- Lua "lua_ls", -- Go (Cosmos SDK) "gopls", }) end, }, -- Note: mason-tool-installer is configured in lua/plugins/mason.lua -- ============================================================================ -- Package Management Tools -- ============================================================================ { "vuki656/package-info.nvim", event = "BufRead package.json", dependencies = { "MunifTanjim/nui.nvim" }, opts = {}, }, -- Crates.io integration (useful for Rust tooling in Workers) { "saecki/crates.nvim", event = "BufRead Cargo.toml", dependencies = { "nvim-lua/plenary.nvim" }, opts = { lsp = { enabled = true, actions = true, completion = true, hover = true, }, }, }, -- ============================================================================ -- Diagnostics UI -- ============================================================================ { "folke/trouble.nvim", optional = true, opts = { modes = { lsp = { win = { position = "right" }, }, }, }, }, -- Inline linting hints { "https://git.sr.ht/~whynothugo/lsp_lines.nvim", event = { "BufReadPost", "BufNewFile" }, config = function() require("lsp_lines").setup() -- Disable virtual_text since lsp_lines will show diagnostics vim.diagnostic.config { virtual_text = false } -- Toggle command vim.api.nvim_create_user_command("LspLinesToggle", function() local new_value = not require("lsp_lines").toggle() vim.diagnostic.config { virtual_text = new_value } end, { desc = "Toggle LSP lines" }) end, }, -- Better quickfix for showing lint errors { "kevinhwang91/nvim-bqf", ft = "qf", opts = { auto_resize_height = true, func_map = { vsplit = "v", split = "s", tab = "t", tabb = "T", tabc = "", ptogglemode = "z,", }, }, }, }, }