Files

709 lines
22 KiB
Lua
Raw Permalink Normal View History

--- 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
["<C-a>a"] = {
function() vim.lsp.buf.code_action() end,
desc = "LSP Code Action",
},
-- LSP Hover
["<C-a>h"] = {
function() vim.lsp.buf.hover() end,
desc = "LSP Hover",
},
-- LSP Rename (using inc-rename plugin)
["<C-a>r"] = {
function() return ":IncRename " .. vim.fn.expand "<cword>" end,
expr = true,
desc = "Rename current symbol",
cond = "textDocument/rename",
},
-- LSP Diagnostics (using snacks.picker)
["<C-a>d"] = {
function() require("snacks").picker.diagnostics() end,
desc = "Find LSP diagnostics",
},
-- LSP Symbols (using snacks.picker)
["<C-a>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,
},
},
},
-- ============================================================================
-- 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 = "<C-t>",
ptogglemode = "z,",
},
},
},
},
}