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
15 let try_relativize_path x
=
16 Option.try_with
(fun () -> Relative_path.(create Root x
))
18 let get_file_content_from_disk path
=
19 let f () = Sys_utils.cat
(Relative_path.to_absolute path
) in
22 let get_file_content = function
23 | ServerCommandTypes.FileContent s
-> s
24 | ServerCommandTypes.FileName path
->
25 try_relativize_path path
27 match File_provider.get path
with
28 | Some
(File_provider.Ide
f) -> Some
f
29 | Some
(File_provider.Disk c
) -> Some c
30 | None
-> get_file_content_from_disk path
)
31 (* In case of errors, proceed with empty file contents *)
32 |> Option.value ~default
:""
34 (* Warning: this takes O(global error list) time. Should be OK while
35 * it's only used in editor, where opening a file is a rare (compared to other
36 * kind of queries) operation, but if this ever ends up being called by
37 * other automation there is room for improvement (i.e finally changing global
38 * error list to be a error map)
40 let update_diagnostics diag_subscribe editor_open_files errorl
=
41 Option.map diag_subscribe ~
f:(fun diag_subscribe
->
42 Diagnostic_subscription.update
44 ~priority_files
:editor_open_files
45 ~reparsed
:Relative_path.Set.empty
46 ~rechecked
:Relative_path.Set.empty
48 ~full_check_done
:true)
50 let open_file ~predeclare env path content
=
51 let prev_content = get_file_content (ServerCommandTypes.FileName path
) in
52 let full_path = path
in
54 try_relativize_path path
>>= fun path
->
55 (* Before making any changes, pre-load (into Decl_heap) currently existing
56 * declarations so there is always a previous version to compare against,
57 * which makes incremental mode perform better. *)
58 ( if predeclare
&& not
(Relative_path.Set.mem env
.editor_open_files path
)
60 let ctx = Provider_utils.ctx_from_server_env env
in
61 Decl.make_env
ctx path
);
62 let editor_open_files = Relative_path.Set.add env
.editor_open_files path
in
63 File_provider.remove_batch
(Relative_path.Set.singleton path
);
64 File_provider.provide_file path
(File_provider.Ide content
);
65 let (ide_needs_parsing
, diag_subscribe
) =
66 if content
= prev_content && env
.full_check
= Full_check_done
then
67 (* Try to avoid telling the user that a check is needed when the file
68 * was unchanged. But even in this case, we might need to push
69 * errors that were previously throttled. They are available only
70 * when full recheck was completed and there were no further changes. In
71 * all other cases, we will need to (lazily) recheck the file. *)
73 update_diagnostics env
.diag_subscribe editor_open_files env
.errorl
76 match (env
.diag_subscribe, diag_subscribe) with
78 Hh_logger.log
"Diag_subscribe: open, none before or after"
79 | (None
, Some _
) -> Hh_logger.log
"Diag_subscribe: open, now active"
81 Hh_logger.log
"Diag_subscribe: open, remains active"
83 Hh_logger.log
"Diag_subscribe: open, REMOVED - %s" full_path
85 (env
.ide_needs_parsing
, diag_subscribe)
87 let () = Hh_logger.log
"open_file; diag_subscribe remains as it was" in
88 (Relative_path.Set.add env
.ide_needs_parsing path
, env
.diag_subscribe)
90 (* Need to re-parse this file during next full check to update
91 * global error list positions that refer to it *)
92 let disk_needs_parsing =
93 Relative_path.Set.add env
.disk_needs_parsing path
95 let last_command_time = Unix.gettimeofday
() in
106 Option.value new_env ~default
:env
108 let close_relative_path env path
=
109 let editor_open_files = Relative_path.Set.remove env
.editor_open_files path
in
111 match File_provider.get_unsafe path
with
112 | File_provider.Ide
f -> f
115 File_provider.remove_batch
(Relative_path.Set.singleton path
);
116 let new_contents = File_provider.get_contents path
in
117 let ide_needs_parsing =
118 if new_contents = Some
contents then
119 env
.ide_needs_parsing
121 Relative_path.Set.add env
.ide_needs_parsing path
123 let disk_needs_parsing = Relative_path.Set.add env
.disk_needs_parsing path
in
124 let last_command_time = Unix.gettimeofday
() in
133 let close_file env path
=
134 let new_env = try_relativize_path path
>>| close_relative_path env
in
135 Option.value new_env ~default
:env
137 let edit_file ~predeclare env path
(edits
: File_content.text_edit list
) =
139 try_relativize_path path
>>= fun path
->
140 (* See similar predeclare in open_file function *)
141 ( if predeclare
&& not
(Relative_path.Set.mem env
.editor_open_files path
)
143 let ctx = Provider_utils.ctx_from_server_env env
in
144 Decl.make_env
ctx path
);
145 ServerBusyStatus.send env
ServerCommandTypes.Needs_local_typecheck
;
147 match File_provider.get path
with
148 | Some
(File_provider.Ide
f) -> f
149 | Some
(File_provider.Disk content
) -> content
151 (try Sys_utils.cat
(Relative_path.to_absolute path
) with _
-> "")
154 match edit_file fc edits
with
156 | Error
(reason
, _stack
) ->
157 Hh_logger.log
"%s" reason
;
159 (* TODO: do not crash, but surface this to the client somehow *)
162 let editor_open_files = Relative_path.Set.add env
.editor_open_files path
in
163 File_provider.remove_batch
(Relative_path.Set.singleton path
);
164 File_provider.provide_file path
(File_provider.Ide
edited_fc);
165 let ide_needs_parsing = Relative_path.Set.add env
.ide_needs_parsing path
in
166 let disk_needs_parsing =
167 Relative_path.Set.add env
.disk_needs_parsing path
169 let last_command_time = Unix.gettimeofday
() in
179 Option.value new_env ~default
:env
181 let clear_sync_data env
=
183 Relative_path.Set.fold
env.editor_open_files ~init
:env ~
f:(fun x
env ->
184 close_relative_path env x
)
186 { env with persistent_client
= None
; diag_subscribe = None
}
188 let get_unsaved_changes env =
189 Relative_path.Set.fold
190 env.editor_open_files
191 ~init
:Relative_path.Map.empty
193 match File_provider.get path
with
194 | Some
(File_provider.Ide ide_contents
) ->
196 match get_file_content_from_disk path
with
197 | Some disk_contents
when ide_contents
<> disk_contents
->
198 Relative_path.Map.add acc path
(ide_contents
, disk_contents
)
201 (* If one creates a new file, then there will not be corresponding
202 * disk contents, and we should consider there to be unsaved changes in
204 Relative_path.Map.add acc path
(ide_contents
, "")
208 let has_unsaved_changes env =
209 not
@@ Relative_path.Map.is_empty
(get_unsaved_changes env)
211 let toggle_dynamic_view (env : env) (t
: bool) : env =
212 ServerDynamicView.toggle
:= t
;
213 { env with ide_needs_parsing = env.editor_open_files }