This commit is contained in:
Qyriad 2026-01-28 19:30:59 +01:00
parent 34a9c3f864
commit 551e5a7851
4 changed files with 163 additions and 33 deletions

View file

@ -48,6 +48,8 @@ pub struct SourcePath {
pub struct SourceFile {
path: Arc<Path>,
file: Arc<Mutex<File>>,
/// References to `SourceFile` do not prevent mutating `lines`.
/// Also `lines` is lazily initialized.
lines: Arc<OnceLock<RefCell<Vec<SourceLine>>>>,
}
@ -119,6 +121,75 @@ impl SourceFile {
Ref::map(self.lines.get().unwrap().borrow(), |lines| lines.as_slice())
}
/// With debug assertions, panics if `lines` are not contiguous.
pub fn insert_lines(&mut self, new_lines: &[SourceLine]) -> Result<(), IoError> {
if new_lines.is_empty() {
return Ok(());
}
debug_assert!(new_lines.is_sorted_by(|lhs, rhs| lhs.line.next() == rhs.line));
let path = self.path();
let cur_lines = self.lines()?;
let first_half = cur_lines
.iter()
.take(new_lines.last().unwrap().line.prev().index() as usize)
.map(SourceLine::text);
let middle = new_lines.iter().map(SourceLine::text);
let second_half = cur_lines
.iter()
.skip(new_lines.last().unwrap().line.prev().index() as usize)
.map(SourceLine::text);
let final_lines: Vec<SourceLine> = first_half
.chain(middle)
.chain(second_half)
.enumerate()
.map(|(idx, text)| SourceLine {
line: Line::from_index(idx as u64),
text,
path: self.path(),
})
.collect();
// Assert lines are continuous.
debug_assert!(final_lines.is_sorted_by(|lhs, rhs| lhs.line.next() == rhs.line));
debug_assert_eq!(cur_lines.len() + new_lines.len(), final_lines.len());
drop(cur_lines);
// 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 final_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)?;
// Finally, update state.
self.lines.get().unwrap().replace(final_lines);
Ok(())
}
pub fn insert_line(&mut self, at: Line, text: Arc<str>) -> Result<(), IoError> {
self.lines()?;
let path = self.path();