Permalink
Browse files

Support signature help in Hack

Summary: The LSP request `textDocument/signatureHelp` displays function information when the user places the caret inside of a function invocation, and with the recent addition of support for this in Nuclide, it would be nice to enable it for Hack.

Reviewed By: ljw1004

Differential Revision: D7820649
  • Loading branch information...
pittsw authored and fredemmott committed May 11, 2018
1 parent 5d98b50 commit 7874525f86ad1621d5f8e5a5f23d318b861ebc46
Showing with 819 additions and 83 deletions.
  1. +20 −1 hphp/hack/src/client/clientLsp.ml
  2. +28 −19 hphp/hack/src/parser/full_fidelity_ast.ml
  3. +18 −3 hphp/hack/src/parser/full_fidelity_ast.mli
  4. +17 −0 hphp/hack/src/parser/full_fidelity_ast_types.ml
  5. +6 −0 hphp/hack/src/parser/full_fidelity_positioned_syntax.ml
  6. +0 −3 hphp/hack/src/server/identifySymbolService.ml
  7. +1 −0 hphp/hack/src/server/serverCommandTypes.ml
  8. +8 −0 hphp/hack/src/server/serverCommandTypesUtils.ml
  9. +2 −1 hphp/hack/src/server/serverHover.ml
  10. +19 −13 hphp/hack/src/server/serverIdeUtils.ml
  11. +8 −1 hphp/hack/src/server/serverIdeUtils.mli
  12. +2 −0 hphp/hack/src/server/serverRpc.ml
  13. +141 −0 hphp/hack/src/server/serverSignatureHelp.ml
  14. +14 −0 hphp/hack/src/server/serverSignatureHelp.mli
  15. +1 −5 hphp/hack/src/server/serverSymbolDefinition.ml
  16. +25 −0 hphp/hack/src/utils/lsp.ml
  17. +33 −0 hphp/hack/src/utils/lsp_fmt.ml
  18. +6 −0 hphp/hack/test/integration/data/lsp_exchanges/completion.expected
  19. +6 −0 hphp/hack/test/integration/data/lsp_exchanges/completion_legacy.expected
  20. +14 −8 hphp/hack/test/integration/data/lsp_exchanges/definition.expected
  21. +14 −8 hphp/hack/test/integration/data/lsp_exchanges/formatting.expected
  22. +14 −8 hphp/hack/test/integration/data/lsp_exchanges/highlight.expected
  23. +31 −0 hphp/hack/test/integration/data/lsp_exchanges/hover.expected
  24. +11 −5 hphp/hack/test/integration/data/lsp_exchanges/initialize_shutdown.expected
  25. +14 −8 hphp/hack/test/integration/data/lsp_exchanges/nomethod.expected
  26. +183 −0 hphp/hack/test/integration/data/lsp_exchanges/signaturehelp.expected
  27. +166 −0 hphp/hack/test/integration/data/lsp_exchanges/signaturehelp.json
  28. +12 −0 hphp/hack/test/integration/data/lsp_exchanges/signaturehelp.php
  29. +5 −0 hphp/hack/test/integration/test_lsp.py
@@ -1181,6 +1181,18 @@ let do_documentRangeFormatting
do_formatting_common conn ref_unblocked_time editor_open_files action
let do_signatureHelp
(conn: server_conn)
(ref_unblocked_time: float ref)
(params: SignatureHelp.params)
: SignatureHelp.result =
let (file, line, column) = lsp_file_position_to_hack params in
let command =
ServerCommandTypes.IDE_SIGNATURE_HELP (ServerCommandTypes.FileName file, line, column)
in
rpc conn ref_unblocked_time command
let do_documentOnTypeFormatting
(conn: server_conn)
(ref_unblocked_time: float ref)
@@ -1455,7 +1467,7 @@ let do_initialize () : Initialize.result =
resolveProvider = true;
completion_triggerCharacters = ["$"; ">"; "\\"; ":"; "<"];
};
signatureHelpProvider = None;
signatureHelpProvider = Some { sighelp_triggerCharacters = ["("; ","] };
definitionProvider = true;
referencesProvider = true;
documentHighlightProvider = true;
@@ -2103,6 +2115,13 @@ let handle_event
| Main_loop _menv, Client_message c when c.method_ = "textDocument/didSave" ->
()
(* textDocument/signatureHelp notification *)
| Main_loop menv, Client_message c when c.method_ = "textDocument/signatureHelp" ->
parse_textDocumentPositionParams c.params
|> do_signatureHelp menv.conn ref_unblocked_time
|> print_signatureHelp
|> Jsonrpc.respond to_stdout c
(* server busy status *)
| _, Server_message {push=ServerCommandTypes.BUSY_STATUS status; _} ->
state := do_server_busy !state status
@@ -6,6 +6,7 @@
* LICENSE file in the "hack" directory of this source tree.
*
*)
module SyntaxError = Full_fidelity_syntax_error
open Prim_defs
open Hh_core
@@ -2854,27 +2855,22 @@ let lower env ~source_text ~script : result =
end (* FromPositionedSyntax *)
(* TODO: Make these not default to positioned_syntax *)
module PosSyntax = Full_fidelity_positioned_syntax
module CoroutineSC = Coroutine_smart_constructor.WithSyntax(PosSyntax)
module SyntaxTree_ = Full_fidelity_syntax_tree
.WithSyntax(PosSyntax)
module SyntaxTree = SyntaxTree_.WithSmartConstructors(CoroutineSC)
module ParserErrors_ = Full_fidelity_parser_errors
.WithSyntax(PosSyntax)
include Full_fidelity_ast_types
module ParserErrors_ = Full_fidelity_parser_errors.WithSyntax(PositionedSyntax)
module ParserErrors = ParserErrors_.WithSmartConstructors(CoroutineSC)
module SourceText = Full_fidelity_source_text
module DeclModeSC = DeclModeSmartConstructors.WithSyntax(PosSyntax)
module DeclModeParser_ = Full_fidelity_parser.WithSyntax(PosSyntax)
module DeclModeSC = DeclModeSmartConstructors.WithSyntax(PositionedSyntax)
module DeclModeParser_ = Full_fidelity_parser.WithSyntax(PositionedSyntax)
module DeclModeParser = DeclModeParser_.WithSmartConstructors(DeclModeSC)
module FromPositionedSyntax = WithPositionedSyntax(PosSyntax)
module FromPositionedSyntax = WithPositionedSyntax(PositionedSyntax)
module FromEditablePositionedSyntax =
WithPositionedSyntax(Full_fidelity_editable_positioned_syntax)
let from_text (env : env) (source_text : SourceText.t) : result =
let parse_text
(env : env)
(source_text : SourceText.t)
: (FileInfo.file_type * FileInfo.mode option * PositionedSyntaxTree.t) =
let lang, mode = Full_fidelity_parser.get_language_and_mode source_text in
let tree =
let env' =
@@ -2889,11 +2885,20 @@ let from_text (env : env) (source_text : SourceText.t) : result =
let parser = DeclModeParser.make env' source_text in
let (parser, root) = DeclModeParser.parse_script parser in
let errors = DeclModeParser.errors parser in
SyntaxTree.create source_text root errors lang mode false
PositionedSyntaxTree.create source_text root errors lang mode false
else
SyntaxTree.make ~env:env' source_text
PositionedSyntaxTree.make ~env:env' source_text
in
let lower_coroutines = env.lower_coroutines && SyntaxTree.sc_state tree in
(lang, mode, tree)
let lower_tree
(env : env)
(source_text : SourceText.t)
(lang : FileInfo.file_type)
(mode : FileInfo.mode option)
(tree : PositionedSyntaxTree.t)
: result =
let lower_coroutines = env.lower_coroutines && PositionedSyntaxTree.sc_state tree in
let () =
if env.codegen && not lower_coroutines then
let hhvm_compat_mode = if env.systemlib_compat_mode
@@ -2924,7 +2929,7 @@ let from_text (env : env) (source_text : SourceText.t) : result =
let is_hhi =
String_utils.string_ends_with Relative_path.(suffix env.file) "hhi"
in
match List.last (SyntaxTree.all_errors tree) with
match List.last (PositionedSyntaxTree.all_errors tree) with
| None when is_hhi -> ()
| None ->
let errors : SyntaxError.t list =
@@ -2952,7 +2957,7 @@ let from_text (env : env) (source_text : SourceText.t) : result =
let popt = ParserOptions.with_disallow_elvis_space popt
env.disallow_elvis_space in
let env = { env with parser_options = popt } in
let script = SyntaxTree.root tree in
let script = PositionedSyntaxTree.root tree in
if lower_coroutines
then
let script =
@@ -2969,6 +2974,10 @@ let from_text (env : env) (source_text : SourceText.t) : result =
~source_text
~script
let from_text (env : env) (source_text : SourceText.t) : result =
let (lang, mode, tree) = parse_text env source_text in
lower_tree env source_text lang mode tree
let from_file (env : env) : result =
let source_text = SourceText.from_file env.file in
from_text env source_text
@@ -7,7 +7,9 @@
*
*)
module SourceText = Full_fidelity_source_text
include module type of struct
include Full_fidelity_ast_types
end
(**
* The `env` of the lowerer is "full request." It provides all the settings the
@@ -55,13 +57,26 @@ module WithPositionedSyntax : functor (Syntax : Positioned_syntax_sig.Positioned
val lower
: env
-> source_text:SourceText.t
-> source_text:Full_fidelity_source_text.t
-> script:Syntax.t
-> result
end (* WithPositionedSyntax *)
val from_text : env -> SourceText.t -> result
val parse_text
: env
-> Full_fidelity_source_text.t
-> (FileInfo.file_type *
FileInfo.mode option *
PositionedSyntaxTree.t)
val lower_tree
: env
-> Full_fidelity_source_text.t
-> FileInfo.file_type
-> FileInfo.mode option
-> PositionedSyntaxTree.t
-> result
val from_text : env -> Full_fidelity_source_text.t -> result
val from_file : env -> result
(**
@@ -0,0 +1,17 @@
(**
* Copyright (c) 2018, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the "hack" directory of this source tree.
*
*)
(* TODO: Make these not default to positioned_syntax *)
module PositionedSyntax = Full_fidelity_positioned_syntax
module CoroutineSC = Coroutine_smart_constructor.WithSyntax(PositionedSyntax)
module PositionedSyntaxTree_ =
Full_fidelity_syntax_tree.WithSyntax(PositionedSyntax)
module PositionedSyntaxTree =
PositionedSyntaxTree_.WithSmartConstructors(CoroutineSC)
@@ -257,6 +257,12 @@ let trailing_text node =
let text node =
SourceText.sub (source_text node) (start_offset node) (width node)
let trailing_token node =
match value node with
| PositionedSyntaxValue.TokenValue token -> Some token
| PositionedSyntaxValue.TokenSpan {right; _} -> Some right
| PositionedSyntaxValue.Missing _ -> None
let extract_text node =
Some (text node)
@@ -40,9 +40,6 @@ let filter_redundant results =
let is_target target_line target_char { pos; _ } =
let l, start, end_ = Pos.info_pos pos in
(* We need to filter out by filename too, due to lazy decl firing hooks in
* unrelated files *)
(Pos.filename pos = ServerIdeUtils.path) &&
l = target_line && start <= target_char && target_char - 1 <= end_
let process_class_id ?(is_declaration=false) cid =
@@ -121,6 +121,7 @@ type _ t =
| IDE_HOVER : file_input * int * int ->
HoverService.result t
| DOCBLOCK_AT : (string * int * int * string option) -> DocblockService.result t
| IDE_SIGNATURE_HELP : (file_input * int * int) -> Lsp.SignatureHelp.result t
| COVERAGE_LEVELS : file_input -> Coverage_level.result t
| AUTOCOMPLETE : string -> AutocompleteTypes.result t
| IDENTIFY_FUNCTION : file_input * int * int ->
@@ -7,6 +7,7 @@ let debug_describe_t : type a. a t -> string = function
| TYPED_AST _ -> "TYPED_AST"
| IDE_HOVER _ -> "IDE_HOVER"
| DOCBLOCK_AT _ -> "DOCBLOCK_AT"
| IDE_SIGNATURE_HELP _ -> "SIGNATURE_HELP"
| COVERAGE_LEVELS _ -> "COVERAGE_LEVELS"
| AUTOCOMPLETE _ -> "AUTOCOMPLETE"
| IDENTIFY_FUNCTION _ -> "IDENTIFY_FUNCTION"
@@ -56,3 +57,10 @@ let debug_describe_cmd : type a. a command -> string = function
| Rpc rpc -> debug_describe_t rpc
| Stream _ -> "Stream"
| Debug -> "Debug"
let source_tree_of_file_input file_input =
match file_input with
| ServerCommandTypes.FileName filename ->
Full_fidelity_source_text.from_file (Relative_path.create_detect_prefix filename)
| ServerCommandTypes.FileContent content ->
Full_fidelity_source_text.make Relative_path.default content
@@ -63,7 +63,8 @@ let make_hover_info tcopt env_and_ty file (occurrence, def_opt) =
let addendum = [
(match def_opt with
| Some def ->
ServerDocblockAt.go_def tcopt def ~base_class_name:(SymbolOccurrence.enclosing_class occurrence) ~file
let base_class_name = SymbolOccurrence.enclosing_class occurrence in
ServerDocblockAt.go_def tcopt def ~base_class_name ~file
|> Option.to_list
| None -> []);
(match occurrence, env_and_ty with
@@ -68,22 +68,13 @@ let make_then_revert_local_changes f () =
result
let path = Relative_path.default
(* This will parse, declare and check all functions and classes in content
* buffer.
*
* Declaring will overwrite definitions on shared heap, so before doing this,
* the function will also "shelve" them (see functions above and
* SharedMem.S.shelve_batch) - after working with local content is done,
* original definitions can (and should) be restored using "unshelve".
*)
let declare_and_check content ~f tcopt =
let declare_and_check_ast ~make_ast ~f tcopt =
let tcopt = TypecheckerOptions.make_permissive tcopt in
Autocomplete.auto_complete := false;
Errors.ignore_ @@ make_then_revert_local_changes begin fun () ->
Fixmes.HH_FIXMES.(remove_batch @@ KeySet.singleton path);
let {Parser_hack.file_mode = _; comments = _; content = _; ast; is_hh_file = _ } =
Parser_hack.program tcopt path content
in
let ast = make_ast () in
let funs, classes, typedefs, consts =
List.fold_left ast ~f:begin fun (funs, classes, typedefs, consts) def ->
match def with
@@ -121,9 +112,21 @@ let declare_and_check content ~f tcopt =
f path file_info tast
end
(* This will parse, declare and check all functions and classes in content
* buffer.
*
* Declaring will overwrite definitions on shared heap, so before doing this,
* the function will also "shelve" them (see functions above and
* SharedMem.S.shelve_batch) - after working with local content is done,
* original definitions can (and should) be restored using "unshelve".
*)
let declare_and_check content ~f tcopt =
try
declare_and_check content ~f tcopt
declare_and_check_ast
~make_ast:(fun () -> (Parser_hack.program tcopt path content).Parser_hack.ast)
~f
tcopt
with Decl_class.Decl_heap_elems_bug -> begin
Hh_logger.log "%s" content;
Exit_status.(exit Decl_heap_elems_bug)
@@ -144,3 +147,6 @@ let check_file_input tcopt files_info fi =
match Relative_path.Map.get files_info path with
| Some fileinfo -> List.hd_exn (recheck tcopt [(path, fileinfo)])
| None -> path, []
let check_ast tcopt ast =
declare_and_check_ast ~make_ast:(fun () -> ast) ~f:(fun _ _ tast -> tast) tcopt
@@ -11,7 +11,7 @@
* this is the path that will be assigned to it *)
val path: Relative_path.t
(* Runs the typecheck phase on a single file. *)
(** Runs the declaration, naming, and typecheck phases on a single file. *)
val check_file_input :
TypecheckerOptions.t ->
(* What are the definitions in each file. Most likely coming from
@@ -24,6 +24,13 @@ val check_file_input :
ServerCommandTypes.file_input ->
Relative_path.t * Tast.program
(** Runs the declaration, naming, and typecheck phases on an already-parsed
AST. *)
val check_ast :
TypecheckerOptions.t ->
Ast.program ->
Tast.program
(* Parses, names, declares and typechecks the content buffer, then run f
* while the declared definitions are still available in shared memory.
* The declarations will be removed from shared memory afterwards. *)
@@ -40,6 +40,8 @@ let handle : type a. genv -> env -> is_stale:bool -> a t -> env * a =
env, ServerHover.go env (fn, line, char)
| DOCBLOCK_AT (filename, line, char, base_class_name) ->
env, ServerDocblockAt.go_location env (filename, line, char) ~base_class_name
| IDE_SIGNATURE_HELP (fn, line, char) ->
env, ServerSignatureHelp.go env (fn, line, char)
| AUTOCOMPLETE content ->
let result = try
let autocomplete_context = { AutocompleteTypes.
Oops, something went wrong.

0 comments on commit 7874525

Please sign in to comment.