use std::collections::HashMap; use languageserver_types::{ Diagnostic, DiagnosticSeverity, Url, DocumentSymbol, Command, TextDocumentIdentifier, WorkspaceEdit, SymbolInformation, }; use libanalysis::{World, Query}; use libeditor; use libsyntax2::TextUnit; use serde_json::{to_value, from_value}; use ::{ req::{self, Decoration}, Result, util::FilePath, conv::{Conv, ConvWith, TryConvWith, MapConvWith}, }; pub fn handle_syntax_tree( world: World, params: req::SyntaxTreeParams, ) -> Result { let path = params.text_document.file_path()?; let file = world.file_syntax(&path)?; Ok(libeditor::syntax_tree(&file)) } pub fn handle_extend_selection( world: World, params: req::ExtendSelectionParams, ) -> Result { let path = params.text_document.file_path()?; let file = world.file_syntax(&path)?; let line_index = world.file_line_index(&path)?; 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_document_symbol( world: World, params: req::DocumentSymbolParams, ) -> Result> { let path = params.text_document.file_path()?; let file = world.file_syntax(&path)?; let line_index = world.file_line_index(&path)?; 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: World, params: req::CodeActionParams, ) -> Result>> { let path = params.text_document.file_path()?; let file = world.file_syntax(&path)?; let line_index = world.file_line_index(&path)?; 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: World, 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: &World, query: Query) -> Result> { let mut res = Vec::new(); for (path, symbol) in world.world_symbols(query) { let line_index = world.file_line_index(path)?; let info = SymbolInformation { name: symbol.name.to_string(), kind: symbol.kind.conv(), location: (path, symbol.node_range).try_conv_with(&line_index)?, container_name: None, }; res.push(info); }; Ok(res) } } pub fn handle_goto_definition( world: World, params: req::TextDocumentPositionParams, ) -> Result> { let path = params.text_document.file_path()?; let line_index = world.file_line_index(&path)?; let offset = params.position.conv_with(&line_index); let mut res = Vec::new(); for (path, symbol) in world.approximately_resolve_symbol(&path, offset)? { let line_index = world.file_line_index(path)?; let location = (path, symbol.node_range).try_conv_with(&line_index)?; res.push(location) } Ok(Some(req::GotoDefinitionResponse::Array(res))) } pub fn handle_execute_command( world: World, mut params: req::ExecuteCommandParams, ) -> Result { 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 path = arg.text_document.file_path()?; let file = world.file_syntax(&path)?; let edit = match arg.id { ActionId::FlipComma => libeditor::flip_comma(&file, arg.offset).map(|edit| edit()), ActionId::AddDerive => libeditor::add_derive(&file, arg.offset).map(|edit| edit()), }; let edit = match edit { Some(edit) => edit, None => bail!("command not applicable"), }; let line_index = world.file_line_index(&path)?; let mut changes = HashMap::new(); changes.insert( arg.text_document.uri, edit.conv_with(&line_index), ); let edit = WorkspaceEdit { changes: Some(changes), document_changes: None, }; Ok(req::ApplyWorkspaceEditParams { edit }) } #[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: World, uri: Url) -> Result { let path = uri.file_path()?; let file = world.file_syntax(&path)?; let line_index = world.file_line_index(&path)?; 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: World, uri: Url) -> Result { let path = uri.file_path()?; let file = world.file_syntax(&path)?; let line_index = world.file_line_index(&path)?; 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 }) }