From 5a3a28badc5dd8d3fb263e2a01c195516c31eb714dcbb93a904dbd1ec314cb05 Mon Sep 17 00:00:00 2001 From: Timothy Pidashev Date: Mon, 27 Apr 2026 11:57:14 -0700 Subject: [PATCH] Add mdx lsp support --- lsp/lemminx.lua | 5 +++ lsp/lwc_ls.lua | 7 ++++ lsp/mdx_analyzer.lua | 24 +++++++++++ lua/timmypidashev/plugins/lint.lua | 63 ++++++++++++++++++++++++++++- lua/timmypidashev/plugins/lsp.lua | 16 ++++++++ lua/timmypidashev/plugins/mason.lua | 2 + 6 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 lsp/lemminx.lua create mode 100644 lsp/lwc_ls.lua create mode 100644 lsp/mdx_analyzer.lua diff --git a/lsp/lemminx.lua b/lsp/lemminx.lua new file mode 100644 index 0000000..4221084 --- /dev/null +++ b/lsp/lemminx.lua @@ -0,0 +1,5 @@ +return { + cmd = { "lemminx" }, + filetypes = { "xml", "xsd", "xsl", "xslt", "svg" }, + root_markers = { "sfdx-project.json", ".git" }, +} diff --git a/lsp/lwc_ls.lua b/lsp/lwc_ls.lua new file mode 100644 index 0000000..cc62338 --- /dev/null +++ b/lsp/lwc_ls.lua @@ -0,0 +1,7 @@ +-- LWC LSP: install with `npm i -g @salesforce/lwc-language-server`. +-- Attaches only inside SFDX projects via root marker. +return { + cmd = { "lwc-language-server", "--stdio" }, + filetypes = { "javascript", "html" }, + root_markers = { "sfdx-project.json" }, +} diff --git a/lsp/mdx_analyzer.lua b/lsp/mdx_analyzer.lua new file mode 100644 index 0000000..f3b30e5 --- /dev/null +++ b/lsp/mdx_analyzer.lua @@ -0,0 +1,24 @@ +local function tsdk() + local local_ts = vim.fs.find("node_modules/typescript/lib", { + upward = true, + path = vim.fn.expand("%:p:h"), + type = "directory", + })[1] + if local_ts then return local_ts end + return vim.fn.stdpath("data") + .. "/mason/packages/typescript-language-server/node_modules/typescript/lib" +end + +return { + cmd = { "mdx-language-server", "--stdio" }, + filetypes = { "mdx", "markdown.mdx" }, + root_markers = { "package.json", "tsconfig.json", ".git" }, + init_options = { + typescript = { tsdk = tsdk() }, + }, + capabilities = { + workspace = { + didChangeWatchedFiles = { dynamicRegistration = false }, + }, + }, +} diff --git a/lua/timmypidashev/plugins/lint.lua b/lua/timmypidashev/plugins/lint.lua index 7029fa0..fe880f2 100644 --- a/lua/timmypidashev/plugins/lint.lua +++ b/lua/timmypidashev/plugins/lint.lua @@ -1,5 +1,49 @@ local lint = require("lint") +-- PMD for Apex. JVM startup ~3s per run, so we trigger on save only. +-- Uses project-local pmd-ruleset.xml if present, else bundled Apex categories. +lint.linters.pmd_apex = { + cmd = "pmd", + stdin = false, + append_fname = true, + args = function() + local ruleset = vim.fs.find("pmd-ruleset.xml", { + upward = true, + path = vim.fn.expand("%:p:h"), + })[1] or + "category/apex/bestpractices.xml,category/apex/errorprone.xml," .. + "category/apex/security.xml,category/apex/performance.xml," .. + "category/apex/design.xml" + return { "check", "-R", ruleset, "-f", "json", "--no-progress", "-d" } + end, + stream = "stdout", + ignore_exitcode = true, + parser = function(output) + if not output or output == "" then return {} end + local ok, decoded = pcall(vim.json.decode, output) + if not ok or not decoded.files then return {} end + local diags = {} + for _, file in ipairs(decoded.files) do + for _, v in ipairs(file.violations or {}) do + local sev = vim.diagnostic.severity.WARN + if (v.priority or 3) <= 2 then sev = vim.diagnostic.severity.ERROR + elseif (v.priority or 3) >= 4 then sev = vim.diagnostic.severity.INFO end + table.insert(diags, { + lnum = (v.beginline or 1) - 1, + end_lnum = (v.endline or v.beginline or 1) - 1, + col = (v.begincolumn or 1) - 1, + end_col = v.endcolumn or v.begincolumn or 1, + severity = sev, + source = "pmd", + code = v.rule, + message = v.description, + }) + end + end + return diags + end, +} + lint.linters_by_ft = { javascript = { "eslint_d" }, typescript = { "eslint_d" }, @@ -9,10 +53,25 @@ lint.linters_by_ft = { sh = { "shellcheck" }, bash = { "shellcheck" }, markdown = { "markdownlint-cli2" }, + -- apex handled via separate save-only autocmd below (pmd is slow) } vim.api.nvim_create_autocmd({ "BufWritePost", "InsertLeave" }, { - callback = function() lint.try_lint() end, + callback = function() + if vim.bo.filetype == "apex" then return end + lint.try_lint() + end, }) -vim.keymap.set("n", "l", function() lint.try_lint() end, { desc = "Lint buffer" }) +vim.api.nvim_create_autocmd("BufWritePost", { + pattern = { "*.cls", "*.trigger", "*.apex" }, + callback = function() lint.try_lint("pmd_apex") end, +}) + +vim.keymap.set("n", "l", function() + if vim.bo.filetype == "apex" then + lint.try_lint("pmd_apex") + else + lint.try_lint() + end +end, { desc = "Lint buffer" }) diff --git a/lua/timmypidashev/plugins/lsp.lua b/lua/timmypidashev/plugins/lsp.lua index 9e9da22..7656c6f 100644 --- a/lua/timmypidashev/plugins/lsp.lua +++ b/lua/timmypidashev/plugins/lsp.lua @@ -1,8 +1,21 @@ vim.filetype.add({ extension = { + mdx = "markdown.mdx", cls = "apex", trigger = "apex", apex = "apex", + -- Visualforce + page = "html", + component = "html", + -- Aura + cmp = "html", + app = "html", + evt = "xml", + -- SOQL + soql = "soql", + }, + pattern = { + [".*%-meta%.xml"] = "xml", }, }) @@ -26,6 +39,9 @@ vim.lsp.enable({ "bashls", "apex_ls", "htmx", + "lemminx", + "lwc_ls", + "mdx_analyzer", }) vim.diagnostic.config({ diff --git a/lua/timmypidashev/plugins/mason.lua b/lua/timmypidashev/plugins/mason.lua index 15bf883..7108a04 100644 --- a/lua/timmypidashev/plugins/mason.lua +++ b/lua/timmypidashev/plugins/mason.lua @@ -25,6 +25,8 @@ require("mason-tool-installer").setup({ "taplo", "bash-language-server", "htmx-lsp", + "lemminx", + "mdx-analyzer", -- Apex LSP not in mason registry — install JAR manually, set APEX_JAR env -- Formatters