use std::collections::HashMap; use languageserver_types::{ Diagnostic, DiagnosticSeverity, Url, DocumentSymbol, Command, TextDocumentIdentifier, WorkspaceEdit, SymbolInformation, Position, Location, }; use libanalysis::{Query}; use libeditor; use libsyntax2::TextUnit; use serde_json::{to_value, from_value}; use ::{ req::{self, Decoration}, Result, conv::{Conv, ConvWith, TryConvWith, MapConvWith, to_location}, server_world::ServerWorld, }; pub fn handle_syntax_tree( world: ServerWorld, params: req::SyntaxTreeParams, ) -> Result { let id = params.text_document.try_conv_with(&world)?; let file = world.analysis().file_syntax(id)?; Ok(libeditor::syntax_tree(&file)) } pub fn handle_extend_selection( world: ServerWorld, params: req::ExtendSelectionParams, ) -> Result { let file_id = params.text_document.try_conv_with(&world)?; let file = world.analysis().file_syntax(file_id)?; let line_index = world.analysis().file_line_index(file_id)?; let selections = params.selections.into_iter() .map_conv_with(&line_index) .map(|r| libeditor::extend_selection(&file, r).unwrap_or(r)) .map_conv_with(&line_index) .collect(); Ok(req::ExtendSelectionResult { selections }) } pub fn handle_find_matching_brace( world: ServerWorld, params: req::FindMatchingBraceParams, ) -> Result> { let file_id = params.text_document.try_conv_with(&world)?; let file = world.analysis().file_syntax(file_id)?; let line_index = world.analysis().file_line_index(file_id)?; let res = params.offsets .into_iter() .map_conv_with(&line_index) .map(|offset| { libeditor::matching_brace(&file, offset).unwrap_or(offset) }) .map_conv_with(&line_index) .collect(); Ok(res) } pub fn handle_document_symbol( world: ServerWorld, params: req::DocumentSymbolParams, ) -> Result> { let file_id = params.text_document.try_conv_with(&world)?; let file = world.analysis().file_syntax(file_id)?; let line_index = world.analysis().file_line_index(file_id)?; let mut parents: Vec<(DocumentSymbol, Option)> = Vec::new(); for symbol in libeditor::file_structure(&file) { let doc_symbol = DocumentSymbol { name: symbol.label, detail: Some("".to_string()), kind: symbol.kind.conv(), deprecated: None, range: symbol.node_range.conv_with(&line_index), selection_range: symbol.navigation_range.conv_with(&line_index), children: None, }; parents.push((doc_symbol, symbol.parent)); } let mut res = Vec::new(); while let Some((node, parent)) = parents.pop() { match parent { None => res.push(node), Some(i) => { let children = &mut parents[i].0.children; if children.is_none() { *children = Some(Vec::new()); } children.as_mut().unwrap().push(node); } } } Ok(Some(req::DocumentSymbolResponse::Nested(res))) } pub fn handle_code_action( world: ServerWorld, params: req::CodeActionParams, ) -> Result>> { let file_id = params.text_document.try_conv_with(&world)?; let file = world.analysis().file_syntax(file_id)?; let line_index = world.analysis().file_line_index(file_id)?; let offset = params.range.conv_with(&line_index).start(); let mut ret = Vec::new(); let actions = &[ (ActionId::FlipComma, libeditor::flip_comma(&file, offset).is_some()), (ActionId::AddDerive, libeditor::add_derive(&file, offset).is_some()), ]; for (id, edit) in actions { if *edit { let cmd = apply_code_action_cmd(*id, params.text_document.clone(), offset); ret.push(cmd); } } return Ok(Some(ret)); } pub fn handle_workspace_symbol( world: ServerWorld, params: req::WorkspaceSymbolParams, ) -> Result>> { let all_symbols = params.query.contains("#"); let query = { let query: String = params.query.chars() .filter(|&c| c != '#') .collect(); let mut q = Query::new(query); if !all_symbols { q.only_types(); } q.limit(128); q }; let mut res = exec_query(&world, query)?; if res.is_empty() && !all_symbols { let mut query = Query::new(params.query); query.limit(128); res = exec_query(&world, query)?; } return Ok(Some(res)); fn exec_query(world: &ServerWorld, query: Query) -> Result> { let mut res = Vec::new(); for (file_id, symbol) in world.analysis().world_symbols(query) { let line_index = world.analysis().file_line_index(file_id)?; let info = SymbolInformation { name: symbol.name.to_string(), kind: symbol.kind.conv(), location: to_location( file_id, symbol.node_range, world, &line_index )?, container_name: None, }; res.push(info); }; Ok(res) } } pub fn handle_goto_definition( world: ServerWorld, params: req::TextDocumentPositionParams, ) -> Result> { let file_id = params.text_document.try_conv_with(&world)?; let line_index = world.analysis().file_line_index(file_id)?; let offset = params.position.conv_with(&line_index); let mut res = Vec::new(); for (file_id, symbol) in world.analysis().approximately_resolve_symbol(file_id, offset)? { let line_index = world.analysis().file_line_index(file_id)?; let location = to_location( file_id, symbol.node_range, &world, &line_index, )?; res.push(location) } Ok(Some(req::GotoDefinitionResponse::Array(res))) } pub fn handle_parent_module( world: ServerWorld, params: TextDocumentIdentifier, ) -> Result> { let file_id = params.try_conv_with(&world)?; let mut res = Vec::new(); for (file_id, symbol) in world.analysis().parent_module(file_id) { let line_index = world.analysis().file_line_index(file_id)?; let location = to_location( file_id, symbol.node_range, &world, &line_index )?; res.push(location); } Ok(res) } pub fn handle_execute_command( world: ServerWorld, mut params: req::ExecuteCommandParams, ) -> Result<(req::ApplyWorkspaceEditParams, Option)> { if params.command.as_str() != "apply_code_action" { bail!("unknown cmd: {:?}", params.command); } if params.arguments.len() != 1 { bail!("expected single arg, got {}", params.arguments.len()); } let arg = params.arguments.pop().unwrap(); let arg: ActionRequest = from_value(arg)?; let file_id = arg.text_document.try_conv_with(&world)?; let file = world.analysis().file_syntax(file_id)?; let action_result = match arg.id { ActionId::FlipComma => libeditor::flip_comma(&file, arg.offset).map(|f| f()), ActionId::AddDerive => libeditor::add_derive(&file, arg.offset).map(|f| f()), }.ok_or_else(|| format_err!("command not applicable"))?; let line_index = world.analysis().file_line_index(file_id)?; let mut changes = HashMap::new(); changes.insert( arg.text_document.uri, action_result.edit.conv_with(&line_index), ); let edit = WorkspaceEdit { changes: Some(changes), document_changes: None, }; let edit = req::ApplyWorkspaceEditParams { edit }; let cursor_pos = action_result.cursor_position .map(|off| off.conv_with(&line_index)); Ok((edit, cursor_pos)) } #[derive(Serialize, Deserialize)] struct ActionRequest { id: ActionId, text_document: TextDocumentIdentifier, offset: TextUnit, } fn apply_code_action_cmd(id: ActionId, doc: TextDocumentIdentifier, offset: TextUnit) -> Command { let action_request = ActionRequest { id, text_document: doc, offset, }; Command { title: id.title().to_string(), command: "apply_code_action".to_string(), arguments: Some(vec![to_value(action_request).unwrap()]), } } #[derive(Serialize, Deserialize, Clone, Copy)] enum ActionId { FlipComma, AddDerive, } impl ActionId { fn title(&self) -> &'static str { match *self { ActionId::FlipComma => "Flip `,`", ActionId::AddDerive => "Add `#[derive]`", } } } pub fn publish_diagnostics( world: ServerWorld, uri: Url ) -> Result { let file_id = world.uri_to_file_id(&uri)?; let file = world.analysis().file_syntax(file_id)?; let line_index = world.analysis().file_line_index(file_id)?; let diagnostics = libeditor::diagnostics(&file) .into_iter() .map(|d| Diagnostic { range: d.range.conv_with(&line_index), severity: Some(DiagnosticSeverity::Error), code: None, source: Some("libsyntax2".to_string()), message: d.msg, related_information: None, }).collect(); Ok(req::PublishDiagnosticsParams { uri, diagnostics }) } pub fn publish_decorations( world: ServerWorld, uri: Url ) -> Result { let file_id = world.uri_to_file_id(&uri)?; let file = world.analysis().file_syntax(file_id)?; let line_index = world.analysis().file_line_index(file_id)?; let decorations = libeditor::highlight(&file) .into_iter() .map(|h| Decoration { range: h.range.conv_with(&line_index), tag: h.tag, }).collect(); Ok(req::PublishDecorationsParams { uri, decorations }) }