Skip to content

Commit 16ad913

Browse files
committed
Refactor undo/redo system with unified types across frontend and backend
Backend (Rust): - Move Operation and Edit types from diff.rs to code.rs as single source of truth - Rename Operation::Delete to Operation::Remove for consistency - Replace EditBatch with Change struct (edits + timestamp) - Add History module for managing undo/redo stack with configurable limit - Rename methods: get_undo_batch -> undo_change, get_redo_batch -> redo_change - Add tx()/commit() transaction pattern for grouping edits - Send history state on file:open for frontend sync - Remove unused file:set handler and related methods (set_text, ensure_file_exists) - Keep file state in memory on disconnect to preserve unsaved changes Frontend (TypeScript): - Rename Operation.Delete to Operation.Remove - Add isUndo/isRedo flags to Change type for backend sync - Add setHistory() method to restore history from backend - Add setRawHistory() to History class
1 parent d9c047d commit 16ad913

11 files changed

Lines changed: 269 additions & 211 deletions

File tree

anycode-backend/src/code.rs

Lines changed: 123 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,38 @@
11
use ropey::Rope;
2-
use std::fs;
32
use std::fs::File;
43
use std::io::{BufReader, BufWriter};
5-
use std::path::Path;
6-
74
use crate::config::{Config};
85
use crate::utils::{self};
9-
use log2::*;
10-
116
use serde::{Deserialize, Serialize};
7+
use crate::history::History;
128

13-
#[derive(Debug, Serialize, Deserialize, Clone)]
9+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1410
#[serde(rename_all = "lowercase")]
1511
pub enum Operation {
1612
Insert,
1713
Remove,
18-
Start,
19-
End,
2014
}
2115

22-
#[derive(Debug, Serialize, Deserialize, Clone)]
23-
pub struct Change {
24-
pub start: usize,
25-
pub operation: Operation,
16+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17+
pub struct Edit {
18+
pub start: usize, // UTF-16 offset
2619
pub text: String,
20+
pub operation: Operation,
21+
}
22+
23+
#[derive(Clone, Serialize, Deserialize)]
24+
pub struct Change {
25+
pub edits: Vec<Edit>,
26+
pub timestamp: usize,
27+
}
28+
29+
impl Change {
30+
pub fn new() -> Self {
31+
Self {
32+
edits: Vec::new(),
33+
timestamp: 0,
34+
}
35+
}
2736
}
2837

2938
pub struct Code {
@@ -32,8 +41,9 @@ pub struct Code {
3241
pub lang: String,
3342
pub text: ropey::Rope,
3443
pub changed: bool,
35-
pub undo_history: Vec<Change>,
36-
pub redo_history: Vec<Change>,
44+
pub applying_history: bool,
45+
pub history: History,
46+
pub change: Change,
3747
pub self_updated: bool,
3848
}
3949

@@ -45,15 +55,22 @@ impl Code {
4555
abs_path: String::new(),
4656
changed: false,
4757
lang: String::new(),
48-
undo_history: Vec::new(),
49-
redo_history: Vec::new(),
58+
applying_history: true,
59+
history: History::new(1000),
60+
change: Change::new(),
5061
self_updated: false,
5162
}
5263
}
5364

65+
pub fn get_content(&self) -> String {
66+
self.text.to_string()
67+
}
68+
5469
pub fn from_str(text: &str) -> Self {
5570
let mut code = Self::new();
56-
code.insert_text(text, 0, 0);
71+
code.applying_history = false;
72+
code.insert_text(text, 0);
73+
code.applying_history = true;
5774
code
5875
}
5976

@@ -79,20 +96,13 @@ impl Code {
7996
abs_path,
8097
changed: false,
8198
lang,
82-
undo_history: Vec::new(),
83-
redo_history: Vec::new(),
99+
applying_history: true,
100+
history: History::new(1000),
101+
change: Change::new(),
84102
self_updated: false,
85103
})
86104
}
87105

