aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2018-08-29 16:03:14 +0100
committerAleksey Kladov <[email protected]>2018-08-29 16:03:14 +0100
commit8abf5363433e977c5393bb569e2a5d559cb0a602 (patch)
tree8bb7bc3097cb9e22af9e3be8605cb4745c2fae5f
parent2007ccfcfe0bf01c934589dd3c87fda83b06b272 (diff)
Grand refactoring
-rw-r--r--code/src/extension.ts91
-rw-r--r--crates/libanalysis/src/api.rs136
-rw-r--r--crates/libanalysis/src/lib.rs91
-rw-r--r--crates/libeditor/src/code_actions.rs15
-rw-r--r--crates/libeditor/src/completion.rs3
-rw-r--r--crates/libeditor/src/lib.rs4
-rw-r--r--crates/libeditor/src/test_utils.rs4
-rw-r--r--crates/libeditor/src/typing.rs48
-rw-r--r--crates/server/src/conv.rs80
-rw-r--r--crates/server/src/main.rs3
-rw-r--r--crates/server/src/main_loop/handlers.rs384
-rw-r--r--crates/server/src/main_loop/mod.rs169
-rw-r--r--crates/server/src/req.rs35
-rw-r--r--crates/server/src/server_world.rs8
14 files changed, 589 insertions, 482 deletions
diff --git a/code/src/extension.ts b/code/src/extension.ts
index 3b24b73b6..f2589ef2f 100644
--- a/code/src/extension.ts
+++ b/code/src/extension.ts
@@ -60,11 +60,8 @@ export function activate(context: vscode.ExtensionContext) {
60 textDocument: { uri: editor.document.uri.toString() }, 60 textDocument: { uri: editor.document.uri.toString() },
61 range: client.code2ProtocolConverter.asRange(editor.selection), 61 range: client.code2ProtocolConverter.asRange(editor.selection),
62 } 62 }
63 let response = await client.sendRequest<lc.TextEdit[]>("m/joinLines", request) 63 let change = await client.sendRequest<SourceChange>("m/joinLines", request)
64 let edits = client.protocol2CodeConverter.asTextEdits(response) 64 await applySourceChange(change)
65 let wsEdit = new vscode.WorkspaceEdit()
66 wsEdit.set(editor.document.uri, edits)
67 return vscode.workspace.applyEdit(wsEdit)
68 }) 65 })
69 registerCommand('libsyntax-rust.parentModule', async () => { 66 registerCommand('libsyntax-rust.parentModule', async () => {
70 let editor = vscode.window.activeTextEditor 67 let editor = vscode.window.activeTextEditor
@@ -113,28 +110,7 @@ export function activate(context: vscode.ExtensionContext) {
113 return await vscode.tasks.executeTask(task) 110 return await vscode.tasks.executeTask(task)
114 } 111 }
115 }) 112 })
116 registerCommand('libsyntax-rust.fsEdit', async (ops: FsOp[]) => { 113 registerCommand('libsyntax-rust.applySourceChange', applySourceChange)
117 let edit = new vscode.WorkspaceEdit()
118 let created;
119 let moved;
120 for (let op of ops) {
121 if (op.type == "createFile") {
122 let uri = vscode.Uri.parse(op.uri!)
123 edit.createFile(uri)
124 created = uri
125 } else if (op.type == "moveFile") {
126 let src = vscode.Uri.parse(op.src!)
127 let dst = vscode.Uri.parse(op.dst!)
128 edit.renameFile(src, dst)
129 moved = dst
130 } else {
131 console.error(`unknown op: ${JSON.stringify(op)}`)
132 }
133 }
134 await vscode.workspace.applyEdit(edit)
135 let doc = await vscode.workspace.openTextDocument((created || moved)!)
136 await vscode.window.showTextDocument(doc)
137 })
138 114
139 dispose(vscode.workspace.registerTextDocumentContentProvider( 115 dispose(vscode.workspace.registerTextDocumentContentProvider(
140 'libsyntax-rust', 116 'libsyntax-rust',
@@ -207,18 +183,6 @@ function startServer() {
207 ) 183 )
208 } 184 }
209 ) 185 )
210 client.onRequest(
211 new lc.RequestType<lc.Position, void, any, any>("m/moveCursor"),
212 (params: lc.Position, token: lc.CancellationToken) => {
213 let editor = vscode.window.activeTextEditor;
214 if (!editor) return
215 if (!editor.selection.isEmpty) return
216 let position = client.protocol2CodeConverter.asPosition(params)
217 afterLs(() => {
218 editor!.selection = new vscode.Selection(position, position)
219 })
220 }
221 )
222 }) 186 })
223 client.start(); 187 client.start();
224} 188}
@@ -383,9 +347,56 @@ function createTask(spec: Runnable): vscode.Task {
383 return t; 347 return t;
384} 348}
385 349
386interface FsOp { 350interface FileSystemEdit {
387 type: string; 351 type: string;
388 uri?: string; 352 uri?: string;
389 src?: string; 353 src?: string;
390 dst?: string; 354 dst?: string;
391} 355}
356
357interface SourceChange {
358 label: string,
359 sourceFileEdits: lc.TextDocumentEdit[],
360 fileSystemEdits: FileSystemEdit[],
361 cursorPosition?: lc.TextDocumentPositionParams,
362}
363
364async function applySourceChange(change: SourceChange) {
365 console.log(`applySOurceChange ${JSON.stringify(change)}`)
366 let wsEdit = new vscode.WorkspaceEdit()
367 for (let sourceEdit of change.sourceFileEdits) {
368 let uri = client.protocol2CodeConverter.asUri(sourceEdit.textDocument.uri)
369 let edits = client.protocol2CodeConverter.asTextEdits(sourceEdit.edits)
370 wsEdit.set(uri, edits)
371 }
372 let created;
373 let moved;
374 for (let fsEdit of change.fileSystemEdits) {
375 if (fsEdit.type == "createFile") {
376 let uri = vscode.Uri.parse(fsEdit.uri!)
377 wsEdit.createFile(uri)
378 created = uri
379 } else if (fsEdit.type == "moveFile") {
380 let src = vscode.Uri.parse(fsEdit.src!)
381 let dst = vscode.Uri.parse(fsEdit.dst!)
382 wsEdit.renameFile(src, dst)
383 moved = dst
384 } else {
385 console.error(`unknown op: ${JSON.stringify(fsEdit)}`)
386 }
387 }
388 let toOpen = created || moved
389 let toReveal = change.cursorPosition
390 await vscode.workspace.applyEdit(wsEdit)
391 if (toOpen) {
392 let doc = await vscode.workspace.openTextDocument(toOpen)
393 await vscode.window.showTextDocument(doc)
394 } else if (toReveal) {
395 let uri = client.protocol2CodeConverter.asUri(toReveal.textDocument.uri)
396 let position = client.protocol2CodeConverter.asPosition(toReveal.position)
397 let editor = vscode.window.activeTextEditor;
398 if (!editor || editor.document.uri != uri) return
399 if (!editor.selection.isEmpty) return
400 editor!.selection = new vscode.Selection(position, position)
401 }
402}
diff --git a/crates/libanalysis/src/api.rs b/crates/libanalysis/src/api.rs
new file mode 100644
index 000000000..bb4fee398
--- /dev/null
+++ b/crates/libanalysis/src/api.rs
@@ -0,0 +1,136 @@
1use relative_path::RelativePathBuf;
2use libsyntax2::{File, TextRange, TextUnit, AtomEdit};
3use libeditor;
4use {World, FileId, Query};
5
6pub use libeditor::{
7 LocalEdit, StructureNode, LineIndex, FileSymbol,
8 Runnable, RunnableKind, HighlightedRange, CompletionItem
9};
10
11#[derive(Clone, Debug)]
12pub struct Analysis {
13 pub(crate) imp: World
14}
15
16impl Analysis {
17 pub fn file_syntax(&self, file_id: FileId) -> File {
18 self.imp.file_syntax(file_id)
19 .unwrap()
20 }
21 pub fn file_line_index(&self, file_id: FileId) -> LineIndex {
22 self.imp.file_line_index(file_id)
23 .unwrap()
24 }
25 pub fn extend_selection(&self, file: &File, range: TextRange) -> TextRange {
26 libeditor::extend_selection(file, range).unwrap_or(range)
27 }
28 pub fn matching_brace(&self, file: &File, offset: TextUnit) -> Option<TextUnit> {
29 libeditor::matching_brace(file, offset)
30 }
31 pub fn syntax_tree(&self, file_id: FileId) -> String {
32 let file = self.file_syntax(file_id);
33 libeditor::syntax_tree(&file)
34 }
35 pub fn join_lines(&self, file_id: FileId, range: TextRange) -> SourceChange {
36 let file = self.file_syntax(file_id);
37 SourceChange::from_local_edit(
38 file_id, "join lines",
39 libeditor::join_lines(&file, range),
40 )
41 }
42 pub fn on_eq_typed(&self, file_id: FileId, offset: TextUnit) -> Option<SourceChange> {
43 let file = self.file_syntax(file_id);
44 Some(SourceChange::from_local_edit(
45 file_id, "add semicolon",
46 libeditor::on_eq_typed(&file, offset)?,
47 ))
48 }
49 pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> {
50 let file = self.file_syntax(file_id);
51 libeditor::file_structure(&file)
52 }
53 pub fn symbol_search(&self, query: Query) -> Vec<(FileId, FileSymbol)> {
54 self.imp.world_symbols(query)
55 }
56 pub fn approximately_resolve_symbol(&self, file_id: FileId, offset: TextUnit) -> Vec<(FileId, FileSymbol)> {
57 self.imp.approximately_resolve_symbol(file_id, offset)
58 .unwrap()
59 }
60 pub fn parent_module(&self, file_id: FileId) -> Vec<(FileId, FileSymbol)> {
61 self.imp.parent_module(file_id)
62 }
63 pub fn runnables(&self, file_id: FileId) -> Vec<Runnable> {
64 let file = self.file_syntax(file_id);
65 libeditor::runnables(&file)
66 }
67 pub fn highlight(&self, file_id: FileId) -> Vec<HighlightedRange> {
68 let file = self.file_syntax(file_id);
69 libeditor::highlight(&file)
70 }
71 pub fn completions(&self, file_id: FileId, offset: TextUnit) -> Option<Vec<CompletionItem>> {
72 let file = self.file_syntax(file_id);
73 libeditor::scope_completion(&file, offset)
74 }
75 pub fn assists(&self, file_id: FileId, offset: TextUnit) -> Vec<SourceChange> {
76 self.imp.assists(file_id, offset)
77 }
78 pub fn diagnostics(&self, file_id: FileId) -> Vec<Diagnostic> {
79 self.imp.diagnostics(file_id)
80 }
81}
82
83#[derive(Debug)]
84pub struct SourceChange {
85 pub label: String,
86 pub source_file_edits: Vec<SourceFileEdit>,
87 pub file_system_edits: Vec<FileSystemEdit>,
88 pub cursor_position: Option<Position>,
89}
90
91#[derive(Debug)]
92pub struct Position {
93 pub file_id: FileId,
94 pub offset: TextUnit,
95}
96
97#[derive(Debug)]
98pub struct SourceFileEdit {
99 pub file_id: FileId,
100 pub edits: Vec<AtomEdit>,
101}
102
103#[derive(Debug)]
104pub enum FileSystemEdit {
105 CreateFile {
106 anchor: FileId,
107 path: RelativePathBuf,
108 },
109 MoveFile {
110 file: FileId,
111 path: RelativePathBuf,
112 }
113}
114
115#[derive(Debug)]
116pub struct Diagnostic {
117 pub message: String,
118 pub range: TextRange,
119 pub fix: Option<SourceChange>,
120}
121
122impl SourceChange {
123 pub(crate) fn from_local_edit(file_id: FileId, label: &str, edit: LocalEdit) -> SourceChange {
124 let file_edit = SourceFileEdit {
125 file_id,
126 edits: edit.edit.into_atoms(),
127 };
128 SourceChange {
129 label: label.to_string(),
130 source_file_edits: vec![file_edit],
131 file_system_edits: vec![],
132 cursor_position: edit.cursor_position
133 .map(|offset| Position { offset, file_id })
134 }
135 }
136}
diff --git a/crates/libanalysis/src/lib.rs b/crates/libanalysis/src/lib.rs
index 96d10a087..ec20d106f 100644
--- a/crates/libanalysis/src/lib.rs
+++ b/crates/libanalysis/src/lib.rs
@@ -12,6 +12,7 @@ extern crate relative_path;
12 12
13mod symbol_index; 13mod symbol_index;
14mod module_map; 14mod module_map;
15mod api;
15 16
16use std::{ 17use std::{
17 fmt, 18 fmt,
@@ -34,13 +35,14 @@ use libsyntax2::{
34 ast::{self, AstNode, NameOwner}, 35 ast::{self, AstNode, NameOwner},
35 SyntaxKind::*, 36 SyntaxKind::*,
36}; 37};
37use libeditor::{Diagnostic, LineIndex, FileSymbol, find_node_at_offset}; 38use libeditor::{LineIndex, FileSymbol, find_node_at_offset};
38 39
39use self::{ 40use self::{
40 symbol_index::FileSymbols, 41 symbol_index::FileSymbols,
41 module_map::{ModuleMap, ChangeKind, Problem}, 42 module_map::{ModuleMap, ChangeKind, Problem},
42}; 43};
43pub use self::symbol_index::Query; 44pub use self::symbol_index::Query;
45pub use self::api::*;
44 46
45pub type Result<T> = ::std::result::Result<T, ::failure::Error>; 47pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
46 48
@@ -97,6 +99,13 @@ impl WorldState {
97 } 99 }
98 } 100 }
99 101
102 pub fn analysis(
103 &self,
104 file_resolver: impl FileResolver,
105 ) -> Analysis {
106 Analysis { imp: self.snapshot(file_resolver) }
107 }
108
100 pub fn change_file(&mut self, file_id: FileId, text: Option<String>) { 109 pub fn change_file(&mut self, file_id: FileId, text: Option<String>) {
101 self.change_files(::std::iter::once((file_id, text))); 110 self.change_files(::std::iter::once((file_id, text)));
102 } 111 }
@@ -231,11 +240,11 @@ impl World {
231 Ok(vec![]) 240 Ok(vec![])
232 } 241 }
233 242
234 pub fn diagnostics(&self, file_id: FileId) -> Result<Vec<(Diagnostic, Option<QuickFix>)>> { 243 pub fn diagnostics(&self, file_id: FileId) -> Vec<Diagnostic> {
235 let syntax = self.file_syntax(file_id)?; 244 let syntax = self.file_syntax(file_id).unwrap();
236 let mut res = libeditor::diagnostics(&syntax) 245 let mut res = libeditor::diagnostics(&syntax)
237 .into_iter() 246 .into_iter()
238 .map(|d| (d, None)) 247 .map(|d| Diagnostic { range: d.range, message: d.msg, fix: None })
239 .collect::<Vec<_>>(); 248 .collect::<Vec<_>>();
240 249
241 self.data.module_map.problems( 250 self.data.module_map.problems(
@@ -243,44 +252,62 @@ impl World {
243 &*self.file_resolver, 252 &*self.file_resolver,
244 &|file_id| self.file_syntax(file_id).unwrap(), 253 &|file_id| self.file_syntax(file_id).unwrap(),
245 |name_node, problem| { 254 |name_node, problem| {
246 let (diag, fix) = match problem { 255 let diag = match problem {
247 Problem::UnresolvedModule { candidate } => { 256 Problem::UnresolvedModule { candidate } => {
248 let diag = Diagnostic { 257 let create_file = FileSystemEdit::CreateFile {
249 range: name_node.syntax().range(), 258 anchor: file_id,
250 msg: "unresolved module".to_string(), 259 path: candidate.clone(),
251 }; 260 };
252 let fix = QuickFix { 261 let fix = SourceChange {
253 fs_ops: vec![FsOp::CreateFile { 262 label: "create module".to_string(),
254 anchor: file_id, 263 source_file_edits: Vec::new(),
255 path: candidate.clone(), 264 file_system_edits: vec![create_file],
256 }] 265 cursor_position: None,
257 }; 266 };
258 (diag, fix) 267 Diagnostic {
268 range: name_node.syntax().range(),
269 message: "unresolved module".to_string(),
270 fix: Some(fix),
271 }
259 } 272 }
260 Problem::NotDirOwner { move_to, candidate } => { 273 Problem::NotDirOwner { move_to, candidate } => {
261 let diag = Diagnostic { 274 let move_file = FileSystemEdit::MoveFile { file: file_id, path: move_to.clone() };
262 range: name_node.syntax().range(), 275 let create_file = FileSystemEdit::CreateFile { anchor: file_id, path: move_to.join(candidate) };
263 msg: "can't declare module at this location".to_string(), 276 let fix = SourceChange {
277 label: "move file and create module".to_string(),
278 source_file_edits: Vec::new(),
279 file_system_edits: vec![move_file, create_file],
280 cursor_position: None,
264 }; 281 };
265 let fix = QuickFix { 282 Diagnostic {
266 fs_ops: vec![ 283 range: name_node.syntax().range(),
267 FsOp::MoveFile { 284 message: "can't declare module at this location".to_string(),
268 file: file_id, 285 fix: Some(fix),
269 path: move_to.clone(), 286 }
270 },
271 FsOp::CreateFile {
272 anchor: file_id,
273 path: move_to.join(candidate),
274 }
275 ],
276 };
277 (diag, fix)
278 } 287 }
279 }; 288 };
280 res.push((diag, Some(fix))) 289 res.push(diag)
281 } 290 }
282 ); 291 );
283 Ok(res) 292 res
293 }
294
295 pub fn assists(&self, file_id: FileId, offset: TextUnit) -> Vec<SourceChange> {
296 let file = self.file_syntax(file_id).unwrap();
297 let actions = vec![
298 ("flip comma", libeditor::flip_comma(&file, offset).map(|f| f())),
299 ("add `#[derive]`", libeditor::add_derive(&file, offset).map(|f| f())),
300 ("add impl", libeditor::add_impl(&file, offset).map(|f| f())),
301 ];
302 let mut res = Vec::new();
303 for (name, local_edit) in actions {
304 if let Some(local_edit) = local_edit {
305 res.push(SourceChange::from_local_edit(
306 file_id, name, local_edit
307 ))
308 }
309 }
310 res
284 } 311 }
285 312
286 fn index_resolve(&self, name_ref: ast::NameRef) -> Vec<(FileId, FileSymbol)> { 313 fn index_resolve(&self, name_ref: ast::NameRef) -> Vec<(FileId, FileSymbol)> {
diff --git a/crates/libeditor/src/code_actions.rs b/crates/libeditor/src/code_actions.rs
index 08a85f6e2..dadbd63ab 100644
--- a/crates/libeditor/src/code_actions.rs
+++ b/crates/libeditor/src/code_actions.rs
@@ -13,13 +13,14 @@ use libsyntax2::{
13 13
14use {EditBuilder, Edit, find_node_at_offset}; 14use {EditBuilder, Edit, find_node_at_offset};
15 15
16// TODO: rename to FileEdit
16#[derive(Debug)] 17#[derive(Debug)]
17pub struct ActionResult { 18pub struct LocalEdit {
18 pub edit: Edit, 19 pub edit: Edit,
19 pub cursor_position: Option<TextUnit>, 20 pub cursor_position: Option<TextUnit>,
20} 21}
21 22
22pub fn flip_comma<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> ActionResult + 'a> { 23pub fn flip_comma<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> LocalEdit + 'a> {
23 let syntax = file.syntax(); 24 let syntax = file.syntax();
24 25
25 let comma = find_leaf_at_offset(syntax, offset).find(|leaf| leaf.kind() == COMMA)?; 26 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()
29 let mut edit = EditBuilder::new(); 30 let mut edit = EditBuilder::new();
30 edit.replace(left.range(), right.text().to_string()); 31 edit.replace(left.range(), right.text().to_string());
31 edit.replace(right.range(), left.text().to_string()); 32 edit.replace(right.range(), left.text().to_string());
32 ActionResult { 33 LocalEdit {
33 edit: edit.finish(), 34 edit: edit.finish(),
34 cursor_position: None, 35 cursor_position: None,
35 } 36 }
36 }) 37 })
37} 38}
38 39
39pub fn add_derive<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> ActionResult + 'a> { 40pub fn add_derive<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> LocalEdit + 'a> {
40 let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?; 41 let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?;
41 Some(move || { 42 Some(move || {
42 let derive_attr = nominal 43 let derive_attr = nominal
@@ -56,14 +57,14 @@ pub fn add_derive<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce()
56 tt.syntax().range().end() - TextUnit::of_char(')') 57 tt.syntax().range().end() - TextUnit::of_char(')')
57 } 58 }
58 }; 59 };
59 ActionResult { 60 LocalEdit {
60 edit: edit.finish(), 61 edit: edit.finish(),
61 cursor_position: Some(offset), 62 cursor_position: Some(offset),
62 } 63 }
63 }) 64 })
64} 65}
65 66
66pub fn add_impl<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> ActionResult + 'a> { 67pub fn add_impl<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> LocalEdit + 'a> {
67 let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?; 68 let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?;
68 let name = nominal.name()?; 69 let name = nominal.name()?;
69 70
@@ -90,7 +91,7 @@ pub fn add_impl<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() ->
90 let offset = start_offset + TextUnit::of_str(&buf); 91 let offset = start_offset + TextUnit::of_str(&buf);
91 buf.push_str("\n}"); 92 buf.push_str("\n}");
92 edit.insert(start_offset, buf); 93 edit.insert(start_offset, buf);
93 ActionResult { 94 LocalEdit {
94 edit: edit.finish(), 95 edit: edit.finish(),
95 cursor_position: Some(offset), 96 cursor_position: Some(offset),
96 } 97 }
diff --git a/crates/libeditor/src/completion.rs b/crates/libeditor/src/completion.rs
index fe4c9b217..d95c40773 100644
--- a/crates/libeditor/src/completion.rs
+++ b/crates/libeditor/src/completion.rs
@@ -12,8 +12,7 @@ use {
12}; 12};
13 13
14#[derive(Debug)] 14#[derive(Debug)]
15pub struct 15pub struct CompletionItem {
16 CompletionItem {
17 pub name: String, 16 pub name: String,
18 pub snippet: Option<String> 17 pub snippet: Option<String>
19} 18}
diff --git a/crates/libeditor/src/lib.rs b/crates/libeditor/src/lib.rs
index 4895f6fa9..4700ef328 100644
--- a/crates/libeditor/src/lib.rs
+++ b/crates/libeditor/src/lib.rs
@@ -30,11 +30,11 @@ pub use self::{
30 symbols::{StructureNode, file_structure, FileSymbol, file_symbols}, 30 symbols::{StructureNode, file_structure, FileSymbol, file_symbols},
31 edit::{EditBuilder, Edit}, 31 edit::{EditBuilder, Edit},
32 code_actions::{ 32 code_actions::{
33 ActionResult, 33 LocalEdit,
34 flip_comma, add_derive, add_impl, 34 flip_comma, add_derive, add_impl,
35 }, 35 },
36 typing::{join_lines, on_eq_typed}, 36 typing::{join_lines, on_eq_typed},
37 completion::scope_completion, 37 completion::{scope_completion, CompletionItem},
38}; 38};
39 39
40#[derive(Debug)] 40#[derive(Debug)]
diff --git a/crates/libeditor/src/test_utils.rs b/crates/libeditor/src/test_utils.rs
index 475f560fa..037319cd0 100644
--- a/crates/libeditor/src/test_utils.rs
+++ b/crates/libeditor/src/test_utils.rs
@@ -1,8 +1,8 @@
1use libsyntax2::{File, TextUnit}; 1use libsyntax2::{File, TextUnit};
2pub use _test_utils::*; 2pub use _test_utils::*;
3use ActionResult; 3use LocalEdit;
4 4
5pub fn check_action<F: Fn(&File, TextUnit) -> Option<ActionResult>> ( 5pub fn check_action<F: Fn(&File, TextUnit) -> Option<LocalEdit>> (
6 before: &str, 6 before: &str,
7 after: &str, 7 after: &str,
8 f: F, 8 f: F,
diff --git a/crates/libeditor/src/typing.rs b/crates/libeditor/src/typing.rs
index 5008b8d49..f888f3240 100644
--- a/crates/libeditor/src/typing.rs
+++ b/crates/libeditor/src/typing.rs
@@ -11,14 +11,14 @@ use libsyntax2::{
11 SyntaxKind::*, 11 SyntaxKind::*,
12}; 12};
13 13
14use {ActionResult, EditBuilder, find_node_at_offset}; 14use {LocalEdit, EditBuilder, find_node_at_offset};
15 15
16pub fn join_lines(file: &File, range: TextRange) -> ActionResult { 16pub fn join_lines(file: &File, range: TextRange) -> LocalEdit {
17 let range = if range.is_empty() { 17 let range = if range.is_empty() {
18 let syntax = file.syntax(); 18 let syntax = file.syntax();
19 let text = syntax.text().slice(range.start()..); 19 let text = syntax.text().slice(range.start()..);
20 let pos = match text.find('\n') { 20 let pos = match text.find('\n') {
21 None => return ActionResult { 21 None => return LocalEdit {
22 edit: EditBuilder::new().finish(), 22 edit: EditBuilder::new().finish(),
23 cursor_position: None 23 cursor_position: None
24 }, 24 },
@@ -50,13 +50,13 @@ pub fn join_lines(file: &File, range: TextRange) -> ActionResult {
50 } 50 }
51 eprintln!("{:?}", edit); 51 eprintln!("{:?}", edit);
52 52
53 ActionResult { 53 LocalEdit {
54 edit: edit.finish(), 54 edit: edit.finish(),
55 cursor_position: None, 55 cursor_position: None,
56 } 56 }
57} 57}
58 58
59pub fn on_eq_typed(file: &File, offset: TextUnit) -> Option<ActionResult> { 59pub fn on_eq_typed(file: &File, offset: TextUnit) -> Option<LocalEdit> {
60 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; 60 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
61 if let_stmt.has_semi() { 61 if let_stmt.has_semi() {
62 return None; 62 return None;
@@ -75,7 +75,7 @@ pub fn on_eq_typed(file: &File, offset: TextUnit) -> Option<ActionResult> {
75 let offset = let_stmt.syntax().range().end(); 75 let offset = let_stmt.syntax().range().end();
76 let mut edit = EditBuilder::new(); 76 let mut edit = EditBuilder::new();
77 edit.insert(offset, ";".to_string()); 77 edit.insert(offset, ";".to_string());
78 Some(ActionResult { 78 Some(LocalEdit {
79 edit: edit.finish(), 79 edit: edit.finish(),
80 cursor_position: None, 80 cursor_position: None,
81 }) 81 })
@@ -277,7 +277,41 @@ fn foo() {
277}", r" 277}", r"
278fn foo() { 278fn foo() {
279 join(type_params.type_params().filter_map(|it| it.name()).map(|it| it.text())) 279 join(type_params.type_params().filter_map(|it| it.name()).map(|it| it.text()))
280}") 280}");
281
282 do_check(r"
283pub fn handle_find_matching_brace(
284 world: ServerWorld,
285 params: req::FindMatchingBraceParams,
286) -> Result<Vec<Position>> {
287 let file_id = params.text_document.try_conv_with(&world)?;
288 let file = world.analysis().file_syntax(file_id);
289 let line_index = world.analysis().file_line_index(file_id);
290 let res = params.offsets
291 .into_iter()
292 .map_conv_with(&line_index)
293 .map(|offset| <|>{
294 world.analysis().matching_brace(&file, offset).unwrap_or(offset)
295 }<|>)
296 .map_conv_with(&line_index)
297 .collect();
298 Ok(res)
299}", r"
300pub fn handle_find_matching_brace(
301 world: ServerWorld,
302 params: req::FindMatchingBraceParams,
303) -> Result<Vec<Position>> {
304 let file_id = params.text_document.try_conv_with(&world)?;
305 let file = world.analysis().file_syntax(file_id);
306 let line_index = world.analysis().file_line_index(file_id);
307 let res = params.offsets
308 .into_iter()
309 .map_conv_with(&line_index)
310 .map(|offset| world.analysis().matching_brace(&file, offset).unwrap_or(offset))
311 .map_conv_with(&line_index)
312 .collect();
313 Ok(res)
314}");
281 } 315 }
282 316
283 #[test] 317 #[test]
diff --git a/crates/server/src/conv.rs b/crates/server/src/conv.rs
index fc9056914..a59308c3f 100644
--- a/crates/server/src/conv.rs
+++ b/crates/server/src/conv.rs
@@ -1,14 +1,16 @@
1use languageserver_types::{ 1use languageserver_types::{
2 Range, SymbolKind, Position, TextEdit, Location, Url, 2 Range, SymbolKind, Position, TextEdit, Location, Url,
3 TextDocumentIdentifier, VersionedTextDocumentIdentifier, TextDocumentItem, 3 TextDocumentIdentifier, VersionedTextDocumentIdentifier, TextDocumentItem,
4 TextDocumentPositionParams, TextDocumentEdit,
4}; 5};
5use libeditor::{LineIndex, LineCol, Edit, AtomEdit}; 6use libeditor::{LineIndex, LineCol, Edit, AtomEdit};
6use libsyntax2::{SyntaxKind, TextUnit, TextRange}; 7use libsyntax2::{SyntaxKind, TextUnit, TextRange};
7use libanalysis::FileId; 8use libanalysis::{FileId, SourceChange, SourceFileEdit, FileSystemEdit};
8 9
9use { 10use {
10 Result, 11 Result,
11 server_world::ServerWorld, 12 server_world::ServerWorld,
13 req,
12}; 14};
13 15
14pub trait Conv { 16pub trait Conv {
@@ -168,6 +170,82 @@ impl<'a> TryConvWith for &'a TextDocumentIdentifier {
168 } 170 }
169} 171}
170 172
173impl<T: TryConvWith> TryConvWith for Vec<T> {
174 type Ctx = <T as TryConvWith>::Ctx;
175 type Output = Vec<<T as TryConvWith>::Output>;
176 fn try_conv_with(self, ctx: &Self::Ctx) -> Result<Self::Output> {
177 let mut res = Vec::with_capacity(self.len());
178 for item in self {
179 res.push(item.try_conv_with(ctx)?);
180 }
181 Ok(res)
182 }
183}
184
185impl TryConvWith for SourceChange {
186 type Ctx = ServerWorld;
187 type Output = req::SourceChange;
188 fn try_conv_with(self, world: &ServerWorld) -> Result<req::SourceChange> {
189 let cursor_position = match self.cursor_position {
190 None => None,
191 Some(pos) => {
192 let line_index = world.analysis().file_line_index(pos.file_id);
193 Some(TextDocumentPositionParams {
194 text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?),
195 position: pos.offset.conv_with(&line_index),
196 })
197 }
198 };
199 let source_file_edits = self.source_file_edits.try_conv_with(world)?;
200 let file_system_edits = self.file_system_edits.try_conv_with(world)?;
201 Ok(req::SourceChange {
202 label: self.label,
203 source_file_edits,
204 file_system_edits,
205 cursor_position,
206 })
207 }
208}
209
210impl TryConvWith for SourceFileEdit {
211 type Ctx = ServerWorld;
212 type Output = TextDocumentEdit;
213 fn try_conv_with(self, world: &ServerWorld) -> Result<TextDocumentEdit> {
214 let text_document = VersionedTextDocumentIdentifier {
215 uri: self.file_id.try_conv_with(world)?,
216 version: None,
217 };
218 let line_index = world.analysis().file_line_index(self.file_id);
219 let edits = self.edits
220 .into_iter()
221 .map_conv_with(&line_index)
222 .collect();
223 Ok(TextDocumentEdit { text_document, edits })
224 }
225}
226
227impl TryConvWith for FileSystemEdit {
228 type Ctx = ServerWorld;
229 type Output = req::FileSystemEdit;
230 fn try_conv_with(self, world: &ServerWorld) -> Result<req::FileSystemEdit> {
231 let res = match self {
232 FileSystemEdit::CreateFile { anchor, path } => {
233 let uri = world.file_id_to_uri(anchor)?;
234 let path = &path.as_str()[3..]; // strip `../` b/c url is weird
235 let uri = uri.join(path)?;
236 req::FileSystemEdit::CreateFile { uri }
237 },
238 FileSystemEdit::MoveFile { file, path } => {
239 let src = world.file_id_to_uri(file)?;
240 let path = &path.as_str()[3..]; // strip `../` b/c url is weird
241 let dst = src.join(path)?;
242 req::FileSystemEdit::MoveFile { src, dst }
243 },
244 };
245 Ok(res)
246 }
247}
248
171pub fn to_location( 249pub fn to_location(
172 file_id: FileId, 250 file_id: FileId,
173 range: TextRange, 251 range: TextRange,
diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs
index 1a93af65b..6af8bf81b 100644
--- a/crates/server/src/main.rs
+++ b/crates/server/src/main.rs
@@ -35,7 +35,7 @@ use crossbeam_channel::bounded;
35use flexi_logger::{Logger, Duplicate}; 35use flexi_logger::{Logger, Duplicate};
36 36
37use ::{ 37use ::{
38 io::{Io, RawMsg, RawResponse, RawRequest, RawNotification}, 38 io::{Io, RawMsg, RawResponse, RawNotification},
39}; 39};
40 40
41pub type Result<T> = ::std::result::Result<T, ::failure::Error>; 41pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
@@ -109,7 +109,6 @@ fn initialize(io: &mut Io) -> Result<()> {
109 109
110enum Task { 110enum Task {
111 Respond(RawResponse), 111 Respond(RawResponse),
112 Request(RawRequest),
113 Notify(RawNotification), 112 Notify(RawNotification),
114 Die(::failure::Error), 113 Die(::failure::Error),
115} 114}
diff --git a/crates/server/src/main_loop/handlers.rs b/crates/server/src/main_loop/handlers.rs
index 3ee0873f4..45083b084 100644
--- a/crates/server/src/main_loop/handlers.rs
+++ b/crates/server/src/main_loop/handlers.rs
@@ -2,16 +2,13 @@ use std::collections::HashMap;
2 2
3use languageserver_types::{ 3use languageserver_types::{
4 Diagnostic, DiagnosticSeverity, Url, DocumentSymbol, 4 Diagnostic, DiagnosticSeverity, Url, DocumentSymbol,
5 Command, TextDocumentIdentifier, WorkspaceEdit, 5 Command, TextDocumentIdentifier,
6 SymbolInformation, Position, Location, TextEdit, 6 SymbolInformation, Position, Location, TextEdit,
7 CompletionItem, InsertTextFormat, CompletionItemKind, 7 CompletionItem, InsertTextFormat, CompletionItemKind,
8}; 8};
9use serde_json::{to_value, from_value}; 9use serde_json::to_value;
10use url_serde; 10use libanalysis::{Query, FileId, RunnableKind};
11use libanalysis::{self, Query, FileId};
12use libeditor;
13use libsyntax2::{ 11use libsyntax2::{
14 TextUnit,
15 text_utils::contains_offset_nonstrict, 12 text_utils::contains_offset_nonstrict,
16}; 13};
17 14
@@ -26,8 +23,8 @@ pub fn handle_syntax_tree(
26 params: req::SyntaxTreeParams, 23 params: req::SyntaxTreeParams,
27) -> Result<String> { 24) -> Result<String> {
28 let id = params.text_document.try_conv_with(&world)?; 25 let id = params.text_document.try_conv_with(&world)?;
29 let file = world.analysis().file_syntax(id)?; 26 let res = world.analysis().syntax_tree(id);
30 Ok(libeditor::syntax_tree(&file)) 27 Ok(res)
31} 28}
32 29
33pub fn handle_extend_selection( 30pub fn handle_extend_selection(
@@ -35,11 +32,11 @@ pub fn handle_extend_selection(
35 params: req::ExtendSelectionParams, 32 params: req::ExtendSelectionParams,
36) -> Result<req::ExtendSelectionResult> { 33) -> Result<req::ExtendSelectionResult> {
37 let file_id = params.text_document.try_conv_with(&world)?; 34 let file_id = params.text_document.try_conv_with(&world)?;
38 let file = world.analysis().file_syntax(file_id)?; 35 let file = world.analysis().file_syntax(file_id);
39 let line_index = world.analysis().file_line_index(file_id)?; 36 let line_index = world.analysis().file_line_index(file_id);
40 let selections = params.selections.into_iter() 37 let selections = params.selections.into_iter()
41 .map_conv_with(&line_index) 38 .map_conv_with(&line_index)
42 .map(|r| libeditor::extend_selection(&file, r).unwrap_or(r)) 39 .map(|r| world.analysis().extend_selection(&file, r))
43 .map_conv_with(&line_index) 40 .map_conv_with(&line_index)
44 .collect(); 41 .collect();
45 Ok(req::ExtendSelectionResult { selections }) 42 Ok(req::ExtendSelectionResult { selections })
@@ -50,13 +47,13 @@ pub fn handle_find_matching_brace(
50 params: req::FindMatchingBraceParams, 47 params: req::FindMatchingBraceParams,
51) -> Result<Vec<Position>> { 48) -> Result<Vec<Position>> {
52 let file_id = params.text_document.try_conv_with(&world)?; 49 let file_id = params.text_document.try_conv_with(&world)?;
53 let file = world.analysis().file_syntax(file_id)?; 50 let file = world.analysis().file_syntax(file_id);
54 let line_index = world.analysis().file_line_index(file_id)?; 51 let line_index = world.analysis().file_line_index(file_id);
55 let res = params.offsets 52 let res = params.offsets
56 .into_iter() 53 .into_iter()
57 .map_conv_with(&line_index) 54 .map_conv_with(&line_index)
58 .map(|offset| { 55 .map(|offset| {
59 libeditor::matching_brace(&file, offset).unwrap_or(offset) 56 world.analysis().matching_brace(&file, offset).unwrap_or(offset)
60 }) 57 })
61 .map_conv_with(&line_index) 58 .map_conv_with(&line_index)
62 .collect(); 59 .collect();
@@ -66,13 +63,31 @@ pub fn handle_find_matching_brace(
66pub fn handle_join_lines( 63pub fn handle_join_lines(
67 world: ServerWorld, 64 world: ServerWorld,
68 params: req::JoinLinesParams, 65 params: req::JoinLinesParams,
69) -> Result<Vec<TextEdit>> { 66) -> Result<req::SourceChange> {
70 let file_id = params.text_document.try_conv_with(&world)?; 67 let file_id = params.text_document.try_conv_with(&world)?;
71 let file = world.analysis().file_syntax(file_id)?; 68 let line_index = world.analysis().file_line_index(file_id);
72 let line_index = world.analysis().file_line_index(file_id)?;
73 let range = params.range.conv_with(&line_index); 69 let range = params.range.conv_with(&line_index);
74 let res = libeditor::join_lines(&file, range); 70 world.analysis().join_lines(file_id, range)
75 Ok(res.edit.conv_with(&line_index)) 71 .try_conv_with(&world)
72}
73
74pub fn handle_on_type_formatting(
75 world: ServerWorld,
76 params: req::DocumentOnTypeFormattingParams,
77) -> Result<Option<Vec<TextEdit>>> {
78 if params.ch != "=" {
79 return Ok(None);
80 }
81
82 let file_id = params.text_document.try_conv_with(&world)?;
83 let line_index = world.analysis().file_line_index(file_id);
84 let offset = params.position.conv_with(&line_index);
85 let edits = match world.analysis().on_eq_typed(file_id, offset) {
86 None => return Ok(None),
87 Some(mut action) => action.source_file_edits.pop().unwrap().edits,
88 };
89 let edits = edits.into_iter().map_conv_with(&line_index).collect();
90 Ok(Some(edits))
76} 91}
77 92
78pub fn handle_document_symbol( 93pub fn handle_document_symbol(
@@ -80,12 +95,11 @@ pub fn handle_document_symbol(
80 params: req::DocumentSymbolParams, 95 params: req::DocumentSymbolParams,
81) -> Result<Option<req::DocumentSymbolResponse>> { 96) -> Result<Option<req::DocumentSymbolResponse>> {
82 let file_id = params.text_document.try_conv_with(&world)?; 97 let file_id = params.text_document.try_conv_with(&world)?;
83 let file = world.analysis().file_syntax(file_id)?; 98 let line_index = world.analysis().file_line_index(file_id);
84 let line_index = world.analysis().file_line_index(file_id)?;
85 99
86 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new(); 100 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
87 101
88 for symbol in libeditor::file_structure(&file) { 102 for symbol in world.analysis().file_structure(file_id) {
89 let doc_symbol = DocumentSymbol { 103 let doc_symbol = DocumentSymbol {
90 name: symbol.label, 104 name: symbol.label,
91 detail: Some("".to_string()), 105 detail: Some("".to_string()),
@@ -114,130 +128,6 @@ pub fn handle_document_symbol(
114 Ok(Some(req::DocumentSymbolResponse::Nested(res))) 128 Ok(Some(req::DocumentSymbolResponse::Nested(res)))
115} 129}
116 130
117pub fn handle_code_action(
118 world: ServerWorld,
119 params: req::CodeActionParams,
120) -> Result<Option<Vec<Command>>> {
121 let file_id = params.text_document.try_conv_with(&world)?;
122 let file = world.analysis().file_syntax(file_id)?;
123 let line_index = world.analysis().file_line_index(file_id)?;
124 let offset = params.range.conv_with(&line_index).start();
125 let mut res = Vec::new();
126
127 let actions = &[
128 (ActionId::FlipComma, libeditor::flip_comma(&file, offset).is_some()),
129 (ActionId::AddDerive, libeditor::add_derive(&file, offset).is_some()),
130 (ActionId::AddImpl, libeditor::add_impl(&file, offset).is_some()),
131 ];
132
133 for (id, edit) in actions {
134 if *edit {
135 let cmd = apply_code_action_cmd(*id, params.text_document.clone(), offset);
136 res.push(cmd);
137 }
138 }
139
140 for (diag, quick_fix) in world.analysis().diagnostics(file_id)? {
141 let quick_fix = match quick_fix {
142 Some(quick_fix) => quick_fix,
143 None => continue,
144 };
145 if !contains_offset_nonstrict(diag.range, offset) {
146 continue;
147 }
148 let mut ops = Vec::new();
149 for op in quick_fix.fs_ops {
150 let op = match op {
151 libanalysis::FsOp::CreateFile { anchor, path } => {
152 let uri = world.file_id_to_uri(anchor)?;
153 let path = &path.as_str()[3..]; // strip `../` b/c url is weird
154 let uri = uri.join(path)?;
155 FsOp::CreateFile { uri }
156 },
157 libanalysis::FsOp::MoveFile { file, path } => {
158 let src = world.file_id_to_uri(file)?;
159 let path = &path.as_str()[3..]; // strip `../` b/c url is weird
160 let dst = src.join(path)?;
161 FsOp::MoveFile { src, dst }
162 },
163 };
164 ops.push(op)
165 }
166 let cmd = Command {
167 title: "Create module".to_string(),
168 command: "libsyntax-rust.fsEdit".to_string(),
169 arguments: Some(vec![to_value(ops).unwrap()]),
170 };
171 res.push(cmd)
172 }
173 return Ok(Some(res));
174}
175
176#[derive(Serialize)]
177#[serde(tag = "type", rename_all = "camelCase")]
178enum FsOp {
179 CreateFile {
180 #[serde(with = "url_serde")]
181 uri: Url
182 },
183 MoveFile {
184 #[serde(with = "url_serde")]
185 src: Url,
186 #[serde(with = "url_serde")]
187 dst: Url,
188 }
189}
190
191pub fn handle_runnables(
192 world: ServerWorld,
193 params: req::RunnablesParams,
194) -> Result<Vec<req::Runnable>> {
195 let file_id = params.text_document.try_conv_with(&world)?;
196 let file = world.analysis().file_syntax(file_id)?;
197 let line_index = world.analysis().file_line_index(file_id)?;
198 let offset = params.position.map(|it| it.conv_with(&line_index));
199 let mut res = Vec::new();
200 for runnable in libeditor::runnables(&file) {
201 if let Some(offset) = offset {
202 if !contains_offset_nonstrict(runnable.range, offset) {
203 continue;
204 }
205 }
206
207 let r = req::Runnable {
208 range: runnable.range.conv_with(&line_index),
209 label: match &runnable.kind {
210 libeditor::RunnableKind::Test { name } =>
211 format!("test {}", name),
212 libeditor::RunnableKind::Bin =>
213 "run binary".to_string(),
214 },
215 bin: "cargo".to_string(),
216 args: match runnable.kind {
217 libeditor::RunnableKind::Test { name } => {
218 vec![
219 "test".to_string(),
220 "--".to_string(),
221 name,
222 "--nocapture".to_string(),
223 ]
224 }
225 libeditor::RunnableKind::Bin => vec!["run".to_string()]
226 },
227 env: {
228 let mut m = HashMap::new();
229 m.insert(
230 "RUST_BACKTRACE".to_string(),
231 "short".to_string(),
232 );
233 m
234 }
235 };
236 res.push(r);
237 }
238 return Ok(res);
239}
240
241pub fn handle_workspace_symbol( 131pub fn handle_workspace_symbol(
242 world: ServerWorld, 132 world: ServerWorld,
243 params: req::WorkspaceSymbolParams, 133 params: req::WorkspaceSymbolParams,
@@ -265,8 +155,8 @@ pub fn handle_workspace_symbol(
265 155
266 fn exec_query(world: &ServerWorld, query: Query) -> Result<Vec<SymbolInformation>> { 156 fn exec_query(world: &ServerWorld, query: Query) -> Result<Vec<SymbolInformation>> {
267 let mut res = Vec::new(); 157 let mut res = Vec::new();
268 for (file_id, symbol) in world.analysis().world_symbols(query) { 158 for (file_id, symbol) in world.analysis().symbol_search(query) {
269 let line_index = world.analysis().file_line_index(file_id)?; 159 let line_index = world.analysis().file_line_index(file_id);
270 let info = SymbolInformation { 160 let info = SymbolInformation {
271 name: symbol.name.to_string(), 161 name: symbol.name.to_string(),
272 kind: symbol.kind.conv(), 162 kind: symbol.kind.conv(),
@@ -287,11 +177,11 @@ pub fn handle_goto_definition(
287 params: req::TextDocumentPositionParams, 177 params: req::TextDocumentPositionParams,
288) -> Result<Option<req::GotoDefinitionResponse>> { 178) -> Result<Option<req::GotoDefinitionResponse>> {
289 let file_id = params.text_document.try_conv_with(&world)?; 179 let file_id = params.text_document.try_conv_with(&world)?;
290 let line_index = world.analysis().file_line_index(file_id)?; 180 let line_index = world.analysis().file_line_index(file_id);
291 let offset = params.position.conv_with(&line_index); 181 let offset = params.position.conv_with(&line_index);
292 let mut res = Vec::new(); 182 let mut res = Vec::new();
293 for (file_id, symbol) in world.analysis().approximately_resolve_symbol(file_id, offset)? { 183 for (file_id, symbol) in world.analysis().approximately_resolve_symbol(file_id, offset) {
294 let line_index = world.analysis().file_line_index(file_id)?; 184 let line_index = world.analysis().file_line_index(file_id);
295 let location = to_location( 185 let location = to_location(
296 file_id, symbol.node_range, 186 file_id, symbol.node_range,
297 &world, &line_index, 187 &world, &line_index,
@@ -308,7 +198,7 @@ pub fn handle_parent_module(
308 let file_id = params.try_conv_with(&world)?; 198 let file_id = params.try_conv_with(&world)?;
309 let mut res = Vec::new(); 199 let mut res = Vec::new();
310 for (file_id, symbol) in world.analysis().parent_module(file_id) { 200 for (file_id, symbol) in world.analysis().parent_module(file_id) {
311 let line_index = world.analysis().file_line_index(file_id)?; 201 let line_index = world.analysis().file_line_index(file_id);
312 let location = to_location( 202 let location = to_location(
313 file_id, symbol.node_range, 203 file_id, symbol.node_range,
314 &world, &line_index 204 &world, &line_index
@@ -318,15 +208,71 @@ pub fn handle_parent_module(
318 Ok(res) 208 Ok(res)
319} 209}
320 210
211pub fn handle_runnables(
212 world: ServerWorld,
213 params: req::RunnablesParams,
214) -> Result<Vec<req::Runnable>> {
215 let file_id = params.text_document.try_conv_with(&world)?;
216 let line_index = world.analysis().file_line_index(file_id);
217 let offset = params.position.map(|it| it.conv_with(&line_index));
218 let mut res = Vec::new();
219 for runnable in world.analysis().runnables(file_id) {
220 if let Some(offset) = offset {
221 if !contains_offset_nonstrict(runnable.range, offset) {
222 continue;
223 }
224 }
225
226 let r = req::Runnable {
227 range: runnable.range.conv_with(&line_index),
228 label: match &runnable.kind {
229 RunnableKind::Test { name } =>
230 format!("test {}", name),
231 RunnableKind::Bin =>
232 "run binary".to_string(),
233 },
234 bin: "cargo".to_string(),
235 args: match runnable.kind {
236 RunnableKind::Test { name } => {
237 vec![
238 "test".to_string(),
239 "--".to_string(),
240 name,
241 "--nocapture".to_string(),
242 ]
243 }
244 RunnableKind::Bin => vec!["run".to_string()]
245 },
246 env: {
247 let mut m = HashMap::new();
248 m.insert(
249 "RUST_BACKTRACE".to_string(),
250 "short".to_string(),
251 );
252 m
253 }
254 };
255 res.push(r);
256 }
257 return Ok(res);
258}
259
260pub fn handle_decorations(
261 world: ServerWorld,
262 params: TextDocumentIdentifier,
263) -> Result<Vec<Decoration>> {
264 let file_id = params.try_conv_with(&world)?;
265 Ok(highlight(&world, file_id))
266}
267
321pub fn handle_completion( 268pub fn handle_completion(
322 world: ServerWorld, 269 world: ServerWorld,
323 params: req::CompletionParams, 270 params: req::CompletionParams,
324) -> Result<Option<req::CompletionResponse>> { 271) -> Result<Option<req::CompletionResponse>> {
325 let file_id = params.text_document.try_conv_with(&world)?; 272 let file_id = params.text_document.try_conv_with(&world)?;
326 let file = world.analysis().file_syntax(file_id)?; 273 let line_index = world.analysis().file_line_index(file_id);
327 let line_index = world.analysis().file_line_index(file_id)?;
328 let offset = params.position.conv_with(&line_index); 274 let offset = params.position.conv_with(&line_index);
329 let items = match libeditor::scope_completion(&file, offset) { 275 let items = match world.analysis().completions(file_id, offset) {
330 None => return Ok(None), 276 None => return Ok(None),
331 Some(items) => items, 277 Some(items) => items,
332 }; 278 };
@@ -348,91 +294,33 @@ pub fn handle_completion(
348 Ok(Some(req::CompletionResponse::Array(items))) 294 Ok(Some(req::CompletionResponse::Array(items)))
349} 295}
350 296
351pub fn handle_on_type_formatting( 297pub fn handle_code_action(
352 world: ServerWorld, 298 world: ServerWorld,
353 params: req::DocumentOnTypeFormattingParams, 299 params: req::CodeActionParams,
354) -> Result<Option<Vec<TextEdit>>> { 300) -> Result<Option<Vec<Command>>> {
355 if params.ch != "=" {
356 return Ok(None);
357 }
358
359 let file_id = params.text_document.try_conv_with(&world)?; 301 let file_id = params.text_document.try_conv_with(&world)?;
360 let line_index = world.analysis().file_line_index(file_id)?; 302 let line_index = world.analysis().file_line_index(file_id);
361 let offset = params.position.conv_with(&line_index); 303 let offset = params.range.conv_with(&line_index).start();
362 let file = world.analysis().file_syntax(file_id)?;
363 let action = match libeditor::on_eq_typed(&file, offset) {
364 None => return Ok(None),
365 Some(action) => action,
366 };
367 Ok(Some(action.edit.conv_with(&line_index)))
368}
369
370pub fn handle_execute_command(
371 world: ServerWorld,
372 mut params: req::ExecuteCommandParams,
373) -> Result<(req::ApplyWorkspaceEditParams, Option<Position>)> {
374 if params.command.as_str() != "apply_code_action" {
375 bail!("unknown cmd: {:?}", params.command);
376 }
377 if params.arguments.len() != 1 {
378 bail!("expected single arg, got {}", params.arguments.len());
379 }
380 let arg = params.arguments.pop().unwrap();
381 let arg: ActionRequest = from_value(arg)?;
382 let file_id = arg.text_document.try_conv_with(&world)?;
383 let file = world.analysis().file_syntax(file_id)?;
384 let action_result = match arg.id {
385 ActionId::FlipComma => libeditor::flip_comma(&file, arg.offset).map(|f| f()),
386 ActionId::AddDerive => libeditor::add_derive(&file, arg.offset).map(|f| f()),
387 ActionId::AddImpl => libeditor::add_impl(&file, arg.offset).map(|f| f()),
388 }.ok_or_else(|| format_err!("command not applicable"))?;
389 let line_index = world.analysis().file_line_index(file_id)?;
390 let mut changes = HashMap::new();
391 changes.insert(
392 arg.text_document.uri,
393 action_result.edit.conv_with(&line_index),
394 );
395 let edit = WorkspaceEdit {
396 changes: Some(changes),
397 document_changes: None,
398 };
399 let edit = req::ApplyWorkspaceEditParams { edit };
400 let cursor_pos = action_result.cursor_position
401 .map(|off| off.conv_with(&line_index));
402 Ok((edit, cursor_pos))
403}
404 304
405#[derive(Serialize, Deserialize)] 305 let assists = world.analysis().assists(file_id, offset).into_iter();
406struct ActionRequest { 306 let fixes = world.analysis().diagnostics(file_id).into_iter()
407 id: ActionId, 307 .filter_map(|d| Some((d.range, d.fix?)))
408 text_document: TextDocumentIdentifier, 308 .filter(|(range, _fix)| contains_offset_nonstrict(*range, offset))
409 offset: TextUnit, 309 .map(|(_range, fix)| fix);
410}
411 310
412fn apply_code_action_cmd(id: ActionId, doc: TextDocumentIdentifier, offset: TextUnit) -> Command { 311 let mut res = Vec::new();
413 let action_request = ActionRequest { id, text_document: doc, offset }; 312 for source_edit in assists.chain(fixes) {
414 Command { 313 let title = source_edit.label.clone();
415 title: id.title().to_string(), 314 let edit = source_edit.try_conv_with(&world)?;
416 command: "apply_code_action".to_string(), 315 let cmd = Command {
417 arguments: Some(vec![to_value(action_request).unwrap()]), 316 title,
317 command: "libsyntax-rust.applySourceChange".to_string(),
318 arguments: Some(vec![to_value(edit).unwrap()]),
319 };
320 res.push(cmd);
418 } 321 }
419}
420 322
421#[derive(Serialize, Deserialize, Clone, Copy)] 323 Ok(Some(res))
422enum ActionId {
423 FlipComma,
424 AddDerive,
425 AddImpl,
426}
427
428impl ActionId {
429 fn title(&self) -> &'static str {
430 match *self {
431 ActionId::FlipComma => "Flip `,`",
432 ActionId::AddDerive => "Add `#[derive]`",
433 ActionId::AddImpl => "Add impl",
434 }
435 }
436} 324}
437 325
438pub fn publish_diagnostics( 326pub fn publish_diagnostics(
@@ -440,28 +328,20 @@ pub fn publish_diagnostics(
440 uri: Url 328 uri: Url
441) -> Result<req::PublishDiagnosticsParams> { 329) -> Result<req::PublishDiagnosticsParams> {
442 let file_id = world.uri_to_file_id(&uri)?; 330 let file_id = world.uri_to_file_id(&uri)?;
443 let line_index = world.analysis().file_line_index(file_id)?; 331 let line_index = world.analysis().file_line_index(file_id);
444 let diagnostics = world.analysis().diagnostics(file_id)? 332 let diagnostics = world.analysis().diagnostics(file_id)
445 .into_iter() 333 .into_iter()
446 .map(|(d, _quick_fix)| Diagnostic { 334 .map(|d| Diagnostic {
447 range: d.range.conv_with(&line_index), 335 range: d.range.conv_with(&line_index),
448 severity: Some(DiagnosticSeverity::Error), 336 severity: Some(DiagnosticSeverity::Error),
449 code: None, 337 code: None,
450 source: Some("libsyntax2".to_string()), 338 source: Some("libsyntax2".to_string()),
451 message: d.msg, 339 message: d.message,
452 related_information: None, 340 related_information: None,
453 }).collect(); 341 }).collect();
454 Ok(req::PublishDiagnosticsParams { uri, diagnostics }) 342 Ok(req::PublishDiagnosticsParams { uri, diagnostics })
455} 343}
456 344
457pub fn handle_decorations(
458 world: ServerWorld,
459 params: TextDocumentIdentifier,
460) -> Result<Vec<Decoration>> {
461 let file_id = params.try_conv_with(&world)?;
462 highlight(&world, file_id)
463}
464
465pub fn publish_decorations( 345pub fn publish_decorations(
466 world: ServerWorld, 346 world: ServerWorld,
467 uri: Url 347 uri: Url
@@ -469,18 +349,16 @@ pub fn publish_decorations(
469 let file_id = world.uri_to_file_id(&uri)?; 349 let file_id = world.uri_to_file_id(&uri)?;
470 Ok(req::PublishDecorationsParams { 350 Ok(req::PublishDecorationsParams {
471 uri, 351 uri,
472 decorations: highlight(&world, file_id)? 352 decorations: highlight(&world, file_id),
473 }) 353 })
474} 354}
475 355
476fn highlight(world: &ServerWorld, file_id: FileId) -> Result<Vec<Decoration>> { 356fn highlight(world: &ServerWorld, file_id: FileId) -> Vec<Decoration> {
477 let file = world.analysis().file_syntax(file_id)?; 357 let line_index = world.analysis().file_line_index(file_id);
478 let line_index = world.analysis().file_line_index(file_id)?; 358 world.analysis().highlight(file_id)
479 let res = libeditor::highlight(&file)
480 .into_iter() 359 .into_iter()
481 .map(|h| Decoration { 360 .map(|h| Decoration {
482 range: h.range.conv_with(&line_index), 361 range: h.range.conv_with(&line_index),
483 tag: h.tag, 362 tag: h.tag,
484 }).collect(); 363 }).collect()
485 Ok(res)
486} 364}
diff --git a/crates/server/src/main_loop/mod.rs b/crates/server/src/main_loop/mod.rs
index accb13878..0f66248a5 100644
--- a/crates/server/src/main_loop/mod.rs
+++ b/crates/server/src/main_loop/mod.rs
@@ -7,7 +7,6 @@ use std::{
7use threadpool::ThreadPool; 7use threadpool::ThreadPool;
8use crossbeam_channel::{Sender, Receiver}; 8use crossbeam_channel::{Sender, Receiver};
9use languageserver_types::Url; 9use languageserver_types::Url;
10use serde_json::to_value;
11 10
12use { 11use {
13 req, dispatch, 12 req, dispatch,
@@ -15,24 +14,6 @@ use {
15 io::{Io, RawMsg, RawRequest, RawNotification}, 14 io::{Io, RawMsg, RawRequest, RawNotification},
16 vfs::FileEvent, 15 vfs::FileEvent,
17 server_world::{ServerWorldState, ServerWorld}, 16 server_world::{ServerWorldState, ServerWorld},
18 main_loop::handlers::{
19 handle_syntax_tree,
20 handle_extend_selection,
21 publish_diagnostics,
22 publish_decorations,
23 handle_document_symbol,
24 handle_code_action,
25 handle_execute_command,
26 handle_workspace_symbol,
27 handle_goto_definition,
28 handle_find_matching_brace,
29 handle_parent_module,
30 handle_join_lines,
31 handle_completion,
32 handle_runnables,
33 handle_decorations,
34 handle_on_type_formatting,
35 },
36}; 17};
37 18
38pub(super) fn main_loop( 19pub(super) fn main_loop(
@@ -45,7 +26,6 @@ pub(super) fn main_loop(
45 info!("server initialized, serving requests"); 26 info!("server initialized, serving requests");
46 let mut state = ServerWorldState::new(); 27 let mut state = ServerWorldState::new();
47 28
48 let mut next_request_id = 0;
49 let mut pending_requests: HashSet<u64> = HashSet::new(); 29 let mut pending_requests: HashSet<u64> = HashSet::new();
50 let mut fs_events_receiver = Some(&fs_events_receiver); 30 let mut fs_events_receiver = Some(&fs_events_receiver);
51 loop { 31 loop {
@@ -78,12 +58,6 @@ pub(super) fn main_loop(
78 } 58 }
79 Event::Task(task) => { 59 Event::Task(task) => {
80 match task { 60 match task {
81 Task::Request(mut request) => {
82 request.id = next_request_id;
83 pending_requests.insert(next_request_id);
84 next_request_id += 1;
85 io.send(RawMsg::Request(request));
86 }
87 Task::Respond(response) => 61 Task::Respond(response) =>
88 io.send(RawMsg::Response(response)), 62 io.send(RawMsg::Response(response)),
89 Task::Notify(n) => 63 Task::Notify(n) =>
@@ -125,79 +99,26 @@ fn on_request(
125 sender: &Sender<Task>, 99 sender: &Sender<Task>,
126 req: RawRequest, 100 req: RawRequest,
127) -> Result<bool> { 101) -> Result<bool> {
128 let mut req = Some(req); 102 let mut pool_dispatcher = PoolDispatcher {
129 handle_request_on_threadpool::<req::SyntaxTree>( 103 req: Some(req),
130 &mut req, pool, world, sender, handle_syntax_tree, 104 pool, world, sender
131 )?; 105 };
132 handle_request_on_threadpool::<req::ExtendSelection>( 106 pool_dispatcher
133 &mut req, pool, world, sender, handle_extend_selection, 107 .on::<req::SyntaxTree>(handlers::handle_syntax_tree)?
134 )?; 108 .on::<req::ExtendSelection>(handlers::handle_extend_selection)?
135 handle_request_on_threadpool::<req::FindMatchingBrace>( 109 .on::<req::FindMatchingBrace>(handlers::handle_find_matching_brace)?
136 &mut req, pool, world, sender, handle_find_matching_brace, 110 .on::<req::JoinLines>(handlers::handle_join_lines)?
137 )?; 111 .on::<req::OnTypeFormatting>(handlers::handle_on_type_formatting)?
138 handle_request_on_threadpool::<req::DocumentSymbolRequest>( 112 .on::<req::DocumentSymbolRequest>(handlers::handle_document_symbol)?
139 &mut req, pool, world, sender, handle_document_symbol, 113 .on::<req::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
140 )?; 114 .on::<req::GotoDefinition>(handlers::handle_goto_definition)?
141 handle_request_on_threadpool::<req::CodeActionRequest>( 115 .on::<req::ParentModule>(handlers::handle_parent_module)?
142 &mut req, pool, world, sender, handle_code_action, 116 .on::<req::Runnables>(handlers::handle_runnables)?
143 )?; 117 .on::<req::DecorationsRequest>(handlers::handle_decorations)?
144 handle_request_on_threadpool::<req::Runnables>( 118 .on::<req::Completion>(handlers::handle_completion)?
145 &mut req, pool, world, sender, handle_runnables, 119 .on::<req::CodeActionRequest>(handlers::handle_code_action)?;
146 )?;
147 handle_request_on_threadpool::<req::WorkspaceSymbol>(
148 &mut req, pool, world, sender, handle_workspace_symbol,
149 )?;
150 handle_request_on_threadpool::<req::GotoDefinition>(
151 &mut req, pool, world, sender, handle_goto_definition,
152 )?;
153 handle_request_on_threadpool::<req::Completion>(
154 &mut req, pool, world, sender, handle_completion,
155 )?;
156 handle_request_on_threadpool::<req::ParentModule>(
157 &mut req, pool, world, sender, handle_parent_module,
158 )?;
159 handle_request_on_threadpool::<req::JoinLines>(
160 &mut req, pool, world, sender, handle_join_lines,
161 )?;
162 handle_request_on_threadpool::<req::DecorationsRequest>(
163 &mut req, pool, world, sender, handle_decorations,
164 )?;
165 handle_request_on_threadpool::<req::OnTypeFormatting>(
166 &mut req, pool, world, sender, handle_on_type_formatting,
167 )?;
168 dispatch::handle_request::<req::ExecuteCommand, _>(&mut req, |params, resp| {
169 io.send(RawMsg::Response(resp.into_response(Ok(None))?));
170
171 let world = world.snapshot();
172 let sender = sender.clone();
173 pool.execute(move || {
174 let (edit, cursor) = match handle_execute_command(world, params) {
175 Ok(res) => res,
176 Err(e) => return sender.send(Task::Die(e)),
177 };
178 match to_value(edit) {
179 Err(e) => return sender.send(Task::Die(e.into())),
180 Ok(params) => {
181 let request = RawRequest {
182 id: 0,
183 method: <req::ApplyWorkspaceEdit as req::ClientRequest>::METHOD.to_string(),
184 params,
185 };
186 sender.send(Task::Request(request))
187 }
188 }
189 if let Some(cursor) = cursor {
190 let request = RawRequest {
191 id: 0,
192 method: <req::MoveCursor as req::ClientRequest>::METHOD.to_string(),
193 params: to_value(cursor).unwrap(),
194 };
195 sender.send(Task::Request(request))
196 }
197 });
198 Ok(())
199 })?;
200 120
121 let mut req = pool_dispatcher.req;
201 let mut shutdown = false; 122 let mut shutdown = false;
202 dispatch::handle_request::<req::Shutdown, _>(&mut req, |(), resp| { 123 dispatch::handle_request::<req::Shutdown, _>(&mut req, |(), resp| {
203 let resp = resp.into_response(Ok(()))?; 124 let resp = resp.into_response(Ok(()))?;
@@ -273,27 +194,33 @@ fn on_notification(
273 Ok(()) 194 Ok(())
274} 195}
275 196
276fn handle_request_on_threadpool<R: req::ClientRequest>( 197struct PoolDispatcher<'a> {
277 req: &mut Option<RawRequest>, 198 req: Option<RawRequest>,
278 pool: &ThreadPool, 199 pool: &'a ThreadPool,
279 world: &ServerWorldState, 200 world: &'a ServerWorldState,
280 sender: &Sender<Task>, 201 sender: &'a Sender<Task>,
281 f: fn(ServerWorld, R::Params) -> Result<R::Result>, 202}
282) -> Result<()> 203
283{ 204impl<'a> PoolDispatcher<'a> {
284 dispatch::handle_request::<R, _>(req, |params, resp| { 205 fn on<'b, R: req::ClientRequest>(&'b mut self, f: fn(ServerWorld, R::Params) -> Result<R::Result>) -> Result<&'b mut Self> {
285 let world = world.snapshot(); 206 let world = self.world;
286 let sender = sender.clone(); 207 let sender = self.sender;
287 pool.execute(move || { 208 let pool = self.pool;
288 let res = f(world, params); 209 dispatch::handle_request::<R, _>(&mut self.req, |params, resp| {
289 let task = match resp.into_response(res) { 210 let world = world.snapshot();
290 Ok(resp) => Task::Respond(resp), 211 let sender = sender.clone();
291 Err(e) => Task::Die(e), 212 pool.execute(move || {
292 }; 213 let res = f(world, params);
293 sender.send(task); 214 let task = match resp.into_response(res) {
294 }); 215 Ok(resp) => Task::Respond(resp),
295 Ok(()) 216 Err(e) => Task::Die(e),
296 }) 217 };
218 sender.send(task);
219 });
220 Ok(())
221 })?;
222 Ok(self)
223 }
297} 224}
298 225
299fn update_file_notifications_on_threadpool( 226fn update_file_notifications_on_threadpool(
@@ -303,7 +230,7 @@ fn update_file_notifications_on_threadpool(
303 uri: Url, 230 uri: Url,
304) { 231) {
305 pool.execute(move || { 232 pool.execute(move || {
306 match publish_diagnostics(world.clone(), uri.clone()) { 233 match handlers::publish_diagnostics(world.clone(), uri.clone()) {
307 Err(e) => { 234 Err(e) => {
308 error!("failed to compute diagnostics: {:?}", e) 235 error!("failed to compute diagnostics: {:?}", e)
309 } 236 }
@@ -312,7 +239,7 @@ fn update_file_notifications_on_threadpool(
312 sender.send(Task::Notify(not)); 239 sender.send(Task::Notify(not));
313 } 240 }
314 } 241 }
315 match publish_decorations(world, uri) { 242 match handlers::publish_decorations(world, uri) {
316 Err(e) => { 243 Err(e) => {
317 error!("failed to compute decorations: {:?}", e) 244 error!("failed to compute decorations: {:?}", e)
318 } 245 }
diff --git a/crates/server/src/req.rs b/crates/server/src/req.rs
index 881069b1f..c6d2f2efb 100644
--- a/crates/server/src/req.rs
+++ b/crates/server/src/req.rs
@@ -15,6 +15,7 @@ pub use languageserver_types::{
15 TextEdit, 15 TextEdit,
16 CompletionParams, CompletionResponse, 16 CompletionParams, CompletionResponse,
17 DocumentOnTypeFormattingParams, 17 DocumentOnTypeFormattingParams,
18 TextDocumentEdit,
18}; 19};
19 20
20 21
@@ -115,14 +116,6 @@ pub struct Decoration {
115 pub tag: &'static str 116 pub tag: &'static str
116} 117}
117 118
118pub enum MoveCursor {}
119
120impl Request for MoveCursor {
121 type Params = Position;
122 type Result = ();
123 const METHOD: &'static str = "m/moveCursor";
124}
125
126pub enum ParentModule {} 119pub enum ParentModule {}
127 120
128impl Request for ParentModule { 121impl Request for ParentModule {
@@ -135,7 +128,7 @@ pub enum JoinLines {}
135 128
136impl Request for JoinLines { 129impl Request for JoinLines {
137 type Params = JoinLinesParams; 130 type Params = JoinLinesParams;
138 type Result = Vec<TextEdit>; 131 type Result = SourceChange;
139 const METHOD: &'static str = "m/joinLines"; 132 const METHOD: &'static str = "m/joinLines";
140} 133}
141 134
@@ -170,3 +163,27 @@ pub struct Runnable {
170 pub args: Vec<String>, 163 pub args: Vec<String>,
171 pub env: HashMap<String, String>, 164 pub env: HashMap<String, String>,
172} 165}
166
167#[derive(Serialize, Debug)]
168#[serde(rename_all = "camelCase")]
169pub struct SourceChange {
170 pub label: String,
171 pub source_file_edits: Vec<TextDocumentEdit>,
172 pub file_system_edits: Vec<FileSystemEdit>,
173 pub cursor_position: Option<TextDocumentPositionParams>,
174}
175
176#[derive(Serialize, Debug)]
177#[serde(tag = "type", rename_all = "camelCase")]
178pub enum FileSystemEdit {
179 CreateFile {
180 #[serde(with = "url_serde")]
181 uri: Url
182 },
183 MoveFile {
184 #[serde(with = "url_serde")]
185 src: Url,
186 #[serde(with = "url_serde")]
187 dst: Url,
188 }
189}
diff --git a/crates/server/src/server_world.rs b/crates/server/src/server_world.rs
index 1982e727f..6c85914ba 100644
--- a/crates/server/src/server_world.rs
+++ b/crates/server/src/server_world.rs
@@ -5,7 +5,7 @@ use std::{
5}; 5};
6 6
7use languageserver_types::Url; 7use languageserver_types::Url;
8use libanalysis::{FileId, WorldState, World}; 8use libanalysis::{FileId, WorldState, Analysis};
9 9
10use { 10use {
11 Result, 11 Result,
@@ -22,7 +22,7 @@ pub struct ServerWorldState {
22 22
23#[derive(Clone)] 23#[derive(Clone)]
24pub struct ServerWorld { 24pub struct ServerWorld {
25 pub analysis: World, 25 pub analysis: Analysis,
26 pub path_map: PathMap, 26 pub path_map: PathMap,
27} 27}
28 28
@@ -91,14 +91,14 @@ impl ServerWorldState {
91 91
92 pub fn snapshot(&self) -> ServerWorld { 92 pub fn snapshot(&self) -> ServerWorld {
93 ServerWorld { 93 ServerWorld {
94 analysis: self.analysis.snapshot(self.path_map.clone()), 94 analysis: self.analysis.analysis(self.path_map.clone()),
95 path_map: self.path_map.clone() 95 path_map: self.path_map.clone()
96 } 96 }
97 } 97 }
98} 98}
99 99
100impl ServerWorld { 100impl ServerWorld {
101 pub fn analysis(&self) -> &World { 101 pub fn analysis(&self) -> &Analysis {
102 &self.analysis 102 &self.analysis
103 } 103 }
104 104