Unified symbol-to-docblock server command
[hiphop-php.git] / hphp / hack / src / server / ffpAutocompleteService.ml
blob3f6d80629b8856df076aa246e2a04847e7834060
1 (**
2 * Copyright (c) 2017, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
8 *)
10 module PositionedSyntax = Full_fidelity_positioned_syntax
11 module SourceText = Full_fidelity_source_text
12 module SyntaxKind = Full_fidelity_syntax_kind
13 module SyntaxTree = Full_fidelity_syntax_tree.WithSyntax(PositionedSyntax)
14 module TokenKind = Full_fidelity_token_kind
16 open Core_kernel
17 open AutocompleteTypes
19 let empty_autocomplete_token = "PLACEHOLDER"
21 let make_keyword_completion (replace_pos:Ide_api_types.range) (keyword_name:string) =
23 res_pos = Pos.none |> Pos.to_absolute;
24 res_replace_pos = replace_pos;
25 res_base_class = None;
26 res_ty = "keyword";
27 res_name = keyword_name;
28 res_kind = Keyword_kind;
29 func_details = None;
32 let handle_empty_autocomplete (pos: File_content.position) file_content =
33 let open File_content in
34 let offset = File_content.get_offset file_content pos in
35 let prev_char = File_content.get_char file_content (offset-1) in
36 let next_char = File_content.get_char file_content offset in
37 let is_whitespace = function ' ' | '\n' | '\r' | '\t' -> true | _ -> false in
38 if is_whitespace prev_char && is_whitespace next_char then
39 let edits = [{range = Some {st = pos; ed = pos}; text = empty_autocomplete_token}] in
40 File_content.edit_file_unsafe file_content edits
41 else
42 file_content
44 let auto_complete
45 (tcopt:TypecheckerOptions.t)
46 (file_content:string)
47 (pos:File_content.position)
48 ~(basic_only:bool)
49 ~(filter_by_token:bool)
50 ~(env: SearchUtils.local_tracking_env): result =
51 let open File_content in
52 (* The part of the line from the far left end to the point where the caret is. *)
53 let new_file_content = handle_empty_autocomplete pos file_content in
54 let dummy_path = Relative_path.(create Dummy "<autocomplete>") in
55 let source_text = SourceText.make dummy_path new_file_content in
56 let offset = SourceText.position_to_offset source_text (pos.line, pos.column) in
57 let syntax_tree = SyntaxTree.make source_text in
58 let positioned_tree = SyntaxTree.root syntax_tree in
59 let replace_pos =
60 let syntax = List.hd_exn (PositionedSyntax.parentage positioned_tree offset) in
61 let (start_line, start_col) =
62 SourceText.offset_to_position source_text (PositionedSyntax.start_offset syntax)
64 let (end_line, end_col) =
65 SourceText.offset_to_position source_text (PositionedSyntax.end_offset syntax)
68 Ide_api_types.st = {
69 Ide_api_types.line = start_line;
70 column = start_col;
72 ed = {
73 Ide_api_types.line = end_line;
74 column = end_col;
79 let (context, stub) = FfpAutocompleteContextParser.get_context_and_stub positioned_tree offset in
80 (* If we are running a test, filter the keywords and local variables based on
81 the token we are completing. *)
82 let stub = if file_content <> new_file_content then
83 String_utils.rstrip stub empty_autocomplete_token
84 else
85 stub
87 let filter_results res = List.filter res ~f:begin fun res ->
88 if filter_by_token
89 then String_utils.string_starts_with res.res_name stub
90 else true
91 end in
92 (* Delegate to each type of completion to determine whether or not that
93 type is valid in the current context *)
94 let keyword_completions =
95 FfpAutocompleteKeywords.autocomplete_keyword context
96 |> List.map ~f:(make_keyword_completion replace_pos)
98 let type_based_completions =
99 FfpAutocompleteTypeCheck.run ~context ~file_content ~stub ~pos ~tcopt ~basic_only ~env
101 let global_completions =
102 FfpAutocompleteGlobals.get_globals context stub positioned_tree replace_pos
104 [keyword_completions; type_based_completions; global_completions]
105 |> List.concat_no_order
106 |> filter_results
107 |> List.sort ~compare:(fun a b -> compare a.res_name b.res_name)
108 |> List.remove_consecutive_duplicates ~equal:(fun a b -> a.res_name = b.res_name)