Skip to content
2 changes: 1 addition & 1 deletion benches/benchmarks/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use std::{

const SCRIPT: &str = include_str!("../../examples/scripts/fancy_impl_blocks.ad");
const ZIPLIST: &str = include_str!("../../src/ziplist.rs");
const GAP_BUFFER: &str = include_str!("../../src/buffer/internal.rs");
const GAP_BUFFER: &str = include_str!("../../src/buffer/internal/mod.rs");

fn criterion_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("Structural regex");
Expand Down
11 changes: 7 additions & 4 deletions benches/benchmarks/gap_buffer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! GapBuffer benchmarks for measuring line-ending tracking performance.
use ad_editor::buffer::GapBuffer;
use ad_editor::buffer::{CharIdx, GapBuffer};
use criterion::{BatchSize, BenchmarkId, Criterion, criterion_group};
use libflate::gzip::Decoder;
use serde::Deserialize;
Expand Down Expand Up @@ -55,9 +55,12 @@ impl From<(usize, usize, String)> for TestPatch {

fn apply_patch(patch: &TestPatch, gb: &mut GapBuffer) {
if patch.delete_len > 0 {
gb.remove_range(patch.position, patch.position + patch.delete_len);
gb.remove_range(
CharIdx(patch.position),
CharIdx(patch.position + patch.delete_len),
);
}
gb.insert_str(patch.position, &patch.insert_content);
gb.insert_str(CharIdx(patch.position), &patch.insert_content);
}

fn single_char_insert(c: &mut Criterion) {
Expand All @@ -82,7 +85,7 @@ fn single_char_insert(c: &mut Criterion) {
|b, (content, pos)| {
b.iter_batched(
|| GapBuffer::from(content.as_str()),
|mut gb| gb.insert_char(*pos, 'a'),
|mut gb| gb.insert_char(CharIdx(*pos), 'a'),
BatchSize::SmallInput,
);
},
Expand Down
15 changes: 10 additions & 5 deletions benches/benchmarks/ts_update.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
// Update times for running TreeSitter syntax highlighting
use ad_editor::{Config, buffer::Buffer, dot::TextObject, editor::BAction};
use ad_editor::{
Config,
buffer::{Buffer, LineIdx},
dot::TextObject,
editor::BAction,
};
use ad_event::Source;
use criterion::{Criterion, criterion_group};
use parking_lot::RwLock;
Expand All @@ -13,22 +18,22 @@ fn criterion_benchmark(c: &mut Criterion) {
let config = Arc::new(RwLock::new(Config::try_load().unwrap()));

let mut buf = Buffer::new_from_canonical_file_path(0, path.clone(), config.clone()).unwrap();
buf.update_ts_state(0, 70);
buf.update_ts_state(LineIdx(0), 70);

group.bench_function("update without edit", |b| {
b.iter(|| {
buf.update_ts_state(0, 70);
buf.update_ts_state(LineIdx(0), 70);
})
});

let mut buf = Buffer::new_from_canonical_file_path(0, path.clone(), config.clone()).unwrap();
buf.update_ts_state(0, 70);
buf.update_ts_state(LineIdx(0), 70);

group.bench_function("append newline and update", |b| {
b.iter(|| {
buf.handle_action(BAction::DotSet(TextObject::BufferEnd, 1), Source::Fsys);
buf.handle_action(BAction::InsertChar { c: '\n' }, Source::Fsys);
buf.update_ts_state(0, 70);
buf.update_ts_state(LineIdx(0), 70);
})
});

Expand Down
23 changes: 23 additions & 0 deletions data/plumbing_tests/match-tests/multibyte-narrowing
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
The cursor position is a character offset into the message data, so leading multi-byte characters
must not shift which match the cursor lands in. (Character 15 is in "two.pdf" while byte 15 is in
"one.pdf".)

-- rules --
data narrows [a-zA-Z0-9_\-./@]+
data matches ^[a-zA-Z0-9_\-./@]+\.pdf$
plumb to edit

-- initial --
src: bash
dst: ad
wdir: /home/foo/bar
cur: 15
ndata: 27
data: ééééé one.pdf two.pdf

-- processed --
src: bash
dst: ad
wdir: /home/foo/bar
ndata: 7
data: two.pdf
14 changes: 9 additions & 5 deletions fuzz/fuzz_targets/fuzz_gap_buffer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![no_main]

use ad_editor::buffer::GapBuffer;
use ad_editor::buffer::{CharIdx, GapBuffer};
use arbitrary::Arbitrary;
use libfuzzer_sys::fuzz_target;
use std::cmp::{max, min};
Expand All @@ -9,13 +9,17 @@ fuzz_target!(|data: FuzzData| {
let mut gb = GapBuffer::from(data.initial_content);
for action in data.actions.into_iter() {
match action {
TestAction::InsertChar(idx, ch) if idx < gb.len_chars() => gb.insert_char(idx, ch),
TestAction::InsertString(idx, s) if idx < gb.len_chars() => gb.insert_str(idx, &s),
TestAction::RemoveChar(idx) if idx < gb.len_chars() => gb.remove_char(idx),
TestAction::InsertChar(idx, ch) if idx < gb.len_chars() => {
gb.insert_char(CharIdx(idx), ch)
}
TestAction::InsertString(idx, s) if idx < gb.len_chars() => {
gb.insert_str(CharIdx(idx), &s)
}
TestAction::RemoveChar(idx) if idx < gb.len_chars() => gb.remove_char(CharIdx(idx)),
TestAction::RemoveRange(a, b) => {
let (from, to) = (min(a, b), max(a, b));
if to < gb.len_chars() {
gb.remove_range(from, to);
gb.remove_range(CharIdx(from), CharIdx(to));
}
}

Expand Down
13 changes: 10 additions & 3 deletions reference-tests/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
//! Reference editing tests for the ad Buffer implementation using
//! <https://github.com/josephg/editing-traces>
use ad_editor::{Source, buffer::Buffer, editor::BAction};
use ad_editor::{
Source,
buffer::{Buffer, CharIdx},
editor::BAction,
};
use libflate::gzip::Decoder;
use serde::Deserialize;
use std::{
Expand Down Expand Up @@ -69,11 +73,14 @@ impl TestPatch {
// https://github.com/josephg/editing-traces/blob/master/sequential_traces/README.md#data-format
pub fn apply(self, b: &mut Buffer) {
if self.delete_len > 0 {
b.set_dot_from_range(self.position, self.position + self.delete_len - 1);
b.set_dot_from_range(
CharIdx(self.position),
CharIdx(self.position + self.delete_len - 1),
);
b.handle_action(BAction::Delete, Source::Fsys);
}

b.set_dot_from_cursor(self.position);
b.set_dot_from_cursor(CharIdx(self.position));
b.handle_action(
BAction::InsertString {
s: self.insert_content,
Expand Down
25 changes: 12 additions & 13 deletions src/buffer/buffers.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
buffer::{Buffer, BufferKind, Cur, ScratchBuf, WELCOME_SQUIRREL},
buffer::{Buffer, BufferKind, CharIdx, Cur, ScratchBuf, WELCOME_SQUIRREL},
config::Config,
dot::TextObject,
fsys::InputFilter,
Expand Down Expand Up @@ -226,8 +226,8 @@ impl Buffers {
self.focus_id(id);
self.inner.focus.txt = content.into();
let n = self.inner.focus.txt.len_chars();
self.inner.focus.dot.clamp_idx(n);
self.inner.focus.xdot.clamp_idx(n);
self.inner.focus.dot.clamp_idx(CharIdx(n));
self.inner.focus.xdot.clamp_idx(CharIdx(n));

return id;
}
Expand Down Expand Up @@ -407,7 +407,7 @@ impl Buffers {
if new_id == bufid {
// Clamp the cursor to the buffer's valid range before setting it
// in case the buffer was edited since this position was recorded
cur.clamp_idx(self.inner.focus.txt.len_chars());
cur.clamp_idx(CharIdx(self.inner.focus.txt.len_chars()));
self.inner.focus.dot = cur.into();
}

Expand Down Expand Up @@ -583,21 +583,20 @@ mod tests {
let initial_content = "This is a test buffer with enough content to demonstrate the bug.";
buffers.inner.focus.txt = initial_content.into();

buffers.inner.focus.set_dot_from_cursor(60);
assert_eq!(buffers.inner.focus.dot.active_cur().idx, 60);
buffers.inner.focus.set_dot_from_cursor(CharIdx(60));
assert_eq!(buffers.inner.focus.dot.active_cur().idx, CharIdx(60));

buffers.record_jump_position();

assert_eq!(buffers.inner.focus.txt.len_chars(), 65);
let new_len = 30;
buffers
.inner
.focus
.txt
.remove_range(new_len, buffers.inner.focus.txt.len_chars());
buffers.inner.focus.txt.remove_range(
CharIdx(new_len),
CharIdx(buffers.inner.focus.txt.len_chars()),
);
assert_eq!(buffers.inner.focus.txt.len_chars(), new_len);

buffers.inner.focus.set_dot_from_cursor(0);
buffers.inner.focus.set_dot_from_cursor(CharIdx(0));
let result = buffers.jump_list_backward();

assert!(result.is_some());
Expand All @@ -606,7 +605,7 @@ mod tests {
let (_y, _x) = cur.as_yx(&buffers.inner.focus);

assert!(
cur.idx <= buffers.inner.focus.txt.len_chars(),
cur.idx.0 <= buffers.inner.focus.txt.len_chars(),
"cursor index {} exceeds buffer length {}",
cur.idx,
buffers.inner.focus.txt.len_chars()
Expand Down
39 changes: 30 additions & 9 deletions src/buffer/edit.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::buffer::{Buffer, Cur};
use crate::buffer::{Buffer, CharOffset, Cur};

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(super) enum Kind {
Expand Down Expand Up @@ -95,11 +95,11 @@ impl Edit {
None
} else {
match &self.txt {
Txt::Char(_) if e.cur.idx == self.cur.idx + 1 => {
Txt::Char(_) if e.cur.idx == self.cur.idx.uadd(1) => {
self.txt.append(e.txt);
None
}
Txt::String(s) if e.cur.idx == self.cur.idx + s.len() => {
Txt::String(s) if e.cur.idx == self.cur.idx + CharOffset::from(s.as_str()) => {
self.txt.append(e.txt);
None
}
Expand All @@ -114,12 +114,12 @@ impl Edit {
None
} else {
match &e.txt {
Txt::Char(_) if e.cur.idx + 1 == self.cur.idx => {
Txt::Char(_) if e.cur.idx.uadd(1) == self.cur.idx => {
self.txt.prepend(e.txt);
self.cur = e.cur;
None
}
Txt::String(s) if e.cur.idx + s.len() == self.cur.idx => {
Txt::String(s) if e.cur.idx + CharOffset::from(s.as_str()) == self.cur.idx => {
self.txt.prepend(e.txt);
self.cur = e.cur;
None
Expand Down Expand Up @@ -262,36 +262,37 @@ impl EditLog {
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::buffer::CharIdx;
use simple_test_case::test_case;

pub(crate) fn in_c(idx: usize, c: char) -> Edit {
Edit {
kind: Kind::Insert,
cur: Cur { idx },
cur: Cur { idx: CharIdx(idx) },
txt: Txt::Char(c),
}
}

pub(crate) fn in_s(idx: usize, s: &str) -> Edit {
Edit {
kind: Kind::Insert,
cur: Cur { idx },
cur: Cur { idx: CharIdx(idx) },
txt: Txt::String(s.to_string()),
}
}

pub(crate) fn del_c(idx: usize, c: char) -> Edit {
Edit {
kind: Kind::Delete,
cur: Cur { idx },
cur: Cur { idx: CharIdx(idx) },
txt: Txt::Char(c),
}
}

pub(crate) fn del_s(idx: usize, s: &str) -> Edit {
Edit {
kind: Kind::Delete,
cur: Cur { idx },
cur: Cur { idx: CharIdx(idx) },
txt: Txt::String(s.to_string()),
}
}
Expand Down Expand Up @@ -336,6 +337,16 @@ pub(crate) mod tests {
&[in_s(0, "dabc")];
"string then char at same cursor"
)]
#[test_case(
vec![in_s(0, "éé"), in_c(2, 'x')],
&[in_s(0, "ééx")];
"multi-byte string then adjacent char"
)]
#[test_case(
vec![in_s(0, "é"), in_c(2, 'x')],
&[in_s(0, "é"), in_c(2, 'x')];
"multi-byte string then non-adjacent char"
)]
#[test]
fn inserts_work(edits: Vec<Edit>, expected: &[Edit]) {
let mut log = EditLog::default();
Expand Down Expand Up @@ -386,6 +397,16 @@ pub(crate) mod tests {
&[del_s(0, "abcd")];
"string then char at same cursor"
)]
#[test_case(
vec![del_c(2, 'x'), del_s(0, "éé")],
&[del_s(0, "ééx")];
"char then adjacent multi-byte string"
)]
#[test_case(
vec![del_c(4, 'x'), del_s(0, "éé")],
&[del_c(4, 'x'), del_s(0, "éé")];
"char then non-adjacent multi-byte string"
)]
#[test]
fn delete_work(edits: Vec<Edit>, expected: &[Edit]) {
let mut log = EditLog::default();
Expand Down
Loading
Loading