1 use std::collections::HashMap;
3 use languageserver_types::{
4 Diagnostic, DiagnosticSeverity, DocumentSymbol,
5 Command, TextDocumentIdentifier,
6 SymbolInformation, Position, Location, TextEdit,
7 CompletionItem, InsertTextFormat, CompletionItemKind,
9 use serde_json::to_value;
10 use ra_analysis::{Query, FileId, RunnableKind, JobToken};
12 text_utils::contains_offset_nonstrict,
16 req::{self, Decoration}, Result,
17 conv::{Conv, ConvWith, TryConvWith, MapConvWith, to_location},
18 server_world::ServerWorld,
19 project_model::TargetKind,
22 pub fn handle_syntax_tree(
24 params: req::SyntaxTreeParams,
27 let id = params.text_document.try_conv_with(&world)?;
28 let res = world.analysis().syntax_tree(id);
32 pub fn handle_extend_selection(
34 params: req::ExtendSelectionParams,
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)
45 Ok(req::ExtendSelectionResult { selections })
48 pub fn handle_find_matching_brace(
50 params: req::FindMatchingBraceParams,
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
58 .map_conv_with(&line_index)
60 world.analysis().matching_brace(&file, offset).unwrap_or(offset)
62 .map_conv_with(&line_index)
67 pub fn handle_join_lines(
69 params: req::JoinLinesParams,
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)
79 pub fn handle_on_type_formatting(
81 params: req::DocumentOnTypeFormattingParams,
83 ) -> Result<Option<Vec<TextEdit>>> {
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,
95 let edits = edits.into_iter().map_conv_with(&line_index).collect();
99 pub fn handle_document_symbol(
101 params: req::DocumentSymbolParams,
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);
107 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
109 for symbol in world.analysis().file_structure(file_id) {
110 let doc_symbol = DocumentSymbol {
112 detail: Some("".to_string()),
113 kind: symbol.kind.conv(),
115 range: symbol.node_range.conv_with(&line_index),
116 selection_range: symbol.navigation_range.conv_with(&line_index),
119 parents.push((doc_symbol, symbol.parent));
121 let mut res = Vec::new();
122 while let Some((node, parent)) = parents.pop() {
124 None => res.push(node),
126 let children = &mut parents[i].0.children;
127 if children.is_none() {
128 *children = Some(Vec::new());
130 children.as_mut().unwrap().push(node);
135 Ok(Some(req::DocumentSymbolResponse::Nested(res)))
138 pub fn handle_workspace_symbol(
140 params: req::WorkspaceSymbolParams,
142 ) -> Result<Option<Vec<SymbolInformation>>> {
143 let all_symbols = params.query.contains("#");
144 let libs = params.query.contains("*");
146 let query: String = params.query.chars()
147 .filter(|&c| c != '#' && c != '*')
149 let mut q = Query::new(query);
159 let mut res = exec_query(&world, query, &token)?;
160 if res.is_empty() && !all_symbols {
161 let mut query = Query::new(params.query);
163 res = exec_query(&world, query, &token)?;
166 return Ok(Some(res));
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,
179 container_name: None,
187 pub fn handle_goto_definition(
189 params: req::TextDocumentPositionParams,
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,
204 Ok(Some(req::GotoDefinitionResponse::Array(res)))
207 pub fn handle_parent_module(
209 params: TextDocumentIdentifier,
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,
225 pub fn handle_runnables(
227 params: req::RunnablesParams,
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) {
241 let args = runnable_args(&world, file_id, &runnable.kind);
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),
249 "run binary".to_string(),
251 bin: "cargo".to_string(),
254 let mut m = HashMap::new();
256 "RUST_BACKTRACE".to_string(),
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()
272 let tgt = ws.target_by_root(path)?;
273 Some((tgt.package(ws).name(ws).clone(), tgt.name(ws).clone(), tgt.kind(ws)))
279 let mut res = Vec::new();
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);
286 res.push("--".to_string());
287 res.push(name.to_string());
288 res.push("--nocapture".to_string());
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);
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());
305 buf.push("--bin".to_string());
306 buf.push(tgt_name.to_string());
308 TargetKind::Test => {
309 buf.push("--test".to_string());
310 buf.push(tgt_name.to_string());
312 TargetKind::Bench => {
313 buf.push("--bench".to_string());
314 buf.push(tgt_name.to_string());
316 TargetKind::Example => {
317 buf.push("--example".to_string());
318 buf.push(tgt_name.to_string());
321 buf.push("--lib".to_string());
323 TargetKind::Other => (),
328 pub fn handle_decorations(
330 params: TextDocumentIdentifier,
332 ) -> Result<Vec<Decoration>> {
333 let file_id = params.try_conv_with(&world)?;
334 Ok(highlight(&world, file_id))
337 pub fn handle_completion(
339 params: req::CompletionParams,
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,
349 let items = items.into_iter()
351 let mut res = CompletionItem {
353 filter_text: item.lookup,
354 .. Default::default()
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);
365 Ok(Some(req::CompletionResponse::Array(items)))
368 pub fn handle_code_action(
370 params: req::CodeActionParams,
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);
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);
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)?;
389 command: "libsyntax-rust.applySourceChange".to_string(),
390 arguments: Some(vec![to_value(edit).unwrap()]),
398 pub fn publish_diagnostics(
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)
406 .map(|d| Diagnostic {
407 range: d.range.conv_with(&line_index),
408 severity: Some(DiagnosticSeverity::Error),
410 source: Some("rust-analyzer".to_string()),
412 related_information: None,
414 Ok(req::PublishDiagnosticsParams { uri, diagnostics })
417 pub fn publish_decorations(
420 ) -> Result<req::PublishDecorationsParams> {
421 let uri = world.file_id_to_uri(file_id)?;
422 Ok(req::PublishDecorationsParams {
424 decorations: highlight(&world, file_id),
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)
432 .map(|h| Decoration {
433 range: h.range.conv_with(&line_index),