Skip to content

Commit

Permalink
feat: function snippets
Browse files Browse the repository at this point in the history
`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`
  • Loading branch information
llllvvuu committed Jul 23, 2023
1 parent bc8f814 commit 7cae371
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 63 deletions.
61 changes: 61 additions & 0 deletions lua/typescript-tools/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
92 changes: 90 additions & 2 deletions lua/typescript-tools/protocol/text_document/completion/resolve.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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))
Expand All @@ -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,
Expand All @@ -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
Expand Down
59 changes: 2 additions & 57 deletions lua/typescript-tools/protocol/text_document/did_open.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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 {
Expand All @@ -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
Expand Down
3 changes: 1 addition & 2 deletions lua/typescript-tools/protocol/text_document/inlay_hint.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions lua/typescript-tools/protocol/utils.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local c = require "typescript-tools.protocol.constants"
local plugin_config = require "typescript-tools.config"

local M = {}

Expand Down Expand Up @@ -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

0 comments on commit 7cae371

Please sign in to comment.