use std::path::{Path, PathBuf};
fn find_root_upward(start_dir: &Path, markers: &[String]) -> PathBuf {
let abs = start_dir
.canonicalize()
.unwrap_or_else(|_| start_dir.to_path_buf());
if markers.is_empty() {
return abs;
}
let mut cur: &Path = &abs;
loop {
if markers.iter().any(|m| cur.join(m).exists()) {
return cur.to_path_buf();
}
match cur.parent() {
Some(p) => cur = p,
None => return abs.clone(),
}
}
}
fn find_root_upward_capped(start_dir: &Path, cap: &Path, markers: &[String]) -> Option<PathBuf> {
let abs = start_dir
.canonicalize()
.unwrap_or_else(|_| start_dir.to_path_buf());
let mut cur: &Path = &abs;
loop {
if markers.iter().any(|m| cur.join(m).exists()) {
return Some(cur.to_path_buf());
}
if cur == cap {
return None;
}
match cur.parent() {
Some(p) => cur = p,
None => return None,
}
}
}
pub fn discover_root(cwd: &Path, file: Option<&Path>, markers: &[String]) -> PathBuf {
let cwd_abs = cwd.canonicalize().unwrap_or_else(|_| cwd.to_path_buf());
if markers.is_empty() {
return cwd_abs;
}
if markers.iter().any(|m| cwd_abs.join(m).exists()) {
return cwd_abs;
}
if let Some(file) = file {
let file_abs = file.canonicalize().unwrap_or_else(|_| file.to_path_buf());
if let Some(parent) = file_abs.parent() {
if file_abs.starts_with(&cwd_abs) {
if let Some(found) = find_root_upward_capped(parent, &cwd_abs, markers) {
return found;
}
} else {
return find_root_upward(parent, markers);
}
}
}
cwd_abs
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn find_root_upward_walks_to_marker() {
let tmp = std::env::temp_dir().join(format!("vorto-lsp-{}", std::process::id()));
let inner = tmp.join("a/b/c");
std::fs::create_dir_all(&inner).unwrap();
std::fs::write(tmp.join("Cargo.toml"), "").unwrap();
let root = find_root_upward(&inner, &["Cargo.toml".to_string()]);
assert_eq!(root, tmp.canonicalize().unwrap());
let _ = std::fs::remove_dir_all(&tmp);
}
#[test]
fn find_root_upward_handles_relative_path() {
let tmp = std::env::temp_dir().join(format!("vorto-lsp-rel-{}", std::process::id()));
let inner = tmp.join("nested");
std::fs::create_dir_all(&inner).unwrap();
std::fs::write(tmp.join("Cargo.toml"), "").unwrap();
let root = find_root_upward(&inner, &["Cargo.toml".to_string()]);
assert_eq!(root, tmp.canonicalize().unwrap());
let _ = std::fs::remove_dir_all(&tmp);
}
#[test]
fn discover_root_picks_cwd_when_marker_at_cwd() {
let tmp = std::env::temp_dir().join(format!("vorto-disc1-{}", std::process::id()));
std::fs::create_dir_all(&tmp).unwrap();
std::fs::write(tmp.join("Cargo.toml"), "").unwrap();
let root = discover_root(&tmp, None, &["Cargo.toml".to_string()]);
assert_eq!(root, tmp.canonicalize().unwrap());
let _ = std::fs::remove_dir_all(&tmp);
}
#[test]
fn discover_root_walks_up_from_file_to_subproject() {
let tmp = std::env::temp_dir().join(format!("vorto-disc2-{}", std::process::id()));
let subproj = tmp.join("apps/foo");
let src = subproj.join("src");
std::fs::create_dir_all(&src).unwrap();
std::fs::write(subproj.join("Cargo.toml"), "").unwrap();
let file = src.join("main.rs");
std::fs::write(&file, "").unwrap();
let root = discover_root(&tmp, Some(&file), &["Cargo.toml".to_string()]);
assert_eq!(root, subproj.canonicalize().unwrap());
let _ = std::fs::remove_dir_all(&tmp);
}
#[test]
fn discover_root_falls_back_to_cwd_when_no_marker() {
let tmp = std::env::temp_dir().join(format!("vorto-disc3-{}", std::process::id()));
let src = tmp.join("src");
std::fs::create_dir_all(&src).unwrap();
let file = src.join("main.rs");
std::fs::write(&file, "").unwrap();
let root = discover_root(&tmp, Some(&file), &["Cargo.toml".to_string()]);
assert_eq!(root, tmp.canonicalize().unwrap());
let _ = std::fs::remove_dir_all(&tmp);
}
#[test]
fn discover_root_walks_up_for_outside_file() {
let tmp = std::env::temp_dir().join(format!("vorto-disc4-{}", std::process::id()));
let other = std::env::temp_dir().join(format!("vorto-disc4other-{}", std::process::id()));
let nested = other.join("src");
std::fs::create_dir_all(&tmp).unwrap();
std::fs::create_dir_all(&nested).unwrap();
std::fs::write(other.join("Cargo.toml"), "").unwrap();
let file = nested.join("main.rs");
std::fs::write(&file, "").unwrap();
let root = discover_root(&tmp, Some(&file), &["Cargo.toml".to_string()]);
assert_eq!(root, other.canonicalize().unwrap());
let _ = std::fs::remove_dir_all(&tmp);
let _ = std::fs::remove_dir_all(&other);
}
#[test]
fn discover_root_upward_walk_does_not_escape_cwd() {
let outer = std::env::temp_dir().join(format!("vorto-disc5-{}", std::process::id()));
let cwd = outer.join("inner");
let src = cwd.join("src");
std::fs::create_dir_all(&src).unwrap();
std::fs::write(outer.join("Cargo.toml"), "").unwrap();
let file = src.join("main.rs");
std::fs::write(&file, "").unwrap();
let root = discover_root(&cwd, Some(&file), &["Cargo.toml".to_string()]);
assert_eq!(root, cwd.canonicalize().unwrap());
let _ = std::fs::remove_dir_all(&outer);
}
}