Grand refactoring

This commit is contained in:
Aleksey Kladov 2018-08-29 18:03:14 +03:00
parent 2007ccfcfe
commit 8abf536343
14 changed files with 591 additions and 484 deletions

View file

@ -60,11 +60,8 @@ export function activate(context: vscode.ExtensionContext) {
textDocument: { uri: editor.document.uri.toString() }, textDocument: { uri: editor.document.uri.toString() },
range: client.code2ProtocolConverter.asRange(editor.selection), range: client.code2ProtocolConverter.asRange(editor.selection),
} }
let response = await client.sendRequest<lc.TextEdit[]>("m/joinLines", request) let change = await client.sendRequest<SourceChange>("m/joinLines", request)
let edits = client.protocol2CodeConverter.asTextEdits(response) await applySourceChange(change)
let wsEdit = new vscode.WorkspaceEdit()
wsEdit.set(editor.document.uri, edits)
return vscode.workspace.applyEdit(wsEdit)
}) })
registerCommand('libsyntax-rust.parentModule', async () => { registerCommand('libsyntax-rust.parentModule', async () => {
let editor = vscode.window.activeTextEditor let editor = vscode.window.activeTextEditor
@ -113,28 +110,7 @@ export function activate(context: vscode.ExtensionContext) {
return await vscode.tasks.executeTask(task) return await vscode.tasks.executeTask(task)
} }
}) })
registerCommand('libsyntax-rust.fsEdit', async (ops: FsOp[]) => { registerCommand('libsyntax-rust.applySourceChange', applySourceChange)
let edit = new vscode.WorkspaceEdit()
let created;
let moved;
for (let op of ops) {
if (op.type == "createFile") {
let uri = vscode.Uri.parse(op.uri!)
edit.createFile(uri)
created = uri
} else if (op.type == "moveFile") {
let src = vscode.Uri.parse(op.src!)
let dst = vscode.Uri.parse(op.dst!)
edit.renameFile(src, dst)
moved = dst
} else {
console.error(`unknown op: ${JSON.stringify(op)}`)
}
}
await vscode.workspace.applyEdit(edit)
let doc = await vscode.workspace.openTextDocument((created || moved)!)
await vscode.window.showTextDocument(doc)
})
dispose(vscode.workspace.registerTextDocumentContentProvider( dispose(vscode.workspace.registerTextDocumentContentProvider(
'libsyntax-rust', 'libsyntax-rust',
@ -207,18 +183,6 @@ function startServer() {
) )
} }
) )
client.onRequest(
new lc.RequestType<lc.Position, void, any, any>("m/moveCursor"),
(params: lc.Position, token: lc.CancellationToken) => {
let editor = vscode.window.activeTextEditor;
if (!editor) return
if (!editor.selection.isEmpty) return
let position = client.protocol2CodeConverter.asPosition(params)
afterLs(() => {
editor!.selection = new vscode.Selection(position, position)
})
}
)
}) })
client.start(); client.start();
} }
@ -383,9 +347,56 @@ function createTask(spec: Runnable): vscode.Task {
return t; return t;
} }
interface FsOp { interface FileSystemEdit {
type: string; type: string;
uri?: string; uri?: string;
src?: string; src?: string;
dst?: string; dst?: string;
} }
interface SourceChange {
label: string,
sourceFileEdits: lc.TextDocumentEdit[],
fileSystemEdits: FileSystemEdit[],
cursorPosition?: lc.TextDocumentPositionParams,
}
async function applySourceChange(change: SourceChange) {
console.log(`applySOurceChange ${JSON.stringify(change)}`)
let wsEdit = new vscode.WorkspaceEdit()
for (let sourceEdit of change.sourceFileEdits) {
let uri = client.protocol2CodeConverter.asUri(sourceEdit.textDocument.uri)
let edits = client.protocol2CodeConverter.asTextEdits(sourceEdit.edits)
wsEdit.set(uri, edits)
}
let created;
let moved;
for (let fsEdit of change.fileSystemEdits) {
if (fsEdit.type == "createFile") {
let uri = vscode.Uri.parse(fsEdit.uri!)
wsEdit.createFile(uri)
created = uri
} else if (fsEdit.type == "moveFile") {
let src = vscode.Uri.parse(fsEdit.src!)
let dst = vscode.Uri.parse(fsEdit.dst!)
wsEdit.renameFile(src, dst)
moved = dst
} else {
console.error(`unknown op: ${JSON.stringify(fsEdit)}`)
}
}
let toOpen = created || moved
let toReveal = change.cursorPosition
await vscode.workspace.applyEdit(wsEdit)
if (toOpen) {
let doc = await vscode.workspace.openTextDocument(toOpen)
await vscode.window.showTextDocument(doc)
} else if (toReveal) {
let uri = client.protocol2CodeConverter.asUri(toReveal.textDocument.uri)
let position = client.protocol2CodeConverter.asPosition(toReveal.position)
let editor = vscode.window.activeTextEditor;
if (!editor || editor.document.uri != uri) return
if (!editor.selection.isEmpty) return
editor!.selection = new vscode.Selection(position, position)
}
}

View file