88-
89-
pub fn set_text(&mut self, text: &str) {
90-
self.text = Rope::new();
91-
self.text.insert(0, text);
92-
self.changed = true;
93-
}
94-
95-
96106
pub fn save_file(&mut self) -> std::io::Result<()> {
97107
if !self.changed {
98108
return Ok(());
@@ -109,17 +119,6 @@ impl Code {
109119
self.file_name = file_name;
110120
}
111121

112-
pub fn ensure_file_exists(&mut self) -> std::io::Result<()> {
113-
if !Path::new(&self.file_name).exists() {
114-
fs::create_dir_all(Path::new(&self.file_name).parent().unwrap())?;
115-
fs::File::create(&self.file_name)?;
116-
117-
self.abs_path = utils::abs_file(&self.file_name)
118-
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
119-
}
120-
Ok(())
121-
}
122-
123122
pub fn position(&self, offset: usize) -> (usize, usize) {
124123
let line_idx = self.text.char_to_line(offset);
125124
let line_char_index = self.text.line_to_char(line_idx);
@@ -147,77 +146,52 @@ impl Code {
147146
self.changed = true;
148147
}
149148

150-
pub fn insert_text(&mut self, text: &str, row: usize, column: usize) {
151-
let from = self.text.line_to_char(row) + column;
152-
self.insert(text, from);
153-
154-
self.undo_history.push(Change {
155-
start: from,
156-
operation: Operation::Insert,
157-
text: text.to_string(),
158-
});
159-
160-
self.redo_history.clear();
161-
}
162-
163-
pub fn insert_text_at(&mut self, text: &str, offset: usize) {
149+
pub fn insert_text(&mut self, text: &str, offset: usize) {
164150
self.insert(text, offset);
165151

166-
self.undo_history.push(Change {
167-
start: offset,
168-
operation: Operation::Insert,
169-
text: text.to_string(),
170-
});
171-
172-
self.redo_history.clear();
152+
if self.applying_history {
153+
self.change.edits.push(Edit {
154+
start: offset, text: text.to_string(), operation: Operation::Insert,
155+
});
156+
}
173157
}
174158

175159
fn remove(&mut self, from: usize, to: usize) {
176160
self.text.remove(from..to);
177161
self.changed = true;
178162
}
179163

180-
pub fn remove_text(&mut self, row: usize, col: usize, row1: usize, col1: usize) {
181-
let from = self.text.line_to_char(row) + col;
182-
let to = self.text.line_to_char(row1) + col1;
164+
pub fn remove_text(&mut self, from: usize, to: usize) {
183165
let text = self.text.slice(from..to).to_string();
184166

185167
self.remove(from, to);
186168

187-
self.undo_history.push(Change {
188-
start: from,
189-
operation: Operation::Remove,
190-
text: text.to_string(),
191-
});
192-
193-
self.redo_history.clear();
169+
if self.applying_history {
170+
self.change.edits.push(Edit {
171+
start: from, text: text.to_string(), operation: Operation::Remove,
172+
});
173+
}
194174
}
195175

196-
pub fn remove_text2(&mut self, from: usize, to: usize) {
197-
let text = self.text.slice(from..to).to_string();
198-
199-
self.remove(from, to);
200-
201-
self.undo_history.push(Change {
202-
start: from,
203-
operation: Operation::Remove,
204-
text: text.to_string(),
205-
});
206-
207-
self.redo_history.clear();
176+
pub fn tx(&mut self) {
177+
self.change = Change::new();
178+
self.applying_history = true;
208179
}
209180

210-
211-
pub fn line_len(&self, idx: usize) -> usize {
212-
let line = self.text.line(idx);
213-
let len = line.len_chars();
214-
if idx == self.text.len_lines() - 1 {
215-
len
216-
} else {
217-
len.saturating_sub(1)
181+
pub fn commit(&mut self) {
182+
if !self.change.edits.is_empty() {
183+
self.history.push(self.change.clone());
184+
self.change = Change::new();
218185
}
219186
}
220187

188+
pub fn undo_change(&mut self) -> Option<Change> {
189+
self.history.undo()
190+
}
191+
192+
pub fn redo_change(&mut self) -> Option<Change> {
193+
self.history.redo()
194+
}
221195
}
222196

223197

@@ -240,19 +214,19 @@ mod code_undo_tests {
240214
#[test]
241215
fn test_code_insert() {
242216
let mut buffer = Code::new();
243-
buffer.insert_text("hello", 0, 0);
244-
buffer.insert_text(" world", 0, 5);
217+
buffer.insert_text("hello", 0);
218+
buffer.insert_text(" world", 5);
245219
assert_eq!(buffer.text.to_string(), "hello world");
246220
}
247221

248222
#[test]
249223
fn test_code_remove() {
250224
let mut buffer = Code::new();
251225

252-
buffer.insert_text("hello world", 0, 0);
226+
buffer.insert_text("hello world", 0);
253227
assert_eq!(buffer.text.to_string(), "hello world");
254228

255-
buffer.remove_text(0, 5, 0, 11);
229+
buffer.remove_text(5, 11);
256230
assert_eq!(buffer.text.to_string(), "hello");
257231
}
258232

@@ -263,4 +237,60 @@ mod code_undo_tests {
263237
assert_eq!(buffer.char_to_position(0), (0, 0));
264238
assert_eq!(buffer.char_to_position(text.len()), (0, text.len()));
265239
}
240+
241+
#[test]
242+
fn test_undo() {
243+
let mut code = Code::new();
244+
245+
code.tx();
246+
code.insert_text("Hello ", 0);
247+
code.commit();
248+
249+
code.tx();
250+
code.insert_text("World", 6);
251+
code.commit();
252+
253+
assert_eq!(code.get_content().to_string(), "Hello World");
254+
assert_eq!(code.history.index, 2);
255+
256+
let batch = code.undo_change().expect("undo should return batch");
257+
assert_eq!(code.history.index, 1);
258+
assert_eq!(batch.edits[0], Edit {
259+
start: 6, text: "World".to_string(), operation: Operation::Insert
260+
});
261+
262+
let batch = code.undo_change().expect("undo should return batch");
263+
assert_eq!(code.history.index, 0);
264+
assert_eq!(batch.edits[0], Edit {
265+
start: 0, text: "Hello ".to_string(), operation: Operation::Insert
266+
});
267+
268+
assert!(code.undo_change().is_none());
269+
}
270+
271+
#[test]
272+
fn test_redo() {
273+
let mut code = Code::new();
274+
275+
code.tx();
276+
code.insert_text("Hello", 0);
277+
code.commit();
278+
279+
assert_eq!(code.history.index, 1);
280+
281+
let batch = code.undo_change().expect("undo should return batch");
282+
assert_eq!(code.history.index, 0);
283+
assert_eq!(batch.edits[0], Edit {
284+
start: 0, text: "Hello".to_string(), operation: Operation::Insert
285+
});
286+
287+
let batch = code.redo_change().expect("redo should return batch");
288+
assert_eq!(code.history.index, 1);
289+
assert_eq!(batch.edits[0], Edit {
290+
start: 0, text: "Hello".to_string(), operation: Operation::Insert
291+
});
292+
293+
let batch = code.redo_change();
294+
assert!(batch.is_none());
295+
}
266296
}

0 commit comments

Comments
 (0)