create module smartly

This commit is contained in:
Aleksey Kladov 2018-08-28 18:22:52 +03:00
parent 748a4cacd2
commit d34588bf83
12 changed files with 344 additions and 182 deletions

View file

@ -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;
}

View file

@ -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"

View file

@ -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)
} }

View file

@ -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,
});
}
} }
} }

View file

@ -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 })]"#,

View file

@ -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()
}
}

View file

@ -0,0 +1,3 @@
mod fn_scope;
pub use self::fn_scope::FnScopes;

View file

@ -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"

View file

@ -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;

View file

@ -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,

View file

@ -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),
) )
} }

View file

@ -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()
} }
} }