aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2026-02-12 16:13:12 -0500
committerGitHub <noreply@github.com>2026-02-12 16:13:12 -0500
commit05d01a4ef7bb17dfbc23c539e8fb4b3a5f1b0a0a (patch)
tree269f5bdb9b310e81ca8e63b5efd13bcc236d0e35
parentdocs: update configs.md (diff)
parentfix(vtsls): apply the same deno-excluding logic that ts_ls uses (diff)
downloadnvim-lspconfig-05d01a4ef7bb17dfbc23c539e8fb4b3a5f1b0a0a.tar
nvim-lspconfig-05d01a4ef7bb17dfbc23c539e8fb4b3a5f1b0a0a.tar.gz
nvim-lspconfig-05d01a4ef7bb17dfbc23c539e8fb4b3a5f1b0a0a.tar.bz2
nvim-lspconfig-05d01a4ef7bb17dfbc23c539e8fb4b3a5f1b0a0a.tar.lz
nvim-lspconfig-05d01a4ef7bb17dfbc23c539e8fb4b3a5f1b0a0a.tar.xz
nvim-lspconfig-05d01a4ef7bb17dfbc23c539e8fb4b3a5f1b0a0a.tar.zst
nvim-lspconfig-05d01a4ef7bb17dfbc23c539e8fb4b3a5f1b0a0a.zip
Merge #4304 from isaacs/isaacs/deno-ts_ls-inference
-rw-r--r--lsp/denols.lua53
-rw-r--r--lsp/ts_ls.lua41
-rw-r--r--lsp/tsgo.lua47
-rw-r--r--lsp/vtsls.lua19
4 files changed, 136 insertions, 24 deletions
diff --git a/lsp/denols.lua b/lsp/denols.lua
index b5d9ffe2..dc7b9ee7 100644
--- a/lsp/denols.lua
+++ b/lsp/denols.lua
@@ -12,6 +12,37 @@
--- "ts=typescript"
--- }
--- ```
+---
+--- Some care must be taken here to correctly infer whether a file is part of a Deno program, or a TS program that
+--- expects to run in Node or Web Browsers. This supports having a Deno module that is a part of a mostly-not-Deno
+--- monorepo. We do this by finding the nearest package manager lock file, and the nearest deno.json or deno.jsonc.
+--- Note that this means that without a deno.json, deno.jsonc, or deno.lock file, this LSP client will not attach.
+---
+--- Example:
+---
+--- ```
+--- project-root
+--- +-- node_modules/...
+--- +-- package-lock.json
+--- +-- package.json
+--- +-- packages
+--- +-- deno-module
+--- | +-- deno.json
+--- | +-- package.json <-- It's normal for Deno projects to have package.json files!
+--- | +-- src
+--- | +-- index.ts <-- this is a Deno file
+--- +-- node-module
+--- +-- package.json
+--- +-- src
+--- +-- index.ts <-- a non-Deno file (ie, should use ts_ls or tsgo)
+--- ```
+---
+--- From the file being edited, we walk up to find the nearest package manager lockfile. This is PROJECT ROOT.
+--- From the file being edited, find the nearest deno.json or deno.jsonc. This is DENO ROOT.
+--- From the file being edited, find the nearest deno.lock. This is DENO LOCK ROOT
+--- If DENO LOCK ROOT is found, and PROJECT ROOT is missing or shorter, then this is a deno file, and we attach.
+--- If DENO ROOT is found, and it's longer than or equal to PROJECT ROOT, then this is a Deno file, and we attach.
+--- Otherwise, we abort, because this is a non-Deno TS file.
local lsp = vim.lsp
@@ -77,21 +108,23 @@ return {
},
root_dir = function(bufnr, on_dir)
-- The project root is where the LSP can be started from
- local root_markers = { 'deno.lock' }
+ local root_markers = { 'deno.lock', 'deno.json', 'deno.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, { '.git' } }
or vim.list_extend(root_markers, { '.git' })
- -- exclude non-deno projects (npm, yarn, pnpm, bun)
- local non_deno_path = vim.fs.root(
- bufnr,
- { 'package.json', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb', 'bun.lock' }
- )
+ -- only include deno projects
+ local deno_root = vim.fs.root(bufnr, { 'deno.json', 'deno.jsonc' })
+ local deno_lock_root = vim.fs.root(bufnr, { 'deno.lock' })
local project_root = vim.fs.root(bufnr, root_markers)
- if non_deno_path and (not project_root or #non_deno_path >= #project_root) then
- return
+ if
+ (deno_lock_root and (not project_root or #deno_lock_root > #project_root))
+ or (deno_root and (not project_root or #deno_root >= #project_root))
+ then
+ -- deno config is closer than or equal to package manager lock,
+ -- or deno lock is closer than package manager lock. Attach at the project root,
+ -- or deno lock or deno config path. At least one of these is always set at this point.
+ on_dir(project_root or deno_lock_root or deno_root)
end
- -- We fallback to the current working directory if no project root is found
- on_dir(project_root or vim.fn.getcwd())
end,
settings = {
deno = {
diff --git a/lsp/ts_ls.lua b/lsp/ts_ls.lua
index 23ac144e..4779c1a2 100644
--- a/lsp/ts_ls.lua
+++ b/lsp/ts_ls.lua
@@ -42,6 +42,36 @@
---
--- It is recommended to use the same version of TypeScript in all packages, and therefore have it available in your workspace root. The location of the TypeScript binary will be determined automatically, but only once.
---
+--- Some care must be taken here to correctly infer whether a file is part of a Deno program, or a TS program that
+--- expects to run in Node or Web Browsers. This supports having a Deno module using the denols LSP as a part of a
+--- mostly-not-Deno monorepo. We do this by finding the nearest package manager lock file, and the nearest deno.json
+--- or deno.jsonc.
+---
+--- Example:
+---
+--- ```
+--- project-root
+--- +-- node_modules/...
+--- +-- package-lock.json
+--- +-- package.json
+--- +-- packages
+--- +-- deno-module
+--- | +-- deno.json
+--- | +-- package.json <-- It's normal for Deno projects to have package.json files!
+--- | +-- src
+--- | +-- index.ts <-- this is a Deno file
+--- +-- node-module
+--- +-- package.json
+--- +-- src
+--- +-- index.ts <-- a non-Deno file (ie, should use ts_ls or tsgols)
+--- ```
+---
+--- From the file being edited, we walk up to find the nearest package manager lockfile. This is PROJECT ROOT.
+--- From the file being edited, find the nearest deno.json or deno.jsonc. This is DENO ROOT.
+--- From the file being edited, find the nearest deno.lock. This is DENO LOCK ROOT
+--- If DENO LOCK ROOT is found, and PROJECT ROOT is missing or shorter, then this is a deno file, and we abort.
+--- If DENO ROOT is found, and it's longer than or equal to PROJECT ROOT, then this is a Deno file, and we abort.
+--- Otherwise, attach at PROJECT ROOT, or the cwd if not found.
---@type vim.lsp.Config
return {
@@ -65,11 +95,18 @@ return {
root_markers = vim.fn.has('nvim-0.11.3') == 1 and { root_markers, { '.git' } }
or vim.list_extend(root_markers, { '.git' })
-- exclude deno
- local deno_path = vim.fs.root(bufnr, { 'deno.json', 'deno.jsonc', 'deno.lock' })
+ local deno_root = vim.fs.root(bufnr, { 'deno.json', 'deno.jsonc' })
+ local deno_lock_root = vim.fs.root(bufnr, { 'deno.lock' })
local project_root = vim.fs.root(bufnr, root_markers)
- if deno_path and (not project_root or #deno_path >= #project_root) then
+ if deno_lock_root and (not project_root or #deno_lock_root > #project_root) then
+ -- deno lock is closer than package manager lock, abort
+ return
+ end
+ if deno_root and (not project_root or #deno_root >= #project_root) then
+ -- deno config is closer than or equal to package manager lock, abort
return
end
+ -- project is standard TS, not deno
-- We fallback to the current working directory if no project root is found
on_dir(project_root or vim.fn.getcwd())
end,
diff --git a/lsp/tsgo.lua b/lsp/tsgo.lua
index 343ed4c9..d27a61c2 100644
--- a/lsp/tsgo.lua
+++ b/lsp/tsgo.lua
@@ -13,6 +13,36 @@
---
--- It is recommended to use the same version of TypeScript in all packages, and therefore have it available in your workspace root. The location of the TypeScript binary will be determined automatically, but only once.
---
+--- Some care must be taken here to correctly infer whether a file is part of a Deno program, or a TS program that
+--- expects to run in Node or Web Browsers. This supports having a Deno module using the denols LSP as a part of a
+--- mostly-not-Deno monorepo. We do this by finding the nearest package manager lock file, and the nearest deno.json
+--- or deno.jsonc.
+---
+--- Example:
+---
+--- ```
+--- project-root
+--- +-- node_modules/...
+--- +-- package-lock.json
+--- +-- package.json
+--- +-- packages
+--- +-- deno-module
+--- | +-- deno.json
+--- | +-- package.json <-- It's normal for Deno projects to have package.json files!
+--- | +-- src
+--- | +-- index.ts <-- this is a Deno file
+--- +-- node-module
+--- +-- package.json
+--- +-- src
+--- +-- index.ts <-- a non-Deno file (ie, should use ts_ls or tsgols)
+--- ```
+---
+--- From the file being edited, we walk up to find the nearest package manager lockfile. This is PROJECT ROOT.
+--- From the file being edited, find the nearest deno.json or deno.jsonc. This is DENO ROOT.
+--- From the file being edited, find the nearest deno.lock. This is DENO LOCK ROOT
+--- If DENO LOCK ROOT is found, and PROJECT ROOT is missing or shorter, then this is a deno file, and we abort.
+--- If DENO ROOT is found, and it's longer than or equal to PROJECT ROOT, then this is a Deno file, and we abort.
+--- Otherwise, attach at PROJECT ROOT, or the cwd if not found.
---@type vim.lsp.Config
return {
@@ -40,14 +70,19 @@ return {
root_markers = vim.fn.has('nvim-0.11.3') == 1 and { root_markers, { '.git' } }
or vim.list_extend(root_markers, { '.git' })
- -- exclude deno
- if vim.fs.root(bufnr, { 'deno.json', 'deno.jsonc', 'deno.lock' }) then
+ local deno_root = vim.fs.root(bufnr, { 'deno.json', 'deno.jsonc' })
+ local deno_lock_root = vim.fs.root(bufnr, { 'deno.lock' })
+ local project_root = vim.fs.root(bufnr, root_markers)
+ if deno_lock_root and (not project_root or #deno_lock_root > #project_root) then
+ -- deno lock is closer than package manager lock, abort
return
end
-
+ if deno_root and (not project_root or #deno_root >= #project_root) then
+ -- deno config is closer than or equal to package manager lock, abort
+ return
+ end
+ -- project is standard TS, not deno
-- 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()
-
- on_dir(project_root)
+ on_dir(project_root or vim.fn.getcwd())
end,
}
diff --git a/lsp/vtsls.lua b/lsp/vtsls.lua
index 29466f5e..e53b9439 100644
--- a/lsp/vtsls.lua
+++ b/lsp/vtsls.lua
@@ -64,6 +64,8 @@
--- This works without the need of spawning multiple instances of `vtsls`, saving memory.
---
--- It is recommended to use the same version of TypeScript in all packages, and therefore have it available in your workspace root. The location of the TypeScript binary will be determined automatically, but only once.
+---
+--- This includes the same Deno-excluding logic from `ts_ls`. It is not recommended to enable both `vtsls` and `ts_ls` at the same time!
---@type vim.lsp.Config
return {
@@ -88,15 +90,20 @@ return {
-- 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, { '.git' } }
or vim.list_extend(root_markers, { '.git' })
-
-- exclude deno
- if vim.fs.root(bufnr, { 'deno.json', 'deno.jsonc', 'deno.lock' }) then
+ local deno_root = vim.fs.root(bufnr, { 'deno.json', 'deno.jsonc' })
+ local deno_lock_root = vim.fs.root(bufnr, { 'deno.lock' })
+ local project_root = vim.fs.root(bufnr, root_markers)
+ if deno_lock_root and (not project_root or #deno_lock_root > #project_root) then
+ -- deno lock is closer than package manager lock, abort
return
end
-
+ if deno_root and (not project_root or #deno_root >= #project_root) then
+ -- deno config is closer than or equal to package manager lock, abort
+ return
+ end
+ -- project is standard TS, not deno
-- 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()
-
- on_dir(project_root)
+ on_dir(project_root or vim.fn.getcwd())
end,
}