use crate::{Line, Operation, OperationType, Update};
#[derive(Clone, Debug, Default)]
pub struct LineCache {
invalid_before: u64,
lines: Vec<Line>,
invalid_after: u64,
}
impl LineCache {
pub fn before(&self) -> u64 {
self.invalid_before
}
pub fn after(&self) -> u64 {
self.invalid_after
}
pub fn lines(&self) -> &Vec<Line> {
&self.lines
}
pub fn height(&self) -> u64 {
self.before() + self.lines.len() as u64 + self.after()
}
pub fn update(&mut self, update: Update) {
debug!("line cache before update: {:?}", self);
debug!(
"operations to be applied to the line cache: {:?}",
&update.operations
);
let mut helper = UpdateHelper {
old_cache: self,
new_cache: LineCache::default(),
};
helper.update(update.operations);
}
pub fn is_empty(&self) -> bool {
self.lines.is_empty()
}
}
#[derive(Debug)]
struct UpdateHelper<'a> {
old_cache: &'a mut LineCache,
new_cache: LineCache,
}
impl<'a> UpdateHelper<'a> {
fn apply_copy(&mut self, nb_lines: u64, first_line_num: Option<u64>) {
debug!("copying {} lines", nb_lines);
let UpdateHelper {
old_cache:
LineCache {
invalid_before: ref mut old_invalid_before,
lines: ref mut old_lines,
invalid_after: ref mut old_invalid_after,
},
new_cache:
LineCache {
invalid_before: ref mut new_invalid_before,
lines: ref mut new_lines,
invalid_after: ref mut new_invalid_after,
},
} = self;
let mut nb_lines = nb_lines;
if *old_invalid_before >= nb_lines {
*old_invalid_before -= nb_lines;
*new_invalid_before += nb_lines;
return;
} else {
nb_lines -= *old_invalid_before;
*new_invalid_before += *old_invalid_before;
*old_invalid_before = 0;
}
let nb_valid_lines = old_lines.len();
let range;
if nb_lines <= (nb_valid_lines as u64) {
range = 0..nb_lines as usize;
nb_lines = 0;
} else {
range = 0..nb_valid_lines;
nb_lines -= nb_valid_lines as u64;
}
if nb_valid_lines > 0 {
let diff = if let Some(new_first_line_num) = first_line_num {
old_lines
.iter()
.find_map(|line| {
line.line_num
.map(|num| new_first_line_num as i64 - num as i64)
})
.unwrap_or(0)
} else {
0
};
let copied_lines = old_lines.drain(range).map(|mut line| {
line.line_num = line
.line_num
.map(|line_num| (line_num as i64 + diff) as u64);
line
});
new_lines.extend(copied_lines);
}
if nb_lines == 0 {
return;
}
if *old_invalid_after >= nb_lines {
*old_invalid_after -= nb_lines;
*new_invalid_after += nb_lines;
} else {
error!(
"{} lines left to copy, but only {} lines in the old cache",
nb_lines, *old_invalid_after
);
panic!("cache update failed");
}
}
fn apply_skip(&mut self, nb_lines: u64) {
debug!("skipping {} lines", nb_lines);
let LineCache {
invalid_before: ref mut old_invalid_before,
lines: ref mut old_lines,
invalid_after: ref mut old_invalid_after,
} = self.old_cache;
let mut nb_lines = nb_lines;
if *old_invalid_before > nb_lines {
*old_invalid_before -= nb_lines;
return;
} else if *old_invalid_before > 0 {
nb_lines -= *old_invalid_before;
*old_invalid_before = 0;
}
let nb_valid_lines = old_lines.len();
if nb_lines < nb_valid_lines as u64 {
old_lines.drain(0..nb_lines as usize).last();
return;
} else {
old_lines.drain(..).last();
nb_lines -= nb_valid_lines as u64;
}
if *old_invalid_after >= nb_lines {
*old_invalid_after -= nb_lines;
return;
}
error!(
"{} lines left to skip, but only {} lines in the old cache",
nb_lines, *old_invalid_after
);
panic!("cache update failed");
}
fn apply_invalidate(&mut self, nb_lines: u64) {
debug!("invalidating {} lines", nb_lines);
if self.new_cache.lines.is_empty() {
self.new_cache.invalid_before += nb_lines;
} else {
self.new_cache.invalid_after += nb_lines;
}
}
fn apply_insert(&mut self, mut lines: Vec<Line>) {
debug!("inserting {} lines", lines.len());
self.new_cache.lines.extend(lines.drain(..).map(|mut line| {
trim_new_line(&mut line.text);
line
}));
}
fn apply_update(&mut self, nb_lines: u64, lines: Vec<Line>, first_line_num: Option<u64>) {
debug!("updating {} lines", nb_lines);
let old_lines = &mut self.old_cache.lines;
let new_lines = &mut self.new_cache.lines;
if nb_lines > old_lines.len() as u64 {
error!(
"{} lines to update, but only {} lines in cache",
nb_lines,
old_lines.len()
);
panic!("failed to update the cache");
}
let diff = if let Some(new_first_line_num) = first_line_num {
old_lines
.iter()
.find_map(|line| {
line.line_num
.map(|num| new_first_line_num as i64 - num as i64)
})
.unwrap_or(0)
} else {
0
};
new_lines.extend(
old_lines
.drain(0..nb_lines as usize)
.zip(lines.into_iter())
.map(|(mut old_line, update)| {
old_line.cursor = update.cursor;
old_line.styles = update.styles;
old_line.line_num = old_line
.line_num
.map(|line_num| (line_num as i64 + diff) as u64);
old_line
}),
)
}
fn update(&mut self, operations: Vec<Operation>) {
self.new_cache = LineCache::default();
trace!("updating the line cache");
trace!("cache state before: {:?}", self);
trace!("operations to be applied: {:?}", &operations);
for op in operations {
debug!("operation: {:?}", &op);
debug!("cache helper before operation {:?}", self);
match op.operation_type {
OperationType::Copy => self.apply_copy(op.nb_lines, op.line_num),
OperationType::Skip => self.apply_skip(op.nb_lines),
OperationType::Invalidate => self.apply_invalidate(op.nb_lines),
OperationType::Insert => self.apply_insert(op.lines),
OperationType::Update => self.apply_update(op.nb_lines, op.lines, op.line_num),
}
debug!("cache helper after operation {:?}", self);
}
std::mem::swap(self.old_cache, &mut self.new_cache);
}
}
fn trim_new_line(text: &mut String) {
if let Some('\n') = text.chars().last() {
text.pop();
}
}
#[test]
fn test_cache_edit() {
let mut cache = LineCache {
invalid_before: 0,
lines: serde_json::from_str::<Vec<Line>>(
r#"
[
{"text":"line1", "ln":1},
{"text":"line2", "ln":2},
{"text":"line3", "ln":3},
{"text":"line4", "ln":4},
{"text":"line5", "ln":5}
]
"#,
)
.unwrap(),
invalid_after: 0,
};
let upd = Update {
operations: serde_json::from_str::<Vec<Operation>>(
r#"
[
{"op":"copy", "n":1},
{"op":"ins", "n":2, "lines": [
{"text":"new_line2", "ln":2},
{"text":"new_line3", "ln":3}
]},
{"op":"skip", "n":2},
{"op":"copy", "n":2}
]
"#,
)
.unwrap(),
pristine: true,
rev: None,
view_id: std::str::FromStr::from_str("view-id-1").unwrap(),
};
cache.update(upd);
assert_eq!(
cache.lines,
serde_json::from_str::<Vec<Line>>(
r#"[{"text":"line1", "ln":1},
{"text":"new_line2", "ln":2},
{"text":"new_line3", "ln":3},
{"text":"line4", "ln":4},
{"text":"line5", "ln":5}]"#
)
.unwrap()
);
}