2 * Copyright (c) 2016, Facebook, Inc.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
12 open Option.Monad_infix
16 let try_relativize_path x
=
17 Option.try_with
(fun () -> Relative_path.(create Root x
))
19 let get_file_content_from_disk path
=
20 let f () = Sys_utils.cat
(Relative_path.to_absolute path
) in
23 let get_file_content = function
24 | ServerCommandTypes.FileContent s
-> s
25 | ServerCommandTypes.FileName path
->
26 begin try_relativize_path path
>>= fun path
->
27 match File_heap.FileHeap.get path
with
28 | Some
(Ide
f) -> Some
f
29 | Some
(Disk c
) -> Some c
30 | None
-> get_file_content_from_disk path
32 (* In case of errors, proceed with empty file contents *)
33 |> Option.value ~default
:""
35 (* Warning: this takes O(global error list) time. Should be OK while
36 * it's only used in editor, where opening a file is a rare (compared to other
37 * kind of queries) operation, but if this ever ends up being called by
38 * other automation there is room for improvement (i.e finally changing global
39 * error list to be a error map)
41 let update_diagnostics diag_subscribe editor_open_files errorl
=
42 Option.map diag_subscribe ~
f:begin fun diag_subscribe
->
43 Diagnostic_subscription.update diag_subscribe
44 ~priority_files
:editor_open_files
45 ~reparsed
:Relative_path.Set.empty
46 ~rechecked
:Relative_path.Map.empty
51 let open_file ~predeclare env path content
=
52 let prev_content = get_file_content (ServerCommandTypes.FileName path
) in
53 let new_env = try_relativize_path path
>>= fun path
->
54 (* Before making any changes, pre-load (into Decl_heap) currently existing
55 * declarations so there is always a previous version to compare against,
56 * which makes incremental mode perform better. *)
57 if predeclare
&& (not
(Relative_path.Set.mem env
.editor_open_files path
)) then
58 Decl.make_env env
.tcopt path
;
59 let editor_open_files = Relative_path.Set.add env
.editor_open_files path
in
60 FileHeap.remove_batch
(Relative_path.Set.singleton path
);
61 FileHeap.add path
(Ide content
);
62 let ide_needs_parsing, diag_subscribe
=
63 if content
= prev_content && (env
.full_check
= Full_check_done
) then begin
64 (* Try to avoid telling the user that a check is needed when the file
65 * was unchanged. But even in this case, we might need to push
66 * errors that were previously throttled. They are available only
67 * when full recheck was completed and there were no further changes. In
68 * all other cases, we will need to (lazily) recheck the file. *)
69 env
.ide_needs_parsing,
70 update_diagnostics env
.diag_subscribe
editor_open_files env
.errorl
72 Relative_path.Set.add env
.ide_needs_parsing path
, env
.diag_subscribe
74 (* Need to re-parse this file during next full check to update
75 * global error list positions that refer to it *)
76 let disk_needs_parsing =
77 Relative_path.Set.add env
.disk_needs_parsing path
in
78 let last_command_time = Unix.gettimeofday
() in
80 editor_open_files; ide_needs_parsing; last_command_time; diag_subscribe
;
83 Option.value new_env ~default
:env
85 let close_relative_path env path
=
86 let editor_open_files = Relative_path.Set.remove env
.editor_open_files path
in
87 let contents = (match (FileHeap.find_unsafe path
) with
89 | _
-> assert false) in
90 FileHeap.remove_batch
(Relative_path.Set.singleton path
);
91 let new_contents = File_heap.get_contents path
in
92 let ide_needs_parsing =
93 if new_contents = Some
contents then env
.ide_needs_parsing
94 else Relative_path.Set.add env
.ide_needs_parsing path
in
95 let disk_needs_parsing =
96 Relative_path.Set.add env
.disk_needs_parsing path
in
97 let last_command_time = Unix.gettimeofday
() in
99 editor_open_files; ide_needs_parsing; last_command_time; disk_needs_parsing
102 let close_file env path
=
103 let new_env = try_relativize_path path
>>| close_relative_path env
in
104 Option.value new_env ~default
:env
106 let edit_file ~predeclare env path
(edits
: File_content.text_edit list
) =
107 let new_env = try_relativize_path path
>>= fun path
->
108 (* See similar predeclare in open_file function *)
109 if predeclare
&& (not
(Relative_path.Set.mem env
.editor_open_files path
)) then
110 Decl.make_env env
.tcopt path
;
111 ServerBusyStatus.send env
ServerCommandTypes.Needs_local_typecheck
;
112 let fc = match FileHeap.get path
with
114 | Some Disk content
-> content
116 try Sys_utils.cat
(Relative_path.to_absolute path
) with _
-> "" in
117 let edited_fc = match edit_file fc edits
with
119 | Error
(reason
, _stack
) ->
120 Hh_logger.log
"%s" reason
;
121 (* TODO: do not crash, but surface this to the client somehow *)
124 let editor_open_files =
125 Relative_path.Set.add env
.editor_open_files path
in
126 FileHeap.remove_batch
(Relative_path.Set.singleton path
);
127 FileHeap.add path
(Ide
edited_fc);
128 let ide_needs_parsing =
129 Relative_path.Set.add env
.ide_needs_parsing path
in
130 let disk_needs_parsing =
131 Relative_path.Set.add env
.disk_needs_parsing path
in
132 let last_command_time = Unix.gettimeofday
() in
134 editor_open_files; ide_needs_parsing; last_command_time;
137 Option.value new_env ~default
:env
139 let clear_sync_data env
=
140 let env = Relative_path.Set.fold
env.editor_open_files
142 ~
f:(fun x
env -> close_relative_path env x
)
145 persistent_client
= None
;
146 diag_subscribe
= None
;
149 let get_unsaved_changes env =
150 Relative_path.Set.fold
env.editor_open_files
151 ~init
:Relative_path.Map.empty
152 ~
f:begin fun path acc
->
153 match FileHeap.get path
with
154 | Some Ide ide_contents
->
155 begin match get_file_content_from_disk path
with
156 | Some disk_contents
when ide_contents
<> disk_contents
->
157 Relative_path.Map.add acc path
(ide_contents
, disk_contents
)
160 (* If one creates a new file, then there will not be corresponding
161 * disk contents, and we should consider there to be unsaved changes in
163 Relative_path.Map.add acc path
(ide_contents
, "")
168 let has_unsaved_changes env =
169 not
@@ Relative_path.Map.is_empty
(get_unsaved_changes env)
171 let toggle_dynamic_view (env : env) (t
: bool) : env =
172 ServerDynamicView.toggle
:= t
;
175 ide_needs_parsing = env.editor_open_files