aboutsummaryrefslogtreecommitdiff
path: root/rprt-engine/src
diff options
context:
space:
mode:
Diffstat (limited to 'rprt-engine/src')
-rw-r--r--rprt-engine/src/buffer.rs51
-rw-r--r--rprt-engine/src/lib.rs7
-rw-r--r--rprt-engine/src/selection.rs288
3 files changed, 346 insertions, 0 deletions
diff --git a/rprt-engine/src/buffer.rs b/rprt-engine/src/buffer.rs
new file mode 100644
index 0000000..f536054
--- /dev/null
+++ b/rprt-engine/src/buffer.rs
@@ -0,0 +1,51 @@
+use std::collections::hash_map::DefaultHasher;
+use std::hash::{Hash, Hasher};
+use std::process::Command;
+use std::time::{SystemTime, UNIX_EPOCH};
+
+pub type BufferID = usize;
+pub struct Buffer {
+ pub content: String,
+ pub name: String,
+}
+
+impl Buffer {
+ pub fn new(name: String, content: String) -> Self {
+ Self {
+ name,
+ content,
+ }
+ }
+
+ fn generate_shell_name(command: &str) -> String {
+ let mut hasher = DefaultHasher::new();
+ command.hash(&mut hasher);
+ let command_hash = hasher.finish();
+
+ let timestamp = match SystemTime::now().duration_since(UNIX_EPOCH) {
+ Ok(x) => x.as_secs(),
+ Err(_) => 0,
+ };
+
+ format!("shell_{:x}_{}", command_hash, timestamp)
+ }
+
+ pub fn from_shell_command(command: &str) -> Self {
+ let name = Self::generate_shell_name(command);
+
+ let output = Command::new("sh").arg("-c").arg(command).output();
+
+ let content = match output {
+ Ok(output) => match String::from_utf8(output.stdout) {
+ Ok(s) => s,
+ Err(e) => format!("UTF-8 decode error: {}", e),
+ },
+ Err(e) => format!("Command failed: {}", e),
+ };
+
+ Self {
+ name,
+ content,
+ }
+ }
+}
diff --git a/rprt-engine/src/lib.rs b/rprt-engine/src/lib.rs
new file mode 100644
index 0000000..416b65f
--- /dev/null
+++ b/rprt-engine/src/lib.rs
@@ -0,0 +1,7 @@
+// RPRT Engine - Core text editing functionality
+
+pub mod buffer;
+pub mod selection;
+
+pub use buffer::*;
+pub use selection::*;
diff --git a/rprt-engine/src/selection.rs b/rprt-engine/src/selection.rs
new file mode 100644
index 0000000..9494544
--- /dev/null
+++ b/rprt-engine/src/selection.rs
@@ -0,0 +1,288 @@
+use crate::buffer::BufferID;
+use either::Either;
+use std::collections::HashMap;
+use std::collections::HashSet;
+use std::fmt::Display;
+
+#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Clone, Copy)]
+pub enum Rank {
+ Zero,
+ One,
+ Two,
+ Three,
+}
+
+impl Display for Rank {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Rank::Zero => write!(f, "Rank 0"),
+ Rank::One => write!(f, "Rank 1"),
+ Rank::Two => write!(f, "Rank 2"),
+ Rank::Three => write!(f, "Rank 3"),
+ }
+ }
+}
+
+pub enum SelectionError {
+ InvalidPromotion { from: Rank, to: Rank },
+}
+
+impl std::fmt::Display for SelectionError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ SelectionError::InvalidPromotion { from, to } => {
+ write!(f, "Cannot change selection of {} to {}", from, to)
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct Interval {
+ pub start: usize,
+ pub end: usize,
+ capture_groups: Vec<(usize, usize)>,
+}
+
+impl Interval {
+ pub fn is_disjoint(&self, other: &Interval) -> bool {
+ self.start >= other.end || self.end <= other.start
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum Selection {
+ Position {
+ pos: usize,
+ buffer: BufferID,
+ },
+ Range {
+ buffer: BufferID,
+ interval: Interval,
+ },
+ Ranges {
+ buffer: BufferID,
+ ranges: Vec<Interval>,
+ },
+ MultiRanges {
+ multi_ranges: HashMap<BufferID, Vec<Interval>>,
+ },
+}
+
+impl Selection {
+ pub fn empty() -> Self {
+ Self::MultiRanges {
+ multi_ranges: HashMap::new(),
+ }
+ }
+
+ pub fn ranges_disjoint(ranges: &[Interval]) -> bool {
+ if ranges.len() < 1 {
+ return true;
+ }
+
+ let mut sorted_by_start: Vec<&Interval> = ranges.iter().collect::<Vec<_>>();
+ sorted_by_start.sort_by_key(|i| i.start);
+ sorted_by_start.windows(2).all(|w| w[0].is_disjoint(&w[1]))
+ }
+
+ pub fn is_disjoint(&self) -> bool {
+ match self {
+ Self::Position { .. } => true,
+ Self::Range { .. } => true,
+ Self::Ranges { ranges, .. } => Self::ranges_disjoint(ranges),
+ Self::MultiRanges { multi_ranges } => {
+ for (_, intervals) in multi_ranges {
+ if !Self::ranges_disjoint(intervals) {
+ return false;
+ }
+ }
+ true
+ }
+ }
+ }
+
+ pub fn rank(&self) -> Rank {
+ match self {
+ Self::Position { .. } => Rank::Zero,
+ Self::Range { .. } => Rank::One,
+ Self::Ranges { .. } => Rank::Two,
+ Self::MultiRanges { .. } => Rank::Three,
+ }
+ }
+
+ pub fn promote(&self, to_rank: Rank) -> Result<Self, SelectionError> {
+ use Rank::*;
+
+ match (self, to_rank) {
+ (Self::Position { .. }, Zero) => Ok(self.clone()),
+ (Self::Range { .. }, One) => Ok(self.clone()),
+ (Self::Ranges { .. }, Two) => Ok(self.clone()),
+ (Self::MultiRanges { .. }, Three) => Ok(self.clone()),
+
+ // Position
+ (Self::Position { pos, buffer }, One) => Ok(Self::Range {
+ interval: Interval {
+ start: pos + 0,
+ end: pos + 1,
+ capture_groups: Vec::new(),
+ },
+ buffer: *buffer,
+ }),
+ (Self::Position { pos, buffer }, Two) => Ok(Self::Ranges {
+ ranges: vec![Interval {
+ start: pos + 0,
+ end: pos + 1,
+ capture_groups: Vec::new(),
+ }],
+ buffer: *buffer,
+ }),
+ (Self::Position { pos, buffer }, Three) => {
+ let mut map = HashMap::new();
+ map.insert(
+ *buffer,
+ vec![Interval {
+ start: pos + 0,
+ end: pos + 1,
+ capture_groups: Vec::new(),
+ }],
+ );
+ Ok(Self::MultiRanges { multi_ranges: map })
+ }
+
+ // Range promotions
+ (Self::Range { interval, buffer }, Two) => Ok(Self::Ranges {
+ ranges: vec![interval.clone()],
+ buffer: *buffer,
+ }),
+ (Self::Range { interval, buffer }, Three) => {
+ let mut map = HashMap::new();
+ map.insert(*buffer, vec![interval.clone()]);
+ Ok(Self::MultiRanges { multi_ranges: map })
+ }
+
+ // Ranges promotion
+ (Self::Ranges { ranges, buffer }, Three) => {
+ let mut map = HashMap::new();
+ map.insert(*buffer, ranges.clone());
+ Ok(Self::MultiRanges { multi_ranges: map })
+ }
+
+ // all other cases
+ (sel, target) => Err(SelectionError::InvalidPromotion {
+ from: sel.rank(),
+ to: target,
+ }),
+ }
+ }
+
+ pub fn buffers(&self) -> Vec<BufferID> {
+ match self {
+ Self::Position { buffer, .. } => vec![*buffer],
+ Self::Range { buffer, .. } => vec![*buffer],
+ Self::Ranges { buffer, .. } => vec![*buffer],
+ Self::MultiRanges { multi_ranges } => multi_ranges.keys().copied().collect(),
+ }
+ }
+
+ pub fn union(selections: Vec<Self>) -> Result<Self, SelectionError> {
+ if selections.is_empty() {
+ return Ok(Self::empty());
+ }
+
+ let all_buffers: HashSet<BufferID> = selections.iter().flat_map(|s| s.buffers()).collect();
+
+ if all_buffers.is_empty() {
+ return Ok(Self::empty());
+ }
+
+ let max_rank = if all_buffers.len() > 1 {
+ Rank::Three
+ } else {
+ selections
+ .iter()
+ .map(|s| s.rank())
+ .max()
+ .unwrap_or(Rank::Two)
+ .max(Rank::Two) // At least rank 2
+ };
+
+ let promoted: Result<Vec<Self>, SelectionError> =
+ selections.iter().map(|s| s.promote(max_rank)).collect();
+ let promoted = promoted?;
+
+ match max_rank {
+ Rank::Two => {
+ let mut all_intervals = Vec::new();
+ let buffer = *all_buffers.iter().next().unwrap();
+
+ for sel in &promoted {
+ match sel {
+ Self::Ranges { ranges, .. } => all_intervals.extend(ranges.clone()),
+ _ => unreachable!("promote() to Rank::Two should always produce Ranges"),
+ }
+ }
+
+ Ok(Self::Ranges {
+ ranges: all_intervals,
+ buffer,
+ })
+ }
+ Rank::Three => {
+ let mut buffer_intervals: HashMap<BufferID, Vec<Interval>> =
+ all_buffers.iter().map(|&b| (b, Vec::new())).collect();
+
+ for sel in &promoted {
+ match sel {
+ Self::MultiRanges { multi_ranges } => {
+ for (&buffer, intervals) in multi_ranges {
+ buffer_intervals
+ .entry(buffer)
+ .or_insert_with(Vec::new)
+ .extend(intervals.clone());
+ }
+ }
+ _ => unreachable!(
+ "promote() to Rank::Three should always produce MultiRanges"
+ ),
+ }
+ }
+
+ Ok(Self::MultiRanges {
+ multi_ranges: buffer_intervals,
+ })
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ pub fn broadcast<E>(
+ &self,
+ fn_rank_zero: &impl Fn(&BufferID, &usize) -> Result<Self, E>,
+ fn_rank_one: &impl Fn(&BufferID, &Interval) -> Result<Self, E>,
+ ) -> Result<Self, Either<E, SelectionError>> {
+ let do_rank_one = |b: &usize, rs: &Vec<Interval>| {
+ rs.iter()
+ .map(|int| fn_rank_one(b, int).map_err(Either::Left))
+ .collect::<Result<_, _>>()
+ };
+
+ match self {
+ Self::Position { buffer, pos } => fn_rank_zero(buffer, pos).map_err(Either::Left),
+ Self::Range { buffer, interval } => fn_rank_one(buffer, interval).map_err(Either::Left),
+ Self::Ranges { buffer, ranges } => {
+ let results = do_rank_one(buffer, ranges)?;
+ Self::union(results).map_err(Either::Right)
+ }
+ Self::MultiRanges { multi_ranges } => {
+ let all_ok: Vec<Vec<Self>> = multi_ranges
+ .iter()
+ .map(|(buffer, ranges)| do_rank_one(buffer, ranges))
+ .collect::<Result<_, _>>()?;
+ // It would seem that Rust has no built in monadic flatten, or in general cannot lift things to operate on Result... :(
+ let results: Vec<Self> = all_ok.into_iter().flatten().collect();
+ Self::union(results).map_err(Either::Right)
+ }
+ }
+ }
+}