Skip to content

Commit

Permalink
Support LSP function parameter auto-completion.
Browse files Browse the repository at this point in the history
  • Loading branch information
vicapow committed Mar 20, 2019
1 parent 94ff166 commit 9fe7c5d
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 14 deletions.
11 changes: 11 additions & 0 deletions newtests/lsp/completion/params.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// @flow

// Tests different auto complete items use the correct `kind`.
// In VSCode, for example, this includes the autocomplete
// icons used.

let aFunction = (arg1: number, arg2: string) => null;

function foo() {
const x = 15;
}
67 changes: 66 additions & 1 deletion newtests/lsp/completion/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,5 +176,70 @@ export default suite(
[...lspIgnoreStatusAndCancellation],
),
]),
],
test('textDocument/completion', [
addFile('params.js'),
ideStartAndConnect(),
ideRequestAndWaitUntilResponse('textDocument/completion', {
textDocument: {uri: '<PLACEHOLDER_PROJECT_URL_SLASH>params.js'},
position: {line: 9, character: 15},
context: { triggerKind: 1 }
}).verifyAllIDEMessagesInStep(
[
(() => {
const expectedResponse = {
isIncomplete: false,
items: [
{
label: "x",
kind: 6,
detail: "number",
inlineDetail: "number",
insertTextFormat: 1
},
{
label: "foo",
kind: 3,
detail: "() => void",
inlineDetail: "()",
itemType: "void",
insertTextFormat: 1
},
{
label: "exports",
kind: 6,
detail: "{||}",
inlineDetail: "{||}",
insertTextFormat: 1
},
{
label: "aFunction",
kind: 3,
detail: "(arg1: number, arg2: string) => null",
inlineDetail: "(arg1: number, arg2: string)",
itemType: "null",
insertTextFormat: 1
},
{
label: "this",
kind: 6,
detail: "empty",
inlineDetail: "empty",
insertTextFormat: 1
},
{
label: "super",
kind: 6,
detail: "typeof Object.prototype",
inlineDetail: "typeof Object.prototype",
insertTextFormat: 1
}
]
};
return `textDocument/completion${JSON.stringify(expectedResponse)}`
})()
],
[...lspIgnoreStatusAndCancellation],
),
]),
]
);
56 changes: 44 additions & 12 deletions src/common/flow_lsp_conversions.ml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,24 @@

module Ast = Flow_ast

let flow_position_to_lsp (line: int) (char: int): Lsp.position =
let open Lsp in
{
line = line - 1;
character = char;
}

let lsp_position_to_flow (position: Lsp.position): int * int =
let open Lsp in
let line = position.line + 1 in
let char = position.character
in
(line, char)

