1use std::sync::Arc;
4
5use azul_core::selection::{
6 CursorAffinity, GraphemeClusterId, Selection, SelectionRange, TextCursor,
7};
8
9use crate::text3::cache::{ContentIndex, InlineContent, StyledRun};
10
11#[derive(Debug, Clone)]
13pub enum TextEdit {
14 Insert(String),
15 DeleteBackward,
16 DeleteForward,
17}
18
19pub fn edit_text(
22 content: &[InlineContent],
23 selections: &[Selection],
24 edit: &TextEdit,
25) -> (Vec<InlineContent>, Vec<Selection>) {
26 if selections.is_empty() {
27 return (content.to_vec(), Vec::new());
28 }
29
30 let mut new_content = content.to_vec();
31 let mut new_selections = Vec::new();
32
33 let mut sorted_selections = selections.to_vec();
37 sorted_selections.sort_by(|a, b| {
38 let cursor_a = match a {
39 Selection::Cursor(c) => c,
40 Selection::Range(r) => &r.start,
41 };
42 let cursor_b = match b {
43 Selection::Cursor(c) => c,
44 Selection::Range(r) => &r.start,
45 };
46 cursor_b.cluster_id.cmp(&cursor_a.cluster_id) });
48
49 for selection in sorted_selections {
50 let (mut temp_content, new_cursor) =
51 apply_edit_to_selection(&new_content, &selection, edit);
52
53 let edit_run = match selection {
56 Selection::Cursor(c) => c.cluster_id.source_run,
57 Selection::Range(r) => r.start.cluster_id.source_run,
58 };
59 let edit_byte = match selection {
60 Selection::Cursor(c) => c.cluster_id.start_byte_in_run,
61 Selection::Range(r) => r.start.cluster_id.start_byte_in_run,
62 };
63
64 let byte_offset_change: i32 = match edit {
66 TextEdit::Insert(text) => text.len() as i32,
67 TextEdit::DeleteBackward | TextEdit::DeleteForward => {
68 -1
71 }
72 };
73
74 for prev_selection in new_selections.iter_mut() {
76 if let Selection::Cursor(cursor) = prev_selection {
77 if cursor.cluster_id.source_run == edit_run
78 && cursor.cluster_id.start_byte_in_run >= edit_byte
79 {
80 cursor.cluster_id.start_byte_in_run =
81 (cursor.cluster_id.start_byte_in_run as i32 + byte_offset_change).max(0)
82 as u32;
83 }
84 }
85 }
86
87 new_content = temp_content;
88 new_selections.push(Selection::Cursor(new_cursor));
89 }
90
91 new_selections.reverse();
93
94 (new_content, new_selections)
95}
96
97pub fn apply_edit_to_selection(
99 content: &[InlineContent],
100 selection: &Selection,
101 edit: &TextEdit,
102) -> (Vec<InlineContent>, TextCursor) {
103 let mut new_content = content.to_vec();
104
105 let cursor_after_delete = match selection {
108 Selection::Range(range) => {
109 let (content_after_delete, cursor_pos) = delete_range(&new_content, range);
110 new_content = content_after_delete;
111 cursor_pos
112 }
113 Selection::Cursor(cursor) => *cursor,
114 };
115
116 match edit {
118 TextEdit::Insert(text_to_insert) => {
119 insert_text(&mut new_content, &cursor_after_delete, text_to_insert)
120 }
121 TextEdit::DeleteBackward => delete_backward(&mut new_content, &cursor_after_delete),
122 TextEdit::DeleteForward => delete_forward(&mut new_content, &cursor_after_delete),
123 }
124}
125
126pub fn delete_range(
128 content: &[InlineContent],
129 range: &SelectionRange,
130) -> (Vec<InlineContent>, TextCursor) {
131 let mut new_content = content.to_vec();
141 let start_run_idx = range.start.cluster_id.source_run as usize;
142 let end_run_idx = range.end.cluster_id.source_run as usize;
143
144 if start_run_idx == end_run_idx {
145 if let Some(InlineContent::Text(run)) = new_content.get_mut(start_run_idx) {
146 let start_byte = range.start.cluster_id.start_byte_in_run as usize;
147 let end_byte = range.end.cluster_id.start_byte_in_run as usize;
148 if start_byte <= end_byte && end_byte <= run.text.len() {
149 run.text.drain(start_byte..end_byte);
150 }
151 }
152 } else {
153 }
155
156 (new_content, range.start) }
158
159pub fn insert_text(
165 content: &mut Vec<InlineContent>,
166 cursor: &TextCursor,
167 text_to_insert: &str,
168) -> (Vec<InlineContent>, TextCursor) {
169 use unicode_segmentation::UnicodeSegmentation;
170
171 let mut new_content = content.clone();
172 let run_idx = cursor.cluster_id.source_run as usize;
173 let cluster_start_byte = cursor.cluster_id.start_byte_in_run as usize;
174
175 if let Some(InlineContent::Text(run)) = new_content.get_mut(run_idx) {
176 let byte_offset = match cursor.affinity {
178 CursorAffinity::Leading => {
179 cluster_start_byte
181 },
182 CursorAffinity::Trailing => {
183 if cluster_start_byte >= run.text.len() {
186 run.text.len()
188 } else {
189 run.text[cluster_start_byte..]
191 .grapheme_indices(true)
192 .next()
193 .map(|(_, grapheme)| cluster_start_byte + grapheme.len())
194 .unwrap_or(run.text.len())
195 }
196 },
197 };
198
199 if byte_offset <= run.text.len() {
200 run.text.insert_str(byte_offset, text_to_insert);
201
202 let new_cursor = TextCursor {
203 cluster_id: GraphemeClusterId {
204 source_run: run_idx as u32,
205 start_byte_in_run: (byte_offset + text_to_insert.len()) as u32,
206 },
207 affinity: CursorAffinity::Leading,
208 };
209 return (new_content, new_cursor);
210 }
211 }
212
213 (content.to_vec(), *cursor)
215}
216
217pub fn delete_backward(
223 content: &mut Vec<InlineContent>,
224 cursor: &TextCursor,
225) -> (Vec<InlineContent>, TextCursor) {
226 use unicode_segmentation::UnicodeSegmentation;
227 let mut new_content = content.clone();
228 let run_idx = cursor.cluster_id.source_run as usize;
229 let cluster_start_byte = cursor.cluster_id.start_byte_in_run as usize;
230
231 if let Some(InlineContent::Text(run)) = new_content.get_mut(run_idx) {
232 let byte_offset = match cursor.affinity {
234 CursorAffinity::Leading => cluster_start_byte,
235 CursorAffinity::Trailing => {
236 if cluster_start_byte >= run.text.len() {
238 run.text.len()
239 } else {
240 run.text[cluster_start_byte..]
241 .grapheme_indices(true)
242 .next()
243 .map(|(_, grapheme)| cluster_start_byte + grapheme.len())
244 .unwrap_or(run.text.len())
245 }
246 },
247 };
248
249 if byte_offset > 0 {
250 let prev_grapheme_start = run.text[..byte_offset]
251 .grapheme_indices(true)
252 .last()
253 .map_or(0, |(i, _)| i);
254 run.text.drain(prev_grapheme_start..byte_offset);
255
256 let new_cursor = TextCursor {
257 cluster_id: GraphemeClusterId {
258 source_run: run_idx as u32,
259 start_byte_in_run: prev_grapheme_start as u32,
260 },
261 affinity: CursorAffinity::Leading,
262 };
263 return (new_content, new_cursor);
264 } else if run_idx > 0 {
265 if let Some(InlineContent::Text(prev_run)) = content.get(run_idx - 1).cloned() {
267 let mut merged_text = prev_run.text;
268 let new_cursor_byte_offset = merged_text.len();
269 merged_text.push_str(&run.text);
270
271 new_content[run_idx - 1] = InlineContent::Text(StyledRun {
272 text: merged_text,
273 style: prev_run.style,
274 logical_start_byte: prev_run.logical_start_byte,
275 source_node_id: prev_run.source_node_id,
276 });
277 new_content.remove(run_idx);
278
279 let new_cursor = TextCursor {
280 cluster_id: GraphemeClusterId {
281 source_run: (run_idx - 1) as u32,
282 start_byte_in_run: new_cursor_byte_offset as u32,
283 },
284 affinity: CursorAffinity::Leading,
285 };
286 return (new_content, new_cursor);
287 }
288 }
289 }
290
291 (content.to_vec(), *cursor)
292}
293
294pub fn delete_forward(
300 content: &mut Vec<InlineContent>,
301 cursor: &TextCursor,
302) -> (Vec<InlineContent>, TextCursor) {
303 use unicode_segmentation::UnicodeSegmentation;
304 let mut new_content = content.clone();
305 let run_idx = cursor.cluster_id.source_run as usize;
306 let cluster_start_byte = cursor.cluster_id.start_byte_in_run as usize;
307
308 if let Some(InlineContent::Text(run)) = new_content.get_mut(run_idx) {
309 let byte_offset = match cursor.affinity {
311 CursorAffinity::Leading => cluster_start_byte,
312 CursorAffinity::Trailing => {
313 if cluster_start_byte >= run.text.len() {
315 run.text.len()
316 } else {
317 run.text[cluster_start_byte..]
318 .grapheme_indices(true)
319 .next()
320 .map(|(_, grapheme)| cluster_start_byte + grapheme.len())
321 .unwrap_or(run.text.len())
322 }
323 },
324 };
325
326 if byte_offset < run.text.len() {
327 let next_grapheme_end = run.text[byte_offset..]
328 .grapheme_indices(true)
329 .nth(1)
330 .map_or(run.text.len(), |(i, _)| byte_offset + i);
331 run.text.drain(byte_offset..next_grapheme_end);
332
333 let new_cursor = TextCursor {
335 cluster_id: GraphemeClusterId {
336 source_run: run_idx as u32,
337 start_byte_in_run: byte_offset as u32,
338 },
339 affinity: CursorAffinity::Leading,
340 };
341 return (new_content, new_cursor);
342 } else if run_idx < content.len() - 1 {
343 if let Some(InlineContent::Text(next_run)) = content.get(run_idx + 1).cloned() {
345 let mut merged_text = run.text.clone();
346 merged_text.push_str(&next_run.text);
347
348 new_content[run_idx] = InlineContent::Text(StyledRun {
349 text: merged_text,
350 style: run.style.clone(),
351 logical_start_byte: run.logical_start_byte,
352 source_node_id: run.source_node_id,
353 });
354 new_content.remove(run_idx + 1);
355
356 return (new_content, *cursor);
357 }
358 }
359 }
360
361 (content.to_vec(), *cursor)
362}
363
364pub fn inspect_delete(
380 content: &[InlineContent],
381 selection: &Selection,
382 forward: bool,
383) -> Option<(SelectionRange, String)> {
384 match selection {
385 Selection::Range(range) => {
386 let deleted_text = extract_text_in_range(content, range);
388 Some((*range, deleted_text))
389 }
390 Selection::Cursor(cursor) => {
391 if forward {
393 inspect_delete_forward(content, cursor)
394 } else {
395 inspect_delete_backward(content, cursor)
396 }
397 }
398 }
399}
400
401fn inspect_delete_forward(
403 content: &[InlineContent],
404 cursor: &TextCursor,
405) -> Option<(SelectionRange, String)> {
406 use unicode_segmentation::UnicodeSegmentation;
407
408 let run_idx = cursor.cluster_id.source_run as usize;
409 let byte_offset = cursor.cluster_id.start_byte_in_run as usize;
410
411 if let Some(InlineContent::Text(run)) = content.get(run_idx) {
412 if byte_offset < run.text.len() {
413 let next_grapheme_end = run.text[byte_offset..]
415 .grapheme_indices(true)
416 .nth(1)
417 .map_or(run.text.len(), |(i, _)| byte_offset + i);
418
419 let deleted_text = run.text[byte_offset..next_grapheme_end].to_string();
420
421 let range = SelectionRange {
422 start: *cursor,
423 end: TextCursor {
424 cluster_id: GraphemeClusterId {
425 source_run: run_idx as u32,
426 start_byte_in_run: next_grapheme_end as u32,
427 },
428 affinity: CursorAffinity::Leading,
429 },
430 };
431
432 return Some((range, deleted_text));
433 } else if run_idx < content.len() - 1 {
434 if let Some(InlineContent::Text(next_run)) = content.get(run_idx + 1) {
436 let deleted_text = next_run.text.graphemes(true).next()?.to_string();
437
438 let next_grapheme_end = next_run
439 .text
440 .grapheme_indices(true)
441 .nth(1)
442 .map_or(next_run.text.len(), |(i, _)| i);
443
444 let range = SelectionRange {
445 start: *cursor,
446 end: TextCursor {
447 cluster_id: GraphemeClusterId {
448 source_run: (run_idx + 1) as u32,
449 start_byte_in_run: next_grapheme_end as u32,
450 },
451 affinity: CursorAffinity::Leading,
452 },
453 };
454
455 return Some((range, deleted_text));
456 }
457 }
458 }
459
460 None }
462
463fn inspect_delete_backward(
465 content: &[InlineContent],
466 cursor: &TextCursor,
467) -> Option<(SelectionRange, String)> {
468 use unicode_segmentation::UnicodeSegmentation;
469
470 let run_idx = cursor.cluster_id.source_run as usize;
471 let byte_offset = cursor.cluster_id.start_byte_in_run as usize;
472
473 if let Some(InlineContent::Text(run)) = content.get(run_idx) {
474 if byte_offset > 0 {
475 let prev_grapheme_start = run.text[..byte_offset]
477 .grapheme_indices(true)
478 .last()
479 .map_or(0, |(i, _)| i);
480
481 let deleted_text = run.text[prev_grapheme_start..byte_offset].to_string();
482
483 let range = SelectionRange {
484 start: TextCursor {
485 cluster_id: GraphemeClusterId {
486 source_run: run_idx as u32,
487 start_byte_in_run: prev_grapheme_start as u32,
488 },
489 affinity: CursorAffinity::Leading,
490 },
491 end: *cursor,
492 };
493
494 return Some((range, deleted_text));
495 } else if run_idx > 0 {
496 if let Some(InlineContent::Text(prev_run)) = content.get(run_idx - 1) {
498 let deleted_text = prev_run.text.graphemes(true).last()?.to_string();
499
500 let prev_grapheme_start = prev_run.text[..]
501 .grapheme_indices(true)
502 .last()
503 .map_or(0, |(i, _)| i);
504
505 let range = SelectionRange {
506 start: TextCursor {
507 cluster_id: GraphemeClusterId {
508 source_run: (run_idx - 1) as u32,
509 start_byte_in_run: prev_grapheme_start as u32,
510 },
511 affinity: CursorAffinity::Leading,
512 },
513 end: *cursor,
514 };
515
516 return Some((range, deleted_text));
517 }
518 }
519 }
520
521 None }
523
524fn extract_text_in_range(content: &[InlineContent], range: &SelectionRange) -> String {
526 let start_run = range.start.cluster_id.source_run as usize;
527 let end_run = range.end.cluster_id.source_run as usize;
528 let start_byte = range.start.cluster_id.start_byte_in_run as usize;
529 let end_byte = range.end.cluster_id.start_byte_in_run as usize;
530
531 if start_run == end_run {
532 if let Some(InlineContent::Text(run)) = content.get(start_run) {
534 if start_byte <= end_byte && end_byte <= run.text.len() {
535 return run.text[start_byte..end_byte].to_string();
536 }
537 }
538 } else {
539 let mut result = String::new();
541
542 for (idx, item) in content.iter().enumerate() {
543 if let InlineContent::Text(run) = item {
544 if idx == start_run {
545 if start_byte < run.text.len() {
547 result.push_str(&run.text[start_byte..]);
548 }
549 } else if idx > start_run && idx < end_run {
550 result.push_str(&run.text);
552 } else if idx == end_run {
553 if end_byte <= run.text.len() {
555 result.push_str(&run.text[..end_byte]);
556 }
557 break;
558 }
559 }
560 }
561
562 return result;
563 }
564
565 String::new()
566}