mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
create module smartly
This commit is contained in:
parent
748a4cacd2
commit
d34588bf83
12 changed files with 344 additions and 182 deletions
|
@ -113,12 +113,26 @@ export function activate(context: vscode.ExtensionContext) {
|
||||||
return await vscode.tasks.executeTask(task)
|
return await vscode.tasks.executeTask(task)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
registerCommand('libsyntax-rust.createFile', async (uri_: string) => {
|
registerCommand('libsyntax-rust.fsEdit', async (ops: FsOp[]) => {
|
||||||
let uri = vscode.Uri.parse(uri_)
|
|
||||||
let edit = new vscode.WorkspaceEdit()
|
let edit = new vscode.WorkspaceEdit()
|
||||||
|
let created;
|
||||||
|
let moved;
|
||||||
|
for (let op of ops) {
|
||||||
|
if (op.type == "createFile") {
|
||||||
|
let uri = vscode.Uri.parse(op.uri!)
|
||||||
edit.createFile(uri)
|
edit.createFile(uri)
|
||||||
|
created = uri
|
||||||
|
} else if (op.type == "moveFile") {
|
||||||
|
let src = vscode.Uri.parse(op.src!)
|
||||||
|
let dst = vscode.Uri.parse(op.dst!)
|
||||||
|
edit.renameFile(src, dst)
|
||||||
|
moved = dst
|
||||||
|
} else {
|
||||||
|
console.error(`unknown op: ${JSON.stringify(op)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
await vscode.workspace.applyEdit(edit)
|
await vscode.workspace.applyEdit(edit)
|
||||||
let doc = await vscode.workspace.openTextDocument(uri)
|
let doc = await vscode.workspace.openTextDocument((created || moved)!)
|
||||||
await vscode.window.showTextDocument(doc)
|
await vscode.window.showTextDocument(doc)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -368,3 +382,10 @@ function createTask(spec: Runnable): vscode.Task {
|
||||||
let t = new vscode.Task(definition, f, definition.label, TASK_SOURCE, exec, ['$rustc']);
|
let t = new vscode.Task(definition, f, definition.label, TASK_SOURCE, exec, ['$rustc']);
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FsOp {
|
||||||
|
type: string;
|
||||||
|
uri?: string;
|
||||||
|
src?: string;
|
||||||
|
dst?: string;
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||||
authors = ["Aleksey Kladov <aleksey.kladov@gmail.com>"]
|
authors = ["Aleksey Kladov <aleksey.kladov@gmail.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
relative-path = "0.3.7"
|
||||||
log = "0.4.2"
|
log = "0.4.2"
|
||||||
failure = "0.1.2"
|
failure = "0.1.2"
|
||||||
parking_lot = "0.6.3"
|
parking_lot = "0.6.3"
|
||||||
|
|
|
@ -8,13 +8,13 @@ extern crate libsyntax2;
|
||||||
extern crate libeditor;
|
extern crate libeditor;
|
||||||
extern crate fst;
|
extern crate fst;
|
||||||
extern crate rayon;
|
extern crate rayon;
|
||||||
|
extern crate relative_path;
|
||||||
|
|
||||||
mod symbol_index;
|
mod symbol_index;
|
||||||
mod module_map;
|
mod module_map;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fmt,
|
fmt,
|
||||||
path::{Path, PathBuf},
|
|
||||||
panic,
|
panic,
|
||||||
sync::{
|
sync::{
|
||||||
Arc,
|
Arc,
|
||||||
|
@ -24,6 +24,7 @@ use std::{
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use relative_path::{RelativePath,RelativePathBuf};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
|
||||||
|
@ -37,13 +38,16 @@ use libeditor::{Diagnostic, LineIndex, FileSymbol, find_node_at_offset};
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
symbol_index::FileSymbols,
|
symbol_index::FileSymbols,
|
||||||
module_map::{ModuleMap, ChangeKind},
|
module_map::{ModuleMap, ChangeKind, Problem},
|
||||||
};
|
};
|
||||||
pub use self::symbol_index::Query;
|
pub use self::symbol_index::Query;
|
||||||
|
|
||||||
pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
|
pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
|
||||||
|
|
||||||
pub type FileResolver = dyn Fn(FileId, &Path) -> Option<FileId> + Send + Sync;
|
pub trait FileResolver: Send + Sync + 'static {
|
||||||
|
fn file_stem(&self, id: FileId) -> String;
|
||||||
|
fn resolve(&self, id: FileId, path: &RelativePath) -> Option<FileId>;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct WorldState {
|
pub struct WorldState {
|
||||||
|
@ -84,7 +88,7 @@ impl WorldState {
|
||||||
|
|
||||||
pub fn snapshot(
|
pub fn snapshot(
|
||||||
&self,
|
&self,
|
||||||
file_resolver: impl Fn(FileId, &Path) -> Option<FileId> + 'static + Send + Sync,
|
file_resolver: impl FileResolver,
|
||||||
) -> World {
|
) -> World {
|
||||||
World {
|
World {
|
||||||
needs_reindex: AtomicBool::new(false),
|
needs_reindex: AtomicBool::new(false),
|
||||||
|
@ -132,8 +136,20 @@ impl WorldState {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum QuickFix {
|
pub struct QuickFix {
|
||||||
CreateFile(PathBuf),
|
pub fs_ops: Vec<FsOp>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FsOp {
|
||||||
|
CreateFile {
|
||||||
|
anchor: FileId,
|
||||||
|
path: RelativePathBuf,
|
||||||
|
},
|
||||||
|
MoveFile {
|
||||||
|
file: FileId,
|
||||||
|
path: RelativePathBuf,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
|
@ -221,20 +237,49 @@ impl World {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|d| (d, None))
|
.map(|d| (d, None))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
for module in syntax.ast().modules() {
|
|
||||||
if module.has_semi() && self.resolve_module(file_id, module).is_empty() {
|
self.data.module_map.problems(
|
||||||
if let Some(name) = module.name() {
|
file_id,
|
||||||
let d = Diagnostic {
|
&*self.file_resolver,
|
||||||
range: name.syntax().range(),
|
&|file_id| self.file_syntax(file_id).unwrap(),
|
||||||
|
|name_node, problem| {
|
||||||
|
let (diag, fix) = match problem {
|
||||||
|
Problem::UnresolvedModule { candidate } => {
|
||||||
|
let diag = Diagnostic {
|
||||||
|
range: name_node.syntax().range(),
|
||||||
msg: "unresolved module".to_string(),
|
msg: "unresolved module".to_string(),
|
||||||
};
|
};
|
||||||
let quick_fix = self.data.module_map.suggested_child_mod_path(module)
|
let fix = QuickFix {
|
||||||
.map(QuickFix::CreateFile);
|
fs_ops: vec![FsOp::CreateFile {
|
||||||
|
anchor: file_id,
|
||||||
res.push((d, quick_fix))
|
path: candidate.clone(),
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
(diag, fix)
|
||||||
}
|
}
|
||||||
|
Problem::NotDirOwner { move_to, candidate } => {
|
||||||
|
let diag = Diagnostic {
|
||||||
|
range: name_node.syntax().range(),
|
||||||
|
msg: "can't declare module at this location".to_string(),
|
||||||
|
};
|
||||||
|
let fix = QuickFix {
|
||||||
|
fs_ops: vec![
|
||||||
|
FsOp::MoveFile {
|
||||||
|
file: file_id,
|
||||||
|
path: move_to.clone(),
|
||||||
|
},
|
||||||
|
FsOp::CreateFile {
|
||||||
|
anchor: file_id,
|
||||||
|
path: move_to.join(candidate),
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
};
|
||||||
|
(diag, fix)
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
res.push((diag, Some(fix)))
|
||||||
|
}
|
||||||
|
);
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use std::{
|
use relative_path::RelativePathBuf;
|
||||||
path::{PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
use libsyntax2::{
|
use libsyntax2::{
|
||||||
|
@ -43,6 +41,18 @@ struct Link {
|
||||||
owner: ModuleId,
|
owner: ModuleId,
|
||||||
syntax: SyntaxNode,
|
syntax: SyntaxNode,
|
||||||
points_to: Vec<ModuleId>,
|
points_to: Vec<ModuleId>,
|
||||||
|
problem: Option<Problem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Problem {
|
||||||
|
UnresolvedModule {
|
||||||
|
candidate: RelativePathBuf,
|
||||||
|
},
|
||||||
|
NotDirOwner {
|
||||||
|
move_to: RelativePathBuf,
|
||||||
|
candidate: RelativePathBuf,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModuleMap {
|
impl ModuleMap {
|
||||||
|
@ -93,9 +103,24 @@ impl ModuleMap {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn suggested_child_mod_path(&self, m: ast::Module) -> Option<PathBuf> {
|
pub fn problems(
|
||||||
let name = m.name()?;
|
&self,
|
||||||
Some(PathBuf::from(format!("../{}.rs", name.text())))
|
file: FileId,
|
||||||
|
file_resolver: &FileResolver,
|
||||||
|
syntax_provider: &SyntaxProvider,
|
||||||
|
mut cb: impl FnMut(ast::Name, &Problem),
|
||||||
|
) {
|
||||||
|
let module = self.file2module(file);
|
||||||
|
let links = self.links(file_resolver, syntax_provider);
|
||||||
|
links
|
||||||
|
.links
|
||||||
|
.iter()
|
||||||
|
.filter(|link| link.owner == module)
|
||||||
|
.filter_map(|link| {
|
||||||
|
let problem = link.problem.as_ref()?;
|
||||||
|
Some((link, problem))
|
||||||
|
})
|
||||||
|
.for_each(|(link, problem)| cb(link.name_node(), problem))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn links(
|
fn links(
|
||||||
|
@ -176,14 +201,17 @@ impl Link {
|
||||||
owner,
|
owner,
|
||||||
syntax: module.syntax().owned(),
|
syntax: module.syntax().owned(),
|
||||||
points_to: Vec::new(),
|
points_to: Vec::new(),
|
||||||
|
problem: None,
|
||||||
};
|
};
|
||||||
Some(link)
|
Some(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> SmolStr {
|
fn name(&self) -> SmolStr {
|
||||||
self.ast().name()
|
self.name_node().text()
|
||||||
.unwrap()
|
}
|
||||||
.text()
|
|
||||||
|
fn name_node(&self) -> ast::Name {
|
||||||
|
self.ast().name().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ast(&self) -> ast::Module {
|
fn ast(&self) -> ast::Module {
|
||||||
|
@ -192,14 +220,30 @@ impl Link {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(&mut self, file_resolver: &FileResolver) {
|
fn resolve(&mut self, file_resolver: &FileResolver) {
|
||||||
let name = self.name();
|
let mod_name = file_resolver.file_stem(self.owner.0);
|
||||||
let paths = &[
|
let is_dir_owner =
|
||||||
PathBuf::from(format!("../{}.rs", name)),
|
mod_name == "mod" || mod_name == "lib" || mod_name == "main";
|
||||||
PathBuf::from(format!("../{}/mod.rs", name)),
|
|
||||||
];
|
let file_mod = RelativePathBuf::from(format!("../{}.rs", self.name()));
|
||||||
self.points_to = paths.iter()
|
let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", self.name()));
|
||||||
.filter_map(|path| file_resolver(self.owner.0, path))
|
if is_dir_owner {
|
||||||
|
self.points_to = [&file_mod, &dir_mod].iter()
|
||||||
|
.filter_map(|path| file_resolver.resolve(self.owner.0, path))
|
||||||
.map(ModuleId)
|
.map(ModuleId)
|
||||||
.collect();
|
.collect();
|
||||||
|
self.problem = if self.points_to.is_empty() {
|
||||||
|
Some(Problem::UnresolvedModule {
|
||||||
|
candidate: file_mod,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.points_to = Vec::new();
|
||||||
|
self.problem = Some(Problem::NotDirOwner {
|
||||||
|
move_to: RelativePathBuf::from(format!("../{}/mod.rs", mod_name)),
|
||||||
|
candidate: file_mod,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,39 @@
|
||||||
extern crate libanalysis;
|
extern crate libanalysis;
|
||||||
|
extern crate relative_path;
|
||||||
extern crate test_utils;
|
extern crate test_utils;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::{Path};
|
||||||
|
|
||||||
use libanalysis::{WorldState, FileId};
|
use relative_path::RelativePath;
|
||||||
|
use libanalysis::{WorldState, FileId, FileResolver};
|
||||||
use test_utils::assert_eq_dbg;
|
use test_utils::assert_eq_dbg;
|
||||||
|
|
||||||
|
struct FileMap(&'static [(u32, &'static str)]);
|
||||||
|
|
||||||
|
impl FileMap {
|
||||||
|
fn path(&self, id: FileId) -> &'static Path {
|
||||||
|
let s = self.0.iter()
|
||||||
|
.find(|it| it.0 == id.0)
|
||||||
|
.unwrap()
|
||||||
|
.1;
|
||||||
|
Path::new(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileResolver for FileMap {
|
||||||
|
fn file_stem(&self, id: FileId) -> String {
|
||||||
|
self.path(id).file_stem().unwrap().to_str().unwrap().to_string()
|
||||||
|
}
|
||||||
|
fn resolve(&self, id: FileId, rel: &RelativePath) -> Option<FileId> {
|
||||||
|
let path = rel.to_path(self.path(id));
|
||||||
|
let path = path.to_str().unwrap();
|
||||||
|
let path = RelativePath::new(&path[1..]).normalize();
|
||||||
|
let &(id, _) = self.0.iter()
|
||||||
|
.find(|it| path == RelativePath::new(&it.1[1..]).normalize())?;
|
||||||
|
Some(FileId(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_resolve_module() {
|
fn test_resolve_module() {
|
||||||
|
@ -13,14 +41,10 @@ fn test_resolve_module() {
|
||||||
world.change_file(FileId(1), Some("mod foo;".to_string()));
|
world.change_file(FileId(1), Some("mod foo;".to_string()));
|
||||||
world.change_file(FileId(2), Some("".to_string()));
|
world.change_file(FileId(2), Some("".to_string()));
|
||||||
|
|
||||||
let snap = world.snapshot(|id, path| {
|
let snap = world.snapshot(FileMap(&[
|
||||||
assert_eq!(id, FileId(1));
|
(1, "/lib.rs"),
|
||||||
if path == PathBuf::from("../foo/mod.rs") {
|
(2, "/foo.rs"),
|
||||||
return None;
|
]));
|
||||||
}
|
|
||||||
assert_eq!(path, PathBuf::from("../foo.rs"));
|
|
||||||
Some(FileId(2))
|
|
||||||
});
|
|
||||||
let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into())
|
let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq_dbg(
|
assert_eq_dbg(
|
||||||
|
@ -28,14 +52,10 @@ fn test_resolve_module() {
|
||||||
&symbols,
|
&symbols,
|
||||||
);
|
);
|
||||||
|
|
||||||
let snap = world.snapshot(|id, path| {
|
let snap = world.snapshot(FileMap(&[
|
||||||
assert_eq!(id, FileId(1));
|
(1, "/lib.rs"),
|
||||||
if path == PathBuf::from("../foo.rs") {
|
(2, "/foo/mod.rs")
|
||||||
return None;
|
]));
|
||||||
}
|
|
||||||
assert_eq!(path, PathBuf::from("../foo/mod.rs"));
|
|
||||||
Some(FileId(2))
|
|
||||||
});
|
|
||||||
let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into())
|
let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq_dbg(
|
assert_eq_dbg(
|
||||||
|
@ -49,11 +69,11 @@ fn test_unresolved_module_diagnostic() {
|
||||||
let mut world = WorldState::new();
|
let mut world = WorldState::new();
|
||||||
world.change_file(FileId(1), Some("mod foo;".to_string()));
|
world.change_file(FileId(1), Some("mod foo;".to_string()));
|
||||||
|
|
||||||
let snap = world.snapshot(|_id, _path| None);
|
let snap = world.snapshot(FileMap(&[(1, "/lib.rs")]));
|
||||||
let diagnostics = snap.diagnostics(FileId(1)).unwrap();
|
let diagnostics = snap.diagnostics(FileId(1)).unwrap();
|
||||||
assert_eq_dbg(
|
assert_eq_dbg(
|
||||||
r#"[(Diagnostic { range: [4; 7), msg: "unresolved module" },
|
r#"[(Diagnostic { range: [4; 7), msg: "unresolved module" },
|
||||||
Some(CreateFile("../foo.rs")))]"#,
|
Some(QuickFix { fs_ops: [CreateFile { anchor: FileId(1), path: "../foo.rs" }] }))]"#,
|
||||||
&diagnostics,
|
&diagnostics,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -64,14 +84,10 @@ fn test_resolve_parent_module() {
|
||||||
world.change_file(FileId(1), Some("mod foo;".to_string()));
|
world.change_file(FileId(1), Some("mod foo;".to_string()));
|
||||||
world.change_file(FileId(2), Some("".to_string()));
|
world.change_file(FileId(2), Some("".to_string()));
|
||||||
|
|
||||||
let snap = world.snapshot(|id, path| {
|
let snap = world.snapshot(FileMap(&[
|
||||||
assert_eq!(id, FileId(1));
|
(1, "/lib.rs"),
|
||||||
if path == PathBuf::from("../foo/mod.rs") {
|
(2, "/foo.rs"),
|
||||||
return None;
|
]));
|
||||||
}
|
|
||||||
assert_eq!(path, PathBuf::from("../foo.rs"));
|
|
||||||
Some(FileId(2))
|
|
||||||
});
|
|
||||||
let symbols = snap.parent_module(FileId(2));
|
let symbols = snap.parent_module(FileId(2));
|
||||||
assert_eq_dbg(
|
assert_eq_dbg(
|
||||||
r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#,
|
r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#,
|
||||||
|
|
|
@ -9,6 +9,95 @@ use libsyntax2::{
|
||||||
algo::{ancestors, generate, walk::preorder}
|
algo::{ancestors, generate, walk::preorder}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ScopeId = usize;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FnScopes {
|
||||||
|
scopes: Vec<ScopeData>,
|
||||||
|
scope_for: HashMap<SyntaxNode, ScopeId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FnScopes {
|
||||||
|
pub fn new(fn_def: ast::FnDef) -> FnScopes {
|
||||||
|
let mut scopes = FnScopes {
|
||||||
|
scopes: Vec::new(),
|
||||||
|
scope_for: HashMap::new()
|
||||||
|
};
|
||||||
|
let root = scopes.root_scope();
|
||||||
|
fn_def.param_list().into_iter()
|
||||||
|
.flat_map(|it| it.params())
|
||||||
|
.filter_map(|it| it.pat())
|
||||||
|
.for_each(|it| scopes.add_bindings(root, it));
|
||||||
|
|
||||||
|
if let Some(body) = fn_def.body() {
|
||||||
|
compute_block_scopes(body, &mut scopes, root)
|
||||||
|
}
|
||||||
|
scopes
|
||||||
|
}
|
||||||
|
pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] {
|
||||||
|
&self.scopes[scope].entries
|
||||||
|
}
|
||||||
|
pub fn scope_chain<'a>(&'a self, node: SyntaxNodeRef) -> impl Iterator<Item=ScopeId> + 'a {
|
||||||
|
generate(self.scope_for(node), move |&scope| self.scopes[scope].parent)
|
||||||
|
}
|
||||||
|
fn root_scope(&mut self) -> ScopeId {
|
||||||
|
let res = self.scopes.len();
|
||||||
|
self.scopes.push(ScopeData { parent: None, entries: vec![] });
|
||||||
|
res
|
||||||
|
}
|
||||||
|
fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
|
||||||
|
let res = self.scopes.len();
|
||||||
|
self.scopes.push(ScopeData { parent: Some(parent), entries: vec![] });
|
||||||
|
res
|
||||||
|
}
|
||||||
|
fn add_bindings(&mut self, scope: ScopeId, pat: ast::Pat) {
|
||||||
|
let entries = preorder(pat.syntax())
|
||||||
|
.filter_map(ast::BindPat::cast)
|
||||||
|
.filter_map(ScopeEntry::new);
|
||||||
|
self.scopes[scope].entries.extend(entries);
|
||||||
|
}
|
||||||
|
fn set_scope(&mut self, node: SyntaxNodeRef, scope: ScopeId) {
|
||||||
|
self.scope_for.insert(node.owned(), scope);
|
||||||
|
}
|
||||||
|
fn scope_for(&self, node: SyntaxNodeRef) -> Option<ScopeId> {
|
||||||
|
ancestors(node)
|
||||||
|
.filter_map(|it| self.scope_for.get(&it.owned()).map(|&scope| scope))
|
||||||
|
.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ScopeEntry {
|
||||||
|
syntax: SyntaxNode
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScopeEntry {
|
||||||
|
fn new(pat: ast::BindPat) -> Option<ScopeEntry> {
|
||||||
|
if pat.name().is_some() {
|
||||||
|
Some(ScopeEntry { syntax: pat.syntax().owned() })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn name(&self) -> SmolStr {
|
||||||
|
self.ast().name()
|
||||||
|
.unwrap()
|
||||||
|
.text()
|
||||||
|
}
|
||||||
|
fn ast(&self) -> ast::BindPat {
|
||||||
|
ast::BindPat::cast(self.syntax.borrowed())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ScopeEntry {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("ScopeEntry")
|
||||||
|
.field("name", &self.name())
|
||||||
|
.field("syntax", &self.syntax)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) {
|
fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) {
|
||||||
for stmt in block.statements() {
|
for stmt in block.statements() {
|
||||||
match stmt {
|
match stmt {
|
||||||
|
@ -95,97 +184,8 @@ fn compute_expr_scopes(expr: ast::Expr, scopes: &mut FnScopes, scope: ScopeId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScopeId = usize;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct FnScopes {
|
|
||||||
scopes: Vec<ScopeData>,
|
|
||||||
scope_for: HashMap<SyntaxNode, ScopeId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FnScopes {
|
|
||||||
pub fn new(fn_def: ast::FnDef) -> FnScopes {
|
|
||||||
let mut scopes = FnScopes {
|
|
||||||
scopes: Vec::new(),
|
|
||||||
scope_for: HashMap::new()
|
|
||||||
};
|
|
||||||
let root = scopes.root_scope();
|
|
||||||
fn_def.param_list().into_iter()
|
|
||||||
.flat_map(|it| it.params())
|
|
||||||
.filter_map(|it| it.pat())
|
|
||||||
.for_each(|it| scopes.add_bindings(root, it));
|
|
||||||
|
|
||||||
if let Some(body) = fn_def.body() {
|
|
||||||
compute_block_scopes(body, &mut scopes, root)
|
|
||||||
}
|
|
||||||
scopes
|
|
||||||
}
|
|
||||||
pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] {
|
|
||||||
&self.scopes[scope].entries
|
|
||||||
}
|
|
||||||
pub fn scope_chain<'a>(&'a self, node: SyntaxNodeRef) -> impl Iterator<Item=ScopeId> + 'a {
|
|
||||||
generate(self.scope_for(node), move |&scope| self.scopes[scope].parent)
|
|
||||||
}
|
|
||||||
fn root_scope(&mut self) -> ScopeId {
|
|
||||||
let res = self.scopes.len();
|
|
||||||
self.scopes.push(ScopeData { parent: None, entries: vec![] });
|
|
||||||
res
|
|
||||||
}
|
|
||||||
fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
|
|
||||||
let res = self.scopes.len();
|
|
||||||
self.scopes.push(ScopeData { parent: Some(parent), entries: vec![] });
|
|
||||||
res
|
|
||||||
}
|
|
||||||
fn add_bindings(&mut self, scope: ScopeId, pat: ast::Pat) {
|
|
||||||
let entries = preorder(pat.syntax())
|
|
||||||
.filter_map(ast::BindPat::cast)
|
|
||||||
.filter_map(ScopeEntry::new);
|
|
||||||
self.scopes[scope].entries.extend(entries);
|
|
||||||
}
|
|
||||||
fn set_scope(&mut self, node: SyntaxNodeRef, scope: ScopeId) {
|
|
||||||
self.scope_for.insert(node.owned(), scope);
|
|
||||||
}
|
|
||||||
fn scope_for(&self, node: SyntaxNodeRef) -> Option<ScopeId> {
|
|
||||||
ancestors(node)
|
|
||||||
.filter_map(|it| self.scope_for.get(&it.owned()).map(|&scope| scope))
|
|
||||||
.next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct ScopeData {
|
struct ScopeData {
|
||||||
parent: Option<ScopeId>,
|
parent: Option<ScopeId>,
|
||||||
entries: Vec<ScopeEntry>
|
entries: Vec<ScopeEntry>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ScopeEntry {
|
|
||||||
syntax: SyntaxNode
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ScopeEntry {
|
|
||||||
fn new(pat: ast::BindPat) -> Option<ScopeEntry> {
|
|
||||||
if pat.name().is_some() {
|
|
||||||
Some(ScopeEntry { syntax: pat.syntax().owned() })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn name(&self) -> SmolStr {
|
|
||||||
self.ast().name()
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
}
|
|
||||||
fn ast(&self) -> ast::BindPat {
|
|
||||||
ast::BindPat::cast(self.syntax.borrowed())
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for ScopeEntry {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.debug_struct("ScopeEntry")
|
|
||||||
.field("name", &self.name())
|
|
||||||
.field("syntax", &self.syntax)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
3
crates/libeditor/src/scope/mod.rs
Normal file
3
crates/libeditor/src/scope/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
mod fn_scope;
|
||||||
|
|
||||||
|
pub use self::fn_scope::FnScopes;
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||||
authors = ["Aleksey Kladov <aleksey.kladov@gmail.com>"]
|
authors = ["Aleksey Kladov <aleksey.kladov@gmail.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
relative-path = "0.3.7"
|
||||||
failure = "0.1.2"
|
failure = "0.1.2"
|
||||||
serde_json = "1.0.24"
|
serde_json = "1.0.24"
|
||||||
serde = "1.0.71"
|
serde = "1.0.71"
|
||||||
|
|
|
@ -18,6 +18,7 @@ extern crate libeditor;
|
||||||
extern crate libanalysis;
|
extern crate libanalysis;
|
||||||
extern crate libsyntax2;
|
extern crate libsyntax2;
|
||||||
extern crate im;
|
extern crate im;
|
||||||
|
extern crate relative_path;
|
||||||
|
|
||||||
mod io;
|
mod io;
|
||||||
mod caps;
|
mod caps;
|
||||||
|
|
|
@ -7,7 +7,8 @@ use languageserver_types::{
|
||||||
CompletionItem,
|
CompletionItem,
|
||||||
};
|
};
|
||||||
use serde_json::{to_value, from_value};
|
use serde_json::{to_value, from_value};
|
||||||
use libanalysis::{Query, QuickFix, FileId};
|
use url_serde;
|
||||||
|
use libanalysis::{self, Query, FileId};
|
||||||
use libeditor;
|
use libeditor;
|
||||||
use libsyntax2::{
|
use libsyntax2::{
|
||||||
TextUnit,
|
TextUnit,
|
||||||
|
@ -144,24 +145,49 @@ pub fn handle_code_action(
|
||||||
if !contains_offset_nonstrict(diag.range, offset) {
|
if !contains_offset_nonstrict(diag.range, offset) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let cmd = match quick_fix {
|
let mut ops = Vec::new();
|
||||||
QuickFix::CreateFile(path) => {
|
for op in quick_fix.fs_ops {
|
||||||
let path = &path.to_str().unwrap()[3..]; // strip `../` b/c url is weird
|
let op = match op {
|
||||||
let uri = params.text_document.uri.join(path)
|
libanalysis::FsOp::CreateFile { anchor, path } => {
|
||||||
.unwrap();
|
let uri = world.file_id_to_uri(anchor)?;
|
||||||
let uri = ::url_serde::Ser::new(&uri);
|
let path = &path.as_str()[3..]; // strip `../` b/c url is weird
|
||||||
Command {
|
let uri = uri.join(path)?;
|
||||||
title: "Create file".to_string(),
|
FsOp::CreateFile { uri }
|
||||||
command: "libsyntax-rust.createFile".to_string(),
|
},
|
||||||
arguments: Some(vec![to_value(uri).unwrap()]),
|
libanalysis::FsOp::MoveFile { file, path } => {
|
||||||
}
|
let src = world.file_id_to_uri(file)?;
|
||||||
|
let path = &path.as_str()[3..]; // strip `../` b/c url is weird
|
||||||
|
let dst = src.join(path)?;
|
||||||
|
FsOp::MoveFile { src, dst }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ops.push(op)
|
||||||
}
|
}
|
||||||
|
let cmd = Command {
|
||||||
|
title: "Create module".to_string(),
|
||||||
|
command: "libsyntax-rust.fsEdit".to_string(),
|
||||||
|
arguments: Some(vec![to_value(ops).unwrap()]),
|
||||||
};
|
};
|
||||||
res.push(cmd)
|
res.push(cmd)
|
||||||
}
|
}
|
||||||
return Ok(Some(res));
|
return Ok(Some(res));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
|
enum FsOp {
|
||||||
|
CreateFile {
|
||||||
|
#[serde(with = "url_serde")]
|
||||||
|
uri: Url
|
||||||
|
},
|
||||||
|
MoveFile {
|
||||||
|
#[serde(with = "url_serde")]
|
||||||
|
src: Url,
|
||||||
|
#[serde(with = "url_serde")]
|
||||||
|
dst: Url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle_runnables(
|
pub fn handle_runnables(
|
||||||
world: ServerWorld,
|
world: ServerWorld,
|
||||||
params: req::RunnablesParams,
|
params: req::RunnablesParams,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::path::{PathBuf, Path, Component};
|
use std::path::{PathBuf, Path, Component};
|
||||||
use im;
|
use im;
|
||||||
use libanalysis::{FileId};
|
use relative_path::RelativePath;
|
||||||
|
use libanalysis::{FileId, FileResolver};
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct PathMap {
|
pub struct PathMap {
|
||||||
|
@ -34,12 +35,6 @@ impl PathMap {
|
||||||
.as_path()
|
.as_path()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve(&self, id: FileId, relpath: &Path) -> Option<FileId> {
|
|
||||||
let path = self.get_path(id).join(relpath);
|
|
||||||
let path = normalize(&path);
|
|
||||||
self.get_id(&path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert(&mut self, path: PathBuf, id: FileId) {
|
fn insert(&mut self, path: PathBuf, id: FileId) {
|
||||||
self.path2id.insert(path.clone(), id);
|
self.path2id.insert(path.clone(), id);
|
||||||
self.id2path.insert(id, path.clone());
|
self.id2path.insert(id, path.clone());
|
||||||
|
@ -52,6 +47,18 @@ impl PathMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FileResolver for PathMap {
|
||||||
|
fn file_stem(&self, id: FileId) -> String {
|
||||||
|
self.get_path(id).file_stem().unwrap().to_str().unwrap().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(&self, id: FileId, path: &RelativePath) -> Option<FileId> {
|
||||||
|
let path = path.to_path(&self.get_path(id));
|
||||||
|
let path = normalize(&path);
|
||||||
|
self.get_id(&path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn normalize(path: &Path) -> PathBuf {
|
fn normalize(path: &Path) -> PathBuf {
|
||||||
let mut components = path.components().peekable();
|
let mut components = path.components().peekable();
|
||||||
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
|
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
|
||||||
|
@ -89,7 +96,7 @@ mod test {
|
||||||
let id1 = m.get_or_insert(PathBuf::from("/foo"));
|
let id1 = m.get_or_insert(PathBuf::from("/foo"));
|
||||||
let id2 = m.get_or_insert(PathBuf::from("/foo/bar.rs"));
|
let id2 = m.get_or_insert(PathBuf::from("/foo/bar.rs"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
m.resolve(id1, &PathBuf::from("bar.rs")),
|
m.resolve(id1, &RelativePath::new("bar.rs")),
|
||||||
Some(id2),
|
Some(id2),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,11 +87,8 @@ impl ServerWorldState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn snapshot(&self) -> ServerWorld {
|
pub fn snapshot(&self) -> ServerWorld {
|
||||||
let pm = self.path_map.clone();
|
|
||||||
ServerWorld {
|
ServerWorld {
|
||||||
analysis: self.analysis.snapshot(move |id, path| {
|
analysis: self.analysis.snapshot(self.path_map.clone()),
|
||||||
pm.resolve(id, path)
|
|
||||||
}),
|
|
||||||
path_map: self.path_map.clone()
|
path_map: self.path_map.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue