use std::{fs, ops::Range, path::PathBuf, sync::Arc};
use arcstr::{ArcStr, Substr};
use tracing::{error, trace};
use crate::syntax::{
TextChunk,
location::{FilePos, LineColCounter, LspCol, OriginId, Pos, ROOT_ORIGIN},
};
use super::{FileHandle, PkgHandle, Project, querry::Queryable};
pub struct File {
pub path: Option<PathBuf>,
}
impl File {
pub fn new(path: Option<PathBuf>) -> Self {
Self { path }
}
}
#[derive(Debug)]
pub struct FileContents {
pub contents: ArcStr,
line_lookup: Vec<(u32, LspCol)>,
end_pos: FilePos,
}
impl Queryable for FileContents {
type Input = FileHandle;
type Ctx = ();
type Backref = PkgHandle;
fn compute(input: Self::Input, _ctx: &Self::Ctx, proj: &Project) -> Arc<Self> {
if let Some(file) = proj.content_overlay.get(&input) {
return file.clone();
}
let file = proj.get_file(input);
let Some(path) = file.path.as_ref() else {
error!("FileContents::compute called on FileHandle without local path");
return Arc::new(FileContents::new("".into()));
};
trace!("reading source file '{}'", path.display());
let text = match fs::read_to_string(path) {
Ok(text) => text,
Err(e) => {
error!("could not open file {}: {e}", path.display());
return Arc::new(FileContents::new("".into()));
}
};
Arc::new(FileContents::new(text.into()))
}
fn invalidate(&self, _input: &Self::Input, _ctx: &Self::Ctx, _proj: &Project) {}
fn invalidate_backref(&self, backref: Self::Backref, proj: &Project) {
proj.syntax.invalidate(proj, &backref);
}
}
impl FileContents {
pub fn new(contents: ArcStr) -> Self {
let (line_lookup, end_pos) = Self::gen_line_lookup(&contents);
FileContents {
contents,
line_lookup,
end_pos,
}
}
fn gen_line_lookup(text: &str) -> (Vec<(u32, LspCol)>, FilePos) {
let mut line_ofs = 0;
let mut cols = LspCol::start();
let mut line = 0;
let mut counter = LineColCounter::default();
let mut out = Vec::new();
for (ofs, c) in text.char_indices() {
if counter.pos(Some(c)).line != line {
out.push((line_ofs, cols));
line_ofs = ofs as u32;
line += 1;
cols = LspCol::start();
}
cols.advance(c);
counter.advance(c);
}
out.push((line_ofs, cols));
if counter.pos(None).col == LspCol::start() {
out.push((text.len() as u32, LspCol::start()));
}
(out, counter.pos(None))
}
pub fn pos_add(&self, start: FilePos, ofs: LspCol) -> FilePos {
let mut line = start.line;
let mut col = start.col.to_raw() + ofs.to_raw();
while col > self.line_width(line) {
col -= self.line_width(line);
line += 1;
}
FilePos {
line,
col: LspCol::from_raw(col),
}
}
pub fn pos_diff(&self, range: Range<FilePos>) -> LspCol {
let diff = (range.start.line..range.end.line)
.map(|l| self.line_width(l))
.sum::<u32>()
+ range.end.col.to_raw()
- range.start.col.to_raw();
LspCol::from_raw(diff)
}
pub fn line_width(&self, line: u32) -> u32 {
self.line_lookup[line as usize].1.to_raw()
}
pub fn chunks(&self, origin: OriginId) -> Vec<TextChunk> {
if self.contents.is_empty() {
Vec::new()
} else {
vec![TextChunk {
pos: Pos::at(origin, 0, 0),
text: Substr::full(self.contents.clone()),
}]
}
}
pub fn end_pos(&self) -> Pos {
Pos {
origin: ROOT_ORIGIN,
text_pos: self.end_pos,
}
}
}