Skip to content

Commit 0662144

Browse files
committed
Refactor to support append_xxx and prepend_xxx
1 parent 1f678b0 commit 0662144

File tree

5 files changed

+196
-53
lines changed

5 files changed

+196
-53
lines changed

Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "string_wizard"
3-
version = "0.0.3"
3+
version = "0.0.4"
44
edition = "2021"
55
license = "MIT"
66
description = "manipulate string like wizards"
@@ -10,4 +10,3 @@ description = "manipulate string like wizards"
1010
[dependencies]
1111
index_vec = { version = "0.1.3" }
1212
rustc-hash = { version = "1.1.0" }
13-
text-size = "1.1.1"

src/chunk.rs

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
use std::collections::VecDeque;
2+
13
use crate::{span::Span, ChunkIdx, CowStr};
24

35
#[derive(Debug, Default)]
4-
pub struct Chunk<'s> {
5-
intro: Vec<CowStr<'s>>,
6+
pub struct Chunk<'str> {
7+
intro: VecDeque<CowStr<'str>>,
68
span: Span,
7-
outro: Vec<CowStr<'s>>,
9+
outro: VecDeque<CowStr<'str>>,
810
len: usize,
911
pub(crate) next: Option<ChunkIdx>,
1012
}
@@ -19,22 +21,50 @@ impl<'s> Chunk<'s> {
1921
}
2022
}
2123

