]> git.lizzy.rs Git - rust.git/blob - crates/ra_lsp_server/src/main_loop/handlers.rs
libsyntax2 -> rust-analyzer
[rust.git] / crates / ra_lsp_server / src / main_loop / handlers.rs
1 use std::collections::HashMap;
2
3 use languageserver_types::{
4     Diagnostic, DiagnosticSeverity, DocumentSymbol,
5     Command, TextDocumentIdentifier,
6     SymbolInformation, Position, Location, TextEdit,
7     CompletionItem, InsertTextFormat, CompletionItemKind,
8 };
9 use serde_json::to_value;
10 use ra_analysis::{Query, FileId, RunnableKind, JobToken};
11 use ra_syntax::{
12     text_utils::contains_offset_nonstrict,
13 };
14
15 use ::{
16     req::{self, Decoration}, Result,
17     conv::{Conv, ConvWith, TryConvWith, MapConvWith, to_location},
18     server_world::ServerWorld,
19     project_model::TargetKind,
20 };
21
22 pub 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
32 pub 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
48 pub 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
67 pub 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
79 pub 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
99 pub 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
138 pub 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
187 pub 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
207 pub 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
225 pub 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
328 pub 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
337 pub 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
368 pub 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
398 pub 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("rust-analyzer".to_string()),
411             message: d.message,
412             related_information: None,
413         }).collect();
414     Ok(req::PublishDiagnosticsParams { uri, diagnostics })
415 }
416
417 pub 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
428 fn 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 }