From b3fad1fd2578b22d519ffc32bc06e9ad8e00ecca Mon Sep 17 00:00:00 2001 From: Tomas Mirchev Date: Thu, 28 May 2026 02:03:38 +0300 Subject: [PATCH] add biome and two lsp at once --- lsp/biome.lua | 82 ++++++++++++++++++++++++++++++++ lua/lspconfig/util.lua | 23 +++++++++ lua/modules/language-specs.lua | 6 +-- lua/plugins/language-manager.lua | 17 +++++-- 4 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 lsp/biome.lua create mode 100644 lua/lspconfig/util.lua diff --git a/lsp/biome.lua b/lsp/biome.lua new file mode 100644 index 0000000..89735af --- /dev/null +++ b/lsp/biome.lua @@ -0,0 +1,82 @@ +---@brief +--- https://biomejs.dev +--- +--- Toolchain of the web. [Successor of Rome](https://biomejs.dev/blog/annoucing-biome). +--- +--- ```sh +--- npm install [-g] @biomejs/biome +--- ``` +--- +--- ### Monorepo support +--- +--- `biome` supports monorepos by default. It will automatically find the `biome.json` corresponding to the package you are working on, as described in the [documentation](https://biomejs.dev/guides/big-projects/#monorepo). This works without the need of spawning multiple instances of `biome`, saving memory. + +local util = require 'lspconfig.util' + +---@type vim.lsp.Config +return { + cmd = function(dispatchers, config) + local cmd = 'biome' + if (config or {}).root_dir then + local local_cmd = vim.fs.joinpath(config.root_dir, 'node_modules/.bin', cmd) + if vim.fn.executable(local_cmd) == 1 then + cmd = local_cmd + end + end + return vim.lsp.rpc.start({ cmd, 'lsp-proxy' }, dispatchers) + end, + filetypes = { + 'astro', + 'css', + 'graphql', + 'html', + 'javascript', + 'javascriptreact', + 'json', + 'jsonc', + 'svelte', + 'typescript', + 'typescriptreact', + 'vue', + }, + workspace_required = true, + root_dir = function(bufnr, on_dir) + -- The project root is where the LSP can be started from + -- As stated in the documentation above, this LSP supports monorepos and simple projects. + -- We select then from the project root, which is identified by the presence of a package + -- manager lock file. + local root_markers = { + 'package-lock.json', + 'yarn.lock', + 'pnpm-lock.yaml', + 'bun.lockb', + 'bun.lock', + 'deno.lock', + } + -- Set a lower priority to avoid spawning multiple servers on monorepos + local biome_config_files = { 'biome.json', 'biome.jsonc' } + -- Give the root markers equal priority by wrapping them in a table + root_markers = vim.fn.has('nvim-0.11.3') == 1 and { root_markers, biome_config_files, { '.git' } } + or vim.list_extend(root_markers, vim.list_extend(biome_config_files, { '.git' })) + + -- We fallback to the current working directory if no project root is found + local project_root = vim.fs.root(bufnr, root_markers) or vim.fn.getcwd() + + -- We know that the buffer is using Biome if it has a config file + -- in its directory tree. + local filename = vim.api.nvim_buf_get_name(bufnr) + biome_config_files = util.insert_package_json(biome_config_files, 'biomejs', filename) + local is_buffer_using_biome = vim.fs.find(biome_config_files, { + path = filename, + type = 'file', + limit = 1, + upward = true, + stop = vim.fs.dirname(project_root), + })[1] + if not is_buffer_using_biome then + return + end + + on_dir(project_root) + end, +} diff --git a/lua/lspconfig/util.lua b/lua/lspconfig/util.lua new file mode 100644 index 0000000..a206624 --- /dev/null +++ b/lua/lspconfig/util.lua @@ -0,0 +1,23 @@ +local M = {} + +function M.root_markers_with_field(root_files, new_names, field, fname) + local path = vim.fn.fnamemodify(fname, ':h') + local found = vim.fs.find(new_names, { path = path, upward = true }) + + for _, f in ipairs(found or {}) do + for line in io.lines(f) do + if line:find(field) then + root_files[#root_files + 1] = vim.fs.basename(f) + break + end + end + end + + return root_files +end + +function M.insert_package_json(root_files, field, fname) + return M.root_markers_with_field(root_files, { 'package.json', 'package.json5' }, field, fname) +end + +return M diff --git a/lua/modules/language-specs.lua b/lua/modules/language-specs.lua index d94f225..fc62e57 100644 --- a/lua/modules/language-specs.lua +++ b/lua/modules/language-specs.lua @@ -39,14 +39,14 @@ function M.get() { ft = 'markdown', ts = { 'markdown', 'markdown_inline' }, format = 'prettier' }, { ft = 'bash', lsp = 'bash-language-server', lint = 'shellcheck', format = 'shfmt' }, { ft = 'lua', lsp = 'lua-language-server', lint = 'luacheck', format = 'stylua' }, - { ft = { 'json', 'jsonc' }, lsp = 'json-lsp' }, + { ft = { 'json', 'jsonc' }, lsp = 'biome', format = 'biome' }, { ft = 'html', lsp = 'html-lsp' }, { ft = 'css', lsp = { 'css-lsp', 'tailwindcss-language-server' } }, { ft = { 'javascript', 'typescript', 'javascriptreact', 'typescriptreact' }, ts = { 'javascript', 'typescript', 'tsx' }, - lsp = { 'vtsls', 'eslint-lsp' }, - format = { 'prettierd', 'prettier' }, + lsp = { 'vtsls', 'biome' }, + format = 'biome', }, } end diff --git a/lua/plugins/language-manager.lua b/lua/plugins/language-manager.lua index 10feecd..622a228 100644 --- a/lua/plugins/language-manager.lua +++ b/lua/plugins/language-manager.lua @@ -206,13 +206,14 @@ end -- ======== Cache ======== function M.load_specs() + local specs_raw = require('modules.language-specs').get() + local hash = hash_spec(specs_raw) local cache = load_cache() - if cache then + if cache and cache.hash == hash then M.general = cache.spec else - local specs_raw = require('modules.language-specs').get() M.general, M.mason = M.generate_specs(specs_raw) - save_cache({ hash = hash_spec(specs_raw), spec = M.general }) + save_cache({ hash = hash, spec = M.general }) end return M.general, M.mason end @@ -293,11 +294,19 @@ function M.ts.setup() end function M.lsp.setup() + vim.lsp.config('*', { + capabilities = { + general = { + positionEncodings = { 'utf-8' }, + }, + }, + }) + for _, lsp_name in ipairs((M.general and M.general.language_servers) or {}) do vim.lsp.enable(lsp_name) end - vim.api.nvim_buf_create_user_command(0, 'LspInfo', function() + vim.api.nvim_create_user_command('LspInfo', function() vim.cmd('checkhealth vim.lsp') end, {}) end