@ -0,0 +1,136 @@
use relative_path::RelativePathBuf;
use libsyntax2::{File, TextRange, TextUnit, AtomEdit};
use libeditor;
use {World, FileId, Query};
pub use libeditor::{
LocalEdit, StructureNode, LineIndex, FileSymbol,
Runnable, RunnableKind, HighlightedRange, CompletionItem
};
#[derive(Clone, Debug)]
pub struct Analysis {
pub(crate) imp: World
}
impl Analysis {
pub fn file_syntax(&self, file_id: FileId) -> File {
self.imp.file_syntax(file_id)
.unwrap()
}
pub fn file_line_index(&self, file_id: FileId) -> LineIndex {
self.imp.file_line_index(file_id)
.unwrap()
}
pub fn extend_selection(&self, file: &File, range: TextRange) -> TextRange {
libeditor::extend_selection(file, range).unwrap_or(range)
}
pub fn matching_brace(&self, file: &File, offset: TextUnit) -> Option<TextUnit> {
libeditor::matching_brace(file, offset)
}
pub fn syntax_tree(&self, file_id: FileId) -> String {
let file = self.file_syntax(file_id);
libeditor::syntax_tree(&file)
}
pub fn join_lines(&self, file_id: FileId, range: TextRange) -> SourceChange {
let file = self.file_syntax(file_id);
SourceChange::from_local_edit(
file_id, "join lines",
libeditor::join_lines(&file, range),
)
}
pub fn on_eq_typed(&self, file_id: FileId, offset: TextUnit) -> Option<SourceChange> {
let file = self.file_syntax(file_id);
Some(SourceChange::from_local_edit(
file_id, "add semicolon",
libeditor::on_eq_typed(&file, offset)?,
))
}
pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> {
let file = self.file_syntax(file_id);
libeditor::file_structure(&file)
}
pub fn symbol_search(&self, query: Query) -> Vec<(FileId, FileSymbol)> {
self.imp.world_symbols(query)
}
pub fn approximately_resolve_symbol(&self, file_id: FileId, offset: TextUnit) -> Vec<(FileId, FileSymbol)> {
self.imp.approximately_resolve_symbol(file_id, offset)
.unwrap()
}
pub fn parent_module(&self, file_id: FileId) -> Vec<(FileId, FileSymbol)> {
self.imp.parent_module(file_id)
}
pub fn runnables(&self, file_id: FileId) -> Vec<Runnable> {
let file = self.file_syntax(file_id);
libeditor::runnables(&file)
}
pub fn highlight(&self, file_id: FileId) -> Vec<HighlightedRange> {
let file = self.file_syntax(file_id);
libeditor::highlight(&file)
}
pub fn completions(&self, file_id: FileId, offset: TextUnit) -> Option<Vec<CompletionItem>> {
let file = self.file_syntax(file_id);
libeditor::scope_completion(&file, offset)
}
pub fn assists(&self, file_id: FileId, offset: TextUnit) -> Vec<SourceChange> {
self.imp.assists(file_id, offset)
}
pub fn diagnostics(&self, file_id: FileId) -> Vec<Diagnostic> {
self.imp.diagnostics(file_id)
}
}
#[derive(Debug)]
pub struct SourceChange {
pub label: String,
pub source_file_edits: Vec<SourceFileEdit>,
pub file_system_edits: Vec<FileSystemEdit>,
pub cursor_position: Option<Position>,
}
#[derive(Debug)]
pub struct Position {
pub file_id: FileId,
pub offset: TextUnit,
}
#[derive(Debug)]
pub struct SourceFileEdit {
pub file_id: FileId,
pub edits: Vec<AtomEdit>,
}
#[derive(Debug)]
pub enum FileSystemEdit {
CreateFile {
anchor: FileId,
path: RelativePathBuf,
},
MoveFile {
file: FileId,
path: RelativePathBuf,
}
}
#[derive(Debug)]
pub struct Diagnostic {
pub message: String,
pub range: TextRange,
pub fix: Option<SourceChange>,
}
impl SourceChange {
pub(crate) fn from_local_edit(file_id: FileId, label: &str, edit: LocalEdit) -> SourceChange {
let file_edit = SourceFileEdit {
file_id,
edits: edit.edit.into_atoms(),
};
SourceChange {
label: label.to_string(),
source_file_edits: vec![file_edit],
file_system_edits: vec![],
cursor_position: edit.cursor_position
.map(|offset| Position { offset, file_id })
}
}
}

View file

@ -12,6 +12,7 @@ extern crate relative_path;
mod symbol_index; mod symbol_index;
mod module_map; mod module_map;
mod api;
use std::{ use std::{
fmt, fmt,
@ -34,13 +35,14 @@ use libsyntax2::{
ast::{self, AstNode, NameOwner}, ast::{self, AstNode, NameOwner},
SyntaxKind::*, SyntaxKind::*,
}; };
use libeditor::{Diagnostic, LineIndex, FileSymbol, find_node_at_offset}; use libeditor::{LineIndex, FileSymbol, find_node_at_offset};
use self::{ use self::{
symbol_index::FileSymbols, symbol_index::FileSymbols,
module_map::{ModuleMap, ChangeKind, Problem}, module_map::{ModuleMap, ChangeKind, Problem},
}; };
pub use self::symbol_index::Query; pub use self::symbol_index::Query;
pub use self::api::*;
pub type Result<T> = ::std::result::Result<T, ::failure::Error>; pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
@ -97,6 +99,13 @@ impl WorldState {
} }
} }
pub fn analysis(
&self,
file_resolver: impl FileResolver,
) -> Analysis {
Analysis { imp: self.snapshot(file_resolver) }
}
pub fn change_file(&mut self, file_id: FileId, text: Option<String>) { pub fn change_file(&mut self, file_id: FileId, text: Option<String>) {
self.change_files(::std::iter::once((file_id, text))); self.change_files(::std::iter::once((file_id, text)));
} }
@ -231,11 +240,11 @@ impl World {
Ok(vec![]) Ok(vec![])
} }
pub fn diagnostics(&self, file_id: FileId) -> Result<Vec<(Diagnostic, Option<QuickFix>)>> { pub fn diagnostics(&self, file_id: FileId) -> Vec<Diagnostic> {
let syntax = self.file_syntax(file_id)?; let syntax = self.file_syntax(file_id).unwrap();
let mut res = libeditor::diagnostics(&syntax) let mut res = libeditor::diagnostics(&syntax)
.into_iter() .into_iter()
.map(|d| (d, None)) .map(|d| Diagnostic { range: d.range, message: d.msg, fix: None })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
self.data.module_map.problems( self.data.module_map.problems(
@ -243,44 +252,62 @@ impl World {
&*self.file_resolver, &*self.file_resolver,
&|file_id| self.file_syntax(file_id).unwrap(), &|file_id| self.file_syntax(file_id).unwrap(),
|name_node, problem| { |name_node, problem| {
let (diag, fix) = match problem { let diag = match problem {
Problem::UnresolvedModule { candidate } => { Problem::UnresolvedModule { candidate } => {
let diag = Diagnostic { let create_file = FileSystemEdit::CreateFile {
anchor: file_id,
path: candidate.clone(),
};
let fix = SourceChange {
label: "create module".to_string(),
source_file_edits: Vec::new(),
file_system_edits: vec![create_file],
cursor_position: None,
};
Diagnostic {
range: name_node.syntax().range(), range: name_node.syntax().range(),
msg: "unresolved module".to_string(), message: "unresolved module".to_string(),
}; fix: Some(fix),
let fix = QuickFix { }
fs_ops: vec![FsOp::CreateFile {
anchor: file_id,
path: candidate.clone(),
}]
};
(diag, fix)
} }
Problem::NotDirOwner { move_to, candidate } => { Problem::NotDirOwner { move_to, candidate } => {
let diag = Diagnostic { let move_file = FileSystemEdit::MoveFile { file: file_id, path: move_to.clone() };
let create_file = FileSystemEdit::CreateFile { anchor: file_id, path: move_to.join(candidate) };
let fix = SourceChange {
label: "move file and create module".to_string(),
source_file_edits: Vec::new(),
file_system_edits: vec![move_file, create_file],
cursor_position: None,
};
Diagnostic {
range: name_node.syntax().range(), range: name_node.syntax().range(),
msg: "can't declare module at this location".to_string(), message: "can't declare module at this location".to_string(),
}; fix: Some(fix),
let fix = QuickFix { }
fs_ops: vec![
FsOp::MoveFile {
file: file_id,
path: move_to.clone(),
},
FsOp::CreateFile {
anchor: file_id,
path: move_to.join(candidate),
}
],
};
(diag, fix)
} }
}; };
res.push((diag, Some(fix))) res.push(diag)
} }
); );
Ok(res) res
}
pub fn assists(&self, file_id: FileId, offset: TextUnit) -> Vec<SourceChange> {
let file = self.file_syntax(file_id).unwrap();
let actions = vec![
("flip comma", libeditor::flip_comma(&file, offset).map(|f| f())),
("add `#[derive]`", libeditor::add_derive(&file, offset).map(|f| f())),
("add impl", libeditor::add_impl(&file, offset).map(|f| f())),
];
let mut res = Vec::new();
for (name, local_edit) in actions {
if let Some(local_edit) = local_edit {
res.push(SourceChange::from_local_edit(
file_id, name, local_edit
))
}
}
res
} }
fn index_resolve(&self, name_ref: ast::NameRef) -> Vec<(FileId, FileSymbol)> { fn index_resolve(&self, name_ref: ast::NameRef) -> Vec<(FileId, FileSymbol)> {

View file

@ -13,13 +13,14 @@ use libsyntax2::{
use {EditBuilder, Edit, find_node_at_offset}; use {EditBuilder, Edit, find_node_at_offset};
// TODO: rename to FileEdit
#[derive(Debug)] #[derive(Debug)]
pub struct ActionResult { pub struct LocalEdit {
pub edit: Edit, pub edit: Edit,
pub cursor_position: Option<TextUnit>, pub cursor_position: Option<TextUnit>,
} }
pub fn flip_comma<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> ActionResult + 'a> { pub fn flip_comma<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> LocalEdit + 'a> {
let syntax = file.syntax(); let syntax = file.syntax();
let comma = find_leaf_at_offset(syntax, offset).find(|leaf| leaf.kind() == COMMA)?; let comma = find_leaf_at_offset(syntax, offset).find(|leaf| leaf.kind() == COMMA)?;
@ -29,14 +30,14 @@ pub fn flip_comma<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce()
let mut edit = EditBuilder::new(); let mut edit = EditBuilder::new();
edit.replace(left.range(), right.text().to_string()); edit.replace(left.range(), right.text().to_string());
edit.replace(right.range(), left.text().to_string()); edit.replace(right.range(), left.text().to_string());
ActionResult { LocalEdit {
edit: edit.finish(), edit: edit.finish(),
cursor_position: None, cursor_position: None,
} }
}) })
} }
pub fn add_derive<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> ActionResult + 'a> { pub fn add_derive<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> LocalEdit + 'a> {
let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?; let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?;
Some(move || { Some(move || {
let derive_attr = nominal let derive_attr = nominal
@ -56,14 +57,14 @@ pub fn add_derive<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce()
tt.syntax().range().end() - TextUnit::of_char(')') tt.syntax().range().end() - TextUnit::of_char(')')
} }
}; };
ActionResult { LocalEdit {
edit: edit.finish(), edit: edit.finish(),
cursor_position: Some(offset), cursor_position: Some(offset),
} }
}) })
} }
pub fn add_impl<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> ActionResult + 'a> { pub fn add_impl<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> LocalEdit + 'a> {
let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?; let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?;
let name = nominal.name()?; let name = nominal.name()?;
@ -90,7 +91,7 @@ pub fn add_impl<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() ->
let offset = start_offset + TextUnit::of_str(&buf); let offset = start_offset + TextUnit::of_str(&buf);
buf.push_str("\n}"); buf.push_str("\n}");
edit.insert(start_offset, buf); edit.insert(start_offset, buf);
ActionResult { LocalEdit {
edit: edit.finish(), edit: edit.finish(),
cursor_position: Some(offset), cursor_position: Some(offset),
} }

View file

@ -12,8 +12,7 @@ use {
}; };
#[derive(Debug)] #[derive(Debug)]
pub struct pub struct CompletionItem {
CompletionItem {
pub name: String, pub name: String,
pub snippet: Option<String> pub snippet: Option<String>
} }

