mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 04:53:34 +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)
|
||||
}
|
||||
})
|
||||
registerCommand('libsyntax-rust.createFile', async (uri_: string) => {
|
||||
let uri = vscode.Uri.parse(uri_)
|
||||
registerCommand('libsyntax-rust.fsEdit', async (ops: FsOp[]) => {
|
||||
let edit = new vscode.WorkspaceEdit()
|
||||
edit.createFile(uri)
|
||||
let created;
|
||||
let moved;
|
||||
for (let op of ops) {
|
||||
if (op.type == "createFile") {
|
||||
let uri = vscode.Uri.parse(op.uri!)
|
||||
edit.createFile(uri)
|
||||
created = uri
|
||||
} else if (op.type == "moveFile") {
|
||||
let src = vscode.Uri.parse(op.src!)
|
||||
let dst = vscode.Uri.parse(op.dst!)
|
||||
edit.renameFile(src, dst)
|
||||
moved = dst
|
||||
} else {
|
||||
console.error(`unknown op: ${JSON.stringify(op)}`)
|
||||
}
|
||||
}
|
||||
await vscode.workspace.applyEdit(edit)
|
||||
let doc = await vscode.workspace.openTextDocument(uri)
|
||||
let doc = await vscode.workspace.openTextDocument((created || moved)!)
|
||||
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']);
|
||||
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>"]
|
||||
|
||||
[dependencies]
|
||||
relative-path = "0.3.7"
|
||||
log = "0.4.2"
|
||||
failure = "0.1.2"
|
||||
parking_lot = "0.6.3"
|
||||
|
|
|
@ -8,13 +8,13 @@ extern crate libsyntax2;
|
|||
extern crate libeditor;
|
||||
extern crate fst;
|
||||
extern crate rayon;
|
||||
extern crate relative_path;
|
||||
|
||||
mod symbol_index;
|
||||
mod module_map;
|
||||
|
||||
use std::{
|
||||
fmt,
|
||||
path::{Path, PathBuf},
|
||||
panic,
|
||||
sync::{
|
||||
Arc,
|
||||
|
@ -24,6 +24,7 @@ use std::{
|
|||
time::Instant,
|
||||
};
|
||||
|
||||
use relative_path::{RelativePath,RelativePathBuf};
|
||||
use once_cell::sync::OnceCell;
|
||||
use rayon::prelude::*;
|
||||
|
||||
|
@ -37,13 +38,16 @@ use libeditor::{Diagnostic, LineIndex, FileSymbol, find_node_at_offset};
|
|||
|
||||
use self::{
|
||||
symbol_index::FileSymbols,
|
||||
module_map::{ModuleMap, ChangeKind},
|
||||
module_map::{ModuleMap, ChangeKind, Problem},
|
||||
};
|
||||
pub use self::symbol_index::Query;
|
||||
|
||||
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)]
|
||||
pub struct WorldState {
|
||||
|
@ -84,7 +88,7 @@ impl WorldState {
|
|||
|
||||
pub fn snapshot(
|
||||
&self,
|
||||
file_resolver: impl Fn(FileId, &Path) -> Option<FileId> + 'static + Send + Sync,
|
||||
file_resolver: impl FileResolver,
|
||||
) -> World {
|
||||
World {
|
||||
needs_reindex: AtomicBool::new(false),
|
||||
|
@ -132,8 +136,20 @@ impl WorldState {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum QuickFix {
|
||||
CreateFile(PathBuf),
|
||||
pub struct QuickFix {
|
||||
pub fs_ops: Vec<FsOp>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FsOp {
|
||||
CreateFile {
|
||||
anchor: FileId,
|
||||
path: RelativePathBuf,
|
||||
},
|
||||
MoveFile {
|
||||
file: FileId,
|
||||
path: RelativePathBuf,
|
||||
}
|
||||
}
|
||||
|
||||
impl World {
|
||||
|
@ -221,20 +237,49 @@ impl World {
|
|||
.into_iter()
|
||||
.map(|d| (d, None))
|
||||
.collect::<Vec<_>>();
|
||||
for module in syntax.ast().modules() {
|
||||
if module.has_semi() && self.resolve_module(file_id, module).is_empty() {
|
||||
if let Some(name) = module.name() {
|
||||
let d = Diagnostic {
|
||||
range: name.syntax().range(),
|
||||
msg: "unresolved module".to_string(),
|
||||
};
|
||||
let quick_fix = self.data.module_map.suggested_child_mod_path(module)
|
||||
.map(QuickFix::CreateFile);
|
||||
|
||||
res.push((d, quick_fix))
|
||||
}
|
||||
self.data.module_map.problems(
|
||||
file_id,
|
||||
&*self.file_resolver,
|
||||
&|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(),
|
||||
};
|
||||
let fix = QuickFix {
|
||||
fs_ops: vec![FsOp::CreateFile {
|
||||
anchor: file_id,
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use std::{
|
||||
path::{PathBuf},
|
||||
};
|
||||
use relative_path::RelativePathBuf;
|
||||
|
||||
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use libsyntax2::{
|
||||
|
@ -43,6 +41,18 @@ struct Link {
|
|||
owner: ModuleId,
|
||||
syntax: SyntaxNode,
|
||||
points_to: Vec<ModuleId>,
|
||||
problem: Option<Problem>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Problem {
|
||||
UnresolvedModule {
|
||||
candidate: RelativePathBuf,
|
||||
},
|
||||
NotDirOwner {
|
||||
move_to: RelativePathBuf,
|
||||
candidate: RelativePathBuf,
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleMap {
|
||||
|
@ -93,9 +103,24 @@ impl ModuleMap {
|
|||
res
|
||||
}
|
||||
|
||||
pub fn suggested_child_mod_path(&self, m: ast::Module) -> Option<PathBuf> {
|
||||
let name = m.name()?;
|
||||
Some(PathBuf::from(format!("../{}.rs", name.text())))
|
||||
pub fn problems(
|
||||
&self,
|
||||
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(
|
||||
|
@ -176,14 +201,17 @@ impl Link {
|
|||
owner,
|
||||
syntax: module.syntax().owned(),
|
||||
points_to: Vec::new(),
|
||||
problem: None,
|
||||
};
|
||||
Some(link)
|
||||
}
|
||||
|
||||
fn name(&self) -> SmolStr {
|
||||
self.ast().name()
|
||||
.unwrap()
|
||||
.text()
|
||||
self.name_node().text()
|
||||
}
|
||||
|
||||
fn name_node(&self) -> ast::Name {
|
||||
self.ast().name().unwrap()
|
||||
}
|
||||
|
||||
fn ast(&self) -> ast::Module {
|
||||
|
@ -192,14 +220,30 @@ impl Link {
|
|||
}
|
||||
|
||||
fn resolve(&mut self, file_resolver: &FileResolver) {
|
||||
let name = self.name();
|
||||
let paths = &[
|
||||
PathBuf::from(format!("../{}.rs", name)),
|
||||
PathBuf::from(format!("../{}/mod.rs", name)),
|
||||
];
|
||||
self.points_to = paths.iter()
|
||||
.filter_map(|path| file_resolver(self.owner.0, path))
|
||||
.map(ModuleId)
|
||||
.collect();
|
||||
let mod_name = file_resolver.file_stem(self.owner.0);
|
||||
let is_dir_owner =
|
||||
mod_name == "mod" || mod_name == "lib" || mod_name == "main";
|
||||
|
||||
let file_mod = RelativePathBuf::from(format!("../{}.rs", self.name()));
|
||||
let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", self.name()));
|
||||
if is_dir_owner {
|
||||
self.points_to = [&file_mod, &dir_mod].iter()
|
||||
.filter_map(|path| file_resolver.resolve(self.owner.0, path))
|
||||
.map(ModuleId)
|
||||
.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 relative_path;
|
||||
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;
|
||||
|
||||
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]
|
||||
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(2), Some("".to_string()));
|
||||
|
||||
let snap = world.snapshot(|id, path| {
|
||||
assert_eq!(id, FileId(1));
|
||||
if path == PathBuf::from("../foo/mod.rs") {
|
||||
return None;
|
||||
}
|
||||
assert_eq!(path, PathBuf::from("../foo.rs"));
|
||||
Some(FileId(2))
|
||||
});
|
||||
let snap = world.snapshot(FileMap(&[
|
||||
(1, "/lib.rs"),
|
||||
(2, "/foo.rs"),
|
||||
]));
|
||||
let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into())
|
||||
.unwrap();
|
||||
assert_eq_dbg(
|
||||
|
@ -28,14 +52,10 @@ fn test_resolve_module() {
|
|||
&symbols,
|
||||
);
|
||||
|
||||
let snap = world.snapshot(|id, path| {
|
||||
assert_eq!(id, FileId(1));
|
||||
if path == PathBuf::from("../foo.rs") {
|
||||
return None;
|
||||
}
|
||||
assert_eq!(path, PathBuf::from("../foo/mod.rs"));
|
||||
Some(FileId(2))
|
||||
});
|
||||
let snap = world.snapshot(FileMap(&[
|
||||
(1, "/lib.rs"),
|
||||
(2, "/foo/mod.rs")
|
||||
]));
|
||||
let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into())
|
||||
.unwrap();
|
||||
assert_eq_dbg(
|
||||
|
@ -49,11 +69,11 @@ fn test_unresolved_module_diagnostic() {
|
|||
let mut world = WorldState::new();
|
||||
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();
|
||||
assert_eq_dbg(
|
||||
r#"[(Diagnostic { range: [4; 7), msg: "unresolved module" },
|
||||
Some(CreateFile("../foo.rs")))]"#,
|
||||
Some(QuickFix { fs_ops: [CreateFile { anchor: FileId(1), path: "../foo.rs" }] }))]"#,
|
||||
&diagnostics,
|
||||
);
|
||||
}
|
||||
|
@ -64,14 +84,10 @@ fn test_resolve_parent_module() {
|
|||
world.change_file(FileId(1), Some("mod foo;".to_string()));
|
||||
world.change_file(FileId(2), Some("".to_string()));
|
||||
|
||||
let snap = world.snapshot(|id, path| {
|
||||
assert_eq!(id, FileId(1));
|
||||
if path == PathBuf::from("../foo/mod.rs") {
|
||||
return None;
|
||||
}
|
||||
assert_eq!(path, PathBuf::from("../foo.rs"));
|
||||
Some(FileId(2))
|
||||
});
|
||||
let snap = world.snapshot(FileMap(&[
|
||||
(1, "/lib.rs"),
|
||||
(2, "/foo.rs"),
|
||||
]));
|
||||
let symbols = snap.parent_module(FileId(2));
|
||||
assert_eq_dbg(
|
||||
r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#,
|
||||
|
|
|
@ -9,6 +9,95 @@ use libsyntax2::{
|
|||
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) {
|
||||
for stmt in block.statements() {
|
||||
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)]
|
||||
struct ScopeData {
|
||||
parent: Option<ScopeId>,
|
||||
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>"]
|
||||
|
||||
[dependencies]
|
||||
relative-path = "0.3.7"
|
||||
failure = "0.1.2"
|
||||
serde_json = "1.0.24"
|
||||
serde = "1.0.71"
|
||||
|
|
|
@ -18,6 +18,7 @@ extern crate libeditor;
|
|||
extern crate libanalysis;
|
||||
extern crate libsyntax2;
|
||||
extern crate im;
|
||||
extern crate relative_path;
|
||||
|
||||
mod io;
|
||||
mod caps;
|
||||
|
|
|
@ -7,7 +7,8 @@ use languageserver_types::{
|
|||
CompletionItem,
|
||||
};
|
||||
use serde_json::{to_value, from_value};
|
||||
use libanalysis::{Query, QuickFix, FileId};
|
||||
use url_serde;
|
||||
use libanalysis::{self, Query, FileId};
|
||||
use libeditor;
|
||||
use libsyntax2::{
|
||||
TextUnit,
|
||||
|
@ -144,24 +145,49 @@ pub fn handle_code_action(
|
|||
if !contains_offset_nonstrict(diag.range, offset) {
|
||||
continue;
|
||||
}
|
||||
let cmd = match quick_fix {
|
||||
QuickFix::CreateFile(path) => {
|
||||
let path = &path.to_str().unwrap()[3..]; // strip `../` b/c url is weird
|
||||
let uri = params.text_document.uri.join(path)
|
||||
.unwrap();
|
||||
let uri = ::url_serde::Ser::new(&uri);
|
||||
Command {
|
||||
title: "Create file".to_string(),
|
||||
command: "libsyntax-rust.createFile".to_string(),
|
||||
arguments: Some(vec![to_value(uri).unwrap()]),
|
||||
}
|
||||
}
|
||||
let mut ops = Vec::new();
|
||||
for op in quick_fix.fs_ops {
|
||||
let op = match op {
|
||||
libanalysis::FsOp::CreateFile { anchor, path } => {
|
||||
let uri = world.file_id_to_uri(anchor)?;
|
||||
let path = &path.as_str()[3..]; // strip `../` b/c url is weird
|
||||
let uri = uri.join(path)?;
|
||||
FsOp::CreateFile { uri }
|
||||
},
|
||||
libanalysis::FsOp::MoveFile { file, path } => {
|
||||
let src = world.file_id_to_uri(file)?;
|
||||
let path = &path.as_str()[3..]; // strip `../` b/c url is weird
|
||||
let dst = src.join(path)?;
|
||||
FsOp::MoveFile { src, dst }
|
||||
},
|
||||
};
|
||||
ops.push(op)
|
||||
}
|
||||
let cmd = Command {
|
||||
title: "Create module".to_string(),
|
||||
command: "libsyntax-rust.fsEdit".to_string(),
|
||||
arguments: Some(vec![to_value(ops).unwrap()]),
|
||||
};
|
||||
res.push(cmd)
|
||||
}
|
||||
return Ok(Some(res));
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
enum FsOp {
|
||||
CreateFile {
|
||||
#[serde(with = "url_serde")]
|
||||
uri: Url
|
||||
},
|
||||
MoveFile {
|
||||
#[serde(with = "url_serde")]
|
||||
src: Url,
|
||||
#[serde(with = "url_serde")]
|
||||
dst: Url,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_runnables(
|
||||
world: ServerWorld,
|
||||
params: req::RunnablesParams,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::path::{PathBuf, Path, Component};
|
||||
use im;
|
||||
use libanalysis::{FileId};
|
||||
use relative_path::RelativePath;
|
||||
use libanalysis::{FileId, FileResolver};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct PathMap {
|
||||
|
@ -34,12 +35,6 @@ impl PathMap {
|
|||
.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) {
|
||||
self.path2id.insert(path.clone(), id);
|
||||
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 {
|
||||
let mut components = path.components().peekable();
|
||||
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 id2 = m.get_or_insert(PathBuf::from("/foo/bar.rs"));
|
||||
assert_eq!(
|
||||
m.resolve(id1, &PathBuf::from("bar.rs")),
|
||||
m.resolve(id1, &RelativePath::new("bar.rs")),
|
||||
Some(id2),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -87,11 +87,8 @@ impl ServerWorldState {
|
|||
}
|
||||
|
||||
pub fn snapshot(&self) -> ServerWorld {
|
||||
let pm = self.path_map.clone();
|
||||
ServerWorld {
|
||||
analysis: self.analysis.snapshot(move |id, path| {
|
||||
pm.resolve(id, path)
|
||||
}),
|
||||
analysis: self.analysis.snapshot(self.path_map.clone()),
|
||||
path_map: self.path_map.clone()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue