Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit ed8e993

Browse files
authored
feat: move api (#20)
1 parent be39c8a commit ed8e993

File tree

5 files changed

+378
-109
lines changed

5 files changed

+378
-109
lines changed

core/src/magic_string.rs

+99
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,100 @@ impl MagicString {
631631
SourceMap::new_from_decoded(decoded_map)
632632
}
633633

634+
/// ## Move
635+
/// Moves the string between start and end to the specified position.Return `self`.
636+
///
637+
/// Example:
638+
/// ```
639+
/// use magic_string::MagicString;
640+
/// let mut s = MagicString::new("abcdefghijkl");
641+
/// s._move(3, 6, 9);
642+
/// assert_eq!(s.to_string(), "abcghidefjkl");
643+
///
644+
/// ```
645+
///
646+
pub fn _move(&mut self, start: i64, end: i64, index: i64) -> Result<&mut Self> {
647+
let start = normalize_index(self.original_str.as_str(), start)?;
648+
let end = normalize_index(self.original_str.as_str(), end)?;
649+
let index = normalize_index(self.original_str.as_str(), index)?;
650+
651+
let start = start as u32;
652+
let end = end as u32;
653+
let index = index as u32;
654+
655+
if index >= start && index <= end {
656+
return Err(Error::new_with_reason(
657+
MagicStringErrorType::MagicStringUnknownError,
658+
"Cannot move a selection inside itself",
659+
));
660+
}
661+
662+
if start > end {
663+
return Err(Error::new_with_reason(
664+
MagicStringErrorType::MagicStringOutOfRangeError,
665+
"Start must be greater than end.",
666+
));
667+
}
668+
669+
self._split_at_index(start)?;
670+
self._split_at_index(end)?;
671+
self._split_at_index(index)?;
672+
673+
let first = self.chunk_by_start.get(&start).map(Rc::clone).unwrap();
674+
let last = self.chunk_by_end.get(&end).map(Rc::clone).unwrap();
675+
676+
let old_left = first.borrow().clone().prev;
677+
let old_right = last.borrow().clone().next;
678+
679+
let new_right = self.chunk_by_start.get(&index).map(Rc::clone);
680+
let new_left = match new_right.clone() {
681+
Some(l) => Rc::clone(&l).borrow().clone().prev,
682+
None => Some(Rc::clone(&self.last_chunk)),
683+
};
684+
let clone_old_left = old_left.clone();
685+
686+
match old_left {
687+
Some(old_left) => {
688+
old_left.borrow_mut().next = old_right.clone();
689+
}
690+
None => self.first_chunk = old_right.clone().unwrap(),
691+
}
692+
693+
match old_right {
694+
Some(old_right) => old_right.borrow_mut().prev = clone_old_left,
695+
None => self.last_chunk = clone_old_left.unwrap(),
696+
}
697+
698+
match new_left {
699+
Some(new_left) => {
700+
new_left.borrow_mut().next = Some(Rc::clone(&first));
701+
first.borrow_mut().prev = Some(new_left);
702+
}
703+
None => {
704+
let first = Rc::clone(&first);
705+
self.first_chunk.borrow_mut().prev = Some(Rc::clone(&first));
706+
last.borrow_mut().next = Some(Rc::clone(&self.first_chunk));
707+
self.first_chunk = first;
708+
}
709+
}
710+
711+
match new_right {
712+
Some(new_right) => {
713+
new_right.borrow_mut().prev = Some(Rc::clone(&last));
714+
last.borrow_mut().next = Some(new_right);
715+
}
716+
None => {
717+
let last = Rc::clone(&last);
718+
self.last_chunk.borrow_mut().next = Some(Rc::clone(&last));
719+
first.borrow_mut().prev = Some(Rc::clone(&self.last_chunk));
720+
last.borrow_mut().next = None;
721+
self.last_chunk = last;
722+
}
723+
}
724+
725+
Ok(self)
726+
}
727+
634728
fn _split_at_index(&mut self, index: u32) -> Result {
635729
if self.chunk_by_end.contains_key(&index) || self.chunk_by_start.contains_key(&index) {
636730
// early bail-out if it's already split
@@ -667,8 +761,13 @@ impl MagicString {
667761
MagicStringErrorType::MagicStringDoubleSplitError,
668762
));
669763
}
764+
let next_chunk = chunk.borrow().clone().next;
670765
let new_chunk = Chunk::split(Rc::clone(&chunk), index);
671766

767+
if let Some(next_chunk) = next_chunk {
768+
next_chunk.borrow_mut().prev = Some(Rc::clone(&new_chunk));
769+
}
770+
672771
let new_chunk_original = new_chunk.borrow();
673772
self.chunk_by_end.insert(index, Rc::clone(&chunk));
674773

core/tests/move.rs

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#[cfg(test)]
2+
mod _move {
3+
use magic_string::{MagicString, OverwriteOptions, Result};
4+
5+
#[test]
6+
fn should_move_from_start() -> Result {
7+
let mut s = MagicString::new("abcdefghijkl");
8+
9+
s._move(0, 3, 6)?;
10+
assert_eq!(s.to_string(), "defabcghijkl");
11+
Ok(())
12+
}
13+
14+
#[test]
15+
fn should_move_to_start() -> Result {
16+
let mut s = MagicString::new("abcdefghijkl");
17+
18+
s._move(3, 6, 0)?;
19+
20+
assert_eq!(s.to_string(), "defabcghijkl");
21+
Ok(())
22+
}
23+
24+
#[test]
25+
fn should_move_from_end() -> Result {
26+
let mut s = MagicString::new("abcdefghijkl");
27+
28+
s._move(9, 12, 3)?;
29+
30+
assert_eq!(s.to_string(), "abcjkldefghi");
31+
Ok(())
32+
}
33+
#[test]
34+
fn should_move_to_end() -> Result {
35+
let mut s = MagicString::new("abcdefghijkl");
36+
37+
s._move(3, 6, 12)?;
38+
39+
assert_eq!(s.to_string(), "abcghijkldef");
40+
Ok(())
41+
}
42+
43+
#[test]
44+
fn should_move_and_remove() -> Result {
45+
let mut s = MagicString::new("abcdefghijkl");
46+
47+
s._move(3, 6, 12)?;
48+
s._move(3, 5, 0)?;
49+
50+
assert_eq!(s.to_string(), "deabcghijklf");
51+
52+
Ok(())
53+
}
54+
55+
#[test]
56+
fn should_move_after_insert() -> Result {
57+
let mut s = MagicString::new("abcdefghijk");
58+
59+
s.prepend("xyz")?;
60+
s.append("mn")?;
61+
s.prepend_left(4, "A")?;
62+
s.append_left(4, "B")?;
63+
s.prepend_right(4, "C")?;
64+
s.append_right(4, "D")?;
65+
s._move(0, 3, 6)?;
66+
assert_eq!(s.to_string(), "xyzdABCDefabcghijkmn");
67+
Ok(())
68+
}
69+
70+
#[test]
71+
fn should_ignores_redundant_move() -> Result {
72+
let mut s = MagicString::new("abcdefghijkl");
73+
s.prepend_right(9, "X")?;
74+
s._move(9, 12, 6)?;
75+
s.append_left(12, "Y")?;
76+
s._move(6, 9, 12)?; // this is redundant – [6,9] is already after [9,12]
77+
78+
assert_eq!(s.to_string(), "abcdefXjklYghi");
79+
80+
Ok(())
81+
}
82+
83+
#[test]
84+
fn should_move_content_to_middle() -> Result {
85+
let mut s = MagicString::new("abcdefghijkl");
86+
s._move(3, 6, 9)?;
87+
88+
assert_eq!(s.to_string(), "abcghidefjkl");
89+
Ok(())
90+
}
91+
92+
#[test]
93+
fn should_handles_multiple_moves_of_same_snippet() -> Result {
94+
let mut s = MagicString::new("abcdefghijkl");
95+
s._move(0, 3, 6)?;
96+
assert_eq!(s.to_string(), "defabcghijkl");
97+
98+
s._move(0, 3, 9)?;
99+
assert_eq!(s.to_string(), "defghiabcjkl");
100+
101+
Ok(())
102+
}
103+
#[test]
104+
fn should_handles_moves_of_adjacent_snippets() -> Result {
105+
let mut s = MagicString::new("abcdefghijkl");
106+
s._move(0, 2, 6)?;
107+
assert_eq!(s.to_string(), "cdefabghijkl");
108+
109+
s._move(2, 4, 6)?;
110+
assert_eq!(s.to_string(), "efabcdghijkl");
111+
112+
Ok(())
113+
}
114+
#[test]
115+
fn should_handles_moves_to_same_index() -> Result {
116+
let mut s = MagicString::new("abcdefghijkl");
117+
s._move(0, 2, 6)?._move(3, 5, 6)?;
118+
assert_eq!(s.to_string(), "cfabdeghijkl");
119+
120+
Ok(())
121+
}
122+
#[test]
123+
fn should_allows_edits_of_moved_content() -> Result {
124+
let mut s = MagicString::new("abcdefghijkl");
125+
s._move(3, 6, 9)?;
126+
s.overwrite(3, 6, "DEF", OverwriteOptions::default())?;
127+
assert_eq!(s.to_string(), "abcghiDEFjkl");
128+
129+
let mut s = MagicString::new("abcdefghijkl");
130+
131+
s._move(3, 6, 9)?;
132+
s.overwrite(4, 5, "E", OverwriteOptions::default())?;
133+
assert_eq!(s.to_string(), "abcghidEfjkl");
134+
Ok(())
135+
}
136+
// #[test]
137+
// fn should_move_follows_inserts() -> Result {
138+
// let mut s = MagicString::new("abcdefghijkl");
139+
// s._move(3, 6, 9)?;
140+
141+
// assert_eq!(s.to_string(), "abcghidefjkl");
142+
// Ok(())
143+
// }
144+
#[test]
145+
fn should_moves_content_inserted_at_end_of_range() -> Result {
146+
let mut s = MagicString::new("abcdefghijkl");
147+
s.append_left(6, "X")?._move(3, 6, 9)?;
148+
149+
assert_eq!(s.to_string(), "abcghidefXjkl");
150+
151+
Ok(())
152+
}
153+
#[test]
154+
fn should_returns_this() -> Result {
155+
let mut s = MagicString::new("abcdefghijkl");
156+
157+
let result = s._move(3, 6, 9)?;
158+
let result_ptr = result as *mut _;
159+
let s_ptr = &s as *const _;
160+
161+
assert_eq!(s_ptr, result_ptr);
162+
Ok(())
163+
}
164+
}

node/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export class MagicString {
4949
trimEnd(pattern?: string | undefined | null): this
5050
trimLines(): this
5151
remove(start: number, end: number): this
52+
move(start: number, end: number, index: number): this
5253
isEmpty(): boolean
5354
generateMap(options?: Partial<GenerateDecodedMapOptions>): {
5455
toString: () => string

node/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ impl MagicString {
104104
Ok(self)
105105
}
106106

107+
#[napi]
108+
pub fn _move(&mut self, start: i64, end: i64, index: i64) -> Result<&Self> {
109+
self.0._move(start, end, index)?;
110+
Ok(self)
111+
}
107112
#[napi]
108113
pub fn is_empty(&self) -> Result<bool> {
109114
Ok(self.0.is_empty())

0 commit comments

Comments
 (0)