View file

@ -30,11 +30,11 @@ pub use self::{
symbols::{StructureNode, file_structure, FileSymbol, file_symbols}, symbols::{StructureNode, file_structure, FileSymbol, file_symbols},
edit::{EditBuilder, Edit}, edit::{EditBuilder, Edit},
code_actions::{ code_actions::{
ActionResult, LocalEdit,
flip_comma, add_derive, add_impl, flip_comma, add_derive, add_impl,
}, },
typing::{join_lines, on_eq_typed}, typing::{join_lines, on_eq_typed},
completion::scope_completion, completion::{scope_completion, CompletionItem},
}; };
#[derive(Debug)] #[derive(Debug)]

View file

@ -1,8 +1,8 @@
use libsyntax2::{File, TextUnit}; use libsyntax2::{File, TextUnit};
pub use _test_utils::*; pub use _test_utils::*;
use ActionResult; use LocalEdit;
pub fn check_action<F: Fn(&File, TextUnit) -> Option<ActionResult>> ( pub fn check_action<F: Fn(&File, TextUnit) -> Option<LocalEdit>> (
before: &str, before: &str,
after: &str, after: &str,
f: F, f: F,

View file

@ -11,14 +11,14 @@ use libsyntax2::{
SyntaxKind::*, SyntaxKind::*,
}; };
use {ActionResult, EditBuilder, find_node_at_offset}; use {LocalEdit, EditBuilder, find_node_at_offset};
pub fn join_lines(file: &File, range: TextRange) -> ActionResult { pub fn join_lines(file: &File, range: TextRange) -> LocalEdit {
let range = if range.is_empty() { let range = if range.is_empty() {
let syntax = file.syntax(); let syntax = file.syntax();
let text = syntax.text().slice(range.start()..); let text = syntax.text().slice(range.start()..);
let pos = match text.find('\n') { let pos = match text.find('\n') {
None => return ActionResult { None => return LocalEdit {
edit: EditBuilder::new().finish(), edit: EditBuilder::new().finish(),
cursor_position: None cursor_position: None
}, },
@ -50,13 +50,13 @@ pub fn join_lines(file: &File, range: TextRange) -> ActionResult {
} }
eprintln!("{:?}", edit); eprintln!("{:?}", edit);
ActionResult { LocalEdit {
edit: edit.finish(), edit: edit.finish(),
cursor_position: None, cursor_position: None,
} }
} }
pub fn on_eq_typed(file: &File, offset: TextUnit) -> Option<ActionResult> { pub fn on_eq_typed(file: &File, offset: TextUnit) -> Option<LocalEdit> {
let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
if let_stmt.has_semi() { if let_stmt.has_semi() {
return None; return None;
@ -75,7 +75,7 @@ pub fn on_eq_typed(file: &File, offset: TextUnit) -> Option<ActionResult> {
let offset = let_stmt.syntax().range().end(); let offset = let_stmt.syntax().range().end();
let mut edit = EditBuilder::new(); let mut edit = EditBuilder::new();
edit.insert(offset, ";".to_string()); edit.insert(offset, ";".to_string());
Some(ActionResult { Some(LocalEdit {
edit: edit.finish(), edit: edit.finish(),
cursor_position: None, cursor_position: None,
}) })
@ -277,7 +277,41 @@ fn foo() {
}", r" }", r"
fn foo() { fn foo() {
join(type_params.type_params().filter_map(|it| it.name()).map(|it| it.text())) join(type_params.type_params().filter_map(|it| it.name()).map(|it| it.text()))
}") }");
do_check(r"
pub fn handle_find_matching_brace(
world: ServerWorld,
params: req::FindMatchingBraceParams,
) -> Result<Vec<Position>> {
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| <|>{
world.analysis().matching_brace(&file, offset).unwrap_or(offset)
}<|>)
.map_conv_with(&line_index)
.collect();
Ok(res)
}", r"
pub fn handle_find_matching_brace(
world: ServerWorld,
params: req::FindMatchingBraceParams,
) -> Result<Vec<Position>> {
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| world.analysis().matching_brace(&file, offset).unwrap_or(offset))
.map_conv_with(&line_index)
.collect();
Ok(res)
}");
} }
#[test] #[test]

View file

@ -1,14 +1,16 @@
use languageserver_types::{ use languageserver_types::{
Range, SymbolKind, Position, TextEdit, Location, Url, Range, SymbolKind, Position, TextEdit, Location, Url,
TextDocumentIdentifier, VersionedTextDocumentIdentifier, TextDocumentItem, TextDocumentIdentifier, VersionedTextDocumentIdentifier, TextDocumentItem,
TextDocumentPositionParams, TextDocumentEdit,
}; };
use libeditor::{LineIndex, LineCol, Edit, AtomEdit}; use libeditor::{LineIndex, LineCol, Edit, AtomEdit};
use libsyntax2::{SyntaxKind, TextUnit, TextRange}; use libsyntax2::{SyntaxKind, TextUnit, TextRange};
use libanalysis::FileId; use libanalysis::{FileId, SourceChange, SourceFileEdit, FileSystemEdit};
use { use {
Result, Result,
server_world::ServerWorld, server_world::ServerWorld,
req,
}; };
pub trait Conv { pub trait Conv {
@ -168,6 +170,82 @@ impl<'a> TryConvWith for &'a TextDocumentIdentifier {
} }
} }
impl<T: TryConvWith> TryConvWith for Vec<T> {
type Ctx = <T as TryConvWith>::Ctx;
type Output = Vec<<T as TryConvWith>::Output>;
fn try_conv_with(self, ctx: &Self::Ctx) -> Result<Self::Output> {
let mut res = Vec::with_capacity(self.len());
for item in self {
res.push(item.try_conv_with(ctx)?);
}
Ok(res)
}
}
impl TryConvWith for SourceChange {
type Ctx = ServerWorld;
type Output = req::SourceChange;
fn try_conv_with(self, world: &ServerWorld) -> Result<req::SourceChange> {
let cursor_position = match self.cursor_position {
None => None,
Some(pos) => {
let line_index = world.analysis().file_line_index(pos.file_id);
Some(TextDocumentPositionParams {
text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?),
position: pos.offset.conv_with(&line_index),
})
}
};
let source_file_edits = self.source_file_edits.try_conv_with(world)?;
let file_system_edits = self.file_system_edits.try_conv_with(world)?;
Ok(req::SourceChange {
label: self.label,
source_file_edits,
file_system_edits,
cursor_position,
})
}
}
impl TryConvWith for SourceFileEdit {
type Ctx = ServerWorld;
type Output = TextDocumentEdit;
fn try_conv_with(self, world: &ServerWorld) -> Result<TextDocumentEdit> {
let text_document = VersionedTextDocumentIdentifier {
uri: self.file_id.try_conv_with(world)?,
version: None,
};
let line_index = world.analysis().file_line_index(self.file_id);
let edits = self.edits
.into_iter()
.map_conv_with(&line_index)
.collect();
Ok(TextDocumentEdit { text_document, edits })
}
}
impl TryConvWith for FileSystemEdit {
type Ctx = ServerWorld;
type Output = req::FileSystemEdit;
fn try_conv_with(self, world: &ServerWorld) -> Result<req::FileSystemEdit> {
let res = match self {
FileSystemEdit::CreateFile { anchor, path } => {
let uri = world.file_id_to_uri(anchor)?;
let path = &path.as_str()[3..]; // strip `../` b/c url is weird
let uri = uri.join(path)?;
req::FileSystemEdit::CreateFile { uri }
},
FileSystemEdit::MoveFile { file, path } => {
let src = world.file_id_to_uri(file)?;
let path = &path.as_str()[3..]; // strip `../` b/c url is weird
let dst = src.join(path)?;
req::FileSystemEdit::MoveFile { src, dst }
},
};
Ok(res)
}
}
pub fn to_location( pub fn to_location(
file_id: FileId, file_id: FileId,
range: TextRange, range: TextRange,

View file

@ -35,7 +35,7 @@ use crossbeam_channel::bounded;
use flexi_logger::{Logger, Duplicate}; use flexi_logger::{Logger, Duplicate};
use ::{ use ::{
io::{Io, RawMsg, RawResponse, RawRequest, RawNotification}, io::{Io, RawMsg, RawResponse, RawNotification},
}; };
pub type Result<T> = ::std::result::Result<T, ::failure::Error>; pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
@ -109,7 +109,6 @@ fn initialize(io: &mut Io) -> Result<()> {
enum Task { enum Task {
Respond(RawResponse), Respond(RawResponse),
Request(RawRequest),
Notify(RawNotification), Notify(RawNotification),
Die(::failure::Error), Die(::failure::Error),
} }

View file

@ -2,16 +2,13 @@ use std::collections::HashMap;
use languageserver_types::{ use languageserver_types::{
Diagnostic, DiagnosticSeverity, Url, DocumentSymbol, Diagnostic, DiagnosticSeverity, Url, DocumentSymbol,
Command, TextDocumentIdentifier, WorkspaceEdit, Command, TextDocumentIdentifier,
SymbolInformation, Position, Location, TextEdit, SymbolInformation, Position, Location, TextEdit,
CompletionItem, InsertTextFormat, CompletionItemKind, CompletionItem, InsertTextFormat, CompletionItemKind,
}; };
use serde_json::{to_value, from_value}; use serde_json::to_value;
use url_serde; use libanalysis::{Query, FileId, RunnableKind};
use libanalysis::{self, Query, FileId};
use libeditor;
use libsyntax2::{ use libsyntax2::{
TextUnit,
text_utils::contains_offset_nonstrict, text_utils::contains_offset_nonstrict,
}; };
@ -26,8 +23,8 @@ pub fn handle_syntax_tree(
params: req::SyntaxTreeParams, params: req::SyntaxTreeParams,
) -> Result<String> { ) -> Result<String> {
let id = params.text_document.try_conv_with(&world)?; let id = params.text_document.try_conv_with(&world)?;
let file = world.analysis().file_syntax(id)?; let res = world.analysis().syntax_tree(id);
Ok(libeditor::syntax_tree(&file)) Ok(res)
} }
pub fn handle_extend_selection( pub fn handle_extend_selection(
@ -35,11 +32,11 @@ pub fn handle_extend_selection(
params: req::ExtendSelectionParams, params: req::ExtendSelectionParams,
) -> Result<req::ExtendSelectionResult> { ) -> Result<req::ExtendSelectionResult> {
let file_id = params.text_document.try_conv_with(&world)?; let file_id = params.text_document.try_conv_with(&world)?;
let file = world.analysis().file_syntax(file_id)?; let file = world.analysis().file_syntax(file_id);
let line_index = world.analysis().file_line_index(file_id)?; let line_index = world.analysis().file_line_index(file_id);
let selections = params.selections.into_iter() let selections = params.selections.into_iter()
.map_conv_with(&line_index) .map_conv_with(&line_index)
.map(|r| libeditor::extend_selection(&file, r).unwrap_or(r)) .map(|r| world.analysis().extend_selection(&file, r))
.map_conv_with(&line_index) .map_conv_with(&line_index)
.collect(); .collect();
Ok(req::ExtendSelectionResult { selections }) Ok(req::ExtendSelectionResult { selections })
@ -50,13 +47,13 @@ pub fn handle_find_matching_brace(
params: req::FindMatchingBraceParams, params: req::FindMatchingBraceParams,
) -> Result<Vec<Position>> { ) -> Result<Vec<Position>> {
let file_id = params.text_document.try_conv_with(&world)?; let file_id = params.text_document.try_conv_with(&world)?;
let file = world.analysis().file_syntax(file_id)?; let file = world.analysis().file_syntax(file_id);
let line_index = world.analysis().file_line_index(file_id)?; let line_index = world.analysis().file_line_index(file_id);
let res = params.offsets let res = params.offsets
.into_iter() .into_iter()
.map_conv_with(&line_index) .map_conv_with(&line_index)
.map(|offset| { .map(|offset| {
libeditor::matching_brace(&file, offset).unwrap_or(offset) world.analysis().matching_brace(&file, offset).unwrap_or(offset)
}) })
.map_conv_with(&line_index) .map_conv_with(&line_index)
.collect(); .collect();
@ -66,13 +63,31 @@ pub fn handle_find_matching_brace(
pub fn handle_join_lines( pub fn handle_join_lines(
world: ServerWorld, world: ServerWorld,
params: req::JoinLinesParams, params: req::JoinLinesParams,
) -> Result<Vec<TextEdit>> { ) -> Result<req::SourceChange> {
let file_id = params.text_document.try_conv_with(&world)?; 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 line_index = world.analysis().file_line_index(file_id)?;
let range = params.range.conv_with(&line_index); let range = params.range.conv_with(&line_index);
let res = libeditor::join_lines(&file, range); world.analysis().join_lines(file_id, range)
Ok(res.edit.conv_with(&line_index)) .try_conv_with(&world)
}
pub fn handle_on_type_formatting(
world: ServerWorld,
params: req::DocumentOnTypeFormattingParams,
) -> Result<Option<Vec<TextEdit>>> {
if params.ch != "=" {
return Ok(None);
}
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 edits = match world.analysis().on_eq_typed(file_id, offset) {
None => return Ok(None),
Some(mut action) => action.source_file_edits.pop().unwrap().edits,
};
let edits = edits.into_iter().map_conv_with(&line_index).collect();
Ok(Some(edits))
} }
pub fn handle_document_symbol( pub fn handle_document_symbol(
@ -80,12 +95,11 @@ pub fn handle_document_symbol(
params: req::DocumentSymbolParams, params: req::DocumentSymbolParams,
) -> Result<Option<req::DocumentSymbolResponse>> { ) -> Result<Option<req::DocumentSymbolResponse>> {
let file_id = params.text_document.try_conv_with(&world)?; 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 line_index = world.analysis().file_line_index(file_id)?;
let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new(); let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
for symbol in libeditor::file_structure(&file) { for symbol in world.analysis().file_structure(file_id) {
let doc_symbol = DocumentSymbol { let doc_symbol = DocumentSymbol {
name: symbol.label, name: symbol.label,
detail: Some("".to_string()), detail: Some("".to_string()),
@ -114,130 +128,6 @@ pub fn handle_document_symbol(
Ok(Some(req::DocumentSymbolResponse::Nested(res))) Ok(Some(req::DocumentSymbolResponse::Nested(res)))
} }
pub fn handle_code_action(
world: ServerWorld,
params: req::CodeActionParams,
) -> Result<Option<Vec<Command>>> {
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 res = Vec::new();
let actions = &[
(ActionId::FlipComma, libeditor::flip_comma(&file, offset).is_some()),
(ActionId::AddDerive, libeditor::add_derive(&file, offset).is_some()),
(ActionId::AddImpl, libeditor::add_impl(&file, offset).is_some()),
];
for (id, edit) in actions {
if *edit {
let cmd = apply_code_action_cmd(*id, params.text_document.clone(), offset);
res.push(cmd);
}
}
for (diag, quick_fix) in world.analysis().diagnostics(file_id)? {
let quick_fix = match quick_fix {
Some(quick_fix) => quick_fix,
None => continue,
};
if !contains_offset_nonstrict(diag.range, offset) {
continue;
}
let mut ops = Vec::new();
for op in quick_fix.fs_ops {
let op = match op {
libanalysis::FsOp::CreateFile { anchor, path } => {
let uri = world.file_id_to_uri(anchor)?;
let path = &path.as_str()[3..]; // strip `../` b/c url is weird
let uri = uri.join(path)?;
FsOp::CreateFile { uri }
},
libanalysis::FsOp::MoveFile { file, path } => {
let src = world.file_id_to_uri(file)?;
let path = &path.as_str()[3..]; // strip `../` b/c url is weird
let dst = src.join(path)?;
FsOp::MoveFile { src, dst }
},
};
ops.push(op)
}
let cmd = Command {
title: "Create module".to_string(),
command: "libsyntax-rust.fsEdit".to_string(),
arguments: Some(vec![to_value(ops).unwrap()]),
};
res.push(cmd)
}
return Ok(Some(res));
}
#[derive(Serialize)]
#[serde(tag = "type", rename_all = "camelCase")]
enum FsOp {
CreateFile {
#[serde(with = "url_serde")]
uri: Url
},
MoveFile {
#[serde(with = "url_serde")]
src: Url,
#[serde(with = "url_serde")]
dst: Url,
}
}
pub fn handle_runnables(
world: ServerWorld,
params: req::RunnablesParams,
) -> Result<Vec<req::Runnable>> {
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.position.map(|it| it.conv_with(&line_index));
let mut res = Vec::new();
for runnable in libeditor::runnables(&file) {
if let Some(offset) = offset {
if !contains_offset_nonstrict(runnable.range, offset) {
continue;
}
}
let r = req::Runnable {
range: runnable.range.conv_with(&line_index),
label: match &runnable.kind {
libeditor::RunnableKind::Test { name } =>
format!("test {}", name),
libeditor::RunnableKind::Bin =>
"run binary".to_string(),
},
bin: "cargo".to_string(),
args: match runnable.kind {
libeditor::RunnableKind::Test { name } => {
vec![
"test".to_string(),
"--".to_string(),
name,
"--nocapture".to_string(),
]
}
libeditor::RunnableKind::Bin => vec!["run".to_string()]
},
env: {
let mut m = HashMap::new();
m.insert(
"RUST_BACKTRACE".to_string(),
"short".to_string(),
);
m
}
};
res.push(r);
}
return Ok(res);
}
pub fn handle_workspace_symbol( pub fn handle_workspace_symbol(
world: ServerWorld, world: ServerWorld,
params: req::WorkspaceSymbolParams, params: req::WorkspaceSymbolParams,
@ -265,8 +155,8 @@ pub fn handle_workspace_symbol(
fn exec_query(world: &ServerWorld, query: Query) -> Result<Vec<SymbolInformation>> { fn exec_query(world: &ServerWorld, query: Query) -> Result<Vec<SymbolInformation>> {
let mut res = Vec::new(); let mut res = Vec::new();
for (file_id, symbol) in world.analysis().world_symbols(query) { for (file_id, symbol) in world.analysis().symbol_search(query) {
let line_index = world.analysis().file_line_index(file_id)?; let line_index = world.analysis().file_line_index(file_id);
let info = SymbolInformation { let info = SymbolInformation {
name: symbol.name.to_string(), name: symbol.name.to_string(),
kind: symbol.kind.conv(), kind: symbol.kind.conv(),
@ -287,11 +177,11 @@ pub fn handle_goto_definition(
params: req::TextDocumentPositionParams, params: req::TextDocumentPositionParams,
) -> Result<Option<req::GotoDefinitionResponse>> { ) -> Result<Option<req::GotoDefinitionResponse>> {
let file_id = params.text_document.try_conv_with(&world)?; let file_id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(file_id)?; let line_index = world.analysis().file_line_index(file_id);
let offset = params.position.conv_with(&line_index); let offset = params.position.conv_with(&line_index);
let mut res = Vec::new(); let mut res = Vec::new();
for (file_id, symbol) in world.analysis().approximately_resolve_symbol(file_id, offset)? { for (file_id, symbol) in world.analysis().approximately_resolve_symbol(file_id, offset) {
let line_index = world.analysis().file_line_index(file_id)?; let line_index = world.analysis().file_line_index(file_id);
let location = to_location( let location = to_location(
file_id, symbol.node_range, file_id, symbol.node_range,
&world, &line_index, &world, &line_index,
@ -308,7 +198,7 @@ pub fn handle_parent_module(
let file_id = params.try_conv_with(&world)?; let file_id = params.try_conv_with(&world)?;
let mut res = Vec::new(); let mut res = Vec::new();
for (file_id, symbol) in world.analysis().parent_module(file_id) { for (file_id, symbol) in world.analysis().parent_module(file_id) {
let line_index = world.analysis().file_line_index(file_id)?; let line_index = world.analysis().file_line_index(file_id);
let location = to_location( let location = to_location(
file_id, symbol.node_range, file_id, symbol.node_range,
&world, &line_index &world, &line_index
@ -318,15 +208,71 @@ pub fn handle_parent_module(
Ok(res) Ok(res)
} }
pub fn handle_runnables(
world: ServerWorld,
params: req::RunnablesParams,
) -> Result<Vec<req::Runnable>> {
let file_id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(file_id);
let offset = params.position.map(|it| it.conv_with(&line_index));
let mut res = Vec::new();
for runnable in world.analysis().runnables(file_id) {
if let Some(offset) = offset {
if !contains_offset_nonstrict(runnable.range, offset) {
continue;
}
}
let r = req::Runnable {
range: runnable.range.conv_with(&line_index),
label: match &runnable.kind {
RunnableKind::Test { name } =>
format!("test {}", name),
RunnableKind::Bin =>
"run binary".to_string(),
},
bin: "cargo".to_string(),
args: match runnable.kind {
RunnableKind::Test { name } => {
vec![
"test".to_string(),
"--".to_string(),
name,
"--nocapture".to_string(),
]
}
RunnableKind::Bin => vec!["run".to_string()]
},
env: {
let mut m = HashMap::new();
m.insert(
"RUST_BACKTRACE".to_string(),
"short".to_string(),
);
m
}
};
res.push(r);
}
return Ok(res);
}
pub fn handle_decorations(
world: ServerWorld,
params: TextDocumentIdentifier,
) -> Result<Vec<Decoration>> {
let file_id = params.try_conv_with(&world)?;
Ok(highlight(&world, file_id))
}
pub fn handle_completion( pub fn handle_completion(
world: ServerWorld, world: ServerWorld,
params: req::CompletionParams, params: req::CompletionParams,
) -> Result<Option<req::CompletionResponse>> { ) -> Result<Option<req::CompletionResponse>> {
let file_id = params.text_document.try_conv_with(&world)?; 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 line_index = world.analysis().file_line_index(file_id)?;
let offset = params.position.conv_with(&line_index); let offset = params.position.conv_with(&line_index);
let items = match libeditor::scope_completion(&file, offset) { let items = match world.analysis().completions(file_id, offset) {
None => return Ok(None), None => return Ok(None),
Some(items) => items, Some(items) => items,
}; };
@ -348,91 +294,33 @@ pub fn handle_completion(
Ok(Some(req::CompletionResponse::Array(items))) Ok(Some(req::CompletionResponse::Array(items)))
} }
pub fn handle_on_type_formatting( pub fn handle_code_action(
world: ServerWorld, world: ServerWorld,
params: req::DocumentOnTypeFormattingParams, params: req::CodeActionParams,
) -> Result<Option<Vec<TextEdit>>> { ) -> Result<Option<Vec<Command>>> {
if params.ch != "=" {
return Ok(None);
}
let file_id = params.text_document.try_conv_with(&world)?; let file_id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(file_id)?; let line_index = world.analysis().file_line_index(file_id);
let offset = params.position.conv_with(&line_index); let offset = params.range.conv_with(&line_index).start();
let file = world.analysis().file_syntax(file_id)?;
let action = match libeditor::on_eq_typed(&file, offset) {
None => return Ok(None),
Some(action) => action,
};
Ok(Some(action.edit.conv_with(&line_index)))
}
pub fn handle_execute_command( let assists = world.analysis().assists(file_id, offset).into_iter();
world: ServerWorld, let fixes = world.analysis().diagnostics(file_id).into_iter()
mut params: req::ExecuteCommandParams, .filter_map(|d| Some((d.range, d.fix?)))
) -> Result<(req::ApplyWorkspaceEditParams, Option<Position>)> { .filter(|(range, _fix)| contains_offset_nonstrict(*range, offset))
if params.command.as_str() != "apply_code_action" { .map(|(_range, fix)| fix);
bail!("unknown cmd: {:?}", params.command);
let mut res = Vec::new();
for source_edit in assists.chain(fixes) {
let title = source_edit.label.clone();
let edit = source_edit.try_conv_with(&world)?;
let cmd = Command {
title,
command: "libsyntax-rust.applySourceChange".to_string(),
arguments: Some(vec![to_value(edit).unwrap()]),
};
res.push(cmd);
} }
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()),
ActionId::AddImpl => libeditor::add_impl(&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)] Ok(Some(res))
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,
AddImpl,
}
impl ActionId {
fn title(&self) -> &'static str {
match *self {
ActionId::FlipComma => "Flip `,`",
ActionId::AddDerive => "Add `#[derive]`",
ActionId::AddImpl => "Add impl",
}
}
} }
pub fn publish_diagnostics( pub fn publish_diagnostics(
@ -440,28 +328,20 @@ pub fn publish_diagnostics(
uri: Url uri: Url
) -> Result<req::PublishDiagnosticsParams> { ) -> Result<req::PublishDiagnosticsParams> {
let file_id = world.uri_to_file_id(&uri)?; let file_id = world.uri_to_file_id(&uri)?;
let line_index = world.analysis().file_line_index(file_id)?; let line_index = world.analysis().file_line_index(file_id);
let diagnostics = world.analysis().diagnostics(file_id)? let diagnostics = world.analysis().diagnostics(file_id)
.into_iter() .into_iter()
.map(|(d, _quick_fix)| Diagnostic { .map(|d| Diagnostic {
range: d.range.conv_with(&line_index), range: d.range.conv_with(&line_index),
severity: Some(DiagnosticSeverity::Error), severity: Some(DiagnosticSeverity::Error),
code: None, code: None,
source: Some("libsyntax2".to_string()), source: Some("libsyntax2".to_string()),
message: d.msg, message: d.message,
related_information: None, related_information: None,
}).collect(); }).collect();
Ok(req::PublishDiagnosticsParams { uri, diagnostics }) Ok(req::PublishDiagnosticsParams { uri, diagnostics })
} }
pub fn handle_decorations(
world: ServerWorld,
params: TextDocumentIdentifier,
) -> Result<Vec<Decoration>> {
let file_id = params.try_conv_with(&world)?;
highlight(&world, file_id)
}
pub fn publish_decorations( pub fn publish_decorations(
world: ServerWorld, world: ServerWorld,
uri: Url uri: Url
@ -469,18 +349,16 @@ pub fn publish_decorations(
let file_id = world.uri_to_file_id(&uri)?; let file_id = world.uri_to_file_id(&uri)?;
Ok(req::PublishDecorationsParams { Ok(req::PublishDecorationsParams {
uri, uri,
decorations: highlight(&world, file_id)? decorations: highlight(&world, file_id),
}) })
} }
fn highlight(world: &ServerWorld, file_id: FileId) -> Result<Vec<Decoration>> { fn highlight(world: &ServerWorld, file_id: FileId) -> Vec<Decoration> {
let file = world.analysis().file_syntax(file_id)?; let line_index = world.analysis().file_line_index(file_id);
let line_index = world.analysis().file_line_index(file_id)?; world.analysis().highlight(file_id)
let res = libeditor::highlight(&file)
.into_iter() .into_iter()
.map(|h| Decoration { .map(|h| Decoration {
range: h.range.conv_with(&line_index), range: h.range.conv_with(&line_index),
tag: h.tag, tag: h.tag,
}).collect(); }).collect()
Ok(res)
} }

View file

@ -7,7 +7,6 @@ use std::{
use threadpool::ThreadPool; use threadpool::ThreadPool;
use crossbeam_channel::{Sender, Receiver}; use crossbeam_channel::{Sender, Receiver};
use languageserver_types::Url; use languageserver_types::Url;
use serde_json::to_value;
use { use {
req, dispatch, req, dispatch,
@ -15,24 +14,6 @@ use {
io::{Io, RawMsg, RawRequest, RawNotification}, io::{Io, RawMsg, RawRequest, RawNotification},
vfs::FileEvent, vfs::FileEvent,
server_world::{ServerWorldState, ServerWorld}, server_world::{ServerWorldState, ServerWorld},
main_loop::handlers::{
handle_syntax_tree,
handle_extend_selection,
publish_diagnostics,
publish_decorations,
handle_document_symbol,
handle_code_action,
handle_execute_command,
handle_workspace_symbol,
handle_goto_definition,
handle_find_matching_brace,
handle_parent_module,
handle_join_lines,
handle_completion,
handle_runnables,
handle_decorations,
handle_on_type_formatting,
},
}; };
pub(super) fn main_loop( pub(super) fn main_loop(
@ -45,7 +26,6 @@ pub(super) fn main_loop(
info!("server initialized, serving requests"); info!("server initialized, serving requests");
let mut state = ServerWorldState::new(); let mut state = ServerWorldState::new();
let mut next_request_id = 0;
let mut pending_requests: HashSet<u64> = HashSet::new(); let mut pending_requests: HashSet<u64> = HashSet::new();
let mut fs_events_receiver = Some(&fs_events_receiver); let mut fs_events_receiver = Some(&fs_events_receiver);
loop { loop {
@ -78,12 +58,6 @@ pub(super) fn main_loop(
} }
Event::Task(task) => { Event::Task(task) => {
match task { match task {
Task::Request(mut request) => {
request.id = next_request_id;
pending_requests.insert(next_request_id);
next_request_id += 1;
io.send(RawMsg::Request(request));
}
Task::Respond(response) => Task::Respond(response) =>
io.send(RawMsg::Response(response)), io.send(RawMsg::Response(response)),
Task::Notify(n) => Task::Notify(n) =>
@ -125,79 +99,26 @@ fn on_request(
sender: &Sender<Task>, sender: &Sender<Task>,
req: RawRequest, req: RawRequest,
) -> Result<bool> { ) -> Result<bool> {
let mut req = Some(req); let mut pool_dispatcher = PoolDispatcher {
handle_request_on_threadpool::<req::SyntaxTree>( req: Some(req),
&mut req, pool, world, sender, handle_syntax_tree, pool, world, sender
)?; };
handle_request_on_threadpool::<req::ExtendSelection>( pool_dispatcher
&mut req, pool, world, sender, handle_extend_selection, .on::<req::SyntaxTree>(handlers::handle_syntax_tree)?
)?; .on::<req::ExtendSelection>(handlers::handle_extend_selection)?
handle_request_on_threadpool::<req::FindMatchingBrace>( .on::<req::FindMatchingBrace>(handlers::handle_find_matching_brace)?
&mut req, pool, world, sender, handle_find_matching_brace, .on::<req::JoinLines>(handlers::handle_join_lines)?
)?; .on::<req::OnTypeFormatting>(handlers::handle_on_type_formatting)?
handle_request_on_threadpool::<req::DocumentSymbolRequest>( .on::<req::DocumentSymbolRequest>(handlers::handle_document_symbol)?
&mut req, pool, world, sender, handle_document_symbol, .on::<req::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
)?; .on::<req::GotoDefinition>(handlers::handle_goto_definition)?
handle_request_on_threadpool::<req::CodeActionRequest>( .on::<req::ParentModule>(handlers::handle_parent_module)?
&mut req, pool, world, sender, handle_code_action, .on::<req::Runnables>(handlers::handle_runnables)?
)?; .on::<req::DecorationsRequest>(handlers::handle_decorations)?
handle_request_on_threadpool::<req::Runnables>( .on::<req::Completion>(handlers::handle_completion)?
&mut req, pool, world, sender, handle_runnables, .on::<req::CodeActionRequest>(handlers::handle_code_action)?;
)?;
handle_request_on_threadpool::<req::WorkspaceSymbol>(
&mut req, pool, world, sender, handle_workspace_symbol,
)?;
handle_request_on_threadpool::<req::GotoDefinition>(
&mut req, pool, world, sender, handle_goto_definition,
)?;
handle_request_on_threadpool::<req::Completion>(
&mut req, pool, world, sender, handle_completion,
)?;
handle_request_on_threadpool::<req::ParentModule>(
&mut req, pool, world, sender, handle_parent_module,
)?;
handle_request_on_threadpool::<req::JoinLines>(
&mut req, pool, world, sender, handle_join_lines,
)?;
handle_request_on_threadpool::<req::DecorationsRequest>(
&mut req, pool, world, sender, handle_decorations,
)?;
handle_request_on_threadpool::<req::OnTypeFormatting>(
&mut req, pool, world, sender, handle_on_type_formatting,
)?;
dispatch::handle_request::<req::ExecuteCommand, _>(&mut req, |params, resp| {
io.send(RawMsg::Response(resp.into_response(Ok(None))?));
let world = world.snapshot();
let sender = sender.clone();
pool.execute(move || {
let (edit, cursor) = match handle_execute_command(world, params) {
Ok(res) => res,
Err(e) => return sender.send(Task::Die(e)),
};
match to_value(edit) {
Err(e) => return sender.send(Task::Die(e.into())),
Ok(params) => {
let request = RawRequest {
id: 0,
method: <req::ApplyWorkspaceEdit as req::ClientRequest>::METHOD.to_string(),
params,
};
sender.send(Task::Request(request))
}
}
if let Some(cursor) = cursor {
let request = RawRequest {
id: 0,
method: <req::MoveCursor as req::ClientRequest>::METHOD.to_string(),
params: to_value(cursor).unwrap(),
};
sender.send(Task::Request(request))
}
});
Ok(())
})?;
let mut req = pool_dispatcher.req;
let mut shutdown = false; let mut shutdown = false;
dispatch::handle_request::<req::Shutdown, _>(&mut req, |(), resp| { dispatch::handle_request::<req::Shutdown, _>(&mut req, |(), resp| {
let resp = resp.into_response(Ok(()))?; let resp = resp.into_response(Ok(()))?;
@ -273,27 +194,33 @@ fn on_notification(
Ok(()) Ok(())
} }
fn handle_request_on_threadpool<R: req::ClientRequest>( struct PoolDispatcher<'a> {
req: &mut Option<RawRequest>, req: Option<RawRequest>,
pool: &ThreadPool, pool: &'a ThreadPool,
world: &ServerWorldState, world: &'a ServerWorldState,
sender: &Sender<Task>, sender: &'a Sender<Task>,
f: fn(ServerWorld, R::Params) -> Result<R::Result>, }
) -> Result<()>
{ impl<'a> PoolDispatcher<'a> {
dispatch::handle_request::<R, _>(req, |params, resp| { fn on<'b, R: req::ClientRequest>(&'b mut self, f: fn(ServerWorld, R::Params) -> Result<R::Result>) -> Result<&'b mut Self> {
let world = world.snapshot(); let world = self.world;
let sender = sender.clone(); let sender = self.sender;
pool.execute(move || { let pool = self.pool;
let res = f(world, params); dispatch::handle_request::<R, _>(&mut self.req, |params, resp| {
let task = match resp.into_response(res) { let world = world.snapshot();
Ok(resp) => Task::Respond(resp), let sender = sender.clone();
Err(e) => Task::Die(e), pool.execute(move || {
}; let res = f(world, params);
sender.send(task); let task = match resp.into_response(res) {
}); Ok(resp) => Task::Respond(resp),
Ok(()) Err(e) => Task::Die(e),
}) };
sender.send(task);
});
Ok(())
})?;
Ok(self)
}
} }
fn update_file_notifications_on_threadpool( fn update_file_notifications_on_threadpool(
@ -303,7 +230,7 @@ fn update_file_notifications_on_threadpool(
uri: Url, uri: Url,
) { ) {
pool.execute(move || { pool.execute(move || {
match publish_diagnostics(world.clone(), uri.clone()) { match handlers::publish_diagnostics(world.clone(), uri.clone()) {
Err(e) => { Err(e) => {
error!("failed to compute diagnostics: {:?}", e) error!("failed to compute diagnostics: {:?}", e)
} }
@ -312,7 +239,7 @@ fn update_file_notifications_on_threadpool(
sender.send(Task::Notify(not)); sender.send(Task::Notify(not));
} }
} }
match publish_decorations(world, uri) { match handlers::publish_decorations(world, uri) {
Err(e) => { Err(e) => {
error!("failed to compute decorations: {:?}", e) error!("failed to compute decorations: {:?}", e)
} }

View file

@ -15,6 +15,7 @@ pub use languageserver_types::{
TextEdit, TextEdit,
CompletionParams, CompletionResponse, CompletionParams, CompletionResponse,
DocumentOnTypeFormattingParams, DocumentOnTypeFormattingParams,
TextDocumentEdit,
}; };
@ -115,14 +116,6 @@ pub struct Decoration {
pub tag: &'static str pub tag: &'static str
} }
pub enum MoveCursor {}
impl Request for MoveCursor {
type Params = Position;
type Result = ();
const METHOD: &'static str = "m/moveCursor";
}
pub enum ParentModule {} pub enum ParentModule {}
impl Request for ParentModule { impl Request for ParentModule {
@ -135,7 +128,7 @@ pub enum JoinLines {}
impl Request for JoinLines { impl Request for JoinLines {
type Params = JoinLinesParams; type Params = JoinLinesParams;
type Result = Vec<TextEdit>; type Result = SourceChange;
const METHOD: &'static str = "m/joinLines"; const METHOD: &'static str = "m/joinLines";
} }
@ -170,3 +163,27 @@ pub struct Runnable {
pub args: Vec<String>, pub args: Vec<String>,
pub env: HashMap<String, String>, pub env: HashMap<String, String>,
} }
#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SourceChange {
pub label: String,
pub source_file_edits: Vec<TextDocumentEdit>,
pub file_system_edits: Vec<FileSystemEdit>,
pub cursor_position: Option<TextDocumentPositionParams>,
}
#[derive(Serialize, Debug)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum FileSystemEdit {
CreateFile {
#[serde(with = "url_serde")]
uri: Url
},
MoveFile {
#[serde(with = "url_serde")]
src: Url,
#[serde(with = "url_serde")]
dst: Url,
}
}

View file

@ -5,7 +5,7 @@ use std::{
}; };
use languageserver_types::Url; use languageserver_types::Url;
use libanalysis::{FileId, WorldState, World}; use libanalysis::{FileId, WorldState, Analysis};
use { use {
Result, Result,
@ -22,7 +22,7 @@ pub struct ServerWorldState {
#[derive(Clone)] #[derive(Clone)]
pub struct ServerWorld { pub struct ServerWorld {
pub analysis: World, pub analysis: Analysis,
pub path_map: PathMap, pub path_map: PathMap,
} }
@ -91,14 +91,14 @@ impl ServerWorldState {
pub fn snapshot(&self) -> ServerWorld { pub fn snapshot(&self) -> ServerWorld {
ServerWorld { ServerWorld {
analysis: self.analysis.snapshot(self.path_map.clone()), analysis: self.analysis.analysis(self.path_map.clone()),
path_map: self.path_map.clone() path_map: self.path_map.clone()
} }
} }
} }
impl ServerWorld { impl ServerWorld {
pub fn analysis(&self) -> &World { pub fn analysis(&self) -> &Analysis {
&self.analysis &self.analysis
} }