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..afb283ef 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 @@ -72,8 +74,9 @@ function M.handler(request, response, params) filterText = insertText, commitCharacters = item_kind_utils.calculate_commit_characters(kind), kind = kind, - insertTextFormat = item.isSnippet and c.InsertTextFormat.Snippet - or c.InsertTextFormat.PlainText, + insertTextFormat = ( + item.isSnippet or utils.should_create_function_snippet(kind, filetype) + ) and c.InsertTextFormat.Snippet or c.InsertTextFormat.PlainText, sortText = sortText, textEdit = calculate_text_edit(item.replacementSpan, insertText), -- for now lsp support only one tag - deprecated - 1 diff --git a/lua/typescript-tools/protocol/text_document/completion/resolve.lua b/lua/typescript-tools/protocol/text_document/completion/resolve.lua index a1f81ddf..b2d78a87 100644 --- a/lua/typescript-tools/protocol/text_document/completion/resolve.lua +++ b/lua/typescript-tools/protocol/text_document/completion/resolve.lua @@ -26,6 +26,85 @@ 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 +129,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 +156,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 +165,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..6516a596 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