diff options
| author | tslil <tslil@posteo.de> | 2025-10-20 20:34:04 +0100 |
|---|---|---|
| committer | tslil <tslil@posteo.de> | 2025-10-20 22:23:02 +0100 |
| commit | caf3cbdd94ec8341f3233bb31c04c72f99502ed9 (patch) | |
| tree | 774b8433258338fe57feda77d7691bc4ee4a9b1a /rprt-engine | |
| parent | d05f62b20fe5ce0d7fa47f2188197912cf9436f6 (diff) | |
Changed monad, impl. some grouped semantics, not completely satisfied
Diffstat (limited to 'rprt-engine')
| -rw-r--r-- | rprt-engine/src/monad.rs | 58 | ||||
| -rw-r--r-- | rprt-engine/src/selection.rs | 1 | ||||
| -rw-r--r-- | rprt-engine/src/state.rs | 277 |
3 files changed, 288 insertions, 48 deletions
diff --git a/rprt-engine/src/monad.rs b/rprt-engine/src/monad.rs index ae0df3e..34ff46b 100644 --- a/rprt-engine/src/monad.rs +++ b/rprt-engine/src/monad.rs @@ -1,6 +1,6 @@ use crate::{ - selection::Selection, - state::{EditorState, EvaluationResult}, + selection::{Selection, SelectionError}, + state::{EditorState, EvaluationResult, GroupedChangeError}, }; pub enum EvaluationStrategy { @@ -8,43 +8,67 @@ pub enum EvaluationStrategy { Grouped, } -pub type MonadicState = (EditorState, EvaluationResult); +#[derive(Debug)] +pub enum EvaluationError { + Selection(SelectionError), + GroupedChange(GroupedChangeError), +} + +impl From<SelectionError> for EvaluationError { + fn from(err: SelectionError) -> Self { + EvaluationError::Selection(err) + } +} + +impl From<GroupedChangeError> for EvaluationError { + fn from(err: GroupedChangeError) -> Self { + EvaluationError::GroupedChange(err) + } +} pub struct EditorStateMonad { - func: Box<dyn Fn(EditorState) -> MonadicState>, + func: Box<dyn Fn(&EditorState) -> EvaluationResult>, } impl EditorStateMonad { - pub fn new(f: impl Fn(EditorState) -> MonadicState + 'static) -> Self { + pub fn new(f: impl Fn(&EditorState) -> EvaluationResult + 'static) -> Self { Self { func: Box::new(f) } } - pub fn run(&self, initial_state: EditorState) -> MonadicState { - (self.func)(initial_state) + pub fn run(&self, state: &EditorState) -> EvaluationResult { + (self.func)(state) } - pub fn run_with_strategy<E>( + pub fn run_with_strategy( monads: Vec<EditorStateMonad>, strategy: EvaluationStrategy, - initial_state: EditorState, - ) -> Result<MonadicState, E> { + state: &mut EditorState, + ) -> Result<Selection, EvaluationError> { match strategy { EvaluationStrategy::Sequential => { - let mut state_changes = Vec::new(); let mut last_selection = Selection::empty(); - let mut current_state = initial_state; for monad in monads { - let (new_state, (sel, st_ch)) = monad.run(current_state); - current_state = new_state; + let (sel, state_changes) = monad.run(state); + state.commit_changes(state_changes); last_selection = sel; - state_changes.extend(st_ch); } - Ok((current_state, (last_selection, state_changes))) + Ok(last_selection) } EvaluationStrategy::Grouped => { - panic!("TODO") + let mut arms = Vec::new(); + let mut all_selections = Vec::new(); + + for monad in monads { + let (sel, state_changes) = monad.run(state); + all_selections.push(sel); + arms.push(state_changes); + } + + state.commit_changes_grouped(arms)?; + + Ok(Selection::union(all_selections)?) } } } diff --git a/rprt-engine/src/selection.rs b/rprt-engine/src/selection.rs index 968d859..9e59ba5 100644 --- a/rprt-engine/src/selection.rs +++ b/rprt-engine/src/selection.rs @@ -23,6 +23,7 @@ impl Display for Rank { } } +#[derive(Debug)] pub enum SelectionError { InvalidPromotion { from: Rank, to: Rank }, } diff --git a/rprt-engine/src/state.rs b/rprt-engine/src/state.rs index 7964876..9487d6d 100644 --- a/rprt-engine/src/state.rs +++ b/rprt-engine/src/state.rs @@ -1,18 +1,10 @@ use crate::buffer::{Buffer, BufferID}; use crate::selection::Selection; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::path::PathBuf; #[derive(Debug, Clone)] pub enum StateChange { - SetCurrentSelection { - old_selection: Selection, - new_selection: Selection, - }, - SetCurrentBuffer { - old_buffer: BufferID, - new_buffer: BufferID, - }, ModifyBuffer { buffer: BufferID, old_start: usize, @@ -29,24 +21,41 @@ pub enum StateChange { }, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum GroupedChangeError { + OverlappingChanges { + buffer: BufferID, + first: (usize, usize), + second: (usize, usize), + }, + BufferNotLive { + buffer: BufferID, + }, + BufferAlreadyExists { + buffer: BufferID, + }, + InconsistentFinalBufferSet, +} + impl StateChange { pub fn invert(&self) -> Option<Self> { match self { - StateChange::SetCurrentSelection { - old_selection, - new_selection, - } => Some(StateChange::SetCurrentSelection { - old_selection: new_selection.clone(), - new_selection: old_selection.clone(), - }), - StateChange::SetCurrentBuffer { - old_buffer, - new_buffer, - } => Some(StateChange::SetCurrentBuffer { - old_buffer: *new_buffer, - new_buffer: *old_buffer, - }), - StateChange::ModifyBuffer { .. } => None, // TODO: implement, irritating coordinate stuff + 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, @@ -89,7 +98,7 @@ impl EditorState { self.buffers.insert(buffer_id, buffer); self.buffer_to_file.insert(buffer_id, file.clone()); - self.history.push(StateChange::CreateBuffer { + self.commit(StateChange::CreateBuffer { buffer: buffer_id, file, }); @@ -107,12 +116,6 @@ impl EditorState { pub fn commit(&mut self, change: StateChange) { match &change { - StateChange::SetCurrentSelection { new_selection, .. } => { - self.current_selection = new_selection.clone(); - } - StateChange::SetCurrentBuffer { new_buffer, .. } => { - self.current_buffer = *new_buffer; - } StateChange::ModifyBuffer { buffer, old_start, @@ -148,6 +151,218 @@ impl EditorState { self.commit(change); } } + + pub fn commit_changes_grouped( + &mut self, + arms: Vec<Vec<StateChange>>, + ) -> Result<(), GroupedChangeError> { + let initial_buffers: std::collections::HashSet<BufferID> = + 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)?; + + let all_changes: Vec<StateChange> = arms.into_iter().flatten().collect(); + + let mut creates = Vec::new(); + let mut modifies: HashMap<BufferID, Vec<StateChange>> = 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<StateChange>], + initial_buffers: &HashSet<BufferID>, + ) -> 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<BufferID, Vec<(usize, usize)>> = 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<StateChange>) -> Vec<StateChange> { + 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 EvaluationResult = (Selection, Vec<StateChange>); |
