2018-08-13 02:38:34 +03:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2018-08-12 21:02:56 +03:00
|
|
|
use languageserver_types::{
|
|
|
|
Diagnostic, DiagnosticSeverity, Url, DocumentSymbol,
|
2018-08-13 15:35:53 +03:00
|
|
|
Command, TextDocumentIdentifier, WorkspaceEdit,
|
2018-08-13 16:35:17 +03:00
|
|
|
SymbolInformation,
|
2018-08-12 21:02:56 +03:00
|
|
|
};
|
2018-08-13 16:07:05 +03:00
|
|
|
use libanalysis::{World, Query};
|
2018-08-12 21:02:56 +03:00
|
|
|
use libeditor;
|
2018-08-13 02:38:34 +03:00
|
|
|
use libsyntax2::TextUnit;
|
|
|
|
use serde_json::{to_value, from_value};
|
2018-08-10 23:30:11 +03:00
|
|
|
|
2018-08-11 00:12:31 +03:00
|
|
|
use ::{
|
2018-08-11 00:55:32 +03:00
|
|
|
req::{self, Decoration}, Result,
|
2018-08-11 00:12:31 +03:00
|
|
|
util::FilePath,
|
2018-08-13 16:35:17 +03:00
|
|
|
conv::{Conv, ConvWith, TryConvWith, MapConvWith},
|
2018-08-11 00:12:31 +03:00
|
|
|
};
|
2018-08-10 21:13:39 +03:00
|
|
|
|
|
|
|
pub fn handle_syntax_tree(
|
|
|
|
world: World,
|
2018-08-10 22:23:17 +03:00
|
|
|
params: req::SyntaxTreeParams,
|
2018-08-10 21:13:39 +03:00
|
|
|
) -> Result<String> {
|
2018-08-10 22:23:17 +03:00
|
|
|
let path = params.text_document.file_path()?;
|
2018-08-10 21:13:39 +03:00
|
|
|
let file = world.file_syntax(&path)?;
|
|
|
|
Ok(libeditor::syntax_tree(&file))
|
|
|
|
}
|
2018-08-10 22:23:17 +03:00
|
|
|
|
|
|
|
pub fn handle_extend_selection(
|
|
|
|
world: World,
|
|
|
|
params: req::ExtendSelectionParams,
|
|
|
|
) -> Result<req::ExtendSelectionResult> {
|
|
|
|
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()
|
2018-08-13 02:38:34 +03:00
|
|
|
.map_conv_with(&line_index)
|
2018-08-12 21:02:56 +03:00
|
|
|
.map(|r| libeditor::extend_selection(&file, r).unwrap_or(r))
|
2018-08-13 02:38:34 +03:00
|
|
|
.map_conv_with(&line_index)
|
2018-08-10 22:23:17 +03:00
|
|
|
.collect();
|
|
|
|
Ok(req::ExtendSelectionResult { selections })
|
|
|
|
}
|
|
|
|
|
2018-08-11 14:44:12 +03:00
|
|
|
pub fn handle_document_symbol(
|
|
|
|
world: World,
|
|
|
|
params: req::DocumentSymbolParams,
|
|
|
|
) -> Result<Option<req::DocumentSymbolResponse>> {
|
|
|
|
let path = params.text_document.file_path()?;
|
|
|
|
let file = world.file_syntax(&path)?;
|
|
|
|
let line_index = world.file_line_index(&path)?;
|
|
|
|
|
|
|
|
let mut res: Vec<DocumentSymbol> = Vec::new();
|
|
|
|
|
|
|
|
for symbol in libeditor::file_symbols(&file) {
|
2018-08-13 14:24:22 +03:00
|
|
|
let name = symbol.name.to_string();
|
2018-08-11 14:44:12 +03:00
|
|
|
let doc_symbol = DocumentSymbol {
|
2018-08-13 14:24:22 +03:00
|
|
|
name: name.clone(),
|
|
|
|
detail: Some(name),
|
2018-08-12 21:02:56 +03:00
|
|
|
kind: symbol.kind.conv(),
|
2018-08-11 14:44:12 +03:00
|
|
|
deprecated: None,
|
2018-08-12 21:02:56 +03:00
|
|
|
range: symbol.node_range.conv_with(&line_index),
|
|
|
|
selection_range: symbol.name_range.conv_with(&line_index),
|
2018-08-11 14:44:12 +03:00
|
|
|
children: None,
|
|
|
|
};
|
|
|
|
if let Some(idx) = symbol.parent {
|
|
|
|
let children = &mut res[idx].children;
|
|
|
|
if children.is_none() {
|
|
|
|
*children = Some(Vec::new());
|
|
|
|
}
|
|
|
|
children.as_mut().unwrap().push(doc_symbol);
|
|
|
|
} else {
|
|
|
|
res.push(doc_symbol);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(Some(req::DocumentSymbolResponse::Nested(res)))
|
|
|
|
}
|
|
|
|
|
2018-08-12 21:02:56 +03:00
|
|
|
pub fn handle_code_action(
|
|
|
|
world: World,
|
|
|
|
params: req::CodeActionParams,
|
|
|
|
) -> Result<Option<Vec<Command>>> {
|
|
|
|
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 ret = if libeditor::flip_comma(&file, offset).is_some() {
|
2018-08-13 02:38:34 +03:00
|
|
|
let cmd = apply_code_action_cmd(
|
|
|
|
ActionId::FlipComma,
|
|
|
|
params.text_document,
|
|
|
|
offset,
|
|
|
|
);
|
|
|
|
Some(vec![cmd])
|
2018-08-12 21:02:56 +03:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
Ok(ret)
|
|
|
|
}
|
|
|
|
|
2018-08-13 15:35:53 +03:00
|
|
|
pub fn handle_workspace_symbol(
|
|
|
|
world: World,
|
|
|
|
params: req::WorkspaceSymbolParams,
|
|
|
|
) -> Result<Option<Vec<SymbolInformation>>> {
|
|
|
|
let mut acc = Vec::new();
|
2018-08-13 16:07:05 +03:00
|
|
|
|
|
|
|
let query = {
|
|
|
|
let all_symbols = params.query.contains("#");
|
|
|
|
let query: String = params.query.chars()
|
|
|
|
.filter(|&c| c != '#')
|
|
|
|
.collect();
|
|
|
|
let mut q = Query::new(query);
|
|
|
|
if !all_symbols {
|
|
|
|
q.only_types();
|
|
|
|
}
|
|
|
|
q
|
|
|
|
};
|
|
|
|
|
|
|
|
for (path, symbol) in world.world_symbols(query).take(128) {
|
2018-08-13 15:35:53 +03:00
|
|
|
let line_index = world.file_line_index(path)?;
|
|
|
|
let info = SymbolInformation {
|
|
|
|
name: symbol.name.to_string(),
|
|
|
|
kind: symbol.kind.conv(),
|
2018-08-13 16:35:17 +03:00
|
|
|
location: (path, symbol.node_range).try_conv_with(&line_index)?,
|
2018-08-13 15:35:53 +03:00
|
|
|
container_name: None,
|
|
|
|
};
|
|
|
|
acc.push(info);
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(Some(acc))
|
|
|
|
}
|
|
|
|
|
2018-08-13 16:35:17 +03:00
|
|
|
pub fn handle_goto_definition(
|
|
|
|
world: World,
|
|
|
|
params: req::TextDocumentPositionParams,
|
|
|
|
) -> Result<Option<req::GotoDefinitionResponse>> {
|
|
|
|
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)))
|
|
|
|
}
|
|
|
|
|
2018-08-13 02:38:34 +03:00
|
|
|
pub fn handle_execute_command(
|
|
|
|
world: World,
|
|
|
|
mut params: req::ExecuteCommandParams,
|
|
|
|
) -> Result<req::ApplyWorkspaceEditParams> {
|
|
|
|
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)?;
|
|
|
|
match arg.id {
|
|
|
|
ActionId::FlipComma => {
|
|
|
|
let path = arg.text_document.file_path()?;
|
|
|
|
let file = world.file_syntax(&path)?;
|
|
|
|
let line_index = world.file_line_index(&path)?;
|
|
|
|
let edit = match libeditor::flip_comma(&file, arg.offset) {
|
|
|
|
Some(edit) => edit(),
|
|
|
|
None => bail!("command not applicable"),
|
|
|
|
};
|
|
|
|
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,
|
|
|
|
};
|
2018-08-12 21:02:56 +03:00
|
|
|
Command {
|
|
|
|
title: id.title().to_string(),
|
|
|
|
command: "apply_code_action".to_string(),
|
2018-08-13 02:38:34 +03:00
|
|
|
arguments: Some(vec![to_value(action_request).unwrap()]),
|
2018-08-12 21:02:56 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Copy)]
|
|
|
|
enum ActionId {
|
|
|
|
FlipComma
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ActionId {
|
|
|
|
fn title(&self) -> &'static str {
|
|
|
|
match *self {
|
|
|
|
ActionId::FlipComma => "Flip `,`",
|
|
|
|
}
|
2018-08-11 14:44:12 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-10 23:30:11 +03:00
|
|
|
pub fn publish_diagnostics(world: World, uri: Url) -> Result<req::PublishDiagnosticsParams> {
|
|
|
|
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 {
|
2018-08-12 21:02:56 +03:00
|
|
|
range: d.range.conv_with(&line_index),
|
2018-08-10 23:30:11 +03:00
|
|
|
severity: Some(DiagnosticSeverity::Error),
|
|
|
|
code: None,
|
|
|
|
source: Some("libsyntax2".to_string()),
|
|
|
|
message: d.msg,
|
|
|
|
related_information: None,
|
|
|
|
}).collect();
|
|
|
|
Ok(req::PublishDiagnosticsParams { uri, diagnostics })
|
|
|
|
}
|
|
|
|
|
2018-08-11 00:55:32 +03:00
|
|
|
pub fn publish_decorations(world: World, uri: Url) -> Result<req::PublishDecorationsParams> {
|
|
|
|
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 {
|
2018-08-12 21:02:56 +03:00
|
|
|
range: h.range.conv_with(&line_index),
|
2018-08-11 00:55:32 +03:00
|
|
|
tag: h.tag,
|
|
|
|
}).collect();
|
|
|
|
Ok(req::PublishDecorationsParams { uri, decorations })
|
|
|
|
}
|