don't require existing override
This commit is contained in:
parent
7bce1e7a6e
commit
80ff0b36cb
5 changed files with 120 additions and 191 deletions
167
src/source.rs
167
src/source.rs
|
|
@ -1,10 +1,10 @@
|
|||
use std::{
|
||||
cell::{Cell, Ref, RefCell},
|
||||
cell::{Ref, RefCell},
|
||||
hash::Hash,
|
||||
io::{BufRead, BufReader, BufWriter},
|
||||
iter, mem,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError, RwLock},
|
||||
ops::Deref,
|
||||
ptr,
|
||||
sync::{Arc, Mutex, OnceLock},
|
||||
};
|
||||
|
||||
use crate::Line;
|
||||
|
|
@ -13,6 +13,33 @@ use crate::color::{ANSI_CYAN, ANSI_GREEN, ANSI_MAGENTA, ANSI_RESET};
|
|||
use crate::prelude::*;
|
||||
|
||||
use fs_err::OpenOptions;
|
||||
use itertools::Itertools;
|
||||
|
||||
pub fn replace_file<'a>(
|
||||
path: &Path,
|
||||
contents: impl IntoIterator<Item = &'a [u8]>,
|
||||
) -> Result<(), IoError> {
|
||||
let tmp_path = path.with_added_extension(".tmp");
|
||||
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 slice in contents {
|
||||
writer.write_all(slice)?;
|
||||
}
|
||||
|
||||
writer.flush()?;
|
||||
drop(writer);
|
||||
|
||||
// Rename the temporary file to the new file, which is atomic (TODO: I think).
|
||||
fs_err::rename(&tmp_path, &path)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub struct SourceLine {
|
||||
|
|
@ -25,6 +52,30 @@ impl SourceLine {
|
|||
pub fn text(&self) -> Arc<str> {
|
||||
Arc::clone(&self.text)
|
||||
}
|
||||
|
||||
pub fn text_ref(&self) -> &str {
|
||||
&self.text
|
||||
}
|
||||
|
||||
pub fn text_bytes(&self) -> Arc<[u8]> {
|
||||
let len: usize = self.text.as_bytes().len();
|
||||
|
||||
// We need to consume an Arc, but we are &self.
|
||||
let text = Arc::clone(&self.text);
|
||||
let str_ptr: *const str = Arc::into_raw(text);
|
||||
let start: *const u8 = str_ptr.cast();
|
||||
let slice_ptr: *const [u8] = ptr::slice_from_raw_parts(start, len);
|
||||
|
||||
unsafe { Arc::<[u8]>::from_raw(slice_ptr) }
|
||||
}
|
||||
|
||||
pub fn text_bytes_ref(&self) -> &[u8] {
|
||||
self.text.as_bytes()
|
||||
}
|
||||
|
||||
pub fn path(&self) -> Arc<Path> {
|
||||
Arc::clone(&self.path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SourceLine {
|
||||
|
|
@ -39,11 +90,6 @@ impl Display for SourceLine {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub struct SourcePath {
|
||||
path: Arc<Path>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SourceFile {
|
||||
path: Arc<Path>,
|
||||
|
|
@ -126,6 +172,7 @@ impl SourceFile {
|
|||
if new_lines.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let num_lines_before_new = new_lines.last().unwrap().line.prev().index() as usize;
|
||||
|
||||
debug_assert!(new_lines.is_sorted_by(|lhs, rhs| lhs.line.next() == rhs.line));
|
||||
|
||||
|
|
@ -133,12 +180,12 @@ impl SourceFile {
|
|||
let cur_lines = self.lines()?;
|
||||
let first_half = cur_lines
|
||||
.iter()
|
||||
.take(new_lines.last().unwrap().line.prev().index() as usize)
|
||||
.take(num_lines_before_new)
|
||||
.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)
|
||||
.skip(num_lines_before_new)
|
||||
.map(SourceLine::text);
|
||||
|
||||
let final_lines: Vec<SourceLine> = first_half
|
||||
|
|
@ -158,31 +205,11 @@ impl SourceFile {
|
|||
|
||||
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)?;
|
||||
let data = final_lines
|
||||
.iter()
|
||||
.map(SourceLine::text_bytes_ref)
|
||||
.pipe(|iterator| Itertools::intersperse(iterator, b"\n"));
|
||||
replace_file(&path, data)?;
|
||||
|
||||
// Finally, update state.
|
||||
self.lines.get().unwrap().replace(final_lines);
|
||||
|
|
@ -190,74 +217,6 @@ impl SourceFile {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
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> {
|
||||
Arc::clone(&self.path)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue