use crate::{ buffer::{Buffer, BufferID}, selection::Selection, }; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use thiserror::Error; #[derive(Debug, Clone)] pub enum StateChange { ModifyBuffer { buffer: BufferID, old_start: usize, old_end: usize, old_content: String, new_content: String, }, CreateBuffer { buffer: BufferID, file: Option, }, DeleteBuffer { buffer: BufferID, }, } #[derive(Error, Debug, Clone)] pub enum GroupedChangeError { #[error("Changes for buffer ID {buffer} overlap in regions {first:#?} and {second:#?}")] OverlappingChanges { buffer: BufferID, first: (usize, usize), second: (usize, usize), }, #[error("Buffer ID {buffer} does not exist at time of processing")] BufferNotLive { buffer: BufferID }, #[error("Buffer ID {buffer} already exists at time of processing")] BufferAlreadyExists { buffer: BufferID }, #[error("Inconsistent final buffer set")] InconsistentFinalBufferSet, } impl StateChange { pub fn invert(&self) -> Option { match self { StateChange::ModifyBuffer { buffer, old_start, old_content, new_content, .. } => { let inversion_old_end = old_start + new_content.len(); Some(StateChange::ModifyBuffer { buffer: *buffer, old_start: *old_start, old_end: inversion_old_end, old_content: new_content.clone(), new_content: old_content.clone(), }) } // TODO: Not supported? StateChange::CreateBuffer { .. } => None, StateChange::DeleteBuffer { .. } => None, } } } #[derive(Debug)] pub struct EditorState { pub current_buffer_id: BufferID, current_selection: Selection, buffers: HashMap, buffer_to_file: HashMap>, history: Vec, next_buffer_id: BufferID, } impl EditorState { pub fn new() -> Self { Self { current_buffer_id: 0, current_selection: Selection::empty(), buffers: HashMap::new(), buffer_to_file: HashMap::new(), history: Vec::new(), next_buffer_id: 0, } } pub fn create_buffer( &mut self, name: String, content: String, file: Option, ) -> BufferID { let buffer_id = self.next_buffer_id; self.next_buffer_id += 1; let buffer = Buffer::new(name, content); self.buffers.insert(buffer_id, buffer); self.buffer_to_file.insert(buffer_id, file.clone()); self.commit(StateChange::CreateBuffer { buffer: buffer_id, file, }); buffer_id } pub fn current_buffer(&self) -> Option<&Buffer> { self.buffers.get(&self.current_buffer_id) } pub fn get_buffer(&self, buffer_id: BufferID) -> Option<&Buffer> { self.buffers.get(&buffer_id) } pub fn commit(&mut self, change: StateChange) { match &change { StateChange::ModifyBuffer { buffer, old_start, old_end, new_content, .. } => { if let Some(buf) = self.buffers.get_mut(buffer) { buf.content = format!( "{}{}{}", &buf.content[..*old_start], new_content, &buf.content[*old_end..] ); } } StateChange::CreateBuffer { buffer, file } => { // TODO: do we want this? if !self.buffer_to_file.contains_key(buffer) { self.buffer_to_file.insert(*buffer, file.clone()); } } StateChange::DeleteBuffer { buffer } => { self.buffers.remove(buffer); self.buffer_to_file.remove(buffer); } } self.history.push(change); } pub fn commit_changes(&mut self, changes: Vec) { for change in changes { self.commit(change); } } pub fn validate_grouped_changes( &self, arms: &Vec>, ) -> Result<(), GroupedChangeError> { let initial_buffers: std::collections::HashSet = self.buffers.keys().copied().collect(); self.validate_arms_buffer_liveness(arms, &initial_buffers)?; let all_modifications: Vec<&StateChange> = arms .iter() .flat_map(|arm| arm.iter()) .filter(|c| matches!(c, StateChange::ModifyBuffer { .. })) .collect(); Self::validate_modifications(&all_modifications)?; Ok(()) } pub fn commit_changes_grouped( &mut self, arms: Vec>, ) -> Result<(), GroupedChangeError> { self.validate_grouped_changes(&arms)?; let all_changes: Vec = arms.into_iter().flatten().collect(); let mut creates = Vec::new(); let mut modifies: HashMap> = HashMap::new(); let mut deletes = Vec::new(); let mut deleted_buffers = HashSet::new(); for change in all_changes { match &change { StateChange::CreateBuffer { .. } => creates.push(change), StateChange::ModifyBuffer { buffer, .. } => { modifies.entry(*buffer).or_default().push(change); } StateChange::DeleteBuffer { buffer } => { deleted_buffers.insert(*buffer); deletes.push(change); } } } for change in creates { if let StateChange::CreateBuffer { buffer, .. } = &change { if !deleted_buffers.contains(buffer) { self.commit(change); } } } for (buffer_id, buffer_modifies) in modifies { if !deleted_buffers.contains(&buffer_id) { let merged = Self::merge_for_buffer(buffer_modifies); for change in merged.into_iter().rev() { self.commit(change); } } } for change in deletes { self.commit(change); } Ok(()) } fn validate_arms_buffer_liveness( &self, arms: &[Vec], initial_buffers: &HashSet, ) -> Result<(), GroupedChangeError> { // TODO: make this even smarter, currently it can't prove the consistency of one arm // deleting a pre-existing buffer and the others _not touching it_. let mut final_sets = Vec::new(); for arm in arms { let mut live_buffers = initial_buffers.clone(); for change in arm { match change { StateChange::CreateBuffer { buffer, .. } => { if !live_buffers.insert(*buffer) { return Err(GroupedChangeError::BufferAlreadyExists { buffer: *buffer, }); } } StateChange::DeleteBuffer { buffer } => { if !live_buffers.remove(buffer) { return Err(GroupedChangeError::BufferNotLive { buffer: *buffer }); } } StateChange::ModifyBuffer { buffer, .. } => { if !live_buffers.contains(buffer) { return Err(GroupedChangeError::BufferNotLive { buffer: *buffer }); } } } } final_sets.push(live_buffers); } if final_sets.len() > 1 { let first = &final_sets[0]; if !final_sets.iter().all(|set| set == first) { return Err(GroupedChangeError::InconsistentFinalBufferSet); } } Ok(()) } fn validate_modifications(modifications: &[&StateChange]) -> Result<(), GroupedChangeError> { let mut by_buffer: HashMap> = HashMap::new(); for change in modifications { if let StateChange::ModifyBuffer { buffer, old_start, old_end, .. } = change { by_buffer .entry(*buffer) .or_default() .push((*old_start, *old_end)); } } for (buffer_id, mut ranges) in by_buffer { ranges.sort_unstable(); for window in ranges.windows(2) { let (prev_start, prev_end) = window[0]; let (curr_start, curr_end) = window[1]; if curr_start < prev_end { return Err(GroupedChangeError::OverlappingChanges { buffer: buffer_id, first: (prev_start, prev_end), second: (curr_start, curr_end), }); } } } Ok(()) } fn merge_for_buffer(changes: Vec) -> Vec { let mut sorted = changes; sorted.sort_by_key(|c| match c { StateChange::ModifyBuffer { old_start, .. } => *old_start, _ => 0, }); let mut result = Vec::new(); let mut current_merge: Option<(BufferID, usize, String)> = None; for change in sorted { if let StateChange::ModifyBuffer { buffer, old_start, old_end, new_content, .. } = &change { if old_start == old_end { match &mut current_merge { Some((buf, pos, content)) if *buf == *buffer && *pos == *old_start => { content.push_str(new_content); } Some((buf, pos, content)) => { result.push(StateChange::ModifyBuffer { buffer: *buf, old_start: *pos, old_end: *pos, old_content: String::new(), new_content: content.clone(), }); current_merge = Some((*buffer, *old_start, new_content.clone())); } None => { current_merge = Some((*buffer, *old_start, new_content.clone())); } } } else { if let Some((buf, pos, content)) = current_merge.take() { result.push(StateChange::ModifyBuffer { buffer: buf, old_start: pos, old_end: pos, old_content: String::new(), new_content: content, }); } result.push(change); } } } if let Some((buf, pos, content)) = current_merge { result.push(StateChange::ModifyBuffer { buffer: buf, old_start: pos, old_end: pos, old_content: String::new(), new_content: content, }); } result } } pub type StateResult = (Selection, Vec);