use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub struct SourceCache {
roots: Vec<PathBuf>,
files: HashMap<String, String>,
}
impl SourceCache {
pub fn new(roots: impl IntoIterator<Item = PathBuf>) -> Self {
Self {
roots: roots.into_iter().collect(),
files: HashMap::new(),
}
}
pub fn load(&mut self, file_name: &str) -> Option<&str> {
if self.files.contains_key(file_name) {
return self.files.get(file_name).map(|s| s.as_str());
}
let path = self.resolve_path(file_name)?;
let text = std::fs::read_to_string(&path).ok()?;
self.files.insert(file_name.to_string(), text);
self.files.get(file_name).map(|s| s.as_str())
}
fn resolve_path(&self, file_name: &str) -> Option<PathBuf> {
let direct = Path::new(file_name);
if direct.is_file() {
return Some(direct.to_path_buf());
}
for root in &self.roots {
let candidate = root.join(file_name);
if candidate.is_file() {
return Some(candidate);
}
}
None
}
}
pub fn extract_span_snippet(source: &str, span: &[i32]) -> Option<String> {
if span.len() < 3 {
return None;
}
let lines: Vec<&str> = source.split('\n').collect();
let (start_line, start_col, end_line, end_col) = match span {
[sl, sc, ec] => (*sl, *sc, *sl, *ec),
[sl, sc, el, ec] => (*sl, *sc, *el, *ec),
_ => return None,
};
let start_line = usize::try_from(start_line).ok()?;
let end_line = usize::try_from(end_line).ok()?;
let start_col = usize::try_from(start_col).ok()?;
let end_col = usize::try_from(end_col).ok()?;
if start_line >= lines.len() || end_line >= lines.len() {
return None;
}
if start_line == end_line {
let line = lines[start_line];
let end = end_col.min(line.len());
if start_col > end {
return None;
}
return Some(line[start_col..end].to_string());
}
let mut out = String::new();
for (i, line) in lines.iter().enumerate().take(end_line + 1).skip(start_line) {
if i == start_line {
out.push_str(&line[start_col.min(line.len())..]);
} else if i == end_line {
out.push_str(&line[..end_col.min(line.len())]);
} else {
out.push_str(line);
}
if i != end_line {
out.push('\n');
}
}
Some(out)
}
pub fn push_indented_lines(out: &mut String, text: &str, indent: &str) {
for line in text.lines() {
out.push_str(indent);
out.push_str(line);
out.push('\n');
}
}
pub fn span_from_location(span: &[i32]) -> Option<switchback_traits::Span> {
if span.len() < 3 {
return None;
}
let (start_line, start_col, end_line, end_col) = match span {
[sl, sc, ec] => (*sl, *sc, *sl, *ec),
[sl, sc, el, ec] => (*sl, *sc, *el, *ec),
_ => return None,
};
Some(switchback_traits::Span {
start_line: (start_line + 1) as u32,
start_col: (start_col + 1) as u32,
end_line: (end_line + 1) as u32,
end_col: (end_col + 1) as u32,
})
}