use interior mutability for SourceFile lines

Using shared ownership for these may or may not have been a mistake in
the first place. We'll find out.
This commit is contained in:
Qyriad 2026-01-28 14:31:34 +01:00
parent e5d0bdf0c0
commit 34a9c3f864
3 changed files with 136 additions and 15 deletions

View file

@ -1,8 +1,10 @@
use std::{
cell::{Cell, Ref, RefCell},
hash::Hash,
io::{BufRead, BufReader},
io::{BufRead, BufReader, BufWriter},
iter, mem,
ops::{Deref, DerefMut},
sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError},
sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError, RwLock},
};
use crate::Line;
@ -19,6 +21,12 @@ pub struct SourceLine {
pub text: Arc<str>,
}
impl SourceLine {
pub fn text(&self) -> Arc<str> {
Arc::clone(&self.text)
}
}
impl Display for SourceLine {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(
@ -40,17 +48,20 @@ pub struct SourcePath {
pub struct SourceFile {
path: Arc<Path>,
file: Arc<Mutex<File>>,
lines: Arc<OnceLock<Vec<SourceLine>>>,
lines: Arc<OnceLock<RefCell<Vec<SourceLine>>>>,
}
impl SourceFile {
/// Panics if `path` is a directory path instead of a file path.
pub fn open_from(path: Arc<Path>, options: OpenOptions) -> Result<Self, IoError> {
assert!(path.file_name().is_some());
let file = Arc::new(Mutex::new(options.open(&*path)?));
Ok(Self {
path,
file,
lines: Arc::new(OnceLock::new()),
lines: Default::default(),
})
}
@ -67,9 +78,10 @@ impl SourceFile {
Ok(reader)
}
pub fn lines(&self) -> Result<&[SourceLine], IoError> {
fn _lines(&self) -> Result<Ref<'_, [SourceLine]>, IoError> {
if let Some(lines) = self.lines.get() {
return Ok(lines);
let as_slice = Ref::map(lines.borrow(), |lines| lines.as_slice());
return Ok(as_slice);
}
let lines = BufReader::new(&*self.file.lock().unwrap())
.lines()
@ -85,9 +97,94 @@ impl SourceFile {
// Mutex should have dropped by now.
debug_assert!(self.file.try_lock().is_ok());
self.lines.set(lines).unwrap();
self.lines.set(RefCell::new(lines)).unwrap();
Ok(self.lines.get().unwrap().as_slice())
Ok(self._lines_slice())
}
pub fn lines(&self) -> Result<impl Deref<Target = [SourceLine]> + '_, IoError> {
self._lines()
}
pub fn line(&self, line: Line) -> Result<impl Deref<Target = SourceLine> + '_, IoError> {
let lines_lock = self._lines()?;
let line = Ref::map(lines_lock, |lines| &lines[line.index() as usize]);
Ok(line)
}
/// `lines` but already be initialized.
fn _lines_slice(&self) -> Ref<'_, [SourceLine]> {
debug_assert!(self.lines.get().is_some());
Ref::map(self.lines.get().unwrap().borrow(), |lines| lines.as_slice())
}
pub fn insert_line(&mut self, at: Line, text: Arc<str>) -> Result<(), IoError> {
self.lines()?;
let path = self.path();
//let lines = Arc::get_mut(&mut self.lines).unwrap().get_mut().unwrap();
let lines_guard = self.lines.get().unwrap().borrow();
let lines = &*lines_guard;
let first_half = lines.iter().take(at.index() as usize).map(SourceLine::text);
let second_half = lines.iter().skip(at.index() as usize).map(SourceLine::text);
let new_lines: Vec<SourceLine> = first_half
.chain(iter::once(Arc::clone(&text)))
.chain(second_half)
.enumerate()
.map(|(idx, text)| SourceLine {
line: Line::from_index(idx as u64),
text,
path: Arc::clone(&path),
})
.collect();
if cfg!(debug_assertions) {
assert_eq!(new_lines.len(), lines.len() + 1);
let newly = new_lines.get(at.index() as usize);
assert_eq!(newly.map(SourceLine::text), Some(text));
// Assert lines are continuous.
let linenrs: Vec<Line> = new_lines
.iter()
.map(|source_line| source_line.line)
.collect();
assert!(linenrs.is_sorted());
}
// Write it to a file in the same directory.
let new_name: OsString = [
// foo
path.file_name().unwrap(),
OsStr::new(".tmp"),
]
.into_iter()
.collect::<OsString>();
let tmp_path = path.with_file_name(&new_name);
let tmp_file = File::options()
.create(true)
.write(true)
.truncate(true)
.custom_flags(libc::O_EXCL | libc::O_CLOEXEC)
.open(&tmp_path)?;
let mut writer = BufWriter::new(tmp_file);
for line in new_lines.iter() {
writer.write_all(line.text().as_bytes())?;
writer.write_all(b"\n")?;
}
writer.flush()?;
drop(writer);
// Rename the temporary file to the new file, which is atomic (TODO: I think).
fs_err::rename(&tmp_path, &path)?;
drop(lines_guard);
let mut lines_guard = self.lines.get().unwrap().borrow_mut();
// Finally, update state.
let _old_lines = mem::replace(&mut *lines_guard, new_lines);
Ok(())
}
pub fn path(&self) -> Arc<Path> {