From f2e24bb80a31b8470f8e9df180e1ab1ca6dbe6bd Mon Sep 17 00:00:00 2001 From: Dmytro Soltys Date: Tue, 4 Mar 2025 19:53:54 +0100 Subject: [PATCH 1/3] feat: limit ftplugin to buffer and avoid early lua requires This changes ftplugin behaviour to: - create user commands confined to the buffer it was triggered on - avoid filesystem lookups of require (they add up) by postponing them until user command gets invoked - skip loading if it has already been executed in a buffer To deduplicate ftplugins, lua's `dofile` is used to load a specific file that holds all shared commands. This is equivalent to vimscript standard `source` approach. These make ftplugin portion of this plugin behave like a good neighbor ftplugin - its changes are buffer local, as it's invoked when a buffer filetype matches. Additionally, ftplugins now read global `disable_typescript_tools` variable, returning early if it's set. Not a common behaviour for ftplugins in general, but a good way to give users fine control over plugin behaviour nonetheless. Not tackled is cleanup in case buffer filetype changes without reloading. I don't think routines for that currently exists, in fact. --- ftplugin/javascript.lua | 2 +- ftplugin/javascriptreact.lua | 2 +- ftplugin/typescript.lua | 2 +- ftplugin/typescriptreact.lua | 2 +- lua/typescript-tools/user_commands.lua | 64 -------------------------- tests/editor_spec.lua | 33 +++++++++++++ tests/ts_project/package-lock.json | 11 +++-- tests/ts_project/package.json | 2 +- utils/ftplugin-common.lua | 64 ++++++++++++++++++++++++++ 9 files changed, 108 insertions(+), 74 deletions(-) delete mode 100644 lua/typescript-tools/user_commands.lua create mode 100644 utils/ftplugin-common.lua diff --git a/ftplugin/javascript.lua b/ftplugin/javascript.lua index 427ac886..22e8f806 100644 --- a/ftplugin/javascript.lua +++ b/ftplugin/javascript.lua @@ -1 +1 @@ -require("typescript-tools.user_commands").setup_user_commands() +dofile(vim.fn.expand ":h:h" .. "/utils/ftplugin-common.lua") diff --git a/ftplugin/javascriptreact.lua b/ftplugin/javascriptreact.lua index 427ac886..22e8f806 100644 --- a/ftplugin/javascriptreact.lua +++ b/ftplugin/javascriptreact.lua @@ -1 +1 @@ -require("typescript-tools.user_commands").setup_user_commands() +dofile(vim.fn.expand ":h:h" .. "/utils/ftplugin-common.lua") diff --git a/ftplugin/typescript.lua b/ftplugin/typescript.lua index 427ac886..22e8f806 100644 --- a/ftplugin/typescript.lua +++ b/ftplugin/typescript.lua @@ -1 +1 @@ -require("typescript-tools.user_commands").setup_user_commands() +dofile(vim.fn.expand ":h:h" .. "/utils/ftplugin-common.lua") diff --git a/ftplugin/typescriptreact.lua b/ftplugin/typescriptreact.lua index 427ac886..22e8f806 100644 --- a/ftplugin/typescriptreact.lua +++ b/ftplugin/typescriptreact.lua @@ -1 +1 @@ -require("typescript-tools.user_commands").setup_user_commands() +dofile(vim.fn.expand ":h:h" .. "/utils/ftplugin-common.lua") diff --git a/lua/typescript-tools/user_commands.lua b/lua/typescript-tools/user_commands.lua deleted file mode 100644 index 2b82fe84..00000000 --- a/lua/typescript-tools/user_commands.lua +++ /dev/null @@ -1,64 +0,0 @@ -local api = require "typescript-tools.api" - -local M = {} - ----@param name string ----@param fn function -local function create_command(name, fn) - local command_completion = { - nargs = "?", - complete = function() - return { "sync" } - end, - } - vim.api.nvim_create_user_command(name, function(cmd) - local words = cmd.fargs - - if #words == 1 and words[1] ~= "sync" then - vim.notify("No such command", vim.log.levels.ERROR) - return - end - - fn(#words == 1) - end, command_completion) -end - -function M.setup_user_commands() - create_command("TSToolsOrganizeImports", function(is_sync) - api.organize_imports(is_sync) - end) - - create_command("TSToolsSortImports", function(is_sync) - api.sort_imports(is_sync) - end) - - create_command("TSToolsRemoveUnusedImports", function(is_sync) - api.remove_unused_imports(is_sync) - end) - - create_command("TSToolsGoToSourceDefinition", function(is_sync) - api.go_to_source_definition(is_sync) - end) - - create_command("TSToolsRemoveUnused", function(is_sync) - api.remove_unused(is_sync) - end) - - create_command("TSToolsAddMissingImports", function(is_sync) - api.add_missing_imports(is_sync) - end) - - create_command("TSToolsFixAll", function(is_sync) - api.fix_all(is_sync) - end) - - create_command("TSToolsRenameFile", function(is_sync) - api.rename_file(is_sync) - end) - - create_command("TSToolsFileReferences", function(is_sync) - api.file_references(is_sync) - end) -end - -return M diff --git a/tests/editor_spec.lua b/tests/editor_spec.lua index b6ee9da4..28bd36e9 100644 --- a/tests/editor_spec.lua +++ b/tests/editor_spec.lua @@ -71,4 +71,37 @@ describe("Lsp request", function() assert.is.same(vim.tbl_contains(lines, 'import { export1 } from "exports";'), true) end ) + + describe("ftplugin", function() + local commands = { + ":TSToolsOrganizeImports", + ":TSToolsOrganizeImports", + ":TSToolsSortImports", + ":TSToolsRemoveUnusedImports", + ":TSToolsGoToSourceDefinition", + ":TSToolsRemoveUnused", + ":TSToolsAddMissingImports", + ":TSToolsFixAll", + ":TSToolsRenameFile", + ":TSToolsFileReferences", + } + + it("should not create usercommands in unhandled buffers", function() + utils.open_file "src/batch_code_actions.ts" + utils.wait_for_lsp_initialization() + for _, command in pairs(commands) do + assert.is.same(vim.fn.exists(command), 2) + end + + utils.open_file "src/package.json" + for _, command in pairs(commands) do + assert.is.same(vim.fn.exists(command), 0) + end + + vim.cmd ":set ft=typescriptreact" + for _, command in pairs(commands) do + assert.is.same(vim.fn.exists(command), 2) + end + end) + end) end) diff --git a/tests/ts_project/package-lock.json b/tests/ts_project/package-lock.json index e694f26c..381caeb6 100644 --- a/tests/ts_project/package-lock.json +++ b/tests/ts_project/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "prettier": "2.8.8", - "typescript": "5.x" + "typescript": "^5.8.2" } }, "node_modules/prettier": { @@ -29,16 +29,17 @@ } }, "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=12.20" + "node": ">=14.17" } } } diff --git a/tests/ts_project/package.json b/tests/ts_project/package.json index 1dc310e5..08c0afdd 100644 --- a/tests/ts_project/package.json +++ b/tests/ts_project/package.json @@ -7,7 +7,7 @@ "license": "MIT", "devDependencies": { "prettier": "2.8.8", - "typescript": "5.x" + "typescript": "^5.8.2" }, "prettier": { "singleQuote": true diff --git a/utils/ftplugin-common.lua b/utils/ftplugin-common.lua new file mode 100644 index 00000000..8b6832c4 --- /dev/null +++ b/utils/ftplugin-common.lua @@ -0,0 +1,64 @@ +if vim.fn.exists "g:disable_typescript_tools" == 1 then + return +end +if vim.fn.exists "b:did_typescript_tools_ftplugin" == 1 then + return +end +vim.b.did_typescript_tools_ftplugin = true + +---@param name string +---@param fn function +local function create_command(name, fn) + local command_completion = { + nargs = "?", + complete = function() + return { "sync" } + end, + } + vim.api.nvim_buf_create_user_command(0, name, function(cmd) + local words = cmd.fargs + + if #words == 1 and words[1] ~= "sync" then + vim.notify("No such command", vim.log.levels.ERROR) + return + end + + fn(#words == 1) + end, command_completion) +end + +create_command("TSToolsOrganizeImports", function(is_sync) + require("typescript-tools.api").organize_imports(is_sync) +end) + +create_command("TSToolsSortImports", function(is_sync) + require("typescript-tools.api").sort_imports(is_sync) +end) + +create_command("TSToolsRemoveUnusedImports", function(is_sync) + require("typescript-tools.api").remove_unused_imports(is_sync) +end) + +create_command("TSToolsGoToSourceDefinition", function(is_sync) + require("typescript-tools.api").go_to_source_definition(is_sync) +end) + +create_command("TSToolsRemoveUnused", function(is_sync) + require("typescript-tools.api").remove_unused(is_sync) +end) + +create_command("TSToolsAddMissingImports", function(is_sync) + require("typescript-tools.api").add_missing_imports(is_sync) +end) + +create_command("TSToolsFixAll", function(is_sync) + require("typescript-tools.api").fix_all(is_sync) +end) + +create_command("TSToolsRenameFile", function(is_sync) + require("typescript-tools.api").rename_file(is_sync) +end) + +create_command("TSToolsFileReferences", function(is_sync) + require("typescript-tools.api").file_references(is_sync) +end) From d0d8bfabfd4a91f3c4fdc67c5659db6cf379657f Mon Sep 17 00:00:00 2001 From: Dmytro Soltys Date: Tue, 4 Mar 2025 19:30:14 +0100 Subject: [PATCH 2/3] ci: use actions/cache@v3 in tests workflow --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6ead6de3..78cc406d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,7 +40,7 @@ jobs: - uses: actions/checkout@v3 - run: date +%F > todays-date - name: Restore cache for today's nightly. - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: _neovim key: ${{ runner.os }}-x64-${{ hashFiles('todays-date') }} From 35e5ce53b7c3c5766fc42e179f07e90a75fdbb8c Mon Sep 17 00:00:00 2001 From: Dmytro Soltys Date: Tue, 4 Mar 2025 20:42:58 +0100 Subject: [PATCH 3/3] fix: use non-deprecated iter_matches with all=true This fixes nightly test failures. This also changes behaviour in complex scenarios most likely, as instead of matching just the last node, we now consider all nodes in the match. --- .../protocol/text_document/code_lens/init.lua | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/lua/typescript-tools/protocol/text_document/code_lens/init.lua b/lua/typescript-tools/protocol/text_document/code_lens/init.lua index ad3f70fc..d0caac6f 100644 --- a/lua/typescript-tools/protocol/text_document/code_lens/init.lua +++ b/lua/typescript-tools/protocol/text_document/code_lens/init.lua @@ -9,26 +9,30 @@ local M = {} ---@param lenses table ---@param implementations boolean|nil local function convert_nodes_to_response(tree, query, text_document, lenses, implementations) - for _, match in query:iter_matches(tree:root(), vim.uri_to_bufnr(text_document.uri)) do - for id, node in pairs(match) do + for _, match in + query:iter_matches(tree:root(), vim.uri_to_bufnr(text_document.uri), 0, -1, { all = true }) + do + for id, nodes in pairs(match) do local name = query.captures[id] - local start_row, start_col, end_row, end_col = node:range() + for _, node in ipairs(nodes) do + local start_row, start_col, end_row, end_col = node:range() - if config.disable_member_code_lens and name == "member" then - goto continue - end + if config.disable_member_code_lens and name == "member" then + goto continue + end - table.insert(lenses, { - range = { - start = { line = start_row, character = start_col }, - ["end"] = { line = end_row, character = end_col }, - }, - data = { - textDocument = text_document, - implementations = implementations, - }, - }) - ::continue:: + table.insert(lenses, { + range = { + start = { line = start_row, character = start_col }, + ["end"] = { line = end_row, character = end_col }, + }, + data = { + textDocument = text_document, + implementations = implementations, + }, + }) + ::continue:: + end end end end