aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--code/src/extension.ts29
-rw-r--r--crates/libanalysis/Cargo.toml1
-rw-r--r--crates/libanalysis/src/lib.rs83
-rw-r--r--crates/libanalysis/src/module_map.rs80
-rw-r--r--crates/libanalysis/tests/tests.rs72
-rw-r--r--crates/libeditor/src/scope/fn_scope.rs (renamed from crates/libeditor/src/scope.rs)178
-rw-r--r--crates/libeditor/src/scope/mod.rs3
-rw-r--r--crates/server/Cargo.toml1
-rw-r--r--crates/server/src/main.rs1
-rw-r--r--crates/server/src/main_loop/handlers.rs52
-rw-r--r--crates/server/src/path_map.rs23
-rw-r--r--crates/server/src/server_world.rs5
12 files changed, 345 insertions, 183 deletions
diff --git a/code/src/extension.ts b/code/src/extension.ts
index 81153b7e6..3b24b73b6 100644
--- a/code/src/extension.ts
+++ b/code/src/extension.ts
@@ -113,12 +113,26 @@ export function activate(context: vscode.ExtensionContext) {
113 return await vscode.tasks.executeTask(task) 113 return await vscode.tasks.executeTask(task)
114 } 114 }
115 }) 115 })
116 registerCommand('libsyntax-rust.createFile', async (uri_: string) => { 116 registerCommand('libsyntax-rust.fsEdit', async (ops: FsOp[]) => {
117 let uri = vscode.Uri.parse(uri_)
118 let edit = new vscode.WorkspaceEdit() 117 let edit = new vscode.WorkspaceEdit()
119 edit.createFile(uri) 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 }
120 await vscode.workspace.applyEdit(edit) 134 await vscode.workspace.applyEdit(edit)
121 let doc = await vscode.workspace.openTextDocument(uri) 135 let doc = await vscode.workspace.openTextDocument((created || moved)!)
122 await vscode.window.showTextDocument(doc) 136 await vscode.window.showTextDocument(doc)
123 }) 137 })
124 138
@@ -368,3 +382,10 @@ function createTask(spec: Runnable): vscode.Task {
368 let t = new vscode.Task(definition, f, definition.label, TASK_SOURCE, exec, ['$rustc']); 382 let t = new vscode.Task(definition, f, definition.label, TASK_SOURCE, exec, ['$rustc']);
369 return t; 383 return t;
370} 384}
385
386interface FsOp {
387 type: string;
388 uri?: string;
389 src?: string;
390 dst?: string;
391}
diff --git a/crates/libanalysis/Cargo.toml b/crates/libanalysis/Cargo.toml
index a8ef5e5f4..5aca84f0e 100644
--- a/crates/libanalysis/Cargo.toml
+++ b/crates/libanalysis/Cargo.toml
@@ -4,6 +4,7 @@ version = "0.1.0"
4authors = ["Aleksey Kladov <[email protected]>"] 4authors = ["Aleksey Kladov <[email protected]>"]
5 5
6[dependencies] 6[dependencies]
7relative-path = "0.3.7"
7log = "0.4.2" 8log = "0.4.2"
8failure = "0.1.2" 9failure = "0.1.2"
9parking_lot = "0.6.3" 10parking_lot = "0.6.3"
diff --git a/crates/libanalysis/src/lib.rs b/crates/libanalysis/src/lib.rs
index fe2c3c2e6..96d10a087 100644
--- a/crates/libanalysis/src/lib.rs
+++ b/crates/libanalysis/src/lib.rs
@@ -8,13 +8,13 @@ extern crate libsyntax2;
8extern crate libeditor; 8extern crate libeditor;
9extern crate fst; 9extern crate fst;
10extern crate rayon; 10extern crate rayon;
11extern crate relative_path;
11 12
12mod symbol_index; 13mod symbol_index;
13mod module_map; 14mod module_map;
14 15
15use std::{ 16use std::{
16 fmt, 17 fmt,
17 path::{Path, PathBuf},
18 panic, 18 panic,
19 sync::{ 19 sync::{
20 Arc, 20 Arc,
@@ -24,6 +24,7 @@ use std::{
24 time::Instant, 24 time::Instant,
25}; 25};
26 26
27use relative_path::{RelativePath,RelativePathBuf};
27use once_cell::sync::OnceCell; 28use once_cell::sync::OnceCell;
28use rayon::prelude::*; 29use rayon::prelude::*;
29 30
@@ -37,13 +38,16 @@ use libeditor::{Diagnostic, LineIndex, FileSymbol, find_node_at_offset};
37 38
38use self::{ 39use self::{
39 symbol_index::FileSymbols, 40 symbol_index::FileSymbols,
40 module_map::{ModuleMap, ChangeKind}, 41 module_map::{ModuleMap, ChangeKind, Problem},
41}; 42};
42pub use self::symbol_index::Query; 43pub use self::symbol_index::Query;
43 44
44pub type Result<T> = ::std::result::Result<T, ::failure::Error>; 45pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
45 46
46pub type FileResolver = dyn Fn(FileId, &Path) -> Option<FileId> + Send + Sync; 47pub trait FileResolver: Send + Sync + 'static {
48 fn file_stem(&self, id: FileId) -> String;
49 fn resolve(&self, id: FileId, path: &RelativePath) -> Option<FileId>;
50}
47 51
48#[derive(Debug)] 52#[derive(Debug)]
49pub struct WorldState { 53pub struct WorldState {
@@ -84,7 +88,7 @@ impl WorldState {
84 88
85 pub fn snapshot( 89 pub fn snapshot(
86 &self, 90 &self,
87 file_resolver: impl Fn(FileId, &Path) -> Option<FileId> + 'static + Send + Sync, 91 file_resolver: impl FileResolver,
88 ) -> World { 92 ) -> World {
89 World { 93 World {
90 needs_reindex: AtomicBool::new(false), 94 needs_reindex: AtomicBool::new(false),
@@ -132,8 +136,20 @@ impl WorldState {
132} 136}
133 137
134#[derive(Debug)] 138#[derive(Debug)]
135pub enum QuickFix { 139pub struct QuickFix {
136 CreateFile(PathBuf), 140 pub fs_ops: Vec<FsOp>,
141}
142
143#[derive(Debug)]
144pub enum FsOp {
145 CreateFile {
146 anchor: FileId,
147 path: RelativePathBuf,
148 },
149 MoveFile {
150 file: FileId,
151 path: RelativePathBuf,
152 }
137} 153}
138 154
139impl World { 155impl World {
@@ -221,20 +237,49 @@ impl World {
221 .into_iter() 237 .into_iter()
222 .map(|d| (d, None)) 238 .map(|d| (d, None))
223 .collect::<Vec<_>>(); 239 .collect::<Vec<_>>();
224 for module in syntax.ast().modules() { 240
225 if module.has_semi() && self.resolve_module(file_id, module).is_empty() { 241 self.data.module_map.problems(
226 if let Some(name) = module.name() { 242 file_id,
227 let d = Diagnostic { 243 &*self.file_resolver,
228 range: name.syntax().range(), 244 &|file_id| self.file_syntax(file_id).unwrap(),
229 msg: "unresolved module".to_string(), 245 |name_node, problem| {
230 }; 246 let (diag, fix) = match problem {
231 let quick_fix = self.data.module_map.suggested_child_mod_path(module) 247 Problem::UnresolvedModule { candidate } => {
232 .map(QuickFix::CreateFile); 248 let diag = Diagnostic {
233 249 range: name_node.syntax().range(),
234 res.push((d, quick_fix)) 250 msg: "unresolved module".to_string(),
235 } 251 };
252 let fix = QuickFix {
253 fs_ops: vec![FsOp::CreateFile {
254 anchor: file_id,
255 path: candidate.clone(),
256 }]
257 };
258 (diag, fix)
259 }
260 Problem::NotDirOwner { move_to, candidate } => {
261 let diag = Diagnostic {
262 range: name_node.syntax().range(),
263 msg: "can't declare module at this location".to_string(),
264 };
265 let fix = QuickFix {
266 fs_ops: vec![
267 FsOp::MoveFile {
268 file: file_id,
269 path: move_to.clone(),
270 },
271 FsOp::CreateFile {
272 anchor: file_id,
273 path: move_to.join(candidate),
274 }
275 ],
276 };
277 (diag, fix)
278 }
279 };
280 res.push((diag, Some(fix)))
236 } 281 }
237 } 282 );
238 Ok(res) 283 Ok(res)
239 } 284 }
240 285
diff --git a/crates/libanalysis/src/module_map.rs b/crates/libanalysis/src/module_map.rs
index 4f480591e..b65569c46 100644
--- a/crates/libanalysis/src/module_map.rs
+++ b/crates/libanalysis/src/module_map.rs
@@ -1,6 +1,4 @@
1use std::{ 1use relative_path::RelativePathBuf;
2 path::{PathBuf},
3};
4 2
5use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; 3use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
6use libsyntax2::{ 4use libsyntax2::{
@@ -43,6 +41,18 @@ struct Link {
43 owner: ModuleId, 41 owner: ModuleId,
44 syntax: SyntaxNode, 42 syntax: SyntaxNode,
45 points_to: Vec<ModuleId>, 43 points_to: Vec<ModuleId>,
44 problem: Option<Problem>,
45}
46
47#[derive(Clone, Debug)]
48pub enum Problem {
49 UnresolvedModule {
50 candidate: RelativePathBuf,
51 },
52 NotDirOwner {
53 move_to: RelativePathBuf,
54 candidate: RelativePathBuf,
55 }
46} 56}
47 57
48impl ModuleMap { 58impl ModuleMap {
@@ -93,9 +103,24 @@ impl ModuleMap {
93 res 103 res
94 } 104 }
95 105
96 pub fn suggested_child_mod_path(&self, m: ast::Module) -> Option<PathBuf> { 106 pub fn problems(
97 let name = m.name()?; 107 &self,
98 Some(PathBuf::from(format!("../{}.rs", name.text()))) 108 file: FileId,
109 file_resolver: &FileResolver,
110 syntax_provider: &SyntaxProvider,
111 mut cb: impl FnMut(ast::Name, &Problem),
112 ) {
113 let module = self.file2module(file);
114 let links = self.links(file_resolver, syntax_provider);
115 links
116 .links
117 .iter()
118 .filter(|link| link.owner == module)
119 .filter_map(|link| {
120 let problem = link.problem.as_ref()?;
121 Some((link, problem))
122 })
123 .for_each(|(link, problem)| cb(link.name_node(), problem))
99 } 124 }
100 125
101 fn links( 126 fn links(
@@ -176,14 +201,17 @@ impl Link {
176 owner, 201 owner,
177 syntax: module.syntax().owned(), 202 syntax: module.syntax().owned(),
178 points_to: Vec::new(), 203 points_to: Vec::new(),
204 problem: None,
179 }; 205 };
180 Some(link) 206 Some(link)
181 } 207 }
182 208
183 fn name(&self) -> SmolStr { 209 fn name(&self) -> SmolStr {
184 self.ast().name() 210 self.name_node().text()
185 .unwrap() 211 }
186 .text() 212
213 fn name_node(&self) -> ast::Name {
214 self.ast().name().unwrap()
187 } 215 }
188 216
189 fn ast(&self) -> ast::Module { 217 fn ast(&self) -> ast::Module {
@@ -192,14 +220,30 @@ impl Link {
192 } 220 }
193 221
194 fn resolve(&mut self, file_resolver: &FileResolver) { 222 fn resolve(&mut self, file_resolver: &FileResolver) {
195 let name = self.name(); 223 let mod_name = file_resolver.file_stem(self.owner.0);
196 let paths = &[ 224 let is_dir_owner =
197 PathBuf::from(format!("../{}.rs", name)), 225 mod_name == "mod" || mod_name == "lib" || mod_name == "main";
198 PathBuf::from(format!("../{}/mod.rs", name)), 226
199 ]; 227 let file_mod = RelativePathBuf::from(format!("../{}.rs", self.name()));
200 self.points_to = paths.iter() 228 let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", self.name()));
201 .filter_map(|path| file_resolver(self.owner.0, path)) 229 if is_dir_owner {
202 .map(ModuleId) 230 self.points_to = [&file_mod, &dir_mod].iter()
203 .collect(); 231 .filter_map(|path| file_resolver.resolve(self.owner.0, path))
232 .map(ModuleId)
233 .collect();
234 self.problem = if self.points_to.is_empty() {
235 Some(Problem::UnresolvedModule {
236 candidate: file_mod,
237 })
238 } else {
239 None
240 }
241 } else {
242 self.points_to = Vec::new();
243 self.problem = Some(Problem::NotDirOwner {
244 move_to: RelativePathBuf::from(format!("../{}/mod.rs", mod_name)),
245 candidate: file_mod,
246 });
247 }
204 } 248 }
205} 249}
diff --git a/crates/libanalysis/tests/tests.rs b/crates/libanalysis/tests/tests.rs
index 7c2950ccc..e378ab986 100644
--- a/crates/libanalysis/tests/tests.rs
+++ b/crates/libanalysis/tests/tests.rs
@@ -1,11 +1,39 @@
1extern crate libanalysis; 1extern crate libanalysis;
2extern crate relative_path;
2extern crate test_utils; 3extern crate test_utils;
3 4
4use std::path::PathBuf; 5use std::path::{Path};
5 6
6use libanalysis::{WorldState, FileId}; 7use relative_path::RelativePath;
8use libanalysis::{WorldState, FileId, FileResolver};
7use test_utils::assert_eq_dbg; 9use test_utils::assert_eq_dbg;
8 10
11struct FileMap(&'static [(u32, &'static str)]);
12
13impl FileMap {
14 fn path(&self, id: FileId) -> &'static Path {
15 let s = self.0.iter()
16 .find(|it| it.0 == id.0)
17 .unwrap()
18 .1;
19 Path::new(s)
20 }
21}
22
23impl FileResolver for FileMap {
24 fn file_stem(&self, id: FileId) -> String {
25 self.path(id).file_stem().unwrap().to_str().unwrap().to_string()
26 }
27 fn resolve(&self, id: FileId, rel: &RelativePath) -> Option<FileId> {
28 let path = rel.to_path(self.path(id));
29 let path = path.to_str().unwrap();
30 let path = RelativePath::new(&path[1..]).normalize();
31 let &(id, _) = self.0.iter()
32 .find(|it| path == RelativePath::new(&it.1[1..]).normalize())?;
33 Some(FileId(id))
34 }
35}
36
9 37
10#[test] 38#[test]
11fn test_resolve_module() { 39fn test_resolve_module() {
@@ -13,14 +41,10 @@ fn test_resolve_module() {
13 world.change_file(FileId(1), Some("mod foo;".to_string())); 41 world.change_file(FileId(1), Some("mod foo;".to_string()));
14 world.change_file(FileId(2), Some("".to_string())); 42 world.change_file(FileId(2), Some("".to_string()));
15 43
16 let snap = world.snapshot(|id, path| { 44 let snap = world.snapshot(FileMap(&[
17 assert_eq!(id, FileId(1)); 45 (1, "/lib.rs"),
18 if path == PathBuf::from("../foo/mod.rs") { 46 (2, "/foo.rs"),
19 return None; 47 ]));
20 }
21 assert_eq!(path, PathBuf::from("../foo.rs"));
22 Some(FileId(2))
23 });
24 let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into()) 48 let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into())
25 .unwrap(); 49 .unwrap();
26 assert_eq_dbg( 50 assert_eq_dbg(
@@ -28,14 +52,10 @@ fn test_resolve_module() {
28 &symbols, 52 &symbols,
29 ); 53 );
30 54
31 let snap = world.snapshot(|id, path| { 55 let snap = world.snapshot(FileMap(&[
32 assert_eq!(id, FileId(1)); 56 (1, "/lib.rs"),
33 if path == PathBuf::from("../foo.rs") { 57 (2, "/foo/mod.rs")
34 return None; 58 ]));
35 }
36 assert_eq!(path, PathBuf::from("../foo/mod.rs"));
37 Some(FileId(2))
38 });
39 let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into()) 59 let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into())
40 .unwrap(); 60 .unwrap();
41 assert_eq_dbg( 61 assert_eq_dbg(
@@ -49,11 +69,11 @@ fn test_unresolved_module_diagnostic() {
49 let mut world = WorldState::new(); 69 let mut world = WorldState::new();
50 world.change_file(FileId(1), Some("mod foo;".to_string())); 70 world.change_file(FileId(1), Some("mod foo;".to_string()));
51 71
52 let snap = world.snapshot(|_id, _path| None); 72 let snap = world.snapshot(FileMap(&[(1, "/lib.rs")]));
53 let diagnostics = snap.diagnostics(FileId(1)).unwrap(); 73 let diagnostics = snap.diagnostics(FileId(1)).unwrap();
54 assert_eq_dbg( 74 assert_eq_dbg(
55 r#"[(Diagnostic { range: [4; 7), msg: "unresolved module" }, 75 r#"[(Diagnostic { range: [4; 7), msg: "unresolved module" },
56 Some(CreateFile("../foo.rs")))]"#, 76 Some(QuickFix { fs_ops: [CreateFile { anchor: FileId(1), path: "../foo.rs" }] }))]"#,
57 &diagnostics, 77 &diagnostics,
58 ); 78 );
59} 79}
@@ -64,14 +84,10 @@ fn test_resolve_parent_module() {
64 world.change_file(FileId(1), Some("mod foo;".to_string())); 84 world.change_file(FileId(1), Some("mod foo;".to_string()));
65 world.change_file(FileId(2), Some("".to_string())); 85 world.change_file(FileId(2), Some("".to_string()));
66 86
67 let snap = world.snapshot(|id, path| { 87 let snap = world.snapshot(FileMap(&[
68 assert_eq!(id, FileId(1)); 88 (1, "/lib.rs"),
69 if path == PathBuf::from("../foo/mod.rs") { 89 (2, "/foo.rs"),
70 return None; 90 ]));
71 }
72 assert_eq!(path, PathBuf::from("../foo.rs"));
73 Some(FileId(2))
74 });
75 let symbols = snap.parent_module(FileId(2)); 91 let symbols = snap.parent_module(FileId(2));
76 assert_eq_dbg( 92 assert_eq_dbg(
77 r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#, 93 r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#,
diff --git a/crates/libeditor/src/scope.rs b/crates/libeditor/src/scope/fn_scope.rs
index 3d398a74c..e807c6c71 100644
--- a/crates/libeditor/src/scope.rs
+++ b/crates/libeditor/src/scope/fn_scope.rs
@@ -9,6 +9,95 @@ use libsyntax2::{
9 algo::{ancestors, generate, walk::preorder} 9 algo::{ancestors, generate, walk::preorder}
10}; 10};
11 11
12type ScopeId = usize;
13
14#[derive(Debug)]
15pub struct FnScopes {
16 scopes: Vec<ScopeData>,
17 scope_for: HashMap<SyntaxNode, ScopeId>,
18}
19
20impl FnScopes {
21 pub fn new(fn_def: ast::FnDef) -> FnScopes {
22 let mut scopes = FnScopes {
23 scopes: Vec::new(),
24 scope_for: HashMap::new()
25 };
26 let root = scopes.root_scope();
27 fn_def.param_list().into_iter()
28 .flat_map(|it| it.params())
29 .filter_map(|it| it.pat())
30 .for_each(|it| scopes.add_bindings(root, it));
31
32 if let Some(body) = fn_def.body() {
33 compute_block_scopes(body, &mut scopes, root)
34 }
35 scopes
36 }
37 pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] {
38 &self.scopes[scope].entries
39 }
40 pub fn scope_chain<'a>(&'a self, node: SyntaxNodeRef) -> impl Iterator<Item=ScopeId> + 'a {
41 generate(self.scope_for(node), move |&scope| self.scopes[scope].parent)
42 }
43 fn root_scope(&mut self) -> ScopeId {
44 let res = self.scopes.len();
45 self.scopes.push(ScopeData { parent: None, entries: vec![] });
46 res
47 }
48 fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
49 let res = self.scopes.len();
50 self.scopes.push(ScopeData { parent: Some(parent), entries: vec![] });
51 res
52 }
53 fn add_bindings(&mut self, scope: ScopeId, pat: ast::Pat) {
54 let entries = preorder(pat.syntax())
55 .filter_map(ast::BindPat::cast)
56 .filter_map(ScopeEntry::new);
57 self.scopes[scope].entries.extend(entries);
58 }
59 fn set_scope(&mut self, node: SyntaxNodeRef, scope: ScopeId) {
60 self.scope_for.insert(node.owned(), scope);
61 }
62 fn scope_for(&self, node: SyntaxNodeRef) -> Option<ScopeId> {
63 ancestors(node)
64 .filter_map(|it| self.scope_for.get(&it.owned()).map(|&scope| scope))
65 .next()
66 }
67}
68
69pub struct ScopeEntry {
70 syntax: SyntaxNode
71}
72
73impl ScopeEntry {
74 fn new(pat: ast::BindPat) -> Option<ScopeEntry> {
75 if pat.name().is_some() {
76 Some(ScopeEntry { syntax: pat.syntax().owned() })
77 } else {
78 None
79 }
80 }
81 pub fn name(&self) -> SmolStr {
82 self.ast().name()
83 .unwrap()
84 .text()
85 }
86 fn ast(&self) -> ast::BindPat {
87 ast::BindPat::cast(self.syntax.borrowed())
88 .unwrap()
89 }
90}
91
92impl fmt::Debug for ScopeEntry {
93 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
94 f.debug_struct("ScopeEntry")
95 .field("name", &self.name())
96 .field("syntax", &self.syntax)
97 .finish()
98 }
99}
100
12fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) { 101fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) {
13 for stmt in block.statements() { 102 for stmt in block.statements() {
14 match stmt { 103 match stmt {
@@ -95,97 +184,8 @@ fn compute_expr_scopes(expr: ast::Expr, scopes: &mut FnScopes, scope: ScopeId) {
95 } 184 }
96} 185}
97 186
98type ScopeId = usize;
99
100#[derive(Debug)]
101pub struct FnScopes {
102 scopes: Vec<ScopeData>,
103 scope_for: HashMap<SyntaxNode, ScopeId>,
104}
105
106impl FnScopes {
107 pub fn new(fn_def: ast::FnDef) -> FnScopes {
108 let mut scopes = FnScopes {
109 scopes: Vec::new(),
110 scope_for: HashMap::new()
111 };
112 let root = scopes.root_scope();
113 fn_def.param_list().into_iter()
114 .flat_map(|it| it.params())
115 .filter_map(|it| it.pat())
116 .for_each(|it| scopes.add_bindings(root, it));
117
118 if let Some(body) = fn_def.body() {
119 compute_block_scopes(body, &mut scopes, root)
120 }
121 scopes
122 }
123 pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] {
124 &self.scopes[scope].entries
125 }
126 pub fn scope_chain<'a>(&'a self, node: SyntaxNodeRef) -> impl Iterator<Item=ScopeId> + 'a {
127 generate(self.scope_for(node), move |&scope| self.scopes[scope].parent)
128 }
129 fn root_scope(&mut self) -> ScopeId {
130 let res = self.scopes.len();
131 self.scopes.push(ScopeData { parent: None, entries: vec![] });
132 res
133 }
134 fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
135 let res = self.scopes.len();
136 self.scopes.push(ScopeData { parent: Some(parent), entries: vec![] });
137 res
138 }
139 fn add_bindings(&mut self, scope: ScopeId, pat: ast::Pat) {
140 let entries = preorder(pat.syntax())
141 .filter_map(ast::BindPat::cast)
142 .filter_map(ScopeEntry::new);
143 self.scopes[scope].entries.extend(entries);
144 }
145 fn set_scope(&mut self, node: SyntaxNodeRef, scope: ScopeId) {
146 self.scope_for.insert(node.owned(), scope);
147 }
148 fn scope_for(&self, node: SyntaxNodeRef) -> Option<ScopeId> {
149 ancestors(node)
150 .filter_map(|it| self.scope_for.get(&it.owned()).map(|&scope| scope))
151 .next()
152 }
153}
154
155#[derive(Debug)] 187#[derive(Debug)]
156struct ScopeData { 188struct ScopeData {
157 parent: Option<ScopeId>, 189 parent: Option<ScopeId>,
158 entries: Vec<ScopeEntry> 190 entries: Vec<ScopeEntry>
159} 191}
160
161pub struct ScopeEntry {
162 syntax: SyntaxNode
163}
164
165impl ScopeEntry {
166 fn new(pat: ast::BindPat) -> Option<ScopeEntry> {
167 if pat.name().is_some() {
168 Some(ScopeEntry { syntax: pat.syntax().owned() })
169 } else {
170 None
171 }
172 }
173 pub fn name(&self) -> SmolStr {
174 self.ast().name()
175 .unwrap()
176 .text()
177 }
178 fn ast(&self) -> ast::BindPat {
179 ast::BindPat::cast(self.syntax.borrowed())
180 .unwrap()
181 }
182}
183
184impl fmt::Debug for ScopeEntry {
185 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
186 f.debug_struct("ScopeEntry")
187 .field("name", &self.name())
188 .field("syntax", &self.syntax)
189 .finish()
190 }
191}
diff --git a/crates/libeditor/src/scope/mod.rs b/crates/libeditor/src/scope/mod.rs
new file mode 100644
index 000000000..1a77a8b6e
--- /dev/null
+++ b/crates/libeditor/src/scope/mod.rs
@@ -0,0 +1,3 @@
1mod fn_scope;
2
3pub use self::fn_scope::FnScopes;
diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml
index 058bf36d2..35ced91ac 100644
--- a/crates/server/Cargo.toml
+++ b/crates/server/Cargo.toml
@@ -4,6 +4,7 @@ version = "0.1.0"
4authors = ["Aleksey Kladov <[email protected]>"] 4authors = ["Aleksey Kladov <[email protected]>"]
5 5
6[dependencies] 6[dependencies]
7relative-path = "0.3.7"
7failure = "0.1.2" 8failure = "0.1.2"
8serde_json = "1.0.24" 9serde_json = "1.0.24"
9serde = "1.0.71" 10serde = "1.0.71"
diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs
index 615dd082c..1a93af65b 100644
--- a/crates/server/src/main.rs
+++ b/crates/server/src/main.rs
@@ -18,6 +18,7 @@ extern crate libeditor;
18extern crate libanalysis; 18extern crate libanalysis;
19extern crate libsyntax2; 19extern crate libsyntax2;
20extern crate im; 20extern crate im;
21extern crate relative_path;
21 22
22mod io; 23mod io;
23mod caps; 24mod caps;
diff --git a/crates/server/src/main_loop/handlers.rs b/crates/server/src/main_loop/handlers.rs
index ca5cd5ab1..92ffb30c3 100644
--- a/crates/server/src/main_loop/handlers.rs
+++ b/crates/server/src/main_loop/handlers.rs
@@ -7,7 +7,8 @@ use languageserver_types::{
7 CompletionItem, 7 CompletionItem,
8}; 8};
9use serde_json::{to_value, from_value}; 9use serde_json::{to_value, from_value};
10use libanalysis::{Query, QuickFix, FileId}; 10use url_serde;
11use libanalysis::{self, Query, FileId};
11use libeditor; 12use libeditor;
12use libsyntax2::{ 13use libsyntax2::{
13 TextUnit, 14 TextUnit,
@@ -144,24 +145,49 @@ pub fn handle_code_action(
144 if !contains_offset_nonstrict(diag.range, offset) { 145 if !contains_offset_nonstrict(diag.range, offset) {
145 continue; 146 continue;
146 } 147 }
147 let cmd = match quick_fix { 148 let mut ops = Vec::new();
148 QuickFix::CreateFile(path) => { 149 for op in quick_fix.fs_ops {
149 let path = &path.to_str().unwrap()[3..]; // strip `../` b/c url is weird 150 let op = match op {
150 let uri = params.text_document.uri.join(path) 151 libanalysis::FsOp::CreateFile { anchor, path } => {
151 .unwrap(); 152 let uri = world.file_id_to_uri(anchor)?;
152 let uri = ::url_serde::Ser::new(&uri); 153 let path = &path.as_str()[3..]; // strip `../` b/c url is weird
153 Command { 154 let uri = uri.join(path)?;
154 title: "Create file".to_string(), 155 FsOp::CreateFile { uri }
155 command: "libsyntax-rust.createFile".to_string(), 156 },
156 arguments: Some(vec![to_value(uri).unwrap()]), 157 libanalysis::FsOp::MoveFile { file, path } => {
157 } 158 let src = world.file_id_to_uri(file)?;
158 } 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()]),
159 }; 170 };
160 res.push(cmd) 171 res.push(cmd)
161 } 172 }
162 return Ok(Some(res)); 173 return Ok(Some(res));
163} 174}
164 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
165pub fn handle_runnables( 191pub fn handle_runnables(
166 world: ServerWorld, 192 world: ServerWorld,
167 params: req::RunnablesParams, 193 params: req::RunnablesParams,
diff --git a/crates/server/src/path_map.rs b/crates/server/src/path_map.rs
index d2b811a3b..f4ac47e70 100644
--- a/crates/server/src/path_map.rs
+++ b/crates/server/src/path_map.rs
@@ -1,6 +1,7 @@
1use std::path::{PathBuf, Path, Component}; 1use std::path::{PathBuf, Path, Component};
2use im; 2use im;
3use libanalysis::{FileId}; 3use relative_path::RelativePath;
4use libanalysis::{FileId, FileResolver};
4 5
5#[derive(Debug, Default, Clone)] 6#[derive(Debug, Default, Clone)]
6pub struct PathMap { 7pub struct PathMap {
@@ -34,12 +35,6 @@ impl PathMap {
34 .as_path() 35 .as_path()
35 } 36 }
36 37
37 pub fn resolve(&self, id: FileId, relpath: &Path) -> Option<FileId> {
38 let path = self.get_path(id).join(relpath);
39 let path = normalize(&path);
40 self.get_id(&path)
41 }
42
43 fn insert(&mut self, path: PathBuf, id: FileId) { 38 fn insert(&mut self, path: PathBuf, id: FileId) {
44 self.path2id.insert(path.clone(), id); 39 self.path2id.insert(path.clone(), id);
45 self.id2path.insert(id, path.clone()); 40 self.id2path.insert(id, path.clone());
@@ -52,6 +47,18 @@ impl PathMap {
52 } 47 }
53} 48}
54 49
50impl FileResolver for PathMap {
51 fn file_stem(&self, id: FileId) -> String {
52 self.get_path(id).file_stem().unwrap().to_str().unwrap().to_string()
53 }
54
55 fn resolve(&self, id: FileId, path: &RelativePath) -> Option<FileId> {
56 let path = path.to_path(&self.get_path(id));
57 let path = normalize(&path);
58 self.get_id(&path)
59 }
60}
61
55fn normalize(path: &Path) -> PathBuf { 62fn normalize(path: &Path) -> PathBuf {
56 let mut components = path.components().peekable(); 63 let mut components = path.components().peekable();
57 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { 64 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
@@ -89,7 +96,7 @@ mod test {
89 let id1 = m.get_or_insert(PathBuf::from("/foo")); 96 let id1 = m.get_or_insert(PathBuf::from("/foo"));
90 let id2 = m.get_or_insert(PathBuf::from("/foo/bar.rs")); 97 let id2 = m.get_or_insert(PathBuf::from("/foo/bar.rs"));
91 assert_eq!( 98 assert_eq!(
92 m.resolve(id1, &PathBuf::from("bar.rs")), 99 m.resolve(id1, &RelativePath::new("bar.rs")),
93 Some(id2), 100 Some(id2),
94 ) 101 )
95 } 102 }
diff --git a/crates/server/src/server_world.rs b/crates/server/src/server_world.rs
index c0d2efb86..1593cd59f 100644
--- a/crates/server/src/server_world.rs
+++ b/crates/server/src/server_world.rs
@@ -87,11 +87,8 @@ impl ServerWorldState {
87 } 87 }
88 88
89 pub fn snapshot(&self) -> ServerWorld { 89 pub fn snapshot(&self) -> ServerWorld {
90 let pm = self.path_map.clone();
91 ServerWorld { 90 ServerWorld {
92 analysis: self.analysis.snapshot(move |id, path| { 91 analysis: self.analysis.snapshot(self.path_map.clone()),
93 pm.resolve(id, path)
94 }),
95 path_map: self.path_map.clone() 92 path_map: self.path_map.clone()
96 } 93 }
97 } 94 }