use std::collections::HashMap;
use camino::{Utf8Path, Utf8PathBuf};
use libpijul::ArcTxn;
use libpijul::ChannelRef;
use libpijul::changestore::ChangeStore;
use libpijul::pristine::sanakirja::MutTxn;
use libpijul::working_copy::WorkingCopy;
use libpijul::working_copy::WorkingCopyRead;
use crate::file_system::changes::unrecorded::UnrecordedChangesError;
use crate::file_system::changes::unrecorded::UnrecordedState;
use crate::file_system::open_file::OpenFile;
#[derive(Debug, thiserror::Error)]
pub enum UpdateOpenFileContentsError {
#[error("No file open at {0}")]
NoMatchingFile(Utf8PathBuf),
}
#[derive(Debug, thiserror::Error)]
pub enum UpdateOpenFileCreditsError<C: std::error::Error + 'static, W: std::error::Error + 'static>
{
#[error("No file open at {0}")]
NoMatchingFile(Utf8PathBuf),
#[error(transparent)]
UnrecordedChanges(#[from] UnrecordedChangesError<C, W>),
}
#[derive(Clone)]
pub struct EditorWorkingCopy<W: WorkingCopy + Clone + Send + Sync + 'static> {
pub working_copy: W,
pub open_files: HashMap<Utf8PathBuf, OpenFile>,
}
impl<W: WorkingCopy + Clone + Send + Sync + 'static> EditorWorkingCopy<W> {
pub fn new(working_copy: W) -> Self {
Self {
working_copy,
open_files: HashMap::new(),
}
}
pub fn update_open_file_contents(
&mut self,
path: &Utf8Path,
character_offset: usize,
characters_replaced: usize,
replacement_text: &str,
) -> Result<(), UpdateOpenFileContentsError> {
let open_file = self
.open_files
.get_mut(path)
.ok_or_else(|| UpdateOpenFileContentsError::NoMatchingFile(path.to_path_buf()))?;
if characters_replaced > 0 {
open_file
.contents
.text
.remove(character_offset..(character_offset + characters_replaced));
}
if !replacement_text.is_empty() {
open_file
.contents
.text
.insert(character_offset, replacement_text);
}
Ok(())
}
pub fn update_open_file_credits<C>(
&mut self,
path: &Utf8Path,
transaction: &ArcTxn<MutTxn<()>>,
channel: &ChannelRef<MutTxn<()>>,
change_store: &C,
) -> Result<(), UpdateOpenFileCreditsError<C::Error, W::Error>>
where
C: ChangeStore + Clone + Send + 'static,
{
if !self.open_files.contains_key(path) {
return Err(UpdateOpenFileCreditsError::NoMatchingFile(
path.to_path_buf(),
));
}
let unrecorded_state = match self.open_files.get(path).unwrap().credits.is_some() {
true => Some(UnrecordedState::new(
path,
transaction,
channel,
change_store,
self,
)?),
false => None,
};
if let Some(file_credits) = &mut self.open_files.get_mut(path).unwrap().credits {
file_credits.unrecorded_state = unrecorded_state.unwrap();
}
let open_file = self.open_files.get_mut(path).unwrap();
if let Some(file_credits) = &mut open_file.credits {
file_credits.spans = file_credits.recompute_spans(&open_file.contents);
}
Ok(())
}
}
impl<W: WorkingCopy + Clone + Send + Sync + 'static> WorkingCopyRead for EditorWorkingCopy<W> {
type Error = W::Error;
fn file_metadata(&self, file: &str) -> Result<libpijul::pristine::InodeMetadata, Self::Error> {
self.working_copy.file_metadata(file)
}
fn read_file(&self, file: &str, buffer: &mut Vec<u8>) -> Result<(), Self::Error> {
match self.open_files.get(Utf8Path::new(file)) {
Some(open_file) => buffer.extend(open_file.contents.text.bytes()),
None => self.working_copy.read_file(file, buffer)?,
}
Ok(())
}
fn modified_time(&self, file: &str) -> Result<std::time::SystemTime, Self::Error> {
match self.open_files.get(Utf8Path::new(file)) {
Some(open_file) => Ok(open_file.contents.modified_time.into()),
None => self.working_copy.modified_time(file),
}
}
}