diff options
-rw-r--r-- | code/src/extension.ts | 91 | ||||
-rw-r--r-- | crates/libanalysis/src/api.rs | 136 | ||||
-rw-r--r-- | crates/libanalysis/src/lib.rs | 91 | ||||
-rw-r--r-- | crates/libeditor/src/code_actions.rs | 15 | ||||
-rw-r--r-- | crates/libeditor/src/completion.rs | 3 | ||||
-rw-r--r-- | crates/libeditor/src/lib.rs | 4 | ||||
-rw-r--r-- | crates/libeditor/src/test_utils.rs | 4 | ||||
-rw-r--r-- | crates/libeditor/src/typing.rs | 48 | ||||
-rw-r--r-- | crates/server/src/conv.rs | 80 | ||||
-rw-r--r-- | crates/server/src/main.rs | 3 | ||||
-rw-r--r-- | crates/server/src/main_loop/handlers.rs | 384 | ||||
-rw-r--r-- | crates/server/src/main_loop/mod.rs | 169 | ||||
-rw-r--r-- | crates/server/src/req.rs | 35 | ||||
-rw-r--r-- | crates/server/src/server_world.rs | 8 |
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 | ||
386 | interface FsOp { | 350 | interface 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 | |||
357 | interface SourceChange { | ||
358 | label: string, | ||
359 | sourceFileEdits: lc.TextDocumentEdit[], | ||
360 | fileSystemEdits: FileSystemEdit[], | ||
361 | cursorPosition?: lc.TextDocumentPositionParams, | ||
362 | } | ||
363 | |||
364 | async 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 @@ | |||
1 | use relative_path::RelativePathBuf; | ||
2 | use libsyntax2::{File, TextRange, TextUnit, AtomEdit}; | ||
3 | use libeditor; | ||
4 | use {World, FileId, Query}; | ||
5 | |||
6 | pub use libeditor::{ | ||
7 | LocalEdit, StructureNode, LineIndex, FileSymbol, | ||
8 | Runnable, RunnableKind, HighlightedRange, CompletionItem | ||
9 | }; | ||
10 | |||
11 | #[derive(Clone, Debug)] | ||
12 | pub struct Analysis { | ||
13 | pub(crate) imp: World | ||
14 | } | ||
15 | |||
16 | impl 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)] | ||
84 | pub 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)] | ||
92 | pub struct Position { | ||
93 | pub file_id: FileId, | ||
94 | pub offset: TextUnit, | ||
95 | } | ||
96 | |||
97 | #[derive(Debug)] | ||
98 | pub struct SourceFileEdit { | ||
99 | pub file_id: FileId, | ||
100 | pub edits: Vec<AtomEdit>, | ||
101 | } | ||
102 | |||
103 | #[derive(Debug)] | ||
104 | pub 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)] | ||
116 | pub struct Diagnostic { | ||
117 | pub message: String, | ||
118 | pub range: TextRange, | ||
119 | pub fix: Option<SourceChange>, | ||
120 | } | ||
121 | |||
122 | impl 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 | ||
13 | mod symbol_index; | 13 | mod symbol_index; |
14 | mod module_map; | 14 | mod module_map; |
15 | mod api; | ||
15 | 16 | ||
16 | use std::{ | 17 | use 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 | }; |
37 | use libeditor::{Diagnostic, LineIndex, FileSymbol, find_node_at_offset}; | 38 | use libeditor::{LineIndex, FileSymbol, find_node_at_offset}; |
38 | 39 | ||
39 | use self::{ | 40 | use self::{ |
40 | symbol_index::FileSymbols, | 41 | symbol_index::FileSymbols, |
41 | module_map::{ModuleMap, ChangeKind, Problem}, | 42 | module_map::{ModuleMap, ChangeKind, Problem}, |
42 | }; | 43 | }; |
43 | pub use self::symbol_index::Query; | 44 | pub use self::symbol_index::Query; |
45 | pub use self::api::*; | ||
44 | 46 | ||
45 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; | 47 | pub 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 | ||
14 | use {EditBuilder, Edit, find_node_at_offset}; | 14 | use {EditBuilder, Edit, find_node_at_offset}; |
15 | 15 | ||
16 | // TODO: rename to FileEdit | ||
16 | #[derive(Debug)] | 17 | #[derive(Debug)] |
17 | pub struct ActionResult { | 18 | pub 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 | ||
22 | pub fn flip_comma<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> ActionResult + 'a> { | 23 | pub 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 | ||
39 | pub fn add_derive<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> ActionResult + 'a> { | 40 | pub 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 | ||
66 | pub fn add_impl<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> ActionResult + 'a> { | 67 | pub 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)] |
15 | pub struct | 15 | pub 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 @@ | |||
1 | use libsyntax2::{File, TextUnit}; | 1 | use libsyntax2::{File, TextUnit}; |
2 | pub use _test_utils::*; | 2 | pub use _test_utils::*; |
3 | use ActionResult; | 3 | use LocalEdit; |
4 | 4 | ||
5 | pub fn check_action<F: Fn(&File, TextUnit) -> Option<ActionResult>> ( | 5 | pub 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 | ||
14 | use {ActionResult, EditBuilder, find_node_at_offset}; | 14 | use {LocalEdit, EditBuilder, find_node_at_offset}; |
15 | 15 | ||
16 | pub fn join_lines(file: &File, range: TextRange) -> ActionResult { | 16 | pub 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 | ||
59 | pub fn on_eq_typed(file: &File, offset: TextUnit) -> Option<ActionResult> { | 59 | pub 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" |
278 | fn foo() { | 278 | fn 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" | ||
283 | pub 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" | ||
300 | pub 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 @@ | |||
1 | use languageserver_types::{ | 1 | use 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 | }; |
5 | use libeditor::{LineIndex, LineCol, Edit, AtomEdit}; | 6 | use libeditor::{LineIndex, LineCol, Edit, AtomEdit}; |
6 | use libsyntax2::{SyntaxKind, TextUnit, TextRange}; | 7 | use libsyntax2::{SyntaxKind, TextUnit, TextRange}; |
7 | use libanalysis::FileId; | 8 | use libanalysis::{FileId, SourceChange, SourceFileEdit, FileSystemEdit}; |
8 | 9 | ||
9 | use { | 10 | use { |
10 | Result, | 11 | Result, |
11 | server_world::ServerWorld, | 12 | server_world::ServerWorld, |
13 | req, | ||
12 | }; | 14 | }; |
13 | 15 | ||
14 | pub trait Conv { | 16 | pub trait Conv { |
@@ -168,6 +170,82 @@ impl<'a> TryConvWith for &'a TextDocumentIdentifier { | |||
168 | } | 170 | } |
169 | } | 171 | } |
170 | 172 | ||
173 | impl<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 | |||
185 | impl 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 | |||
210 | impl 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 | |||
227 | impl 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 | |||
171 | pub fn to_location( | 249 | pub 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; | |||
35 | use flexi_logger::{Logger, Duplicate}; | 35 | use flexi_logger::{Logger, Duplicate}; |
36 | 36 | ||
37 | use ::{ | 37 | use ::{ |
38 | io::{Io, RawMsg, RawResponse, RawRequest, RawNotification}, | 38 | io::{Io, RawMsg, RawResponse, RawNotification}, |
39 | }; | 39 | }; |
40 | 40 | ||
41 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; | 41 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; |
@@ -109,7 +109,6 @@ fn initialize(io: &mut Io) -> Result<()> { | |||
109 | 109 | ||
110 | enum Task { | 110 | enum 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 | ||
3 | use languageserver_types::{ | 3 | use 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 | }; |
9 | use serde_json::{to_value, from_value}; | 9 | use serde_json::to_value; |
10 | use url_serde; | 10 | use libanalysis::{Query, FileId, RunnableKind}; |
11 | use libanalysis::{self, Query, FileId}; | ||
12 | use libeditor; | ||
13 | use libsyntax2::{ | 11 | use 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 | ||
33 | pub fn handle_extend_selection( | 30 | pub 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( | |||
66 | pub fn handle_join_lines( | 63 | pub 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 | |||
74 | pub 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 | ||
78 | pub fn handle_document_symbol( | 93 | pub 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 | ||
117 | pub 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")] | ||
178 | enum 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 | |||
191 | pub 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 | |||
241 | pub fn handle_workspace_symbol( | 131 | pub 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 | ||
211 | pub 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 | |||
260 | pub 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 | |||
321 | pub fn handle_completion( | 268 | pub 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 | ||
351 | pub fn handle_on_type_formatting( | 297 | pub 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 | |||
370 | pub 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(); |
406 | struct 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 | ||
412 | fn 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)) |
422 | enum ActionId { | ||
423 | FlipComma, | ||
424 | AddDerive, | ||
425 | AddImpl, | ||
426 | } | ||
427 | |||
428 | impl 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 | ||
438 | pub fn publish_diagnostics( | 326 | pub 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 | ||
457 | pub 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 | |||
465 | pub fn publish_decorations( | 345 | pub 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 | ||
476 | fn highlight(world: &ServerWorld, file_id: FileId) -> Result<Vec<Decoration>> { | 356 | fn 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::{ | |||
7 | use threadpool::ThreadPool; | 7 | use threadpool::ThreadPool; |
8 | use crossbeam_channel::{Sender, Receiver}; | 8 | use crossbeam_channel::{Sender, Receiver}; |
9 | use languageserver_types::Url; | 9 | use languageserver_types::Url; |
10 | use serde_json::to_value; | ||
11 | 10 | ||
12 | use { | 11 | use { |
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 | ||
38 | pub(super) fn main_loop( | 19 | pub(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 | ||
276 | fn handle_request_on_threadpool<R: req::ClientRequest>( | 197 | struct 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 | { | 204 | impl<'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 | ||
299 | fn update_file_notifications_on_threadpool( | 226 | fn 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 | ||
118 | pub enum MoveCursor {} | ||
119 | |||
120 | impl Request for MoveCursor { | ||
121 | type Params = Position; | ||
122 | type Result = (); | ||
123 | const METHOD: &'static str = "m/moveCursor"; | ||
124 | } | ||
125 | |||
126 | pub enum ParentModule {} | 119 | pub enum ParentModule {} |
127 | 120 | ||
128 | impl Request for ParentModule { | 121 | impl Request for ParentModule { |
@@ -135,7 +128,7 @@ pub enum JoinLines {} | |||
135 | 128 | ||
136 | impl Request for JoinLines { | 129 | impl 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")] | ||
169 | pub 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")] | ||
178 | pub 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 | ||
7 | use languageserver_types::Url; | 7 | use languageserver_types::Url; |
8 | use libanalysis::{FileId, WorldState, World}; | 8 | use libanalysis::{FileId, WorldState, Analysis}; |
9 | 9 | ||
10 | use { | 10 | use { |
11 | Result, | 11 | Result, |
@@ -22,7 +22,7 @@ pub struct ServerWorldState { | |||
22 | 22 | ||
23 | #[derive(Clone)] | 23 | #[derive(Clone)] |
24 | pub struct ServerWorld { | 24 | pub 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 | ||
100 | impl ServerWorld { | 100 | impl ServerWorld { |
101 | pub fn analysis(&self) -> &World { | 101 | pub fn analysis(&self) -> &Analysis { |
102 | &self.analysis | 102 | &self.analysis |
103 | } | 103 | } |
104 | 104 | ||