Skip to content

Commit 1c10c06

Browse files
lbqhkasonyang
authored andcommitted
fix: edit history
1 parent e892a35 commit 1c10c06

6 files changed

Lines changed: 205 additions & 95 deletions

File tree

lib.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1844,6 +1844,22 @@ export class TextEditElement extends Element {
18441844
return TextEdit_get_placeholder(this.handle);
18451845
}
18461846

1847+
/**
1848+
*
1849+
* @returns {number}
1850+
*/
1851+
get maxHistory() {
1852+
return TextEdit_get_max_history(this.handle);
1853+
}
1854+
1855+
/**
1856+
*
1857+
* @param maxHistory {number}
1858+
*/
1859+
set maxHistory(maxHistory) {
1860+
TextEdit_set_max_history(this.handle, maxHistory);
1861+
}
1862+
18471863
/**
18481864
*
18491865
* @param start {number}

src/element/common/editable.rs

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate as deft;
22
use crate::app::AppEvent;
33
use crate::base::{Callback, EventContext, Rect};
44
use crate::canvas_util::CanvasHelper;
5-
use crate::element::edit_history::{EditHistory, EditOpType};
5+
use crate::element::edit_history::{EditDetail, EditHistory};
66
use crate::element::util::is_form_event;
77
use crate::element::{Element, ElementBackend, ElementWeak};
88
use crate::event::{BlurEvent, BoundsChangeEvent, CaretChangeEvent, Event, FocusEvent, KeyDownEvent, KeyEventDetail, MouseDownEvent, MouseLeaveEvent, PreeditEvent, ScrollEvent, TextChangeEvent, TextInputEvent, TextUpdateEvent, KEY_MOD_CTRL, KEY_MOD_SHIFT};
@@ -186,6 +186,14 @@ impl Editable {
186186
ele.remove_attribute("disabled".to_string());
187187
}
188188
}
189+
190+
pub fn set_max_history(&mut self, max_history: usize) {
191+
self.edit_history.set_max_history(max_history);
192+
}
193+
194+
pub fn get_max_history(&self) -> usize {
195+
self.edit_history.get_max_history()
196+
}
189197

190198
fn get_caret_pixels_position(&self) -> Option<Rect> {
191199
let element = self.element.upgrade_mut().ok()?;
@@ -354,7 +362,7 @@ impl Editable {
354362
match nk {
355363
NamedKey::Backspace => {
356364
let end = self.paragraph.get_caret();
357-
if self.paragraph.get_selection().is_none() {
365+
if self.paragraph.get_selection().is_empty() {
358366
if end.0 > 0 || end.1 > 0 {
359367
self.move_caret(-1);
360368
let start = self.paragraph.get_caret();
@@ -412,20 +420,35 @@ impl Editable {
412420
_ => {}
413421
}
414422
}
423+
} else if event.modifiers == KEY_MOD_CTRL | KEY_MOD_SHIFT {
424+
if let Some(text) = &event.key_str {
425+
match text.to_lowercase().as_str() {
426+
"z" => {
427+
self.redo();
428+
}
429+
_ => {}
430+
}
431+
}
415432
}
416433
}
417434

418435
fn undo(&mut self) {
419-
if let Some(op) = &self.edit_history.undo() {
420-
match op.op {
421-
EditOpType::Insert => {
422-
//TODO self.insert_text(op.content.as_str(), op.caret, false);
423-
}
424-
EditOpType::Delete => {
425-
//TODO self.base.select(op.caret, op.caret + op.content.chars_count());
426-
//TODO self.insert_text("", op.caret, false);
427-
}
436+
if let Some(op) = self.edit_history.undo() {
437+
if let Some(insert) = op.insert {
438+
self.paragraph.select(op.caret, insert.end);
439+
}
440+
let delete_content = op.delete.map(|it| it.content).unwrap_or_else(String::new);
441+
self.insert_text(&delete_content, op.caret, false);
442+
}
443+
}
444+
445+
fn redo(&mut self) {
446+
if let Some(op) = self.edit_history.redo() {
447+
if let Some(delete) = op.delete {
448+
self.paragraph.select(op.caret, delete.end);
428449
}
450+
let insert_content = op.insert.map(|it| it.content).unwrap_or_else(String::new);
451+
self.insert_text(&insert_content, op.caret, false);
429452
}
430453
}
431454

@@ -456,12 +479,12 @@ impl Editable {
456479
}
457480

458481
fn insert_text(&mut self, input: &str, mut caret: TextCoord, record_history: bool) {
459-
if let Some((start, end)) = self.paragraph.get_selection() {
460-
if record_history {
461-
// let text= self.paragraph.get_selection_text().unwrap();
462-
//TODO self.edit_history.record_delete(begin, &text);
463-
}
464-
482+
let mut delete_detail = None;
483+
let mut insert_detail = None;
484+
let selection = self.paragraph.get_selection();
485+
if !selection.is_empty() {
486+
let (start, end) = selection.normalize();
487+
let selected_text = self.paragraph.get_selection_text().unwrap_or(String::new());
465488
if start.0 == end.0 {
466489
let line_text = self.paragraph.get_line_text(start.0).unwrap();
467490
let left = line_text.substring(0, start.1);
@@ -487,11 +510,15 @@ impl Editable {
487510
self.paragraph.unselect();
488511
self.update_caret_value(start, false);
489512
caret = start;
513+
if start != end {
514+
delete_detail = Some(EditDetail {
515+
content: selected_text,
516+
end,
517+
});
518+
}
490519
}
520+
let start_caret = caret;
491521
if !input.is_empty() {
492-
if record_history {
493-
//TODO self.edit_history.record_input(caret, input);
494-
}
495522
let line_text = self.paragraph.get_line_text(caret.0).unwrap();
496523
let left_str = line_text.substring(0, caret.1);
497524
let right_str = line_text.substring(caret.1, line_text.len() - caret.1);
@@ -526,6 +553,14 @@ impl Editable {
526553
};
527554
//TODO maybe update caret twice?
528555
self.update_caret_value(new_caret, false);
556+
insert_detail = Some(EditDetail {
557+
content: input.to_string(),
558+
end: self.paragraph.get_caret(),
559+
});
560+
}
561+
562+
if record_history {
563+
self.edit_history.record_input(start_caret, delete_detail, insert_detail);
529564
}
530565

531566
// emit text update
@@ -768,7 +803,7 @@ impl ElementBackend for Editable {
768803
align: TextAlign::Left,
769804
multiple_line: false,
770805
element: ele.as_weak(),
771-
edit_history: EditHistory::new(),
806+
edit_history: EditHistory::new(10),
772807
rows: 5,
773808
disabled: false,
774809
line_height: None,

src/element/edit_history.rs

Lines changed: 76 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
use crate::string::StringUtils;
1+
use crate::some_or_return;
2+
use crate::text::textbox::TextCoord;
23

3-
#[derive(PartialEq, Eq)]
4-
pub enum EditOpType {
5-
Insert,
6-
Delete,
4+
#[derive(Clone, Debug)]
5+
pub struct EditDetail {
6+
pub content: String,
7+
pub end: TextCoord,
78
}
89

10+
#[derive(Clone, Debug)]
911
pub struct EditOp {
10-
pub caret: usize,
11-
pub op: EditOpType,
12-
pub content: String,
12+
pub caret: TextCoord,
13+
pub delete: Option<EditDetail>,
14+
pub insert: Option<EditDetail>,
1315
}
1416

1517
pub struct EditHistory {
@@ -19,30 +21,36 @@ pub struct EditHistory {
1921
}
2022

2123
impl EditHistory {
22-
pub fn new() -> Self {
24+
pub fn new(max_history: usize) -> Self {
2325
EditHistory {
2426
history: Vec::new(),
2527
history_ptr: 0,
26-
max_history: 20,
28+
max_history,
2729
}
2830
}
2931

30-
pub fn record_input(&mut self, caret: usize, content: &str) {
31-
if !self.merge_input(caret, content) {
32-
self.push_op(EditOp {
33-
caret,
34-
op: EditOpType::Insert,
35-
content: content.to_string(),
36-
});
37-
}
32+
pub fn set_max_history(&mut self, max_history: usize) {
33+
self.max_history = max_history;
34+
}
35+
36+
pub fn get_max_history(&self) -> usize {
37+
self.max_history
3838
}
3939

40-
pub fn record_delete(&mut self, caret: usize, content: &str) {
41-
if !self.merge_delete(caret, content) {
40+
pub fn record_input(
41+
&mut self,
42+
caret: TextCoord,
43+
delete_detail: Option<EditDetail>,
44+
insert_detail: Option<EditDetail>,
45+
) {
46+
if self.max_history == 0 || (delete_detail.is_none() && insert_detail.is_none()) {
47+
return;
48+
}
49+
if !self.merge_input(caret, &delete_detail, &insert_detail) {
4250
self.push_op(EditOp {
4351
caret,
44-
op: EditOpType::Delete,
45-
content: content.to_string(),
52+
delete: delete_detail,
53+
insert: insert_detail,
4654
});
4755
}
4856
}
@@ -53,55 +61,55 @@ impl EditHistory {
5361
}
5462
let prev_op = unsafe { self.history.get_unchecked(self.history_ptr - 1) };
5563
self.history_ptr -= 1;
56-
let op = match prev_op.op {
57-
EditOpType::Insert => EditOp {
58-
caret: prev_op.caret,
59-
op: EditOpType::Delete,
60-
content: prev_op.content.to_string(),
61-
},
62-
EditOpType::Delete => EditOp {
63-
caret: prev_op.caret,
64-
op: EditOpType::Insert,
65-
content: prev_op.content.to_string(),
66-
},
67-
};
68-
Some(op)
64+
Some(prev_op.clone())
6965
}
7066

71-
fn merge_input(&mut self, op_caret: usize, content: &str) -> bool {
72-
if self.history_ptr == 0 || self.history_ptr != self.history.len() {
73-
return false;
74-
}
75-
let last_op = unsafe { self.history.get_unchecked_mut(self.history_ptr - 1) };
76-
if last_op.op == EditOpType::Insert
77-
&& last_op.caret + last_op.content.chars_count() == op_caret
78-
{
79-
last_op.content.push_str(content);
80-
true
81-
} else {
82-
false
67+
pub fn redo(&mut self) -> Option<EditOp> {
68+
if self.history.is_empty() || self.history_ptr >= self.history.len() {
69+
return None;
8370
}
71+
let next_op = unsafe { self.history.get_unchecked(self.history_ptr) };
72+
self.history_ptr += 1;
73+
Some(next_op.clone())
8474
}
8575

86-
fn merge_delete(&mut self, op_caret: usize, content: &str) -> bool {
76+
fn merge_input(
77+
&mut self,
78+
caret: TextCoord,
79+
delete_detail: &Option<EditDetail>,
80+
insert_detail: &Option<EditDetail>,
81+
) -> bool {
8782
if self.history_ptr == 0 || self.history_ptr != self.history.len() {
8883
return false;
8984
}
9085
let last_op = unsafe { self.history.get_unchecked_mut(self.history_ptr - 1) };
91-
if last_op.op != EditOpType::Delete {
92-
return false;
93-
}
94-
if last_op.caret + last_op.content.chars_count() == op_caret {
95-
last_op.content.push_str(content);
96-
true
97-
} else if op_caret + content.chars_count() == last_op.caret {
98-
last_op.caret = op_caret;
99-
let mut content = content.to_string();
100-
content.push_str(&last_op.content);
101-
last_op.content = content;
102-
true
103-
} else {
104-
false
86+
match (
87+
delete_detail,
88+
insert_detail,
89+
&mut last_op.delete,
90+
&mut last_op.insert,
91+
) {
92+
(None, Some(insert_detail), None, Some(last_insert)) => {
93+
if last_insert.end == caret
94+
&& !Self::starts_with_whitespace(&insert_detail.content)
95+
{
96+
last_insert.content.push_str(&insert_detail.content);
97+
last_insert.end = insert_detail.end;
98+
true
99+
} else {
100+
false
101+
}
102+
}
103+
(Some(delete_detail), None, Some(last_delete), None) => {
104+
if delete_detail.end == last_op.caret {
105+
last_delete.content = format!("{}{}", delete_detail.content, last_delete.content);
106+
last_op.caret = caret;
107+
true
108+
} else {
109+
false
110+
}
111+
}
112+
_ => false,
105113
}
106114
}
107115

@@ -117,4 +125,10 @@ impl EditHistory {
117125
self.history.push(op);
118126
self.history_ptr += 1;
119127
}
128+
129+
fn starts_with_whitespace(content: &str) -> bool {
130+
let first = some_or_return!(content.chars().next(), false);
131+
['\t', '\r', '\n', ' '].contains(&first)
132+
}
133+
120134
}

src/element/textedit.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ impl TextEdit {
3232
self.editable.set_placeholder(placeholder);
3333
}
3434

35+
#[js_func]
36+
pub fn set_max_history(&mut self, max_history: usize) {
37+
self.editable.set_max_history(max_history);
38+
}
39+
40+
#[js_func]
41+
pub fn get_max_history(&self) -> usize {
42+
self.editable.get_max_history()
43+
}
44+
3545
#[js_func]
3646
pub fn get_placeholder(&self) -> String {
3747
self.editable.get_placeholder()

src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#![allow(dead_code)]
22
#![allow(deprecated)]
33

4-
use std::env;
54
use crate::app::{App, AppEvent, AppEventPayload, WinitApp};
65
use anyhow::{anyhow, Error};
76
use measure_time::debug_time;
@@ -99,7 +98,7 @@ pub fn bootstrap(deft_app: App) {
9998
{
10099
use ::winit::platform::wayland::EventLoopBuilderExtWayland;
101100
use ::winit::platform::x11::EventLoopBuilderExtX11;
102-
if env::var("DEFT_FORCE_WAYLAND").is_ok() {
101+
if std::env::var("DEFT_FORCE_WAYLAND").is_ok() {
103102
elb.with_wayland();
104103
} else {
105104
elb.with_x11();

0 commit comments

Comments
 (0)