1use std::sync::Arc;
8
9use azul_core::selection::{
10 CursorAffinity, GraphemeClusterId, Selection, SelectionRange, TextCursor,
11};
12
13use crate::text3::cache::{ContentIndex, InlineContent, StyledRun};
14
15#[derive(Debug, Clone)]
17pub enum TextEdit {
18 Insert(String),
20 DeleteBackward,
22 DeleteForward,
24}
25
26pub fn edit_text(
29 content: &[InlineContent],
30 selections: &[Selection],
31 edit: &TextEdit,
32) -> (Vec<InlineContent>, Vec<Selection>) {
33 if selections.is_empty() {
34 return (content.to_vec(), Vec::new());
35 }
36
37 let mut new_content = content.to_vec();
38 let mut new_selections = Vec::new();
39
40 let mut sorted_selections = selections.to_vec();
44 sorted_selections.sort_by(|a, b| {
45 let cursor_a = match a {
46 Selection::Cursor(c) => c,
47 Selection::Range(r) => &r.start,
48 };
49 let cursor_b = match b {
50 Selection::Cursor(c) => c,
51 Selection::Range(r) => &r.start,
52 };
53 cursor_b.cluster_id.cmp(&cursor_a.cluster_id) });
55
56 for selection in sorted_selections {
57 let (mut temp_content, new_cursor) =
58 apply_edit_to_selection(&new_content, &selection, edit);
59
60 let edit_run = match selection {
63 Selection::Cursor(c) => c.cluster_id.source_run,
64 Selection::Range(r) => r.start.cluster_id.source_run,
65 };
66 let edit_byte = match selection {
67 Selection::Cursor(c) => c.cluster_id.start_byte_in_run,
68 Selection::Range(r) => r.start.cluster_id.start_byte_in_run,
69 };
70
71 let byte_offset_change: i32 = match edit {
73 TextEdit::Insert(text) => text.len() as i32,
74 TextEdit::DeleteBackward | TextEdit::DeleteForward => {
75 -1
78 }
79 };
80
81 for prev_selection in new_selections.iter_mut() {
83 if let Selection::Cursor(cursor) = prev_selection {
84 if cursor.cluster_id.source_run == edit_run
85 && cursor.cluster_id.start_byte_in_run >= edit_byte
86 {
87 cursor.cluster_id.start_byte_in_run =
88 (cursor.cluster_id.start_byte_in_run as i32 + byte_offset_change).max(0)
89 as u32;
90 }
91 }
92 }
93
94 new_content = temp_content;
95 new_selections.push(Selection::Cursor(new_cursor));
96 }
97
98 new_selections.reverse();
100
101 (new_content, new_selections)
102}
103
104pub fn apply_edit_to_selection(
112 content: &[InlineContent],
113 selection: &Selection,
114 edit: &TextEdit,
115) -> (Vec<InlineContent>, TextCursor) {
116 let mut new_content = content.to_vec();
117
118 match selection {
119 Selection::Range(range) => {
120 let (content_after_delete, cursor_pos) = delete_range(&new_content, range);
122 match edit {
123 TextEdit::Insert(text_to_insert) => {
125 let mut c = content_after_delete;
126 insert_text(&mut c, &cursor_pos, text_to_insert)
127 }
128 TextEdit::DeleteBackward | TextEdit::DeleteForward => {
130 (content_after_delete, cursor_pos)
131 }
132 }
133 }
134 Selection::Cursor(cursor) => {
135 match edit {
136 TextEdit::Insert(text_to_insert) => {
137 insert_text(&mut new_content, cursor, text_to_insert)
138 }
139 TextEdit::DeleteBackward => delete_backward(&mut new_content, cursor),
140 TextEdit::DeleteForward => delete_forward(&mut new_content, cursor),
141 }
142 }
143 }
144}
145
146pub fn delete_range(
148 content: &[InlineContent],
149 range: &SelectionRange,
150) -> (Vec<InlineContent>, TextCursor) {
151 let mut new_content = content.to_vec();
161 let start_run_idx = range.start.cluster_id.source_run as usize;
162 let end_run_idx = range.end.cluster_id.source_run as usize;
163
164 if start_run_idx == end_run_idx {
165 if let Some(InlineContent::Text(run)) = new_content.get_mut(start_run_idx) {
166 let start_byte = range.start.cluster_id.start_byte_in_run as usize;
167 let end_byte = range.end.cluster_id.start_byte_in_run as usize;
168 if start_byte <= end_byte && end_byte <= run.text.len() {
169 run.text.drain(start_byte..end_byte);
170 }
171 }
172 } else {
173 }
175
176 (new_content, range.start) }
178
179pub fn insert_text(
185 content: &mut Vec<InlineContent>,
186 cursor: &TextCursor,
187 text_to_insert: &str,
188) -> (Vec<InlineContent>, TextCursor) {
189 use unicode_segmentation::UnicodeSegmentation;
190
191 let mut new_content = content.clone();
192 let run_idx = cursor.cluster_id.source_run as usize;
193 let cluster_start_byte = cursor.cluster_id.start_byte_in_run as usize;
194
195 if let Some(InlineContent::Text(run)) = new_content.get_mut(run_idx) {
196 let byte_offset = match cursor.affinity {
198 CursorAffinity::Leading => {
199 cluster_start_byte
201 },
202 CursorAffinity::Trailing => {
203 if cluster_start_byte >= run.text.len() {
206 run.text.len()
208 } else {
209 run.text[cluster_start_byte..]
211 .grapheme_indices(true)
212 .next()
213 .map(|(_, grapheme)| cluster_start_byte + grapheme.len())
214 .unwrap_or(run.text.len())
215 }
216 },
217 };
218
219 if byte_offset <= run.text.len() {
220 run.text.insert_str(byte_offset, text_to_insert);
221
222 let new_cursor = TextCursor {
223 cluster_id: GraphemeClusterId {
224 source_run: run_idx as u32,
225 start_byte_in_run: (byte_offset + text_to_insert.len()) as u32,
226 },
227 affinity: CursorAffinity::Leading,
228 };
229 return (new_content, new_cursor);
230 }
231 }
232
233 (content.to_vec(), *cursor)
235}
236
237pub fn delete_backward(
243 content: &mut Vec<InlineContent>,
244 cursor: &TextCursor,
245) -> (Vec<InlineContent>, TextCursor) {
246 use unicode_segmentation::UnicodeSegmentation;
247 let mut new_content = content.clone();
248 let run_idx = cursor.cluster_id.source_run as usize;
249 let cluster_start_byte = cursor.cluster_id.start_byte_in_run as usize;
250
251 if let Some(InlineContent::Text(run)) = new_content.get_mut(run_idx) {
252 let byte_offset = match cursor.affinity {
254 CursorAffinity::Leading => cluster_start_byte,
255 CursorAffinity::Trailing => {
256 if cluster_start_byte >= run.text.len() {
258 run.text.len()
259 } else {
260 run.text[cluster_start_byte..]
261 .grapheme_indices(true)
262 .next()
263 .map(|(_, grapheme)| cluster_start_byte + grapheme.len())
264 .unwrap_or(run.text.len())
265 }
266 },
267 };
268
269 if byte_offset > 0 {
270 let prev_grapheme_start = run.text[..byte_offset]
271 .grapheme_indices(true)
272 .last()
273 .map_or(0, |(i, _)| i);
274 run.text.drain(prev_grapheme_start..byte_offset);
275
276 let new_cursor = TextCursor {
277 cluster_id: GraphemeClusterId {
278 source_run: run_idx as u32,
279 start_byte_in_run: prev_grapheme_start as u32,
280 },
281 affinity: CursorAffinity::Leading,
282 };
283 return (new_content, new_cursor);
284 } else if run_idx > 0 {
285 if let Some(InlineContent::Text(prev_run)) = content.get(run_idx - 1).cloned() {
287 let mut merged_text = prev_run.text;
288 let new_cursor_byte_offset = merged_text.len();
289 merged_text.push_str(&run.text);
290
291 new_content[run_idx - 1] = InlineContent::Text(StyledRun {
292 text: merged_text,
293 style: prev_run.style,
294 logical_start_byte: prev_run.logical_start_byte,
295 source_node_id: prev_run.source_node_id,
296 });
297 new_content.remove(run_idx);
298
299 let new_cursor = TextCursor {
300 cluster_id: GraphemeClusterId {
301 source_run: (run_idx - 1) as u32,
302 start_byte_in_run: new_cursor_byte_offset as u32,
303 },
304 affinity: CursorAffinity::Leading,
305 };
306 return (new_content, new_cursor);
307 }
308 }
309 }
310
311 (content.to_vec(), *cursor)
312}
313
314pub fn delete_forward(
320 content: &mut Vec<InlineContent>,
321 cursor: &TextCursor,
322) -> (Vec<InlineContent>, TextCursor) {
323 use unicode_segmentation::UnicodeSegmentation;
324 let mut new_content = content.clone();
325 let run_idx = cursor.cluster_id.source_run as usize;
326 let cluster_start_byte = cursor.cluster_id.start_byte_in_run as usize;
327
328 if let Some(InlineContent::Text(run)) = new_content.get_mut(run_idx) {
329 let byte_offset = match cursor.affinity {
331 CursorAffinity::Leading => cluster_start_byte,
332 CursorAffinity::Trailing => {
333 if cluster_start_byte >= run.text.len() {
335 run.text.len()
336 } else {
337 run.text[cluster_start_byte..]
338 .grapheme_indices(true)
339 .next()
340 .map(|(_, grapheme)| cluster_start_byte + grapheme.len())
341 .unwrap_or(run.text.len())
342 }
343 },
344 };
345
346 if byte_offset < run.text.len() {
347 let next_grapheme_end = run.text[byte_offset..]
348 .grapheme_indices(true)
349 .nth(1)
350 .map_or(run.text.len(), |(i, _)| byte_offset + i);
351 run.text.drain(byte_offset..next_grapheme_end);
352
353 let new_cursor = TextCursor {
355 cluster_id: GraphemeClusterId {
356 source_run: run_idx as u32,
357 start_byte_in_run: byte_offset as u32,
358 },
359 affinity: CursorAffinity::Leading,
360 };
361 return (new_content, new_cursor);
362 } else if run_idx < content.len() - 1 {
363 if let Some(InlineContent::Text(next_run)) = content.get(run_idx + 1).cloned() {
365 let mut merged_text = run.text.clone();
366 merged_text.push_str(&next_run.text);
367
368 new_content[run_idx] = InlineContent::Text(StyledRun {
369 text: merged_text,
370 style: run.style.clone(),
371 logical_start_byte: run.logical_start_byte,
372 source_node_id: run.source_node_id,
373 });
374 new_content.remove(run_idx + 1);
375
376 return (new_content, *cursor);
377 }
378 }
379 }
380
381 (content.to_vec(), *cursor)
382}
383
384pub fn edit_text_multi(
393 content: &[InlineContent],
394 selections: &[Selection],
395 texts: &[&str],
396) -> (Vec<InlineContent>, Vec<Selection>) {
397 assert_eq!(
398 selections.len(),
399 texts.len(),
400 "edit_text_multi: selections and texts must have the same length"
401 );
402
403 if selections.is_empty() {
404 return (content.to_vec(), Vec::new());
405 }
406
407 let mut new_content = content.to_vec();
408 let mut new_selections = Vec::new();
409
410 let mut pairs: Vec<(Selection, &str)> = selections
412 .iter()
413 .copied()
414 .zip(texts.iter().copied())
415 .collect();
416 pairs.sort_by(|a, b| {
417 let cursor_a = match &a.0 {
418 Selection::Cursor(c) => c,
419 Selection::Range(r) => &r.start,
420 };
421 let cursor_b = match &b.0 {
422 Selection::Cursor(c) => c,
423 Selection::Range(r) => &r.start,
424 };
425 cursor_b.cluster_id.cmp(&cursor_a.cluster_id) });
427
428 for (selection, text) in &pairs {
429 let edit = TextEdit::Insert(text.to_string());
430 let (temp_content, new_cursor) =
431 apply_edit_to_selection(&new_content, selection, &edit);
432
433 let edit_run = match selection {
434 Selection::Cursor(c) => c.cluster_id.source_run,
435 Selection::Range(r) => r.start.cluster_id.source_run,
436 };
437 let edit_byte = match selection {
438 Selection::Cursor(c) => c.cluster_id.start_byte_in_run,
439 Selection::Range(r) => r.start.cluster_id.start_byte_in_run,
440 };
441
442 let byte_offset_change = text.len() as i32;
443
444 for prev_selection in new_selections.iter_mut() {
445 if let Selection::Cursor(cursor) = prev_selection {
446 if cursor.cluster_id.source_run == edit_run
447 && cursor.cluster_id.start_byte_in_run >= edit_byte
448 {
449 cursor.cluster_id.start_byte_in_run =
450 (cursor.cluster_id.start_byte_in_run as i32 + byte_offset_change).max(0)
451 as u32;
452 }
453 }
454 }
455
456 new_content = temp_content;
457 new_selections.push(Selection::Cursor(new_cursor));
458 }
459
460 new_selections.reverse();
461 (new_content, new_selections)
462}
463
464pub fn inspect_delete(
468 content: &[InlineContent],
469 selection: &Selection,
470 forward: bool,
471) -> Option<(SelectionRange, String)> {
472 match selection {
473 Selection::Range(range) => {
474 let deleted_text = extract_text_in_range(content, range);
476 Some((*range, deleted_text))
477 }
478 Selection::Cursor(cursor) => {
479 if forward {
481 inspect_delete_forward(content, cursor)
482 } else {
483 inspect_delete_backward(content, cursor)
484 }
485 }
486 }
487}
488
489fn inspect_delete_forward(
491 content: &[InlineContent],
492 cursor: &TextCursor,
493) -> Option<(SelectionRange, String)> {
494 use unicode_segmentation::UnicodeSegmentation;
495
496 let run_idx = cursor.cluster_id.source_run as usize;
497 let byte_offset = cursor.cluster_id.start_byte_in_run as usize;
498
499 if let Some(InlineContent::Text(run)) = content.get(run_idx) {
500 if byte_offset < run.text.len() {
501 let next_grapheme_end = run.text[byte_offset..]
503 .grapheme_indices(true)
504 .nth(1)
505 .map_or(run.text.len(), |(i, _)| byte_offset + i);
506
507 let deleted_text = run.text[byte_offset..next_grapheme_end].to_string();
508
509 let range = SelectionRange {
510 start: *cursor,
511 end: TextCursor {
512 cluster_id: GraphemeClusterId {
513 source_run: run_idx as u32,
514 start_byte_in_run: next_grapheme_end as u32,
515 },
516 affinity: CursorAffinity::Leading,
517 },
518 };
519
520 return Some((range, deleted_text));
521 } else if run_idx < content.len() - 1 {
522 if let Some(InlineContent::Text(next_run)) = content.get(run_idx + 1) {
524 let deleted_text = next_run.text.graphemes(true).next()?.to_string();
525
526 let next_grapheme_end = next_run
527 .text
528 .grapheme_indices(true)
529 .nth(1)
530 .map_or(next_run.text.len(), |(i, _)| i);
531
532 let range = SelectionRange {
533 start: *cursor,
534 end: TextCursor {
535 cluster_id: GraphemeClusterId {
536 source_run: (run_idx + 1) as u32,
537 start_byte_in_run: next_grapheme_end as u32,
538 },
539 affinity: CursorAffinity::Leading,
540 },
541 };
542
543 return Some((range, deleted_text));
544 }
545 }
546 }
547
548 None }
550
551fn inspect_delete_backward(
553 content: &[InlineContent],
554 cursor: &TextCursor,
555) -> Option<(SelectionRange, String)> {
556 use unicode_segmentation::UnicodeSegmentation;
557
558 let run_idx = cursor.cluster_id.source_run as usize;
559 let byte_offset = cursor.cluster_id.start_byte_in_run as usize;
560
561 if let Some(InlineContent::Text(run)) = content.get(run_idx) {
562 if byte_offset > 0 {
563 let prev_grapheme_start = run.text[..byte_offset]
565 .grapheme_indices(true)
566 .last()
567 .map_or(0, |(i, _)| i);
568
569 let deleted_text = run.text[prev_grapheme_start..byte_offset].to_string();
570
571 let range = SelectionRange {
572 start: TextCursor {
573 cluster_id: GraphemeClusterId {
574 source_run: run_idx as u32,
575 start_byte_in_run: prev_grapheme_start as u32,
576 },
577 affinity: CursorAffinity::Leading,
578 },
579 end: *cursor,
580 };
581
582 return Some((range, deleted_text));
583 } else if run_idx > 0 {
584 if let Some(InlineContent::Text(prev_run)) = content.get(run_idx - 1) {
586 let deleted_text = prev_run.text.graphemes(true).last()?.to_string();
587
588 let prev_grapheme_start = prev_run.text[..]
589 .grapheme_indices(true)
590 .last()
591 .map_or(0, |(i, _)| i);
592
593 let range = SelectionRange {
594 start: TextCursor {
595 cluster_id: GraphemeClusterId {
596 source_run: (run_idx - 1) as u32,
597 start_byte_in_run: prev_grapheme_start as u32,
598 },
599 affinity: CursorAffinity::Leading,
600 },
601 end: *cursor,
602 };
603
604 return Some((range, deleted_text));
605 }
606 }
607 }
608
609 None }
611
612fn extract_text_in_range(content: &[InlineContent], range: &SelectionRange) -> String {
614 let start_run = range.start.cluster_id.source_run as usize;
615 let end_run = range.end.cluster_id.source_run as usize;
616 let start_byte = range.start.cluster_id.start_byte_in_run as usize;
617 let end_byte = range.end.cluster_id.start_byte_in_run as usize;
618
619 if start_run == end_run {
620 if let Some(InlineContent::Text(run)) = content.get(start_run) {
622 if start_byte <= end_byte && end_byte <= run.text.len() {
623 return run.text[start_byte..end_byte].to_string();
624 }
625 }
626 } else {
627 let mut result = String::new();
629
630 for (idx, item) in content.iter().enumerate() {
631 if let InlineContent::Text(run) = item {
632 if idx == start_run {
633 if start_byte < run.text.len() {
635 result.push_str(&run.text[start_byte..]);
636 }
637 } else if idx > start_run && idx < end_run {
638 result.push_str(&run.text);
640 } else if idx == end_run {
641 if end_byte <= run.text.len() {
643 result.push_str(&run.text[..end_byte]);
644 }
645 break;
646 }
647 }
648 }
649
650 return result;
651 }
652
653 String::new()
654}