22-
impl<'s> Chunk<'s> {
23-
pub fn fragments(&'s self, original_source: &'s CowStr<'s>) -> impl Iterator<Item = &'s str> {
24-
let intro_iter = self.intro.iter().map(|frag| frag.as_ref());
25-
let source_frag = self.span.text(original_source.as_ref());
26-
let outro_iter = self.outro.iter().map(|frag| frag.as_ref());
27-
intro_iter.chain(Some(source_frag)).chain(outro_iter)
24+
impl<'str> Chunk<'str> {
25+
pub fn start(&self) -> u32 {
26+
self.span.start()
27+
}
28+
29+
pub fn end(&self) -> u32 {
30+
self.span.end()
31+
}
32+
33+
pub fn contains(&self, text_index: u32) -> bool {
34+
self.start() < text_index && text_index < self.end()
35+
}
36+
37+
pub fn append_outro(&mut self, content: CowStr<'str>) {
38+
self.outro.push_back(content)
39+
}
40+
41+
pub fn append_intro(&mut self, content: CowStr<'str>) {
42+
self.intro.push_back(content)
2843
}
2944

30-
pub fn append(&mut self, frag: CowStr<'s>) {
31-
self.len += frag.len();
32-
self.outro.push(frag.into())
45+
pub fn prepend_outro(&mut self, content: CowStr<'str>) {
46+
self.outro.push_front(content)
3347
}
3448

35-
pub fn prepend(&mut self, frag: CowStr<'s>) {
36-
self.len += frag.len();
37-
self.intro.push(frag.into())
49+
pub fn prepend_intro(&mut self, content: CowStr<'str>) {
50+
self.intro.push_front(content)
51+
}
52+
53+
pub fn split<'a,>(&'a mut self, text_index: u32) -> Chunk<'str> {
54+
let first_slice_span = Span(self.start(), text_index);
55+
let last_slice_span = Span(text_index, self.end());
56+
let mut new_chunk = Chunk::new(last_slice_span);
57+
std::mem::swap(&mut new_chunk.outro, &mut self.outro);
58+
self.span = first_slice_span;
59+
new_chunk.next = self.next;
60+
new_chunk
61+
}
62+
63+
pub fn fragments(&'str self, original_source: &'str CowStr<'str>) -> impl Iterator<Item = &'str str> {
64+
let intro_iter = self.intro.iter().map(|frag| frag.as_ref());
65+
let source_frag = self.span.text(original_source.as_ref());
66+
let outro_iter = self.outro.iter().map(|frag| frag.as_ref());
67+
intro_iter.chain(Some(source_frag)).chain(outro_iter)
3868
}
3969

4070
pub fn len(&self) -> usize {

src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ mod span;
44
mod joiner;
55
mod magic_string;
66
use std::borrow::Cow;
7-
mod intersperse_shim;
87

98
type CowStr<'s> = Cow<'s, str>;
109

src/magic_string.rs

Lines changed: 118 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ pub struct MagicStringOptions {
77
pub filename: Option<String>,
88
}
99

10-
#[derive(Default)]
1110
pub struct MagicString<'s> {
1211
pub filename: Option<String>,
13-
12+
1413
source: CowStr<'s>,
1514
source_len: u32,
1615
chunks: ChunkVec<'s>,
17-
first_chunk_idx: Option<ChunkIdx>,
16+
first_chunk_idx: ChunkIdx,
17+
last_chunk_idx: ChunkIdx,
1818
chunk_by_start: FxHashMap<u32, ChunkIdx>,
1919
chunk_by_end: FxHashMap<u32, ChunkIdx>,
2020
}
@@ -29,26 +29,86 @@ impl<'s> MagicString<'s> {
2929
pub fn with_options(source: impl Into<CowStr<'s>>, options: MagicStringOptions) -> Self {
3030
let source = source.into();
3131
let source_len: u32 = source.len().try_into().expect("source is too big");
32+
let initial_chunk = Chunk::new(Span(0, source_len));
33+
let mut chunks = ChunkVec::with_capacity(1);
34+
let initial_chunk_idx = chunks.push(initial_chunk);
3235
let mut magic_string = Self {
3336
source,
3437
source_len,
35-
..Default::default()
38+
first_chunk_idx: initial_chunk_idx,
39+
last_chunk_idx: initial_chunk_idx,
40+
chunks,
41+
chunk_by_start: Default::default(),
42+
chunk_by_end: Default::default(),
43+
// setup options
44+
filename: options.filename,
3645
};
37-
magic_string.split_at(source_len);
38-
magic_string.first_chunk_idx = Some(ChunkIdx::from_raw(0));
3946

40-
magic_string.filename = options.filename;
47+
magic_string.chunk_by_start.insert(0, initial_chunk_idx);
48+
magic_string
49+
.chunk_by_end
50+
.insert(source_len, initial_chunk_idx);
4151

4252
magic_string
4353
}
4454

4555
pub fn append(&mut self, source: impl Into<CowStr<'s>>) -> &mut Self {
46-
self.last_chunk_mut().append(source.into());
56+
self.last_chunk_mut().append_outro(source.into());
57+
self
58+
}
59+
60+
/// # Example
61+
///```rust
62+
/// use string_wizard::MagicString;
63+
/// let mut s = MagicString::new("01234");
64+
/// s.append_left(2, "a");
65+
/// s.append_left(2, "b");
66+
/// assert_eq!(s.to_string(), "01ab234")
67+
///```
68+
pub fn append_left(&mut self, text_index: u32, source: impl Into<CowStr<'s>>) -> &mut Self {
69+
self.split_at(text_index);
70+
let idx = self.chunk_by_end.get(&text_index).unwrap();
71+
let chunk = &mut self.chunks[*idx];
72+
chunk.append_outro(source.into());
73+
self
74+
}
75+
76+
/// # Example
77+
/// ```rust
78+
/// use string_wizard::MagicString;
79+
/// let mut s = MagicString::new("01234");
80+
/// s.append_right(2, "A");
81+
/// s.append_right(2, "B");
82+
/// s.append_left(2, "a");
83+
/// s.append_left(2, "b");
84+
/// assert_eq!(s.to_string(), "01abAB234")
85+
///```
86+
pub fn append_right(&mut self, text_index: u32, source: impl Into<CowStr<'s>>) -> &mut Self {
87+
self.split_at(text_index);
88+
let idx = self.chunk_by_start.get(&text_index).unwrap();
89+
let chunk = &mut self.chunks[*idx];
90+
chunk.append_intro(source.into());
4791
self
4892
}
4993

5094
pub fn prepend(&mut self, source: impl Into<CowStr<'s>>) -> &mut Self {
51-
self.first_chunk_mut().prepend(source.into());
95+
self.first_chunk_mut().prepend_intro(source.into());
96+
self
97+
}
98+
99+
pub fn prepend_left(&mut self, text_index: u32, content: impl Into<CowStr<'s>>) -> &mut Self {
100+
self.split_at(text_index);
101+
let idx = self.chunk_by_end.get(&text_index).unwrap();
102+
let chunk = &mut self.chunks[*idx];
103+
chunk.prepend_outro(content.into());
104+
self
105+
}
106+
107+
pub fn prepend_right(&mut self, text_index: u32, content: impl Into<CowStr<'s>>) -> &mut Self {
108+
self.split_at(text_index);
109+
let idx = self.chunk_by_start.get(&text_index).unwrap();
110+
let chunk = &mut self.chunks[*idx];
111+
chunk.prepend_intro(content.into());
52112
self
53113
}
54114

@@ -67,7 +127,7 @@ impl<'s> MagicString<'s> {
67127

68128
fn iter_chunks(&self) -> impl Iterator<Item = &Chunk> {
69129
ChunkIter {
70-
next: self.first_chunk_idx,
130+
next: Some(self.first_chunk_idx),
71131
chunks: &self.chunks,
72132
}
73133
}
@@ -76,39 +136,62 @@ impl<'s> MagicString<'s> {
76136
self.iter_chunks().flat_map(|c| c.fragments(&self.source))
77137
}
78138

79-
/// a b c d e f g
80-
/// 0 1 2 3 4 5 6
81-
/// split_at(3)
82-
/// a b c d e f g
83-
/// [0 1 2][3 4 5 6]
84-
fn split_at(&mut self, idx: u32) {
85-
let source_len = self.source_len;
86-
debug_assert!(idx <= source_len);
87-
if self.chunks.is_empty() {
88-
let prev_span = Span(0, idx);
89-
self.create_chunk(prev_span);
90-
if idx < source_len {
91-
self.create_chunk(Span(idx, source_len));
92-
}
93-
} else {
139+
/// For input
140+
/// "abcdefg"
141+
/// 0123456
142+
///
143+
/// Chunk{span: (0, 7)} => "abcdefg"
144+
///
145+
/// split_at(3) would create
146+
///
147+
/// Chunk{span: (0, 3)} => "abc"
148+
/// Chunk{span: (3, 7)} => "defg"
149+
fn split_at(&mut self, text_index: u32) {
150+
debug_assert!(text_index <= self.source_len);
151+
152+
if self.chunk_by_start.contains_key(&text_index)
153+
|| self.chunk_by_end.contains_key(&text_index)
154+
{
155+
return;
94156
}
95-
}
96157

97-
fn create_chunk(&mut self, span: Span) -> ChunkIdx {
98-
let idx = self.chunks.push(Chunk::new(span));
99-
self.chunk_by_start.insert(span.start(), idx);
100-
self.chunk_by_end.insert(span.end(), idx);
101-
idx
158+
let mut target = &self.chunks[self.first_chunk_idx];
159+
let mut target_idx = self.first_chunk_idx;
160+
161+
let search_right = text_index > target.end();
162+
163+
while !target.contains(text_index) {
164+
let next_idx = if search_right {
165+
self.chunk_by_start.get(&target.end()).unwrap()
166+
} else {
167+
self.chunk_by_end.get(&target.start()).unwrap()
168+
};
169+
target = &self.chunks[*next_idx];
170+
target_idx = *next_idx;
171+
}
172+
173+
let chunk_contains_index = &mut self.chunks[target_idx];
174+
175+
let new_chunk = chunk_contains_index.split(text_index);
176+
self.chunk_by_end.insert(text_index, target_idx);
177+
let new_chunk_end = new_chunk.end();
178+
let new_chunk_idx = self.chunks.push(new_chunk);
179+
self.chunk_by_start.insert(text_index, new_chunk_idx);
180+
self.chunk_by_end.insert(new_chunk_end, new_chunk_idx);
181+
182+
let chunk_contains_index = &mut self.chunks[target_idx];
183+
if target_idx == self.last_chunk_idx {
184+
self.last_chunk_idx = new_chunk_idx
185+
}
186+
chunk_contains_index.next = Some(new_chunk_idx);
102187
}
103188

104189
fn last_chunk_mut(&mut self) -> &mut Chunk<'s> {
105-
let idx = self.chunk_by_end.get(&(self.source_len)).unwrap();
106-
&mut self.chunks[*idx]
190+
&mut self.chunks[self.last_chunk_idx]
107191
}
108192

109193
fn first_chunk_mut(&mut self) -> &mut Chunk<'s> {
110-
let idx = self.chunk_by_start.get(&0).unwrap();
111-
&mut self.chunks[*idx]
194+
&mut self.chunks[self.first_chunk_idx]
112195
}
113196
}
114197

tests/magic_string.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,38 @@ mod append {
2929
}
3030
}
3131

32+
mod prepend_append_left_right {
33+
use super::*;
34+
35+
#[test]
36+
fn preserves_intended_order() {
37+
let mut s = MagicString::new("0123456789");
38+
s.append_left(5, "A");
39+
s.prepend_right(5, "a");
40+
s.prepend_right(5, "b");
41+
s.append_left(5, "B");
42+
s.append_left(5, "C");
43+
s.prepend_right(5, "c");
44+
45+
assert_eq!(s.to_string(), "01234ABCcba56789");
46+
47+
s.prepend_left(5, "<");
48+
s.prepend_left(5, "{");
49+
assert_eq!(s.to_string(), "01234{<ABCcba56789");
50+
51+
s.append_right(5, ">");
52+
s.append_right(5, "}");
53+
assert_eq!(s.to_string(), "01234{<ABCcba>}56789");
54+
55+
s.append_left(5, "(");
56+
s.append_left(5, "[");
57+
assert_eq!(s.to_string(), "01234{<ABC([cba>}56789");
58+
59+
s.prepend_right(5, ")");
60+
s.prepend_right(5, "]");
61+
assert_eq!(s.to_string(), "01234{<ABC([])cba>}56789");
62+
}
63+
}
3264
mod prepend {
3365
use super::*;
3466

0 commit comments

Comments
 (0)