use anyhow::Result;
use common::database::Store;
use common::database::rope_helpers::{block_char_length, replace_in_block};
use common::entities::Block;
use common::format_runs::ReplaceFormatPolicy;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RangeSpec {
pub position: usize,
pub length: usize,
pub replacement: String,
}
#[derive(Debug, Clone)]
pub(crate) struct PlannedEdit {
pub block_idx: usize,
pub block_offset: usize,
pub length: usize,
pub replacement: String,
}
#[derive(Debug, Default)]
pub(crate) struct Plan {
pub edits: Vec<PlannedEdit>,
pub skipped_cross_block: i64,
pub skipped_overlapping: i64,
}
pub(crate) fn plan(blocks: &[Block], specs: &[RangeSpec], store: &Store) -> Plan {
let mut sorted: Vec<&RangeSpec> = specs.iter().collect();
sorted.sort_by_key(|s| (s.position, s.length));
let mut out = Plan::default();
let mut accepted_end: Option<usize> = None;
for spec in sorted {
if let Some(end) = accepted_end
&& spec.position < end
{
out.skipped_overlapping += 1;
continue;
}
match resolve_in_block(blocks, spec.position, spec.length, store) {
Some((block_idx, block_offset)) => {
accepted_end = Some(spec.position + spec.length);
out.edits.push(PlannedEdit {
block_idx,
block_offset,
length: spec.length,
replacement: spec.replacement.clone(),
});
}
None => out.skipped_cross_block += 1,
}
}
out
}
fn resolve_in_block(
blocks: &[Block],
position: usize,
length: usize,
store: &Store,
) -> Option<(usize, usize)> {
for (i, block) in blocks.iter().enumerate() {
let start = block.document_position as usize;
let len = block_char_length(block, store) as usize;
let end = start + len;
let inside = position >= start && (position < end || (length == 0 && position == end));
if !inside {
continue;
}
let offset = position - start;
return (offset + length <= len).then_some((i, offset));
}
None
}
pub(crate) fn apply_in_block(
store: &Store,
block: &Block,
char_start: usize,
char_end: usize,
replacement: &str,
policy: ReplaceFormatPolicy,
) -> Result<Block> {
Ok(replace_in_block(
store,
block,
char_start as i64,
char_end as i64,
replacement,
policy,
)?)
}
pub(crate) fn rebase_positions(
blocks_in_order: &[Block],
delta_by_block_id: &std::collections::HashMap<common::types::EntityId, i64>,
) -> (Vec<Block>, i64) {
let mut to_update = Vec::new();
let mut cumulative: i64 = 0;
for block in blocks_in_order {
if cumulative != 0 {
let mut moved = block.clone();
moved.document_position += cumulative;
moved.updated_at = chrono::Utc::now();
to_update.push(moved);
}
cumulative += delta_by_block_id.get(&block.id).copied().unwrap_or(0);
}
(to_update, cumulative)
}
pub(crate) fn char_delta(edit: &PlannedEdit) -> i64 {
edit.replacement.chars().count() as i64 - edit.length as i64
}
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct Applied {
pub replacements_count: i64,
pub skipped_cross_block: i64,
pub skipped_overlapping: i64,
}