mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
Grand refactoring
This commit is contained in:
parent
2007ccfcfe
commit
8abf536343
14 changed files with 591 additions and 484 deletions
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
136
crates/libanalysis/src/api.rs
Normal file
136
crates/libanalysis/src/api.rs
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)> {
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue