Skip to content

Commit cdb9719

Browse files
committed
support remove
1 parent d272a67 commit cdb9719

File tree

4 files changed

+174
-40
lines changed

4 files changed

+174
-40
lines changed

src/chunk.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
use std::collections::VecDeque;
1+
use std::{collections::VecDeque, borrow::Cow};
22

33
use crate::{span::Span, ChunkIdx, CowStr};
44

55
#[derive(Debug, Default)]
66
pub struct Chunk<'str> {
7-
intro: VecDeque<CowStr<'str>>,
7+
pub intro: VecDeque<CowStr<'str>>,
8+
pub outro: VecDeque<CowStr<'str>>,
89
span: Span,
9-
outro: VecDeque<CowStr<'str>>,
10+
content: Option<CowStr<'str>>,
1011
pub(crate) next: Option<ChunkIdx>,
1112
}
1213

@@ -60,9 +61,17 @@ impl<'str> Chunk<'str> {
6061

6162
pub fn fragments(&'str self, original_source: &'str CowStr<'str>) -> impl Iterator<Item = &'str str> {
6263
let intro_iter = self.intro.iter().map(|frag| frag.as_ref());
63-
let source_frag = self.span.text(original_source.as_ref());
64+
let source_frag = self.content.as_deref().unwrap_or_else(|| self.span.text(original_source.as_ref()));
6465
let outro_iter = self.outro.iter().map(|frag| frag.as_ref());
6566
intro_iter.chain(Some(source_frag)).chain(outro_iter)
6667
}
68+
69+
pub fn edit(&mut self, content: CowStr<'str>) {
70+
self.content = Some(content);
71+
}
72+
73+
pub fn is_edited(&self) -> bool {
74+
self.content.is_some()
75+
}
6776
}
6877

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ type ChunkVec<'s> = IndexVec<ChunkIdx, Chunk<'s>>;
2222
index_vec::define_index_type! {
2323
struct SourceIdx = u32;
2424
}
25+
26+
pub(crate) type TextSize = u32;

src/magic_string.rs

Lines changed: 125 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
use std::collections::VecDeque;
2+
13
use rustc_hash::FxHashMap;
24

3-
use crate::{chunk::Chunk, span::Span, ChunkIdx, ChunkVec, CowStr};
5+
use crate::{chunk::Chunk, span::Span, ChunkIdx, ChunkVec, CowStr, TextSize};
46

57
#[derive(Debug, Default)]
68
pub struct MagicStringOptions {
@@ -9,6 +11,8 @@ pub struct MagicStringOptions {
911

1012
pub struct MagicString<'s> {
1113
pub filename: Option<String>,
14+
intro: VecDeque<CowStr<'s>>,
15+
outro: VecDeque<CowStr<'s>>,
1216

1317
source: CowStr<'s>,
1418
source_len: u32,
@@ -33,6 +37,8 @@ impl<'s> MagicString<'s> {
3337
let mut chunks = ChunkVec::with_capacity(1);
3438
let initial_chunk_idx = chunks.push(initial_chunk);
3539
let mut magic_string = Self {
40+
intro: Default::default(),
41+
outro: Default::default(),
3642
source,
3743
source_len,
3844
first_chunk_idx: initial_chunk_idx,
@@ -53,7 +59,7 @@ impl<'s> MagicString<'s> {
5359
}
5460

5561
pub fn append(&mut self, source: impl Into<CowStr<'s>>) -> &mut Self {
56-
self.last_chunk_mut().append_outro(source.into());
62+
self.append_outro(source.into());
5763
self
5864
}
5965

@@ -65,11 +71,13 @@ impl<'s> MagicString<'s> {
6571
/// s.append_left(2, "b");
6672
/// assert_eq!(s.to_string(), "01ab234")
6773
///```
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());
74+
pub fn append_left(&mut self, text_index: u32, content: impl Into<CowStr<'s>>) -> &mut Self {
75+
match self.by_end_mut(text_index) {
76+
Some(chunk) => {
77+
chunk.append_outro(content.into());
78+
}
79+
None => self.append_intro(content.into()),
80+
}
7381
self
7482
}
7583

@@ -83,32 +91,44 @@ impl<'s> MagicString<'s> {
8391
/// s.append_left(2, "b");
8492
/// assert_eq!(s.to_string(), "01abAB234")
8593
///```
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());
94+
pub fn append_right(&mut self, text_index: u32, content: impl Into<CowStr<'s>>) -> &mut Self {
95+
match self.by_start_mut(text_index) {
96+
Some(chunk) => {
97+
chunk.append_intro(content.into());
98+
}
99+
None => self.append_outro(content.into()),
100+
}
91101
self
92102
}
93103

94104
pub fn prepend(&mut self, source: impl Into<CowStr<'s>>) -> &mut Self {
95-
self.first_chunk_mut().prepend_intro(source.into());
105+
self.prepend_intro(source.into());
96106
self
97107
}
98108

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());
109+
pub fn prepend_left(
110+
&mut self,
111+
text_index: TextSize,
112+
content: impl Into<CowStr<'s>>,
113+
) -> &mut Self {
114+
match self.by_end_mut(text_index) {
115+
Some(chunk) => chunk.prepend_outro(content.into()),
116+
None => self.prepend_intro(content.into()),
117+
}
104118
self
105119
}
106120

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());
121+
pub fn prepend_right(
122+
&mut self,
123+
text_index: TextSize,
124+
content: impl Into<CowStr<'s>>,
125+
) -> &mut Self {
126+
match self.by_start_mut(text_index) {
127+
Some(chunk) => {
128+
chunk.prepend_intro(content.into());
129+
}
130+
None => self.prepend_outro(content.into()),
131+
}
112132
self
113133
}
114134

@@ -123,8 +143,52 @@ impl<'s> MagicString<'s> {
123143
ret
124144
}
125145

146+
pub fn remove(&mut self, start: u32, end: u32) -> &mut Self {
147+
if start == end {
148+
return self;
149+
}
150+
151+
assert!(end <= self.source_len, "end is out of bounds");
152+
assert!(start < end, "end must be greater than start");
153+
154+
self.split_at(start);
155+
self.split_at(end);
156+
157+
let mut searched = self.chunk_by_start.get(&start).copied();
158+
159+
while let Some(chunk_idx) = searched {
160+
let chunk = &mut self.chunks[chunk_idx];
161+
chunk.intro.clear();
162+
chunk.outro.clear();
163+
chunk.edit("".into());
164+
searched = if end == chunk.end() {
165+
None
166+
} else {
167+
self.chunk_by_start.get(&chunk.end()).copied()
168+
}
169+
}
170+
171+
self
172+
}
173+
126174
// --- private
127175

176+
fn prepend_intro(&mut self, content: impl Into<CowStr<'s>>) {
177+
self.intro.push_front(content.into());
178+
}
179+
180+
fn append_outro(&mut self, content: impl Into<CowStr<'s>>) {
181+
self.outro.push_back(content.into());
182+
}
183+
184+
fn prepend_outro(&mut self, content: impl Into<CowStr<'s>>) {
185+
self.outro.push_front(content.into());
186+
}
187+
188+
fn append_intro(&mut self, content: impl Into<CowStr<'s>>) {
189+
self.intro.push_back(content.into());
190+
}
191+
128192
fn iter_chunks(&self) -> impl Iterator<Item = &Chunk> {
129193
ChunkIter {
130194
next: Some(self.first_chunk_idx),
@@ -133,7 +197,10 @@ impl<'s> MagicString<'s> {
133197
}
134198

135199
pub(crate) fn fragments(&'s self) -> impl Iterator<Item = &'s str> {
136-
self.iter_chunks().flat_map(|c| c.fragments(&self.source))
200+
let intro = self.intro.iter().map(|s| s.as_ref());
201+
let outro = self.outro.iter().map(|s| s.as_ref());
202+
let chunks = self.iter_chunks().flat_map(|c| c.fragments(&self.source));
203+
intro.chain(chunks).chain(outro)
137204
}
138205

139206
/// For input
@@ -146,16 +213,17 @@ impl<'s> MagicString<'s> {
146213
///
147214
/// Chunk{span: (0, 3)} => "abc"
148215
/// 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-
{
216+
fn split_at(&mut self, text_index: TextSize) {
217+
if text_index == 0 || self.chunk_by_end.contains_key(&text_index) {
155218
return;
156219
}
157220

158-
let mut target = &self.chunks[self.first_chunk_idx];
221+
let mut target = if (self.source_len - text_index) > text_index {
222+
self.first_chunk()
223+
} else {
224+
self.last_chunk()
225+
};
226+
159227
let mut target_idx = self.first_chunk_idx;
160228

161229
let search_right = text_index > target.end();
@@ -186,12 +254,34 @@ impl<'s> MagicString<'s> {
186254
chunk_contains_index.next = Some(new_chunk_idx);
187255
}
188256

189-
fn last_chunk_mut(&mut self) -> &mut Chunk<'s> {
190-
&mut self.chunks[self.last_chunk_idx]
257+
fn by_start_mut(&mut self, text_index: TextSize) -> Option<&mut Chunk<'s>> {
258+
if text_index == self.source_len {
259+
None
260+
} else {
261+
self.split_at(text_index);
262+
// TODO: safety: using `unwrap_unchecked` is fine.
263+
let idx = self.chunk_by_start.get(&text_index).unwrap();
264+
Some(&mut self.chunks[*idx])
265+
}
266+
}
267+
268+
fn by_end_mut(&mut self, text_index: TextSize) -> Option<&mut Chunk<'s>> {
269+
if text_index == 0 {
270+
None
271+
} else {
272+
self.split_at(text_index);
273+
// TODO: safety: using `unwrap_unchecked` is fine.
274+
let idx = self.chunk_by_end.get(&text_index).unwrap();
275+
Some(&mut self.chunks[*idx])
276+
}
277+
}
278+
279+
fn last_chunk(&self) -> &Chunk<'s> {
280+
&self.chunks[self.last_chunk_idx]
191281
}
192282

193-
fn first_chunk_mut(&mut self) -> &mut Chunk<'s> {
194-
&mut self.chunks[self.first_chunk_idx]
283+
fn first_chunk(&self) -> &Chunk<'s> {
284+
&self.chunks[self.first_chunk_idx]
195285
}
196286
}
197287

tests/magic_string.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ mod prepend_append_left_right {
3636
fn preserves_intended_order() {
3737
let mut s = MagicString::new("0123456789");
3838
s.append_left(5, "A");
39+
assert_eq!(s.to_string(), "01234A56789");
3940
s.prepend_right(5, "a");
4041
s.prepend_right(5, "b");
4142
s.append_left(5, "B");
@@ -60,8 +61,32 @@ mod prepend_append_left_right {
6061
s.prepend_right(5, "]");
6162
assert_eq!(s.to_string(), "01234{<ABC([])cba>}56789");
6263
}
64+
65+
#[test]
66+
fn preserves_intended_order_at_beginning_of_string() {
67+
let mut s = MagicString::new("x");
68+
s.append_left(0, "1");
69+
s.prepend_left(0, "2");
70+
s.append_left(0, "3");
71+
s.prepend_left(0, "4");
72+
73+
assert_eq!(s.to_string(), "4213x");
74+
}
75+
76+
#[test]
77+
fn preserves_intended_order_at_end_of_string() {
78+
let mut s = MagicString::new("x");
79+
s.append_right(1, "1");
80+
s.prepend_right(1, "2");
81+
s.append_right(1, "3");
82+
s.prepend_right(1, "4");
83+
84+
assert_eq!(s.to_string(), "x4213");
85+
}
86+
87+
6388
}
64-
mod prepend {
89+
mod misc {
6590
use super::*;
6691

6792
#[test]
@@ -73,4 +98,12 @@ mod prepend {
7398
s.prepend("xyz");
7499
assert_eq!(s.to_string(), "xyzxyzabcdefghijkl");
75100
}
101+
102+
#[test]
103+
fn remove() {
104+
// should append content
105+
let mut s = MagicString::new("0123456");
106+
assert_eq!(s.remove(0, 3).to_string(), "3456");
107+
assert_eq!(s.remove(4, 7).to_string(), "");
108+
}
76109
}

0 commit comments

Comments
 (0)