let flow_completion_to_lsp
(line: int)
(character: int)
(is_snippet_supported: bool)
(item: ServerProt.Response.complete_autocomplete_result)
: Lsp.Completion.completionItem =
let open Lsp.Completion in
Expand All @@ -18,17 +35,38 @@ let flow_completion_to_lsp
let params = Core_list.map ~f:(fun p -> p.param_name ^ ": " ^ p.param_ty) params in
"(" ^ (String.concat ", " params) ^ ")"
in
let itemType, inlineDetail, detail = match item.func_details with
let flow_params_to_lsp_snippet name params =
let params = Core_list.mapi ~f:(fun i p -> "${" ^ string_of_int (i +1 ) ^ ":" ^ p.param_name ^ "}") params in
name ^ "(" ^ (String.concat ", " params) ^ ")"
in
let func_snippet item func_details =
let newText = flow_params_to_lsp_snippet item.res_name func_details.param_tys in
let open Lsp in
let start = (flow_position_to_lsp line (character - 1)) in
let end_ = (flow_position_to_lsp line (character + 1)) in
let textEdit: TextEdit.t = {
Lsp.TextEdit.range = {
start;
end_;
};
Lsp.TextEdit.newText = newText;
} in
[textEdit]
in
let itemType, inlineDetail, detail, insertTextFormat, textEdits = match item.func_details with
| Some func_details ->
let itemType = Some (trunc 30 func_details.return_ty) in
let inlineDetail = Some (trunc 40 (flow_params_to_string func_details.param_tys)) in
let detail = Some (trunc80 item.res_ty) in
itemType, inlineDetail, detail
let (insertTextFormat, textEdits) = match is_snippet_supported with
| true -> (Some SnippetFormat, (func_snippet item func_details))
| false -> (Some PlainText, []) in
itemType, inlineDetail, detail, insertTextFormat, textEdits
| None ->
let itemType = None in
let inlineDetail = Some (trunc80 item.res_ty) in
let detail = Some (trunc80 item.res_ty) in
itemType, inlineDetail, detail
itemType, inlineDetail, detail, Some PlainText, []
in
{
label = item.res_name;
Expand All @@ -39,9 +77,10 @@ let flow_completion_to_lsp
documentation = None; (* This will be filled in by completionItem/resolve. *)
sortText = None;
filterText = None;
(* deprecated and should not be used *)
insertText = None;
insertTextFormat = Some PlainText;
textEdits = [];
insertTextFormat;
textEdits;
command = None;
data = None;
}
Expand Down Expand Up @@ -70,13 +109,6 @@ let loc_to_lsp_with_default (loc: Loc.t) ~(default_uri: string): Lsp.Location.t
in
{ Lsp.Location.uri; range = loc_to_lsp_range loc; }

let lsp_position_to_flow (position: Lsp.position): int * int =
let open Lsp in
let line = position.line + 1 in
let char = position.character
in
(line, char)

let flow_edit_to_textedit (edit: Loc.t * string): Lsp.TextEdit.t =
let loc, text = edit in
{ Lsp.TextEdit.range = loc_to_lsp_range loc; newText = text }
Expand Down
3 changes: 2 additions & 1 deletion src/server/command_handler/commandHandler.ml
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,7 @@ let handle_persistent_infer_type ~options ~id ~params ~loc ~metadata ~client ~pr
end

let handle_persistent_autocomplete_lsp ~options ~id ~params ~loc ~metadata ~client ~profiling ~env =
let is_snippet_supported = Persistent_connection.client_snippet_support client in
let open Completion in
let (file, line, char) = match loc with
| Some loc -> loc
Expand Down Expand Up @@ -929,7 +930,7 @@ let handle_persistent_autocomplete_lsp ~options ~id ~params ~loc ~metadata ~clie
let metadata = with_data ~extra_data metadata in
begin match result with
| Ok items ->
let items = Core_list.map ~f:Flow_lsp_conversions.flow_completion_to_lsp items in
let items = Core_list.map ~f: (Flow_lsp_conversions.flow_completion_to_lsp line char is_snippet_supported) items in
let r = CompletionResult { Lsp.Completion.isIncomplete = false; items; } in
let response = ResponseMessage (id, r) in
Lwt.return (LspResponse (Ok ((), Some response, metadata)))
Expand Down
6 changes: 6 additions & 0 deletions src/server/persistent_connection/persistent_connection.ml
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,9 @@ let get_opened_files (clients: t) : SSet.t =
List.fold_left per_client SSet.empty clients

let get_id client = client.client_id

let client_snippet_support (client: single_client) =
let open Lsp.Initialize in
match client.lsp_initialize_params with
| None -> false
| Some params -> params.client_capabilities.textDocument.completion.completionItem.snippetSupport
3 changes: 3 additions & 0 deletions src/server/persistent_connection/persistent_connection.mli
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,6 @@ val get_file: single_client -> string -> File_input.t

val get_client: Prot.client_id -> single_client option
val get_id: single_client -> Prot.client_id

val client_snippet_support: single_client -> bool

0 comments on commit 9fe7c5d

Please sign in to comment.