aboutsummaryrefslogtreecommitdiff
path: root/rprt-engine/src/state.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rprt-engine/src/state.rs')
-rw-r--r--rprt-engine/src/state.rs277
1 files changed, 246 insertions, 31 deletions
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>);