diff options
Diffstat (limited to 'crates/ra_lsp_server/src/main_loop/handlers.rs')
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 436 |
1 files changed, 436 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 @@ | |||
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("libsyntax2".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 | } | ||