From 1de9bd5b3301228c625d6b804f0e36edd2b87fca Mon Sep 17 00:00:00 2001 From: L Lllvvuu Date: Sun, 23 Jul 2023 03:18:00 -0700 Subject: [PATCH 1/4] feat: function snippets `tsserver` does not create snippets for functions in the completions list; only upon completion resolve (presumably this is for performance reasons). `typescript-language-server` deals with this by manually constructing the snippet and tweaking the `inputTextFormat`. We copy that here - as long as `includeCompletionsWithSnippetText = true`. INFO: refactor/fix: move default configs to `config.lua` so they can be referenced outside of just `text_document/did_open` --- lua/typescript-tools/config.lua | 61 +++++++++++++ .../text_document/completion/init.lua | 11 ++- .../text_document/completion/resolve.lua | 86 ++++++++++++++++++- .../protocol/text_document/did_open.lua | 59 +------------ .../protocol/text_document/inlay_hint.lua | 3 +- lua/typescript-tools/protocol/utils.lua | 8 ++ tests/requests_spec.lua | 4 +- 7 files changed, 167 insertions(+), 65 deletions(-) diff --git a/lua/typescript-tools/config.lua b/lua/typescript-tools/config.lua index 5e2e4622..a40156cc 100644 --- a/lua/typescript-tools/config.lua +++ b/lua/typescript-tools/config.lua @@ -12,6 +12,56 @@ local M = {} local __store = {} +-- INFO: this two defaults are same as in vscode +local default_format_options = { + insertSpaceAfterCommaDelimiter = true, + insertSpaceAfterConstructor = false, + insertSpaceAfterSemicolonInForStatements = true, + insertSpaceBeforeAndAfterBinaryOperators = true, + insertSpaceAfterKeywordsInControlFlowStatements = true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions = true, + insertSpaceBeforeFunctionParenthesis = false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis = false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets = false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces = true, + insertSpaceAfterOpeningAndBeforeClosingEmptyBraces = true, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces = false, + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces = false, + insertSpaceAfterTypeAssertion = false, + placeOpenBraceOnNewLineForFunctions = false, + placeOpenBraceOnNewLineForControlBlocks = false, + semicolons = "ignore", + indentSwitchCase = true, +} + +local default_preferences = { + quotePreference = "auto", + importModuleSpecifierEnding = "auto", + jsxAttributeCompletionStyle = "auto", + allowTextChangesInNewFiles = true, + providePrefixAndSuffixTextForRename = true, + allowRenameOfImportPath = true, + includeAutomaticOptionalChainCompletions = true, + provideRefactorNotApplicableReason = true, + generateReturnInDocTemplate = true, + includeCompletionsForImportStatements = true, + includeCompletionsWithSnippetText = true, + includeCompletionsWithClassMemberSnippets = true, + includeCompletionsWithObjectLiteralMethodSnippets = true, + useLabelDetailsInCompletionEntries = true, + allowIncompleteCompletions = true, + displayPartsForJSDoc = true, + disableLineTextInReferences = true, + includeInlayParameterNameHints = "none", + includeInlayParameterNameHintsWhenArgumentMatchesName = false, + includeInlayFunctionParameterTypeHints = false, + includeInlayVariableTypeHints = false, + includeInlayVariableTypeHintsWhenTypeMatchesName = false, + includeInlayPropertyDeclarationTypeHints = false, + includeInlayFunctionLikeReturnTypeHints = false, + includeInlayEnumMemberValueHints = false, +} + ---@enum tsserver_log_level M.tsserver_log_level = { normal = "normal", @@ -104,4 +154,15 @@ setmetatable(M, { end, }) +function M.get_tsserver_file_preferences(filetype) + local preferences = __store.tsserver_file_preferences + return vim.tbl_extend( + "force", + default_preferences, + type(preferences) == "function" and preferences(filetype) or preferences + ) +end + +M.default_format_options = default_format_options + return M diff --git a/lua/typescript-tools/protocol/text_document/completion/init.lua b/lua/typescript-tools/protocol/text_document/completion/init.lua index 3e566708..77bd19a4 100644 --- a/lua/typescript-tools/protocol/text_document/completion/init.lua +++ b/lua/typescript-tools/protocol/text_document/completion/init.lua @@ -25,6 +25,8 @@ function M.handler(request, response, params) local text_document = params.textDocument local context = params.context or {} local trigger_character = context.triggerCharacter + local requested_bufnr = vim.uri_to_bufnr(text_document.uri) + local filetype = vim.bo[requested_bufnr].filetype -- tsserver protocol reference: -- https//github.com/microsoft/TypeScript/blob/8b482b513d87c6fcda8ece18b99f8a01cff5c605/lib/protocol.d.ts#L1631 @@ -65,14 +67,19 @@ function M.handler(request, response, params) sortText = "\u{ffff}" .. item.sortText end + local should_create_snippet = item.isSnippet + or utils.should_create_function_snippet(kind, filetype) + local label = is_optional and (item.name .. "?") or item.name + label = should_create_snippet and (label .. "~") or label + return { - label = is_optional and (item.name .. "?") or item.name, + label = label, labelDetails = item.labelDetails, insertText = insertText, filterText = insertText, commitCharacters = item_kind_utils.calculate_commit_characters(kind), kind = kind, - insertTextFormat = item.isSnippet and c.InsertTextFormat.Snippet + insertTextFormat = should_create_snippet and c.InsertTextFormat.Snippet or c.InsertTextFormat.PlainText, sortText = sortText, textEdit = calculate_text_edit(item.replacementSpan, insertText), diff --git a/lua/typescript-tools/protocol/text_document/completion/resolve.lua b/lua/typescript-tools/protocol/text_document/completion/resolve.lua index a1f81ddf..8c3b8662 100644 --- a/lua/typescript-tools/protocol/text_document/completion/resolve.lua +++ b/lua/typescript-tools/protocol/text_document/completion/resolve.lua @@ -26,6 +26,79 @@ local function make_text_edits(code_actions) return text_edits end +-- @see https://github.com/typescript-language-server/typescript-language-server/blob/983a6923114c39d638e0c7d419ae16e8bca8985c/src/completion.ts#L355-L371 +local function get_parameter_list_parts(display_parts) + local parts = {} + local is_in_method = false + local has_optional_parameters = false + local paren_count = 0 + local brace_count = 0 + + for i, part in ipairs(display_parts) do + if + part.kind == "methodName" + or part.kind == "functionName" + or part.kind == "text" + or part.kind == "propertyName" + then + if paren_count == 0 and brace_count == 0 then + is_in_method = true + end + elseif part.kind == "parameterName" then + if paren_count == 1 and brace_count == 0 and is_in_method then + local next = display_parts[i + 1] + local name_is_followed_by_optional_indicator = next and next.text == "?" + local name_is_this = part.text == "this" + if not name_is_followed_by_optional_indicator and not name_is_this then + table.insert(parts, part) + end + has_optional_parameters = has_optional_parameters or name_is_followed_by_optional_indicator + end + elseif part.kind == "punctuation" then + if part.text == "(" then + paren_count = paren_count + 1 + elseif part.text == ")" then + paren_count = paren_count - 1 + if paren_count <= 0 and is_in_method then + break + end + elseif part.text == "..." and paren_count == 1 then + has_optional_parameters = true + break + elseif part.text == "{" then + brace_count = brace_count + 1 + elseif part.text == "}" then + brace_count = brace_count - 1 + end + end + end + return { has_optional_parameters = has_optional_parameters, parts = parts } +end + +-- @see https://github.com/typescript-language-server/typescript-language-server/blob/983a6923114c39d638e0c7d419ae16e8bca8985c/src/completion.ts#L355-L371 +local function create_snippet(item, display_parts) + local parameter_list_parts = get_parameter_list_parts(display_parts) + local has_optional_parameters = parameter_list_parts.has_optional_parameters + local parts = parameter_list_parts.parts + local snippet = + string.format("%s(", item.insertText or (item.textEdit and item.textEdit.newText) or item.label) + for i, part in ipairs(parts) do + snippet = snippet .. string.format("${%d:%s}", i, part.text:gsub("([$}\\])", "\\%1")) + if i ~= #parts then + snippet = snippet .. ", " + end + end + if has_optional_parameters then + snippet = snippet .. string.format("$%d", #parts + 1) + end + snippet = snippet .. ")$0" + item.insertText = snippet + item.insertTextFormat = c.InsertTextFormat.Snippet + if item.textEdit then + item.textEdit.newText = snippet + end +end + ---@param params table ---@return table local function completion_resolve_request(params) @@ -50,6 +123,9 @@ end ---@type TsserverProtocolHandler function M.handler(request, response, params) + local requested_bufnr = vim.uri_to_bufnr(vim.uri_from_fname(params.data.file)) + local filetype = vim.bo[requested_bufnr].filetype + -- tsserver protocol reference: -- https://github.com/microsoft/TypeScript/blob/549e61d0af1ba885be29d69f341e7d3a00686071/lib/protocol.d.ts#L1661 request(completion_resolve_request(params)) @@ -74,7 +150,7 @@ function M.handler(request, response, params) detail = "Auto import from " .. utils.tsserver_docs_to_plain_text(source) .. "\n" .. detail end - response(vim.tbl_extend("force", params, { + local item = vim.tbl_extend("force", params, { detail = detail, documentation = { kind = c.MarkupKind.Markdown, @@ -83,7 +159,13 @@ function M.handler(request, response, params) additionalTextEdits = make_text_edits(details.codeActions), -- INFO: there is also `command` prop but I don't know there is usecase for that here, -- or neovim even handle that for now i skip this - })) + }) + + if utils.should_create_function_snippet(item.kind, filetype) then + create_snippet(item, details.displayParts) + end + + response(item) else response(nil) end diff --git a/lua/typescript-tools/protocol/text_document/did_open.lua b/lua/typescript-tools/protocol/text_document/did_open.lua index b203c2de..a374d3a3 100644 --- a/lua/typescript-tools/protocol/text_document/did_open.lua +++ b/lua/typescript-tools/protocol/text_document/did_open.lua @@ -4,56 +4,6 @@ local plugin_config = require "typescript-tools.config" local M = {} --- INFO: this two defaults are same as in vscode -local default_format_options = { - insertSpaceAfterCommaDelimiter = true, - insertSpaceAfterConstructor = false, - insertSpaceAfterSemicolonInForStatements = true, - insertSpaceBeforeAndAfterBinaryOperators = true, - insertSpaceAfterKeywordsInControlFlowStatements = true, - insertSpaceAfterFunctionKeywordForAnonymousFunctions = true, - insertSpaceBeforeFunctionParenthesis = false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis = false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets = false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces = true, - insertSpaceAfterOpeningAndBeforeClosingEmptyBraces = true, - insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces = false, - insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces = false, - insertSpaceAfterTypeAssertion = false, - placeOpenBraceOnNewLineForFunctions = false, - placeOpenBraceOnNewLineForControlBlocks = false, - semicolons = "ignore", - indentSwitchCase = true, -} - -local default_preferences = { - quotePreference = "auto", - importModuleSpecifierEnding = "auto", - jsxAttributeCompletionStyle = "auto", - allowTextChangesInNewFiles = true, - providePrefixAndSuffixTextForRename = true, - allowRenameOfImportPath = true, - includeAutomaticOptionalChainCompletions = true, - provideRefactorNotApplicableReason = true, - generateReturnInDocTemplate = true, - includeCompletionsForImportStatements = true, - includeCompletionsWithSnippetText = true, - includeCompletionsWithClassMemberSnippets = true, - includeCompletionsWithObjectLiteralMethodSnippets = true, - useLabelDetailsInCompletionEntries = true, - allowIncompleteCompletions = true, - displayPartsForJSDoc = true, - disableLineTextInReferences = true, - includeInlayParameterNameHints = "none", - includeInlayParameterNameHintsWhenArgumentMatchesName = false, - includeInlayFunctionParameterTypeHints = false, - includeInlayVariableTypeHints = false, - includeInlayVariableTypeHintsWhenTypeMatchesName = false, - includeInlayPropertyDeclarationTypeHints = false, - includeInlayFunctionLikeReturnTypeHints = false, - includeInlayEnumMemberValueHints = false, -} - ---@type table<"mac" | "unix" | "dos", string> local eol_chars = { mac = "\r", @@ -78,7 +28,6 @@ local function configure(params) local convert_tabs_to_spaces = bo.expandtab or true local new_line_character = get_eol_chars(bo) - local preferences = plugin_config.tsserver_file_preferences local format_options = plugin_config.tsserver_format_options return { @@ -93,14 +42,10 @@ local function configure(params) convertTabsToSpaces = convert_tabs_to_spaces, newLineCharacter = new_line_character, }, - default_format_options, + plugin_config.default_format_options, type(format_options) == "function" and format_options(bo.filetype) or format_options ), - preferences = vim.tbl_extend( - "force", - default_preferences, - type(preferences) == "function" and preferences(bo.filetype) or preferences - ), + preferences = plugin_config.get_tsserver_file_preferences(bo.filetype), }, } end diff --git a/lua/typescript-tools/protocol/text_document/inlay_hint.lua b/lua/typescript-tools/protocol/text_document/inlay_hint.lua index 87985561..8c570621 100644 --- a/lua/typescript-tools/protocol/text_document/inlay_hint.lua +++ b/lua/typescript-tools/protocol/text_document/inlay_hint.lua @@ -13,8 +13,7 @@ local inlay_hint_kind_map = { ---@param filetype string ---@return boolean local function are_inlay_hints_enabled(filetype) - local preferences = plugin_config.tsserver_file_preferences - preferences = type(preferences) == "function" and preferences(filetype) or preferences + local preferences = plugin_config.get_tsserver_file_preferences(filetype) if not preferences then return false diff --git a/lua/typescript-tools/protocol/utils.lua b/lua/typescript-tools/protocol/utils.lua index 18f01fb6..42a05c37 100644 --- a/lua/typescript-tools/protocol/utils.lua +++ b/lua/typescript-tools/protocol/utils.lua @@ -1,4 +1,5 @@ local c = require "typescript-tools.protocol.constants" +local plugin_config = require "typescript-tools.config" local M = {} @@ -222,4 +223,11 @@ function M.cancelled_response(data) } end +-- @see https://github.com/typescript-language-server/typescript-language-server/blob/983a6923114c39d638e0c7d419ae16e8bca8985c/src/completion.ts#L355-L371 +function M.should_create_function_snippet(kind, filetype) + local preferences = plugin_config.get_tsserver_file_preferences(filetype) + return preferences.includeCompletionsWithSnippetText + and (kind == c.CompletionItemKind.Function or kind == c.CompletionItemKind.Method) +end + return M diff --git a/tests/requests_spec.lua b/tests/requests_spec.lua index 7e21a3ef..233d44c5 100644 --- a/tests/requests_spec.lua +++ b/tests/requests_spec.lua @@ -151,8 +151,8 @@ describe("Lsp request", function() end, items) table.sort(completions) - assert.are.same(completions[1], "assert") - assert.are.same(completions[#completions], "warn") + assert.are.same(completions[1], "assert~") + assert.are.same(completions[#completions], "warn~") end) it("should return correct response for " .. methods.CompletionResolve, function() From c3046ccc44457725be88407d7d8dc81825c27cea Mon Sep 17 00:00:00 2001 From: L Lllvvuu Date: Mon, 24 Jul 2023 03:34:14 -0700 Subject: [PATCH 2/4] test: add test cases for snippet and non-snippet Also adds type annotations to function snippet feature. --- lua/typescript-tools/config.lua | 2 + lua/typescript-tools/protocol/constants.lua | 1 + .../text_document/completion/resolve.lua | 14 +++++- lua/typescript-tools/protocol/utils.lua | 5 ++- tests/requests_spec.lua | 43 ++++++++++++++++++- 5 files changed, 60 insertions(+), 5 deletions(-) diff --git a/lua/typescript-tools/config.lua b/lua/typescript-tools/config.lua index a40156cc..81cc71c0 100644 --- a/lua/typescript-tools/config.lua +++ b/lua/typescript-tools/config.lua @@ -154,6 +154,8 @@ setmetatable(M, { end, }) +---@param filetype vim.opt.filetype +---@return table function M.get_tsserver_file_preferences(filetype) local preferences = __store.tsserver_file_preferences return vim.tbl_extend( diff --git a/lua/typescript-tools/protocol/constants.lua b/lua/typescript-tools/protocol/constants.lua index 9945af8b..1b72b9b4 100644 --- a/lua/typescript-tools/protocol/constants.lua +++ b/lua/typescript-tools/protocol/constants.lua @@ -270,6 +270,7 @@ return { Imports = "imports", Region = "region", }, + ---@enum InsertTextFormat InsertTextFormat = { PlainText = 1, Snippet = 2, diff --git a/lua/typescript-tools/protocol/text_document/completion/resolve.lua b/lua/typescript-tools/protocol/text_document/completion/resolve.lua index 8c3b8662..5540df79 100644 --- a/lua/typescript-tools/protocol/text_document/completion/resolve.lua +++ b/lua/typescript-tools/protocol/text_document/completion/resolve.lua @@ -26,7 +26,11 @@ local function make_text_edits(code_actions) return text_edits end --- @see https://github.com/typescript-language-server/typescript-language-server/blob/983a6923114c39d638e0c7d419ae16e8bca8985c/src/completion.ts#L355-L371 +---@alias DisplayPart { kind: string, text: string } + +---@param display_parts DisplayPart[] +---@return { has_optional_parameters: boolean, parts: DisplayPart[] } +---@see https://github.com/typescript-language-server/typescript-language-server/blob/983a6923114c39d638e0c7d419ae16e8bca8985c/src/completion.ts#L355-L371 local function get_parameter_list_parts(display_parts) local parts = {} local is_in_method = false @@ -75,7 +79,13 @@ local function get_parameter_list_parts(display_parts) return { has_optional_parameters = has_optional_parameters, parts = parts } end --- @see https://github.com/typescript-language-server/typescript-language-server/blob/983a6923114c39d638e0c7d419ae16e8bca8985c/src/completion.ts#L355-L371 +---@alias PartialCompletionItem +---| { insertText: string, insertTextFormat: InsertTextFormat, textEdit: { newText: string }, label: string } + +---@param item PartialCompletionItem +---@param display_parts DisplayPart[] +---@return nil +---@see https://github.com/typescript-language-server/typescript-language-server/blob/983a6923114c39d638e0c7d419ae16e8bca8985c/src/completion.ts#L355-L371 local function create_snippet(item, display_parts) local parameter_list_parts = get_parameter_list_parts(display_parts) local has_optional_parameters = parameter_list_parts.has_optional_parameters diff --git a/lua/typescript-tools/protocol/utils.lua b/lua/typescript-tools/protocol/utils.lua index 42a05c37..573e450c 100644 --- a/lua/typescript-tools/protocol/utils.lua +++ b/lua/typescript-tools/protocol/utils.lua @@ -223,7 +223,10 @@ function M.cancelled_response(data) } end --- @see https://github.com/typescript-language-server/typescript-language-server/blob/983a6923114c39d638e0c7d419ae16e8bca8985c/src/completion.ts#L355-L371 +---@param kind CompletionItemKind +---@param filetype vim.opt.filetype +---@return boolean +---@see https://github.com/typescript-language-server/typescript-language-server/blob/983a6923114c39d638e0c7d419ae16e8bca8985c/src/completion.ts#L355-L371 function M.should_create_function_snippet(kind, filetype) local preferences = plugin_config.get_tsserver_file_preferences(filetype) return preferences.includeCompletionsWithSnippetText diff --git a/tests/requests_spec.lua b/tests/requests_spec.lua index 233d44c5..e353b511 100644 --- a/tests/requests_spec.lua +++ b/tests/requests_spec.lua @@ -147,12 +147,29 @@ describe("Lsp request", function() assert.is.True(#items >= 20) local completions = vim.tbl_map(function(it) + if it.label == "assert~" or it.label == "warn~" then + assert.are.same(it.insertTextFormat, c.InsertTextFormat.Snippet) + end return it.label end, items) table.sort(completions) assert.are.same(completions[1], "assert~") assert.are.same(completions[#completions], "warn~") + + ret = vim.lsp.buf_request_sync(0, methods.Completion, { + textDocument = utils.get_text_document(), + position = utils.make_position(0, 6), + }) + result = lsp_assert.response(ret) + local can_complete_as_console = false + for _, item in ipairs(result.items) do + if item.label == "console" then + assert.are.same(item.insertTextFormat, c.InsertTextFormat.PlainText) + can_complete_as_console = true + end + end + assert(can_complete_as_console) end) it("should return correct response for " .. methods.CompletionResolve, function() @@ -169,15 +186,37 @@ describe("Lsp request", function() }, filterText = "warn", insertText = "warn", - insertTextFormat = 1, - kind = 2, + insertTextFormat = c.InsertTextFormat.Snippet, + kind = c.CompletionItemKind.Function, label = "warn", sortText = "11", }) local result = lsp_assert.response(ret) assert.is.table(result) + assert.are.same(result.insertText, "warn($1)$0") assert.are.same(result.detail, "(method) Console.warn(...data: any[]): void") + + ret = vim.lsp.buf_request_sync(0, methods.CompletionResolve, { + commitCharacters = { ".", "?" }, + data = { + character = 6, + entryNames = { "console" }, + file = vim.fs.dirname(vim.api.nvim_buf_get_name(0)) .. "/completion.ts", + line = 0, + }, + filterText = "console", + insertText = "console", + insertTextFormat = c.InsertTextFormat.PlainText, + kind = c.CompletionItemKind.Variable, + label = "console", + sortText = "15", + }) + + result = lsp_assert.response(ret) + assert.is.table(result) + assert.are.same(result.insertText, "console") + assert.are.same(result.detail, "var console: Console") end) it("should return correct response for " .. methods.SignatureHelp, function() From 22f454d3d1b6195bbdc82245ea1ca3104bd0b689 Mon Sep 17 00:00:00 2001 From: L Lllvvuu Date: Mon, 24 Jul 2023 22:51:09 -0700 Subject: [PATCH 3/4] fix: ~ should be handled on cmp side. use (...) instead for label feat: copy the VSCode config option `typescript.suggest.completeFunctionCalls` and have it off by default --- .luacheckrc | 3 + README.md | 3 + lua/typescript-tools/config.lua | 9 ++ .../text_document/completion/init.lua | 6 +- lua/typescript-tools/protocol/utils.lua | 1 + tests/requests_spec.lua | 85 ++++++++++--------- 6 files changed, 66 insertions(+), 41 deletions(-) diff --git a/.luacheckrc b/.luacheckrc index 2805f458..d78d31a7 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -2,6 +2,9 @@ ignore = { "631", -- max_line_length } +exclude_files = { + ".tests", +} globals = { "vim", "P" } read_globals = { "describe", diff --git a/README.md b/README.md index 70141828..fcf79058 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,9 @@ require("typescript-tools").setup { -- described below tsserver_format_options = {}, tsserver_file_preferences = {}, + -- mirror of https://github.com/microsoft/vscode/blob/885dba39a5f546c5077196a8c31b03d03a293b31/extensions/typescript-language-features/package.json#L145-L1266 + -- `typescript-tools.nvim` currently only supports `typescript.suggest.completeFunctionCalls` + vscode_configuration = { ["typescript.suggest.completeFunctionCalls"] = true }, }, } ``` diff --git a/lua/typescript-tools/config.lua b/lua/typescript-tools/config.lua index 81cc71c0..b988ab61 100644 --- a/lua/typescript-tools/config.lua +++ b/lua/typescript-tools/config.lua @@ -1,3 +1,6 @@ +---@see https://github.com/microsoft/vscode/blob/885dba39a5f546c5077196a8c31b03d03a293b31/extensions/typescript-language-features/package.json#L145-L1266 +---@alias VsCodeConfiguration { typescript.suggest.completeFunctionCalls: boolean } + ---@class Settings ---@field plugin_name string ---@field separate_diagnostic_server boolean @@ -8,6 +11,7 @@ ---@field tsserver_format_options table|fun(filetype: string): table ---@field tsserver_file_preferences table|fun(filetype: string): table ---@field tsserver_max_memory number|"auto" +---@field vscode_configuration VsCodeConfiguration ---@field expose_as_code_action ("fix_all"| "add_missing_imports"| "remove_unused")[] local M = {} local __store = {} @@ -106,6 +110,7 @@ function M.load_settings(settings) { "number", "string" }, true, }, + ["settings.vscode_configuration"] = { settings.vscode_configuration, "table", true }, ["settings.expose_as_code_action"] = { settings.expose_as_code_action, "table", @@ -143,6 +148,10 @@ function M.load_settings(settings) __store.tsserver_max_memory = "auto" end + if not settings.vscode_configuration then + __store.vscode_configuration = {} + end + if not settings.expose_as_code_action then __store.expose_as_code_action = {} end diff --git a/lua/typescript-tools/protocol/text_document/completion/init.lua b/lua/typescript-tools/protocol/text_document/completion/init.lua index 77bd19a4..e2a68bdb 100644 --- a/lua/typescript-tools/protocol/text_document/completion/init.lua +++ b/lua/typescript-tools/protocol/text_document/completion/init.lua @@ -67,10 +67,10 @@ function M.handler(request, response, params) sortText = "\u{ffff}" .. item.sortText end - local should_create_snippet = item.isSnippet - or utils.should_create_function_snippet(kind, filetype) + local should_create_function_snippet = utils.should_create_function_snippet(kind, filetype) + local should_create_snippet = item.isSnippet or should_create_function_snippet local label = is_optional and (item.name .. "?") or item.name - label = should_create_snippet and (label .. "~") or label + label = should_create_function_snippet and (label .. "(...)") or label return { label = label, diff --git a/lua/typescript-tools/protocol/utils.lua b/lua/typescript-tools/protocol/utils.lua index 573e450c..6b902464 100644 --- a/lua/typescript-tools/protocol/utils.lua +++ b/lua/typescript-tools/protocol/utils.lua @@ -231,6 +231,7 @@ function M.should_create_function_snippet(kind, filetype) local preferences = plugin_config.get_tsserver_file_preferences(filetype) return preferences.includeCompletionsWithSnippetText and (kind == c.CompletionItemKind.Function or kind == c.CompletionItemKind.Method) + and plugin_config.vscode_configuration["typescript.suggest.completeFunctionCalls"] end return M diff --git a/tests/requests_spec.lua b/tests/requests_spec.lua index e353b511..a44f5f71 100644 --- a/tests/requests_spec.lua +++ b/tests/requests_spec.lua @@ -1,6 +1,7 @@ local utils = require "tests.utils" local lsp_assert = require "tests.lsp_asserts" local mocks = require "tests.mocks" +local plugin_config = require "typescript-tools.config" local c = require "typescript-tools.protocol.constants" local methods = c.LspMethods local custom_methods = c.CustomMethods @@ -134,10 +135,11 @@ describe("Lsp request", function() utils.open_file "src/completion.ts" utils.wait_for_lsp_initialization() - local ret = vim.lsp.buf_request_sync(0, methods.Completion, { + local req = { textDocument = utils.get_text_document(), position = utils.make_position(0, 8), - }) + } + local ret = vim.lsp.buf_request_sync(0, methods.Completion, req) local result = lsp_assert.response(ret) @@ -147,36 +149,47 @@ describe("Lsp request", function() assert.is.True(#items >= 20) local completions = vim.tbl_map(function(it) - if it.label == "assert~" or it.label == "warn~" then - assert.are.same(it.insertTextFormat, c.InsertTextFormat.Snippet) + if it.kind == c.CompletionItemKind.Method or it.kind == c.CompletionItemKind.Function then + assert.are.same(it.insertTextFormat, c.InsertTextFormat.PlainText) end return it.label end, items) table.sort(completions) - assert.are.same(completions[1], "assert~") - assert.are.same(completions[#completions], "warn~") + assert.are.same(completions[1], "assert") + assert.are.same(completions[#completions], "warn") - ret = vim.lsp.buf_request_sync(0, methods.Completion, { - textDocument = utils.get_text_document(), - position = utils.make_position(0, 6), - }) + -- same test as above but with function snippets enabled + local prev_config = + plugin_config.vscode_configuration["typescript.suggest.completeFunctionCalls"] + plugin_config.vscode_configuration["typescript.suggest.completeFunctionCalls"] = true + + ret = vim.lsp.buf_request_sync(0, methods.Completion, req) result = lsp_assert.response(ret) - local can_complete_as_console = false - for _, item in ipairs(result.items) do - if item.label == "console" then - assert.are.same(item.insertTextFormat, c.InsertTextFormat.PlainText) - can_complete_as_console = true + assert.is.table(result.items) + + items = result.items + assert.is.True(#items >= 20) + + completions = vim.tbl_map(function(it) + if it.kind == c.CompletionItemKind.Method or it.kind == c.CompletionItemKind.Function then + assert.are.same(it.insertTextFormat, c.InsertTextFormat.Snippet) end - end - assert(can_complete_as_console) + return it.label + end, items) + table.sort(completions) + + assert.are.same(completions[1], "assert(...)") + assert.are.same(completions[#completions], "warn(...)") + + plugin_config.vscode_configuration["typescript.suggest.completeFunctionCalls"] = prev_config end) it("should return correct response for " .. methods.CompletionResolve, function() utils.open_file "src/completion.ts" utils.wait_for_lsp_initialization() - local ret = vim.lsp.buf_request_sync(0, methods.CompletionResolve, { + local req = { commitCharacters = { "(" }, data = { character = 8, @@ -186,37 +199,33 @@ describe("Lsp request", function() }, filterText = "warn", insertText = "warn", - insertTextFormat = c.InsertTextFormat.Snippet, + insertTextFormat = c.InsertTextFormat.PlainText, kind = c.CompletionItemKind.Function, label = "warn", sortText = "11", - }) + } + local ret = vim.lsp.buf_request_sync(0, methods.CompletionResolve, req) local result = lsp_assert.response(ret) assert.is.table(result) - assert.are.same(result.insertText, "warn($1)$0") + assert.are.same(result.insertText, "warn") assert.are.same(result.detail, "(method) Console.warn(...data: any[]): void") - ret = vim.lsp.buf_request_sync(0, methods.CompletionResolve, { - commitCharacters = { ".", "?" }, - data = { - character = 6, - entryNames = { "console" }, - file = vim.fs.dirname(vim.api.nvim_buf_get_name(0)) .. "/completion.ts", - line = 0, - }, - filterText = "console", - insertText = "console", - insertTextFormat = c.InsertTextFormat.PlainText, - kind = c.CompletionItemKind.Variable, - label = "console", - sortText = "15", - }) + -- same test as above but with function snippets enabled + local prev_config = + plugin_config.vscode_configuration["typescript.suggest.completeFunctionCalls"] + plugin_config.vscode_configuration["typescript.suggest.completeFunctionCalls"] = true + req.label = "warn(...)" + req.insertTextFormat = c.InsertTextFormat.Snippet + ret = vim.lsp.buf_request_sync(0, methods.CompletionResolve, req) result = lsp_assert.response(ret) + assert.is.table(result) - assert.are.same(result.insertText, "console") - assert.are.same(result.detail, "var console: Console") + assert.are.same(result.insertText, "warn($1)$0") + assert.are.same(result.detail, "(method) Console.warn(...data: any[]): void") + + plugin_config.vscode_configuration["typescript.suggest.completeFunctionCalls"] = prev_config end) it("should return correct response for " .. methods.SignatureHelp, function() From f21fb2ff0000157b55063c8d718c982037403e5b Mon Sep 17 00:00:00 2001 From: L Lllvvuu Date: Tue, 25 Jul 2023 02:10:27 -0700 Subject: [PATCH 4/4] feat: simplify config path --- README.md | 5 ++--- lua/typescript-tools/config.lua | 11 ++++------- lua/typescript-tools/protocol/utils.lua | 2 +- tests/requests_spec.lua | 14 ++++++-------- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index fcf79058..df07c650 100644 --- a/README.md +++ b/README.md @@ -138,9 +138,8 @@ require("typescript-tools").setup { -- described below tsserver_format_options = {}, tsserver_file_preferences = {}, - -- mirror of https://github.com/microsoft/vscode/blob/885dba39a5f546c5077196a8c31b03d03a293b31/extensions/typescript-language-features/package.json#L145-L1266 - -- `typescript-tools.nvim` currently only supports `typescript.suggest.completeFunctionCalls` - vscode_configuration = { ["typescript.suggest.completeFunctionCalls"] = true }, + -- mirror of VSCode's `typescript.suggest.completeFunctionCalls` + complete_function_calls = false, }, } ``` diff --git a/lua/typescript-tools/config.lua b/lua/typescript-tools/config.lua index b988ab61..0ff56ef1 100644 --- a/lua/typescript-tools/config.lua +++ b/lua/typescript-tools/config.lua @@ -1,6 +1,3 @@ ----@see https://github.com/microsoft/vscode/blob/885dba39a5f546c5077196a8c31b03d03a293b31/extensions/typescript-language-features/package.json#L145-L1266 ----@alias VsCodeConfiguration { typescript.suggest.completeFunctionCalls: boolean } - ---@class Settings ---@field plugin_name string ---@field separate_diagnostic_server boolean @@ -11,7 +8,7 @@ ---@field tsserver_format_options table|fun(filetype: string): table ---@field tsserver_file_preferences table|fun(filetype: string): table ---@field tsserver_max_memory number|"auto" ----@field vscode_configuration VsCodeConfiguration +---@field complete_function_calls boolean ---@field expose_as_code_action ("fix_all"| "add_missing_imports"| "remove_unused")[] local M = {} local __store = {} @@ -110,7 +107,7 @@ function M.load_settings(settings) { "number", "string" }, true, }, - ["settings.vscode_configuration"] = { settings.vscode_configuration, "table", true }, + ["settings.complete_function_calls"] = { settings.complete_function_calls, "boolean", true }, ["settings.expose_as_code_action"] = { settings.expose_as_code_action, "table", @@ -148,8 +145,8 @@ function M.load_settings(settings) __store.tsserver_max_memory = "auto" end - if not settings.vscode_configuration then - __store.vscode_configuration = {} + if not settings.complete_function_calls then + __store.complete_function_calls = false end if not settings.expose_as_code_action then diff --git a/lua/typescript-tools/protocol/utils.lua b/lua/typescript-tools/protocol/utils.lua index 6b902464..d74c6358 100644 --- a/lua/typescript-tools/protocol/utils.lua +++ b/lua/typescript-tools/protocol/utils.lua @@ -231,7 +231,7 @@ function M.should_create_function_snippet(kind, filetype) local preferences = plugin_config.get_tsserver_file_preferences(filetype) return preferences.includeCompletionsWithSnippetText and (kind == c.CompletionItemKind.Function or kind == c.CompletionItemKind.Method) - and plugin_config.vscode_configuration["typescript.suggest.completeFunctionCalls"] + and plugin_config.complete_function_calls end return M diff --git a/tests/requests_spec.lua b/tests/requests_spec.lua index a44f5f71..e641a810 100644 --- a/tests/requests_spec.lua +++ b/tests/requests_spec.lua @@ -160,9 +160,8 @@ describe("Lsp request", function() assert.are.same(completions[#completions], "warn") -- same test as above but with function snippets enabled - local prev_config = - plugin_config.vscode_configuration["typescript.suggest.completeFunctionCalls"] - plugin_config.vscode_configuration["typescript.suggest.completeFunctionCalls"] = true + local prev_config = plugin_config.complete_function_calls + plugin_config.complete_function_calls = true ret = vim.lsp.buf_request_sync(0, methods.Completion, req) result = lsp_assert.response(ret) @@ -182,7 +181,7 @@ describe("Lsp request", function() assert.are.same(completions[1], "assert(...)") assert.are.same(completions[#completions], "warn(...)") - plugin_config.vscode_configuration["typescript.suggest.completeFunctionCalls"] = prev_config + plugin_config.complete_function_calls = prev_config end) it("should return correct response for " .. methods.CompletionResolve, function() @@ -212,9 +211,8 @@ describe("Lsp request", function() assert.are.same(result.detail, "(method) Console.warn(...data: any[]): void") -- same test as above but with function snippets enabled - local prev_config = - plugin_config.vscode_configuration["typescript.suggest.completeFunctionCalls"] - plugin_config.vscode_configuration["typescript.suggest.completeFunctionCalls"] = true + local prev_config = plugin_config.complete_function_calls + plugin_config.complete_function_calls = true req.label = "warn(...)" req.insertTextFormat = c.InsertTextFormat.Snippet @@ -225,7 +223,7 @@ describe("Lsp request", function() assert.are.same(result.insertText, "warn($1)$0") assert.are.same(result.detail, "(method) Console.warn(...data: any[]): void") - plugin_config.vscode_configuration["typescript.suggest.completeFunctionCalls"] = prev_config + plugin_config.complete_function_calls = prev_config end) it("should return correct response for " .. methods.SignatureHelp, function()