aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_lsp_server/src/main_loop
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2018-09-16 10:54:24 +0100
committerAleksey Kladov <[email protected]>2018-09-16 11:07:39 +0100
commitb5021411a84822cb3f1e3aeffad9550dd15bdeb6 (patch)
tree9dca564f8e51b298dced01c4ce669c756dce3142 /crates/ra_lsp_server/src/main_loop
parentba0bfeee12e19da40b5eabc8d0408639af10e96f (diff)
rename all things
Diffstat (limited to 'crates/ra_lsp_server/src/main_loop')
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs436
-rw-r--r--crates/ra_lsp_server/src/main_loop/mod.rs419
-rw-r--r--crates/ra_lsp_server/src/main_loop/subscriptions.rs21
3 files changed, 876 insertions, 0 deletions
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
new file mode 100644
index 000000000..568f5344c
--- /dev/null
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -0,0 +1,436 @@
1use std::collections::HashMap;
2
3use languageserver_types::{
4 Diagnostic, DiagnosticSeverity, DocumentSymbol,
5 Command, TextDocumentIdentifier,
6 SymbolInformation, Position, Location, TextEdit,
7 CompletionItem, InsertTextFormat, CompletionItemKind,
8};
9use serde_json::to_value;
10use ra_analysis::{Query, FileId, RunnableKind, JobToken};
11use ra_syntax::{
12 text_utils::contains_offset_nonstrict,
13};
14
15use ::{
16 req::{self, Decoration}, Result,
17 conv::{Conv, ConvWith, TryConvWith, MapConvWith, to_location},
18 server_world::ServerWorld,
19 project_model::TargetKind,
20};
21
22pub fn handle_syntax_tree(
23 world: ServerWorld,
24 params: req::SyntaxTreeParams,
25 _token: JobToken,
26) -> Result<String> {
27 let id = params.text_document.try_conv_with(&world)?;
28 let res = world.analysis().syntax_tree(id);
29 Ok(res)
30}
31
32pub fn handle_extend_selection(
33 world: ServerWorld,
34 params: req::ExtendSelectionParams,
35 _token: JobToken,
36) -> Result<req::ExtendSelectionResult> {
37 let file_id = params.text_document.try_conv_with(&world)?;
38 let file = world.analysis().file_syntax(file_id);
39 let line_index = world.analysis().file_line_index(file_id);
40 let selections = params.selections.into_iter()
41 .map_conv_with(&line_index)
42 .map(|r| world.analysis().extend_selection(&file, r))
43 .map_conv_with(&line_index)
44 .collect();
45 Ok(req::ExtendSelectionResult { selections })
46}
47
48pub fn handle_find_matching_brace(
49 world: ServerWorld,
50 params: req::FindMatchingBraceParams,
51 _token: JobToken,
52) -> Result<Vec<Position>> {
53 let file_id = params.text_document.try_conv_with(&world)?;
54 let file = world.analysis().file_syntax(file_id);
55 let line_index = world.analysis().file_line_index(file_id);
56 let res = params.offsets
57 .into_iter()
58 .map_conv_with(&line_index)
59 .map(|offset| {
60 world.analysis().matching_brace(&file, offset).unwrap_or(offset)
61 })
62 .map_conv_with(&line_index)
63 .collect();
64 Ok(res)
65}
66
67pub fn handle_join_lines(
68 world: ServerWorld,
69 params: req::JoinLinesParams,
70 _token: JobToken,
71) -> Result<req::SourceChange> {
72 let file_id = params.text_document.try_conv_with(&world)?;
73 let line_index = world.analysis().file_line_index(file_id);
74 let range = params.range.conv_with(&line_index);
75 world.analysis().join_lines(file_id, range)
76 .try_conv_with(&world)
77}
78
79pub fn handle_on_type_formatting(
80 world: ServerWorld,
81 params: req::DocumentOnTypeFormattingParams,
82 _token: JobToken,
83) -> Result<Option<Vec<TextEdit>>> {
84 if params.ch != "=" {
85 return Ok(None);
86 }
87
88 let file_id = params.text_document.try_conv_with(&world)?;
89 let line_index = world.analysis().file_line_index(file_id);
90 let offset = params.position.conv_with(&line_index);
91 let edits = match world.analysis().on_eq_typed(file_id, offset) {
92 None => return Ok(None),
93 Some(mut action) => action.source_file_edits.pop().unwrap().edits,
94 };
95 let edits = edits.into_iter().map_conv_with(&line_index).collect();
96 Ok(Some(edits))
97}
98
99pub fn handle_document_symbol(
100 world: ServerWorld,
101 params: req::DocumentSymbolParams,
102 _token: JobToken,
103) -> Result<Option<req::DocumentSymbolResponse>> {
104 let file_id = params.text_document.try_conv_with(&world)?;
105 let line_index = world.analysis().file_line_index(file_id);
106
107 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
108
109 for symbol in world.analysis().file_structure(file_id) {
110 let doc_symbol = DocumentSymbol {
111 name: symbol.label,
112 detail: Some("".to_string()),
113 kind: symbol.kind.conv(),
114 deprecated: None,
115 range: symbol.node_range.conv_with(&line_index),
116 selection_range: symbol.navigation_range.conv_with(&line_index),
117 children: None,
118 };
119 parents.push((doc_symbol, symbol.parent));
120 }
121 let mut res = Vec::new();
122 while let Some((node, parent)) = parents.pop() {
123 match parent {
124 None => res.push(node),
125 Some(i) => {
126 let children = &mut parents[i].0.children;
127 if children.is_none() {
128 *children = Some(Vec::new());
129 }
130 children.as_mut().unwrap().push(node);
131 }
132 }
133 }
134
135 Ok(Some(req::DocumentSymbolResponse::Nested(res)))
136}
137
138pub fn handle_workspace_symbol(
139 world: ServerWorld,
140 params: req::WorkspaceSymbolParams,
141 token: JobToken,
142) -> Result<Option<Vec<SymbolInformation>>> {
143 let all_symbols = params.query.contains("#");
144 let libs = params.query.contains("*");
145 let query = {
146 let query: String = params.query.chars()
147 .filter(|&c| c != '#' && c != '*')
148 .collect();
149 let mut q = Query::new(query);
150 if !all_symbols {
151 q.only_types();
152 }
153 if libs {
154 q.libs();
155 }
156 q.limit(128);
157 q
158 };
159 let mut res = exec_query(&world, query, &token)?;
160 if res.is_empty() && !all_symbols {
161 let mut query = Query::new(params.query);
162 query.limit(128);
163 res = exec_query(&world, query, &token)?;
164 }
165
166 return Ok(Some(res));
167
168 fn exec_query(world: &ServerWorld, query: Query, token: &JobToken) -> Result<Vec<SymbolInformation>> {
169 let mut res = Vec::new();
170 for (file_id, symbol) in world.analysis().symbol_search(query, token) {
171 let line_index = world.analysis().file_line_index(file_id);
172 let info = SymbolInformation {
173 name: symbol.name.to_string(),
174 kind: symbol.kind.conv(),
175 location: to_location(
176 file_id, symbol.node_range,
177 world, &line_index
178 )?,
179 container_name: None,
180 };
181 res.push(info);
182 };
183 Ok(res)
184 }
185}
186
187pub fn handle_goto_definition(
188 world: ServerWorld,
189 params: req::TextDocumentPositionParams,
190 token: JobToken,
191) -> Result<Option<req::GotoDefinitionResponse>> {
192 let file_id = params.text_document.try_conv_with(&world)?;
193 let line_index = world.analysis().file_line_index(file_id);
194 let offset = params.position.conv_with(&line_index);
195 let mut res = Vec::new();
196 for (file_id, symbol) in world.analysis().approximately_resolve_symbol(file_id, offset, &token) {
197 let line_index = world.analysis().file_line_index(file_id);
198 let location = to_location(
199 file_id, symbol.node_range,
200 &world, &line_index,
201 )?;
202 res.push(location)
203 }
204 Ok(Some(req::GotoDefinitionResponse::Array(res)))
205}
206
207pub fn handle_parent_module(
208 world: ServerWorld,
209 params: TextDocumentIdentifier,
210 _token: JobToken,
211) -> Result<Vec<Location>> {
212 let file_id = params.try_conv_with(&world)?;
213 let mut res = Vec::new();
214 for (file_id, symbol) in world.analysis().parent_module(file_id) {
215 let line_index = world.analysis().file_line_index(file_id);
216 let location = to_location(
217 file_id, symbol.node_range,
218 &world, &line_index
219 )?;
220 res.push(location);
221 }
222 Ok(res)
223}
224
225pub fn handle_runnables(
226 world: ServerWorld,
227 params: req::RunnablesParams,
228 _token: JobToken,
229) -> Result<Vec<req::Runnable>> {
230 let file_id = params.text_document.try_conv_with(&world)?;
231 let line_index = world.analysis().file_line_index(file_id);
232 let offset = params.position.map(|it| it.conv_with(&line_index));
233 let mut res = Vec::new();
234 for runnable in world.analysis().runnables(file_id) {
235 if let Some(offset) = offset {
236 if !contains_offset_nonstrict(runnable.range, offset) {
237 continue;
238 }
239 }
240
241 let args = runnable_args(&world, file_id, &runnable.kind);
242
243 let r = req::Runnable {
244 range: runnable.range.conv_with(&line_index),
245 label: match &runnable.kind {
246 RunnableKind::Test { name } =>
247 format!("test {}", name),
248 RunnableKind::Bin =>
249 "run binary".to_string(),
250 },
251 bin: "cargo".to_string(),
252 args,
253 env: {
254 let mut m = HashMap::new();
255 m.insert(
256 "RUST_BACKTRACE".to_string(),
257 "short".to_string(),
258 );
259 m
260 }
261 };
262 res.push(r);
263 }
264 return Ok(res);
265
266 fn runnable_args(world: &ServerWorld, file_id: FileId, kind: &RunnableKind) -> Vec<String> {
267 let spec = if let Some(&crate_id) = world.analysis().crate_for(file_id).first() {
268 let file_id = world.analysis().crate_root(crate_id);
269 let path = world.path_map.get_path(file_id);
270 world.workspaces.iter()
271 .filter_map(|ws| {
272 let tgt = ws.target_by_root(path)?;
273 Some((tgt.package(ws).name(ws).clone(), tgt.name(ws).clone(), tgt.kind(ws)))
274 })
275 .next()
276 } else {
277 None
278 };
279 let mut res = Vec::new();
280 match kind {
281 RunnableKind::Test { name } => {
282 res.push("test".to_string());
283 if let Some((pkg_name, tgt_name, tgt_kind)) = spec {
284 spec_args(pkg_name, tgt_name, tgt_kind, &mut res);
285 }
286 res.push("--".to_string());
287 res.push(name.to_string());
288 res.push("--nocapture".to_string());
289 }
290 RunnableKind::Bin => {
291 res.push("run".to_string());
292 if let Some((pkg_name, tgt_name, tgt_kind)) = spec {
293 spec_args(pkg_name, tgt_name, tgt_kind, &mut res);
294 }
295 }
296 }
297 res
298 }
299
300 fn spec_args(pkg_name: &str, tgt_name: &str, tgt_kind: TargetKind, buf: &mut Vec<String>) {
301 buf.push("--package".to_string());
302 buf.push(pkg_name.to_string());
303 match tgt_kind {
304 TargetKind::Bin => {
305 buf.push("--bin".to_string());
306 buf.push(tgt_name.to_string());
307 }
308 TargetKind::Test => {
309 buf.push("--test".to_string());
310 buf.push(tgt_name.to_string());
311 }
312 TargetKind::Bench => {
313 buf.push("--bench".to_string());
314 buf.push(tgt_name.to_string());
315 }
316 TargetKind::Example => {
317 buf.push("--example".to_string());
318 buf.push(tgt_name.to_string());
319 }
320 TargetKind::Lib => {
321 buf.push("--lib".to_string());
322 }
323 TargetKind::Other => (),
324 }
325 }
326}
327
328pub fn handle_decorations(
329 world: ServerWorld,
330 params: TextDocumentIdentifier,
331 _token: JobToken,
332) -> Result<Vec<Decoration>> {
333 let file_id = params.try_conv_with(&world)?;
334 Ok(highlight(&world, file_id))
335}
336
337pub fn handle_completion(
338 world: ServerWorld,
339 params: req::CompletionParams,
340 _token: JobToken,
341) -> Result<Option<req::CompletionResponse>> {
342 let file_id = params.text_document.try_conv_with(&world)?;
343 let line_index = world.analysis().file_line_index(file_id);
344 let offset = params.position.conv_with(&line_index);
345 let items = match world.analysis().completions(file_id, offset) {
346 None => return Ok(None),
347 Some(items) => items,
348 };
349 let items = items.into_iter()
350 .map(|item| {
351 let mut res = CompletionItem {
352 label: item.label,
353 filter_text: item.lookup,
354 .. Default::default()
355 };
356 if let Some(snip) = item.snippet {
357 res.insert_text = Some(snip);
358 res.insert_text_format = Some(InsertTextFormat::Snippet);
359 res.kind = Some(CompletionItemKind::Keyword);
360 };
361 res
362 })
363 .collect();
364
365 Ok(Some(req::CompletionResponse::Array(items)))
366}
367
368pub fn handle_code_action(
369 world: ServerWorld,
370 params: req::CodeActionParams,
371 _token: JobToken,
372) -> Result<Option<Vec<Command>>> {
373 let file_id = params.text_document.try_conv_with(&world)?;
374 let line_index = world.analysis().file_line_index(file_id);
375 let range = params.range.conv_with(&line_index);
376
377 let assists = world.analysis().assists(file_id, range).into_iter();
378 let fixes = world.analysis().diagnostics(file_id).into_iter()
379 .filter_map(|d| Some((d.range, d.fix?)))
380 .filter(|(range, _fix)| contains_offset_nonstrict(*range, range.start()))
381 .map(|(_range, fix)| fix);
382
383 let mut res = Vec::new();
384 for source_edit in assists.chain(fixes) {
385 let title = source_edit.label.clone();
386 let edit = source_edit.try_conv_with(&world)?;
387 let cmd = Command {
388 title,
389 command: "libsyntax-rust.applySourceChange".to_string(),
390 arguments: Some(vec![to_value(edit).unwrap()]),
391 };
392 res.push(cmd);
393 }
394
395 Ok(Some(res))
396}
397
398pub fn publish_diagnostics(
399 world: ServerWorld,
400 file_id: FileId,
401) -> Result<req::PublishDiagnosticsParams> {
402 let uri = world.file_id_to_uri(file_id)?;
403 let line_index = world.analysis().file_line_index(file_id);
404 let diagnostics = world.analysis().diagnostics(file_id)
405 .into_iter()
406 .map(|d| Diagnostic {
407 range: d.range.conv_with(&line_index),
408 severity: Some(DiagnosticSeverity::Error),
409 code: None,
410 source: Some("libsyntax2".to_string()),
411 message: d.message,
412 related_information: None,
413 }).collect();
414 Ok(req::PublishDiagnosticsParams { uri, diagnostics })
415}
416
417pub fn publish_decorations(
418 world: ServerWorld,
419 file_id: FileId,
420) -> Result<req::PublishDecorationsParams> {
421 let uri = world.file_id_to_uri(file_id)?;
422 Ok(req::PublishDecorationsParams {
423 uri,
424 decorations: highlight(&world, file_id),
425 })
426}
427
428fn highlight(world: &ServerWorld, file_id: FileId) -> Vec<Decoration> {
429 let line_index = world.analysis().file_line_index(file_id);
430 world.analysis().highlight(file_id)
431 .into_iter()
432 .map(|h| Decoration {
433 range: h.range.conv_with(&line_index),
434 tag: h.tag,
435 }).collect()
436}
diff --git a/crates/ra_lsp_server/src/main_loop/mod.rs b/crates/ra_lsp_server/src/main_loop/mod.rs
new file mode 100644
index 000000000..2b2279e97
--- /dev/null
+++ b/crates/ra_lsp_server/src/main_loop/mod.rs
@@ -0,0 +1,419 @@
1mod handlers;
2mod subscriptions;
3
4use std::{
5 path::PathBuf,
6 collections::{HashMap},
7};
8
9use serde::{Serialize, de::DeserializeOwned};
10use crossbeam_channel::{unbounded, Sender, Receiver};
11use rayon::{self, ThreadPool};
12use languageserver_types::{NumberOrString};
13use ra_analysis::{FileId, JobHandle, JobToken, LibraryData};
14use gen_lsp_server::{
15 RawRequest, RawNotification, RawMessage, RawResponse, ErrorCode,
16 handle_shutdown,
17};
18
19use {
20 req,
21 Result,
22 vfs::{self, FileEvent},
23 server_world::{ServerWorldState, ServerWorld},
24 main_loop::subscriptions::{Subscriptions},
25 project_model::{CargoWorkspace, workspace_loader},
26 thread_watcher::Worker,
27};
28
29#[derive(Debug)]
30enum Task {
31 Respond(RawResponse),
32 Notify(RawNotification),
33}
34
35pub fn main_loop(
36 internal_mode: bool,
37 root: PathBuf,
38 msg_receriver: &mut Receiver<RawMessage>,
39 msg_sender: &mut Sender<RawMessage>,
40) -> Result<()> {
41 let pool = rayon::ThreadPoolBuilder::new()
42 .num_threads(4)
43 .panic_handler(|_| error!("thread panicked :("))
44 .build()
45 .unwrap();
46 let (task_sender, task_receiver) = unbounded::<Task>();
47 let (fs_worker, fs_watcher) = vfs::roots_loader();
48 let (ws_worker, ws_watcher) = workspace_loader();
49
50 info!("server initialized, serving requests");
51 let mut state = ServerWorldState::new();
52
53 let mut pending_requests = HashMap::new();
54 let mut subs = Subscriptions::new();
55 let main_res = main_loop_inner(
56 internal_mode,
57 root,
58 &pool,
59 msg_sender,
60 msg_receriver,
61 task_sender,
62 task_receiver.clone(),
63 fs_worker,
64 ws_worker,
65 &mut state,
66 &mut pending_requests,
67 &mut subs,
68 );
69
70 info!("waiting for tasks to finish...");
71 task_receiver.for_each(|task| on_task(task, msg_sender, &mut pending_requests));
72 info!("...tasks have finished");
73 info!("joining threadpool...");
74 drop(pool);
75 info!("...threadpool has finished");
76
77 let fs_res = fs_watcher.stop();
78 let ws_res = ws_watcher.stop();
79
80 main_res?;
81 fs_res?;
82 ws_res?;
83
84 Ok(())
85}
86
87fn main_loop_inner(
88 internal_mode: bool,
89 ws_root: PathBuf,
90 pool: &ThreadPool,
91 msg_sender: &mut Sender<RawMessage>,
92 msg_receiver: &mut Receiver<RawMessage>,
93 task_sender: Sender<Task>,
94 task_receiver: Receiver<Task>,
95 fs_worker: Worker<PathBuf, (PathBuf, Vec<FileEvent>)>,
96 ws_worker: Worker<PathBuf, Result<CargoWorkspace>>,
97 state: &mut ServerWorldState,
98 pending_requests: &mut HashMap<u64, JobHandle>,
99 subs: &mut Subscriptions,
100) -> Result<()> {
101 let (libdata_sender, libdata_receiver) = unbounded();
102 ws_worker.send(ws_root.clone());
103 fs_worker.send(ws_root.clone());
104 loop {
105 #[derive(Debug)]
106 enum Event {
107 Msg(RawMessage),
108 Task(Task),
109 Fs(PathBuf, Vec<FileEvent>),
110 Ws(Result<CargoWorkspace>),
111 Lib(LibraryData),
112 }
113 trace!("selecting");
114 let event = select! {
115 recv(msg_receiver, msg) => match msg {
116 Some(msg) => Event::Msg(msg),
117 None => bail!("client exited without shutdown"),
118 },
119 recv(task_receiver, task) => Event::Task(task.unwrap()),
120 recv(fs_worker.out, events) => match events {
121 None => bail!("roots watcher died"),
122 Some((pb, events)) => Event::Fs(pb, events),
123 }
124 recv(ws_worker.out, ws) => match ws {
125 None => bail!("workspace watcher died"),
126 Some(ws) => Event::Ws(ws),
127 }
128 recv(libdata_receiver, data) => Event::Lib(data.unwrap())
129 };
130 let mut state_changed = false;
131 match event {
132 Event::Task(task) => on_task(task, msg_sender, pending_requests),
133 Event::Fs(root, events) => {
134 info!("fs change, {}, {} events", root.display(), events.len());
135 if root == ws_root {
136 state.apply_fs_changes(events);
137 } else {
138 let (files, resolver) = state.events_to_files(events);
139 let sender = libdata_sender.clone();
140 pool.spawn(move || {
141 let start = ::std::time::Instant::now();
142 info!("indexing {} ... ", root.display());
143 let data = LibraryData::prepare(files, resolver);
144 info!("indexed {:?} {}", start.elapsed(), root.display());
145 sender.send(data);
146 });
147 }
148 state_changed = true;
149 }
150 Event::Ws(ws) => {
151 match ws {
152 Ok(ws) => {
153 let workspaces = vec![ws];
154 feedback(internal_mode, "workspace loaded", msg_sender);
155 for ws in workspaces.iter() {
156 for pkg in ws.packages().filter(|pkg| !pkg.is_member(ws)) {
157 debug!("sending root, {}", pkg.root(ws).to_path_buf().display());
158 fs_worker.send(pkg.root(ws).to_path_buf());
159 }
160 }
161 state.set_workspaces(workspaces);
162 state_changed = true;
163 }
164 Err(e) => warn!("loading workspace failed: {}", e),
165 }
166 }
167 Event::Lib(lib) => {
168 feedback(internal_mode, "library loaded", msg_sender);
169 state.add_lib(lib);
170 }
171 Event::Msg(msg) => {
172 match msg {
173 RawMessage::Request(req) => {
174 let req = match handle_shutdown(req, msg_sender) {
175 Some(req) => req,
176 None => return Ok(()),
177 };
178 match on_request(state, pending_requests, pool, &task_sender, req)? {
179 None => (),
180 Some(req) => {
181 error!("unknown request: {:?}", req);
182 let resp = RawResponse::err(
183 req.id,
184 ErrorCode::MethodNotFound as i32,
185 "unknown request".to_string(),
186 );
187 msg_sender.send(RawMessage::Response(resp))
188 }
189 }
190 }
191 RawMessage::Notification(not) => {
192 on_notification(msg_sender, state, pending_requests, subs, not)?;
193 state_changed = true;
194 }
195 RawMessage::Response(resp) => {
196 error!("unexpected response: {:?}", resp)
197 }
198 }
199 }
200 };
201
202 if state_changed {
203 update_file_notifications_on_threadpool(
204 pool,
205 state.snapshot(),
206 task_sender.clone(),
207 subs.subscriptions(),
208 )
209 }
210 }
211}
212
213fn on_task(
214 task: Task,
215 msg_sender: &mut Sender<RawMessage>,
216 pending_requests: &mut HashMap<u64, JobHandle>,
217) {
218 match task {
219 Task::Respond(response) => {
220 if let Some(handle) = pending_requests.remove(&response.id) {
221 assert!(handle.has_completed());
222 }
223 msg_sender.send(RawMessage::Response(response))
224 }
225 Task::Notify(n) =>
226 msg_sender.send(RawMessage::Notification(n)),
227 }
228}
229
230fn on_request(
231 world: &mut ServerWorldState,
232 pending_requests: &mut HashMap<u64, JobHandle>,
233 pool: &ThreadPool,
234 sender: &Sender<Task>,
235 req: RawRequest,
236) -> Result<Option<RawRequest>> {
237 let mut pool_dispatcher = PoolDispatcher {
238 req: Some(req),
239 res: None,
240 pool, world, sender
241 };
242 let req = pool_dispatcher
243 .on::<req::SyntaxTree>(handlers::handle_syntax_tree)?
244 .on::<req::ExtendSelection>(handlers::handle_extend_selection)?
245 .on::<req::FindMatchingBrace>(handlers::handle_find_matching_brace)?
246 .on::<req::JoinLines>(handlers::handle_join_lines)?
247 .on::<req::OnTypeFormatting>(handlers::handle_on_type_formatting)?
248 .on::<req::DocumentSymbolRequest>(handlers::handle_document_symbol)?
249 .on::<req::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
250 .on::<req::GotoDefinition>(handlers::handle_goto_definition)?
251 .on::<req::ParentModule>(handlers::handle_parent_module)?
252 .on::<req::Runnables>(handlers::handle_runnables)?
253 .on::<req::DecorationsRequest>(handlers::handle_decorations)?
254 .on::<req::Completion>(handlers::handle_completion)?
255 .on::<req::CodeActionRequest>(handlers::handle_code_action)?
256 .finish();
257 match req {
258 Ok((id, handle)) => {
259 let inserted = pending_requests.insert(id, handle).is_none();
260 assert!(inserted, "duplicate request: {}", id);
261 Ok(None)
262 },
263 Err(req) => Ok(Some(req)),
264 }
265}
266
267fn on_notification(
268 msg_sender: &mut Sender<RawMessage>,
269 state: &mut ServerWorldState,
270 pending_requests: &mut HashMap<u64, JobHandle>,
271 subs: &mut Subscriptions,
272 not: RawNotification,
273) -> Result<()> {
274 let not = match not.cast::<req::Cancel>() {
275 Ok(params) => {
276 let id = match params.id {
277 NumberOrString::Number(id) => id,
278 NumberOrString::String(id) => {
279 panic!("string id's not supported: {:?}", id);
280 }
281 };
282 if let Some(handle) = pending_requests.remove(&id) {
283 handle.cancel();
284 }
285 return Ok(())
286 }
287 Err(not) => not,
288 };
289 let not = match not.cast::<req::DidOpenTextDocument>() {
290 Ok(params) => {
291 let uri = params.text_document.uri;
292 let path = uri.to_file_path()
293 .map_err(|()| format_err!("invalid uri: {}", uri))?;
294 let file_id = state.add_mem_file(path, params.text_document.text);
295 subs.add_sub(file_id);
296 return Ok(())
297 }
298 Err(not) => not,
299 };
300 let not = match not.cast::<req::DidChangeTextDocument>() {
301 Ok(mut params) => {
302 let uri = params.text_document.uri;
303 let path = uri.to_file_path()
304 .map_err(|()| format_err!("invalid uri: {}", uri))?;
305 let text = params.content_changes.pop()
306 .ok_or_else(|| format_err!("empty changes"))?
307 .text;
308 state.change_mem_file(path.as_path(), text)?;
309 return Ok(())
310 }
311 Err(not) => not,
312 };
313 let not = match not.cast::<req::DidCloseTextDocument>() {
314 Ok(params) => {
315 let uri = params.text_document.uri;
316 let path = uri.to_file_path()
317 .map_err(|()| format_err!("invalid uri: {}", uri))?;
318 let file_id = state.remove_mem_file(path.as_path())?;
319 subs.remove_sub(file_id);
320 let params = req::PublishDiagnosticsParams { uri, diagnostics: Vec::new() };
321 let not = RawNotification::new::<req::PublishDiagnostics>(&params);
322 msg_sender.send(RawMessage::Notification(not));
323 return Ok(())
324 }
325 Err(not) => not,
326 };
327 error!("unhandled notification: {:?}", not);
328 Ok(())
329}
330
331struct PoolDispatcher<'a> {
332 req: Option<RawRequest>,
333 res: Option<(u64, JobHandle)>,
334 pool: &'a ThreadPool,
335 world: &'a ServerWorldState,
336 sender: &'a Sender<Task>,
337}
338
339impl<'a> PoolDispatcher<'a> {
340 fn on<'b, R>(
341 &'b mut self,
342 f: fn(ServerWorld, R::Params, JobToken) -> Result<R::Result>
343 ) -> Result<&'b mut Self>
344 where R: req::Request,
345 R::Params: DeserializeOwned + Send + 'static,
346 R::Result: Serialize + 'static,
347 {
348 let req = match self.req.take() {
349 None => return Ok(self),
350 Some(req) => req,
351 };
352 match req.cast::<R>() {
353 Ok((id, params)) => {
354 let (handle, token) = JobHandle::new();
355 let world = self.world.snapshot();
356 let sender = self.sender.clone();
357 self.pool.spawn(move || {
358 let resp = match f(world, params, token) {
359 Ok(resp) => RawResponse::ok::<R>(id, &resp),
360 Err(e) => RawResponse::err(id, ErrorCode::InternalError as i32, e.to_string()),
361 };
362 let task = Task::Respond(resp);
363 sender.send(task);
364 });
365 self.res = Some((id, handle));
366 }
367 Err(req) => {
368 self.req = Some(req)
369 }
370 }
371 Ok(self)
372 }
373
374 fn finish(&mut self) -> ::std::result::Result<(u64, JobHandle), RawRequest> {
375 match (self.res.take(), self.req.take()) {
376 (Some(res), None) => Ok(res),
377 (None, Some(req)) => Err(req),
378 _ => unreachable!(),
379 }
380 }
381}
382
383fn update_file_notifications_on_threadpool(
384 pool: &ThreadPool,
385 world: ServerWorld,
386 sender: Sender<Task>,
387 subscriptions: Vec<FileId>,
388) {
389 pool.spawn(move || {
390 for file_id in subscriptions {
391 match handlers::publish_diagnostics(world.clone(), file_id) {
392 Err(e) => {
393 error!("failed to compute diagnostics: {:?}", e)
394 }
395 Ok(params) => {
396 let not = RawNotification::new::<req::PublishDiagnostics>(&params);
397 sender.send(Task::Notify(not));
398 }
399 }
400 match handlers::publish_decorations(world.clone(), file_id) {
401 Err(e) => {
402 error!("failed to compute decorations: {:?}", e)
403 }
404 Ok(params) => {
405 let not = RawNotification::new::<req::PublishDecorations>(&params);
406 sender.send(Task::Notify(not))
407 }
408 }
409 }
410 });
411}
412
413fn feedback(intrnal_mode: bool, msg: &str, sender: &Sender<RawMessage>) {
414 if !intrnal_mode {
415 return;
416 }
417 let not = RawNotification::new::<req::InternalFeedback>(&msg.to_string());
418 sender.send(RawMessage::Notification(not));
419}
diff --git a/crates/ra_lsp_server/src/main_loop/subscriptions.rs b/crates/ra_lsp_server/src/main_loop/subscriptions.rs
new file mode 100644
index 000000000..27f92cc9a
--- /dev/null
+++ b/crates/ra_lsp_server/src/main_loop/subscriptions.rs
@@ -0,0 +1,21 @@
1use std::collections::HashSet;
2use ra_analysis::FileId;
3
4pub struct Subscriptions {
5 subs: HashSet<FileId>,
6}
7
8impl Subscriptions {
9 pub fn new() -> Subscriptions {
10 Subscriptions { subs: HashSet::new() }
11 }
12 pub fn add_sub(&mut self, file_id: FileId) {
13 self.subs.insert(file_id);
14 }
15 pub fn remove_sub(&mut self, file_id: FileId) {
16 self.subs.remove(&file_id);
17 }
18 pub fn subscriptions(&self) -> Vec<FileId> {
19 self.subs.iter().cloned().collect()
20 }
21}