1use std::cell::RefCell;
2use std::rc::Rc;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use sequoia_buffer::{SpanAttr, StyledSpan};
6use sequoia_core::{Color, Edges, Length, Rect, Size, Vec2};
7use sequoia_input::{
8 CursorIcon, Event, EventPhase, ImeEvent, Key, KeyEvent, Modifiers, PointerButton, PointerEvent,
9};
10use sequoia_layout::{LayoutCtx, LayoutStyle};
11use sequoia_reactive::Signal;
12use sequoia_style::{TextAreaStyle, Theme, VisualState};
13use sequoia_view::{AnyView, EventCtx, FontWeight, PaintCtx, Unit, View};
14
15use crate::buffer::{BufferEngine, StyledDoc};
16use crate::clipboard::SharedClipboard;
17
18#[derive(Clone)]
19pub(crate) enum DocSource {
20 Plain(Signal<String>),
21 Styled(Signal<StyledDoc>),
22}
23
24impl DocSource {
25 fn ensure(&self, engine: &mut BufferEngine) -> bool {
26 match self {
27 DocSource::Plain(s) => engine.ensure(s),
28 DocSource::Styled(s) => engine.ensure_styled(s),
29 }
30 }
31
32 fn commit(&self, engine: &mut BufferEngine) {
33 match self {
34 DocSource::Plain(s) => engine.commit(s),
35 DocSource::Styled(s) => engine.commit_styled(s),
36 }
37 }
38
39 fn get_text(&self) -> String {
40 match self {
41 DocSource::Plain(s) => s.get_untracked(),
42 DocSource::Styled(s) => s.get_untracked().text,
43 }
44 }
45
46 fn set_text(&self, text: String) {
47 match self {
48 DocSource::Plain(s) => s.set(text),
49
50 DocSource::Styled(s) => s.set(StyledDoc {
51 text,
52 spans: Vec::new(),
53 }),
54 }
55 }
56
57 fn track(&self) {
58 match self {
59 DocSource::Plain(s) => {
60 let _ = s.get();
61 }
62 DocSource::Styled(s) => {
63 let _ = s.get();
64 }
65 }
66 }
67}
68
69#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
70struct Caret {
71 offset: usize,
72 anchor: Option<usize>,
73}
74
75impl Caret {
76 fn collapsed(pos: usize) -> Self {
77 Self {
78 offset: pos,
79 anchor: None,
80 }
81 }
82
83 fn ends(&self) -> (usize, usize) {
84 match self.anchor {
85 Some(a) => (a.min(self.offset), a.max(self.offset)),
86 None => (self.offset, self.offset),
87 }
88 }
89
90 fn selected(&self) -> Option<(usize, usize)> {
91 let (a, b) = self.ends();
92 if a < b { Some((a, b)) } else { None }
93 }
94
95 fn move_to(&mut self, pos: usize, extend: bool) {
96 if extend {
97 let anchor = self.anchor.unwrap_or(self.offset);
98 self.anchor = Some(anchor);
99 self.offset = pos;
100 if self.anchor == Some(self.offset) {
101 self.anchor = None;
102 }
103 } else {
104 self.offset = pos;
105 self.anchor = None;
106 }
107 }
108}
109
110fn now_ms() -> u128 {
111 SystemTime::now()
112 .duration_since(UNIX_EPOCH)
113 .map(|d| d.as_millis())
114 .unwrap_or(0)
115}
116
117pub struct TextArea {
118 source: DocSource,
119 placeholder: String,
120 max_length: Option<usize>,
121 max_lines: Option<usize>,
122 min_lines: usize,
123 font_size: Option<f32>,
124 weight: Option<FontWeight>,
125 text_color: Option<Color>,
126 placeholder_color: Option<Color>,
127 background: Option<Color>,
128 border_color: Option<Color>,
129 border_width: Option<f32>,
130 radius: Option<f32>,
131 line_height_factor: Option<f32>,
132 current_line: Option<Color>,
133 highlight_current_line: bool,
134 pad_x: f32,
135 pad_y: f32,
136 indent: String,
137 auto_indent: bool,
138 editable: bool,
139 accept_tab: bool,
140 appearance: Option<TextAreaStyle>,
141 style: LayoutStyle,
142 margin: Edges,
143
144 revision: Signal<u32>,
145
146 state: Rc<RefCell<AreaState>>,
147
148 on_edit_start: Option<Rc<dyn Fn()>>,
149 on_edit_end: Option<Rc<dyn Fn(String)>>,
150 on_scroll: Option<Rc<dyn Fn(Vec2)>>,
151}
152
153const DEFAULT_PAD_X: f32 = 6.0;
154const DEFAULT_PAD_Y: f32 = 6.0;
155const DEFAULT_LINE_HEIGHT: f32 = 1.35;
156const DEFAULT_INDENT: &str = " ";
157const DEFAULT_MIN_LINES: usize = 3;
158const CARET_W: f32 = 1.5;
159const SCROLL_MARGIN: f32 = 2.0;
160
161#[derive(Clone)]
162struct AreaState {
163 carets: Vec<Caret>,
164
165 goal_x: Option<f32>,
166 scroll: Vec2,
167 viewport: Size,
168
169 content: Size,
170 preedit: String,
171 was_focused: bool,
172 session_text: Option<String>,
173
174 escalate: u8,
175 escalate_base: usize,
176
177 last_click_ms: Option<u128>,
178 last_click_pos: usize,
179 click_count: u32,
180
181 alt_drag: Option<usize>,
182
183 engine: BufferEngine,
184}
185
186impl Default for AreaState {
187 fn default() -> Self {
188 Self {
189 carets: vec![Caret::default()],
190 goal_x: None,
191 scroll: Vec2::default(),
192 viewport: Size::default(),
193 content: Size::default(),
194 preedit: String::new(),
195 was_focused: false,
196 session_text: None,
197 escalate: 0,
198 escalate_base: 0,
199 last_click_ms: None,
200 last_click_pos: 0,
201 click_count: 0,
202 alt_drag: None,
203 engine: BufferEngine::new(),
204 }
205 }
206}
207
208fn merge_byte_ranges(ranges: &[(usize, usize)]) -> (Vec<(usize, usize)>, Vec<usize>) {
209 if ranges.is_empty() {
210 return (Vec::new(), Vec::new());
211 }
212 let mut order: Vec<usize> = (0..ranges.len()).collect();
213 order.sort_by_key(|&i| (ranges[i].0, ranges[i].1));
214 let mut merged: Vec<(usize, usize)> = Vec::new();
215 let mut owner = vec![0usize; ranges.len()];
216 for i in order {
217 let (s, e) = ranges[i];
218 if let Some(last) = merged.last_mut()
219 && s <= last.1
220 {
221 last.1 = last.1.max(e);
222 owner[i] = merged.len() - 1;
223 continue;
224 }
225 owner[i] = merged.len();
226 merged.push((s, e));
227 }
228 (merged, owner)
229}
230
231fn map_through_deletes(unions: &[(usize, usize)], x: usize) -> usize {
232 let mut sub = 0usize;
233 for (s, e) in unions {
234 if *e <= x {
235 sub += e - s;
236 } else {
237 break;
238 }
239 }
240 x.saturating_sub(sub)
241}
242
243fn dedupe_carets(carets: &mut Vec<Caret>) {
244 let mut seen = std::collections::HashSet::new();
245 carets.retain(|c| seen.insert(c.offset));
246 if carets.is_empty() {
247 carets.push(Caret::default());
248 }
249}
250
251fn is_word_char(c: char) -> bool {
252 c.is_alphanumeric() || c == '_'
253}
254
255#[derive(Clone, Copy, Debug, PartialEq, Eq)]
256#[non_exhaustive]
257pub(crate) enum StyleFlag {
258 Bold,
259 Italic,
260 Underline,
261 Strike,
262}
263
264impl StyleFlag {
265 fn get(self, a: &SpanAttr) -> bool {
266 match self {
267 StyleFlag::Bold => a.bold,
268 StyleFlag::Italic => a.italic,
269 StyleFlag::Underline => a.underline,
270 StyleFlag::Strike => a.strike,
271 }
272 }
273
274 fn set(self, a: &mut SpanAttr, v: bool) {
275 match self {
276 StyleFlag::Bold => a.bold = v,
277 StyleFlag::Italic => a.italic = v,
278 StyleFlag::Underline => a.underline = v,
279 StyleFlag::Strike => a.strike = v,
280 }
281 }
282}
283
284fn line_starts(chars: &[char]) -> Vec<usize> {
285 let mut starts = vec![0usize];
286 for (i, &c) in chars.iter().enumerate() {
287 if c == '\n' {
288 starts.push(i + 1);
289 }
290 }
291 starts
292}
293
294fn line_len(chars: &[char], starts: &[usize], line: usize) -> usize {
295 let start = starts.get(line).copied().unwrap_or(chars.len());
296 match starts.get(line + 1) {
297 Some(&next) => next.saturating_sub(start + 1),
298 None => chars.len().saturating_sub(start),
299 }
300}
301
302fn line_col_of(chars: &[char], starts: &[usize], caret: usize) -> (usize, usize) {
303 let caret = caret.min(chars.len());
304 let line = starts.partition_point(|&s| s <= caret).saturating_sub(1);
305 let line = line.min(starts.len().saturating_sub(1));
306 (line, caret - starts[line])
307}
308
309fn caret_of_line_col(chars: &[char], starts: &[usize], line: usize, col: usize) -> usize {
310 let line = line.min(starts.len().saturating_sub(1));
311 starts[line] + col.min(line_len(chars, starts, line))
312}
313
314fn split_lines(text: &str) -> Vec<&str> {
315 text.split('\n').collect()
316}
317
318fn leading_indent_len(line: &str) -> usize {
319 line.chars().take_while(|&c| c == ' ' || c == '\t').count()
320}
321
322fn sanitize_multiline(s: &str) -> String {
323 let mut out = String::with_capacity(s.len());
324 let mut chars = s.chars().peekable();
325 while let Some(c) = chars.next() {
326 match c {
327 '\r' => {
328 out.push('\n');
329 if chars.peek() == Some(&'\n') {
330 chars.next();
331 }
332 }
333 '\n' | '\t' => out.push(c),
334 c if c.is_control() => {}
335 c => out.push(c),
336 }
337 }
338 out
339}
340
341impl TextArea {
342 pub fn new(value: Signal<String>) -> Self {
343 Self::with_source(DocSource::Plain(value))
344 }
345
346 pub fn new_styled(value: Signal<StyledDoc>) -> Self {
347 Self::with_source(DocSource::Styled(value))
348 }
349
350 fn with_source(source: DocSource) -> Self {
351 Self {
352 source,
353 placeholder: String::new(),
354 max_length: None,
355 max_lines: None,
356 min_lines: DEFAULT_MIN_LINES,
357 font_size: None,
358 weight: None,
359 text_color: None,
360 placeholder_color: None,
361 background: None,
362 border_color: None,
363 border_width: None,
364 radius: None,
365 line_height_factor: None,
366 current_line: None,
367 highlight_current_line: false,
368 pad_x: DEFAULT_PAD_X,
369 pad_y: DEFAULT_PAD_Y,
370 indent: DEFAULT_INDENT.to_owned(),
371 auto_indent: true,
372 editable: true,
373 accept_tab: true,
374 appearance: None,
375 style: LayoutStyle {
376 width: Length::Fill(1.0),
377 height: Length::Fill(1.0),
378 ..LayoutStyle::default()
379 },
380 margin: Edges::ZERO,
381 revision: Signal::new(0),
382 state: Rc::new(RefCell::new(AreaState::default())),
383 on_edit_start: None,
384 on_edit_end: None,
385 on_scroll: None,
386 }
387 }
388
389 pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
390 self.placeholder = placeholder.into();
391 self
392 }
393
394 pub fn max_length(mut self, max: usize) -> Self {
395 self.max_length = Some(max);
396 self
397 }
398
399 pub fn max_lines(mut self, max: usize) -> Self {
400 self.max_lines = Some(max.max(1));
401 self
402 }
403
404 pub fn min_lines(mut self, min: usize) -> Self {
405 self.min_lines = min.max(1);
406 self
407 }
408
409 pub fn on_edit_start(mut self, f: impl Fn() + 'static) -> Self {
410 self.on_edit_start = Some(Rc::new(f));
411 self
412 }
413
414 pub fn on_edit_end(mut self, f: impl Fn(String) + 'static) -> Self {
415 self.on_edit_end = Some(Rc::new(f));
416 self
417 }
418
419 pub fn on_scroll(mut self, f: impl Fn(Vec2) + 'static) -> Self {
420 self.on_scroll = Some(Rc::new(f));
421 self
422 }
423
424 pub fn font_size(mut self, size: f32) -> Self {
425 self.font_size = Some(size);
426 self
427 }
428
429 pub fn weight(mut self, weight: FontWeight) -> Self {
430 self.weight = Some(weight);
431 self
432 }
433
434 pub fn bold(mut self, bold: bool) -> Self {
435 self.weight = Some(if bold {
436 FontWeight::BOLD
437 } else {
438 FontWeight::NORMAL
439 });
440 self
441 }
442
443 pub fn color(mut self, color: Color) -> Self {
444 self.text_color = Some(color);
445 self
446 }
447
448 pub fn placeholder_color(mut self, color: Color) -> Self {
449 self.placeholder_color = Some(color);
450 self
451 }
452
453 pub fn background(mut self, color: Color) -> Self {
454 self.background = Some(color);
455 self
456 }
457
458 pub fn border_color(mut self, color: Color) -> Self {
459 self.border_color = Some(color);
460 self
461 }
462
463 pub fn border_width(mut self, width: f32) -> Self {
464 self.border_width = Some(width.max(0.0));
465 self
466 }
467
468 pub fn border(mut self, width: f32, color: Color) -> Self {
469 self.border_width = Some(width.max(0.0));
470 self.border_color = Some(color);
471 self
472 }
473
474 pub fn radius(mut self, radius: f32) -> Self {
475 self.radius = Some(radius.max(0.0));
476 self
477 }
478
479 pub fn line_height_factor(mut self, factor: f32) -> Self {
480 self.line_height_factor = Some(factor);
481 self
482 }
483
484 pub fn current_line(mut self, color: Color) -> Self {
485 self.current_line = Some(color);
486 self.highlight_current_line = true;
487 self
488 }
489
490 pub fn highlight_current_line(mut self, on: bool) -> Self {
491 self.highlight_current_line = on;
492 self
493 }
494
495 pub fn padding(mut self, padding: impl Into<Edges>) -> Self {
496 let p = padding.into();
497 self.pad_x = (p.left + p.right) * 0.5;
498 self.pad_y = (p.top + p.bottom) * 0.5;
499 self
500 }
501
502 pub fn padding_xy(mut self, x: f32, y: f32) -> Self {
503 self.pad_x = x.max(0.0);
504 self.pad_y = y.max(0.0);
505 self
506 }
507
508 pub fn indent(mut self, indent: impl Into<String>) -> Self {
509 self.indent = indent.into();
510 self
511 }
512
513 pub fn auto_indent(mut self, on: bool) -> Self {
514 self.auto_indent = on;
515 self
516 }
517
518 pub fn editable(mut self, editable: bool) -> Self {
519 self.editable = editable;
520 self
521 }
522
523 pub fn accept_tab(mut self, on: bool) -> Self {
524 self.accept_tab = on;
525 self
526 }
527
528 pub fn appearance(mut self, style: TextAreaStyle) -> Self {
529 self.appearance = Some(style);
530 self
531 }
532
533 pub fn width(mut self, width: impl Into<Length>) -> Self {
534 self.style = self.style.width(width.into());
535 self
536 }
537
538 pub fn height(mut self, height: impl Into<Length>) -> Self {
539 self.style = self.style.height(height.into());
540 self
541 }
542
543 pub fn min_width(mut self, v: f32) -> Self {
544 self.style = self.style.min_width(v);
545 self
546 }
547
548 pub fn max_width(mut self, v: f32) -> Self {
549 self.style = self.style.max_width(v);
550 self
551 }
552
553 pub fn min_height(mut self, v: f32) -> Self {
554 self.style = self.style.min_height(v);
555 self
556 }
557
558 pub fn max_height(mut self, v: f32) -> Self {
559 self.style = self.style.max_height(v);
560 self
561 }
562
563 pub fn flex(mut self, weight: f32) -> Self {
564 self.style = self.style.flex(weight);
565 self
566 }
567
568 pub fn expand(self) -> Self {
569 self.flex(1.0)
570 }
571
572 pub fn margin(mut self, margin: impl Into<Edges>) -> Self {
573 let m = margin.into();
574 self.margin = Edges::only(
575 sanitize_margin(m.left),
576 sanitize_margin(m.top),
577 sanitize_margin(m.right),
578 sanitize_margin(m.bottom),
579 );
580 self
581 }
582
583 pub fn margin_xy(self, x: f32, y: f32) -> Self {
584 self.margin(Edges::xy(x, y))
585 }
586
587 pub fn margin_x(mut self, x: f32) -> Self {
588 let x = sanitize_margin(x);
589 self.margin.left = x;
590 self.margin.right = x;
591 self
592 }
593
594 pub fn margin_y(mut self, y: f32) -> Self {
595 let y = sanitize_margin(y);
596 self.margin.top = y;
597 self.margin.bottom = y;
598 self
599 }
600
601 pub fn margin_left(mut self, v: f32) -> Self {
602 self.margin.left = sanitize_margin(v);
603 self
604 }
605
606 pub fn margin_right(mut self, v: f32) -> Self {
607 self.margin.right = sanitize_margin(v);
608 self
609 }
610
611 pub fn margin_top(mut self, v: f32) -> Self {
612 self.margin.top = sanitize_margin(v);
613 self
614 }
615
616 pub fn margin_bottom(mut self, v: f32) -> Self {
617 self.margin.bottom = sanitize_margin(v);
618 self
619 }
620
621 fn with_state<R>(&self, f: impl FnOnce(&mut AreaState) -> R) -> R {
622 f(&mut self.state.borrow_mut())
623 }
624
625 fn main_mut(s: &mut AreaState) -> &mut Caret {
626 if s.carets.is_empty() {
627 s.carets.push(Caret::default());
628 }
629 &mut s.carets[0]
630 }
631
632 fn main_of(s: &AreaState) -> Caret {
633 s.carets.first().copied().unwrap_or_default()
634 }
635
636 fn reset_escalate(s: &mut AreaState) {
637 s.escalate = 0;
638 s.escalate_base = 0;
639 }
640
641 fn chars_via(source: &DocSource) -> Vec<char> {
642 source.get_text().chars().collect()
643 }
644
645 fn chars(&self) -> Vec<char> {
646 Self::chars_via(&self.source)
647 }
648
649 fn starts(&self, chars: &[char]) -> Vec<usize> {
650 line_starts(chars)
651 }
652
653 pub fn caret(&self) -> usize {
654 self.with_state(|s| Self::main_of(s).offset)
655 }
656
657 pub fn caret_count(&self) -> usize {
658 self.with_state(|s| s.carets.len().max(1))
659 }
660
661 pub fn set_caret(&self, caret: usize) {
662 let source = self.source.clone();
663 self.with_state(|s| {
664 source.ensure(&mut s.engine);
665 let caret = caret.min(s.engine.len_chars());
666 s.carets = vec![Caret::collapsed(caret)];
667 Self::reset_escalate(s);
668 s.goal_x = None;
669 });
670 self.bump();
671 }
672
673 pub fn selection(&self) -> Option<(usize, usize)> {
674 self.with_state(|s| {
675 let m = Self::main_of(s);
676 m.anchor.map(|a| (a.min(m.offset), a.max(m.offset)))
677 })
678 }
679
680 pub fn selections(&self) -> Vec<(usize, usize)> {
681 self.with_state(|s| s.carets.iter().filter_map(|c| c.selected()).collect())
682 }
683
684 pub fn caret_line_col(&self) -> (usize, usize) {
685 let chars = self.chars();
686 let starts = self.starts(&chars);
687 let caret = self.caret();
688 line_col_of(&chars, &starts, caret)
689 }
690
691 pub fn line_count(&self) -> usize {
692 let chars = self.chars();
693 self.starts(&chars).len()
694 }
695
696 pub fn scroll_offset(&self) -> Vec2 {
697 self.with_state(|s| s.scroll)
698 }
699
700 pub fn scroll_to(&self, offset: Vec2) {
701 let (viewport, content) = self.with_state(|s| (s.viewport, s.content));
702 let next = clamp_scroll(offset, content.width, content.height, viewport, self.pad());
703 let changed = self.with_state(|s| {
704 if s.scroll != next {
705 s.scroll = next;
706 true
707 } else {
708 false
709 }
710 });
711 if changed {
712 if let Some(f) = &self.on_scroll {
713 f(next);
714 }
715 self.bump();
716 }
717 }
718
719 fn pad(&self) -> (f32, f32) {
720 (self.pad_x, self.pad_y)
721 }
722
723 fn begin_session(&self) {
724 let cur = self.source.get_text();
725 self.with_state(|s| {
726 s.session_text = Some(cur);
727 s.was_focused = true;
728 });
729 if let Some(f) = &self.on_edit_start {
730 f();
731 }
732 }
733
734 fn note_focus(&self, focused: bool) {
735 enum Trans {
736 Start,
737 End(String),
738 None,
739 }
740 let cur = self.source.get_text();
741 let trans = self.with_state(|s| {
742 let was = s.was_focused;
743 s.was_focused = focused;
744 if !was && focused {
745 s.session_text = Some(cur.clone());
746 Trans::Start
747 } else if was && !focused {
748 match s.session_text.take() {
749 Some(before) if before != cur => Trans::End(cur.clone()),
750 _ => Trans::None,
751 }
752 } else {
753 Trans::None
754 }
755 });
756 match trans {
757 Trans::Start => {
758 if let Some(f) = &self.on_edit_start {
759 f();
760 }
761 }
762 Trans::End(text) => {
763 if let Some(f) = &self.on_edit_end {
764 f(text);
765 }
766 }
767 Trans::None => {}
768 }
769 }
770
771 fn set_caret_extend(&self, caret: usize, extend: bool) {
772 let source = self.source.clone();
773 self.with_state(|s| {
774 source.ensure(&mut s.engine);
775 let caret = caret.min(s.engine.len_chars());
776 let main = Self::main_of(s);
777 s.carets = vec![main];
778 Self::main_mut(s).move_to(caret, extend);
779 Self::reset_escalate(s);
780 s.goal_x = None;
781 });
782 }
783
784 fn bump(&self) {
785 self.revision
786 .set(self.revision.get_untracked().wrapping_add(1));
787 }
788
789 fn font_weight(&self) -> FontWeight {
790 self.weight
791 .or_else(|| {
792 self.appearance
793 .and_then(|s| s.font_weight)
794 .map(FontWeight::new)
795 })
796 .unwrap_or_default()
797 }
798
799 fn resolved_font_size(&self, theme: &Theme) -> f32 {
800 self.font_size
801 .or_else(|| self.appearance.and_then(|s| s.font_size))
802 .unwrap_or(theme.font_size)
803 }
804
805 fn line_height(&self) -> f32 {
806 let size = self
807 .font_size
808 .or_else(|| self.appearance.and_then(|s| s.font_size))
809 .unwrap_or(14.0);
810 size * self
811 .line_height_factor
812 .or_else(|| self.appearance.and_then(|s| s.line_height))
813 .unwrap_or(DEFAULT_LINE_HEIGHT)
814 .clamp(1.0, 3.0)
815 }
816
817 fn resolved_line_height(&self, theme: &Theme) -> f32 {
818 self.resolved_font_size(theme)
819 * self
820 .line_height_factor
821 .or_else(|| self.appearance.and_then(|s| s.line_height))
822 .unwrap_or(DEFAULT_LINE_HEIGHT)
823 .clamp(1.0, 3.0)
824 }
825
826 fn resolved_text_color(&self, theme: &Theme) -> Color {
827 self.text_color
828 .or_else(|| self.appearance.and_then(|s| s.text))
829 .unwrap_or(theme.text)
830 }
831
832 fn resolved_placeholder_color(&self, theme: &Theme) -> Color {
833 self.placeholder_color
834 .or_else(|| self.appearance.and_then(|s| s.placeholder))
835 .unwrap_or(theme.text_secondary)
836 }
837
838 fn resolved_background(&self, theme: &Theme) -> Color {
839 self.background
840 .or_else(|| self.appearance.and_then(|s| s.background))
841 .unwrap_or(theme.surface)
842 }
843
844 fn resolved_border(&self, theme: &Theme) -> Option<(f32, Color)> {
845 let width = self
846 .border_width
847 .or_else(|| self.appearance.and_then(|s| s.border_width))
848 .unwrap_or(1.0)
849 .max(0.0);
850 if width <= 0.0 {
851 return None;
852 }
853 let color = self
854 .border_color
855 .or_else(|| self.appearance.and_then(|s| s.border))
856 .unwrap_or(theme.border);
857 if color.a <= 0.0 {
858 return None;
859 }
860 Some((width, color))
861 }
862
863 fn resolved_radius(&self, theme: &Theme) -> f32 {
864 self.radius
865 .or_else(|| self.appearance.and_then(|s| s.radius))
866 .unwrap_or(theme.radius)
867 .max(0.0)
868 }
869
870 fn delete_selection(&self) -> bool {
871 let source = self.source.clone();
872 let mut deleted = false;
873 self.with_state(|s| {
874 source.ensure(&mut s.engine);
875 let len = s.engine.len_chars();
876
877 if s.carets.len() <= 1 {
878 let m = Self::main_of(s);
879 let Some((mut a, mut b)) = m.anchor.map(|x| (x.min(m.offset), x.max(m.offset)))
880 else {
881 return;
882 };
883 if a == b {
884 Self::main_mut(s).anchor = None;
885 return;
886 }
887 (a, b) = (a.min(len), b.min(len));
888 let (ab, bb) = (s.engine.to_bytes(a), s.engine.to_bytes(b));
889 s.engine.delete(ab..bb);
890 source.commit(&mut s.engine);
891 let offset = s.engine.to_chars(ab);
892 let main = Self::main_mut(s);
893 main.offset = offset;
894 main.anchor = None;
895 s.goal_x = None;
896 Self::reset_escalate(s);
897 deleted = true;
898 return;
899 }
900
901 let mut pre: Vec<(usize, usize, usize)> = Vec::new();
902 for (i, c) in s.carets.iter().enumerate() {
903 if let Some((a, b)) = c.selected() {
904 let (a, b) = (a.min(len), b.min(len));
905 if a < b {
906 pre.push((i, s.engine.to_bytes(a), s.engine.to_bytes(b)));
907 }
908 } else if c.anchor.is_some() {
909 }
910 }
911 for c in s.carets.iter_mut() {
912 if c.selected().is_none() {
913 c.anchor = None;
914 }
915 }
916 if pre.is_empty() {
917 return;
918 }
919 let raw: Vec<(usize, usize)> = pre.iter().map(|&(_, a, b)| (a, b)).collect();
920 let (merged, owner) = merge_byte_ranges(&raw);
921 let ranges: Vec<std::ops::Range<usize>> = merged.iter().map(|&(a, b)| a..b).collect();
922 s.engine.delete_many(ranges);
923 source.commit(&mut s.engine);
924 let new_len = s.engine.len_chars();
925
926 let mut new_offsets = vec![0usize; s.carets.len()];
927 let mut touched = vec![false; s.carets.len()];
928 for (k, &(i, _, _)) in pre.iter().enumerate() {
929 let (us, _) = merged[owner[k]];
930 let mapped = map_through_deletes(&merged, us);
931 new_offsets[i] = s.engine.to_chars(mapped).min(new_len);
932 touched[i] = true;
933 }
934 for (i, c) in s.carets.iter_mut().enumerate() {
935 if touched[i] {
936 c.offset = new_offsets[i];
937 c.anchor = None;
938 }
939 }
940
941 dedupe_carets(&mut s.carets);
942 s.goal_x = None;
943 Self::reset_escalate(s);
944 deleted = true;
945 });
946 if deleted {
947 self.bump();
948 }
949 deleted
950 }
951
952 pub fn selected_text(&self) -> Option<String> {
953 let source = self.source.clone();
954 self.with_state(|s| {
955 source.ensure(&mut s.engine);
956 let len = s.engine.len_chars();
957 let mut parts = Vec::new();
958 for c in &s.carets {
959 if let Some((a, b)) = c.selected() {
960 let (a, b) = (a.min(len), b.min(len));
961 if a < b {
962 let text = s
963 .engine
964 .buffer()
965 .substring(s.engine.to_bytes(a)..s.engine.to_bytes(b));
966 if !text.is_empty() {
967 parts.push(text);
968 }
969 }
970 }
971 }
972 if parts.is_empty() {
973 None
974 } else {
975 Some(parts.join("\n"))
976 }
977 })
978 }
979
980 pub fn select_range(&self, a: usize, b: usize) {
981 let source = self.source.clone();
982 self.with_state(|s| {
983 source.ensure(&mut s.engine);
984 let len = s.engine.len_chars();
985 let (a, b) = (a.min(len), b.min(len));
986
987 s.carets = vec![Caret {
988 offset: b,
989 anchor: if a == b { None } else { Some(a) },
990 }];
991 Self::reset_escalate(s);
992 s.goal_x = None;
993 });
994 self.bump();
995 }
996
997 pub fn clear_selection(&self) -> bool {
998 let had = self.with_state(|s| {
999 let had = s.carets.iter().any(|c| c.anchor.is_some());
1000 for c in s.carets.iter_mut() {
1001 c.anchor = None;
1002 }
1003 Self::reset_escalate(s);
1004 had
1005 });
1006 if had {
1007 self.bump();
1008 }
1009 had
1010 }
1011
1012 pub fn select_all(&self) {
1013 let source = self.source.clone();
1014 self.with_state(|s| {
1015 source.ensure(&mut s.engine);
1016 let len = s.engine.len_chars();
1017 s.carets = vec![Caret {
1018 offset: len,
1019 anchor: Some(0),
1020 }];
1021 Self::reset_escalate(s);
1022 s.goal_x = None;
1023 });
1024 self.bump();
1025 }
1026
1027 fn insert_str(&self, text: &str) {
1028 if !self.editable || text.is_empty() {
1029 return;
1030 }
1031 let source = self.source.clone();
1032 let single = self.with_state(|s| s.carets.len() <= 1);
1033 if single {
1034 self.with_state(|st| {
1035 source.ensure(&mut st.engine);
1036 let total = st.engine.len_chars();
1037
1038 let sel_bytes = match Self::main_of(st).anchor.map(|a| {
1039 (
1040 a.min(Self::main_of(st).offset),
1041 a.max(Self::main_of(st).offset),
1042 )
1043 }) {
1044 None => None,
1045 Some((a, b)) if a == b => {
1046 Self::main_mut(st).anchor = None;
1047 None
1048 }
1049 Some((a, b)) => {
1050 let (a, b) = (a.min(total), b.min(total));
1051 Some((st.engine.to_bytes(a), st.engine.to_bytes(b)))
1052 }
1053 };
1054
1055 let allowed = match self.max_length {
1056 Some(max) => max.saturating_sub(total),
1057 None => usize::MAX,
1058 };
1059 let ins = BufferEngine::fit_prefix(text, allowed);
1060 let caret_b = match sel_bytes {
1061 Some((ab, bb)) => {
1062 st.engine.replace(ab..bb, ins);
1063 ab + ins.len()
1064 }
1065 None => {
1066 let caret_b = st.engine.to_bytes(Self::main_of(st).offset.min(total));
1067 st.engine.insert(caret_b, ins);
1068 caret_b + ins.len()
1069 }
1070 };
1071 if let Some(max) = self.max_lines {
1072 st.engine.enforce_max_lines(max);
1073 }
1074 source.commit(&mut st.engine);
1075
1076 let offset = st.engine.to_chars(caret_b).min(st.engine.len_chars());
1077 let main = Self::main_mut(st);
1078 main.offset = offset;
1079 main.anchor = None;
1080 st.goal_x = None;
1081 Self::reset_escalate(st);
1082 });
1083 self.bump();
1084 return;
1085 }
1086
1087 self.with_state(|st| {
1088 source.ensure(&mut st.engine);
1089 let total = st.engine.len_chars();
1090 let mut sel_pre: Vec<(usize, usize, usize)> = Vec::new();
1091 for (i, c) in st.carets.iter().enumerate() {
1092 if let Some((a, b)) = c.selected() {
1093 let (a, b) = (a.min(total), b.min(total));
1094 if a < b {
1095 sel_pre.push((i, st.engine.to_bytes(a), st.engine.to_bytes(b)));
1096 }
1097 }
1098 }
1099 let raw: Vec<(usize, usize)> = sel_pre.iter().map(|&(_, a, b)| (a, b)).collect();
1100 let (merged, owner) = merge_byte_ranges(&raw);
1101 if !merged.is_empty() {
1102 let ranges: Vec<std::ops::Range<usize>> =
1103 merged.iter().map(|&(a, b)| a..b).collect();
1104 st.engine.delete_many(ranges);
1105 }
1106
1107 let mut points: Vec<usize> = Vec::with_capacity(st.carets.len());
1108 let mut caret_union: Vec<Option<usize>> = vec![None; st.carets.len()];
1109 for (k, &(i, _, _)) in sel_pre.iter().enumerate() {
1110 caret_union[i] = Some(owner[k]);
1111 }
1112 for (i, c) in st.carets.iter().enumerate() {
1113 let pre_b = match caret_union[i] {
1114 Some(u) => merged[u].0,
1115 None => st.engine.to_bytes(c.offset.min(total)),
1116 };
1117
1118 let mut u_hit = caret_union[i];
1119 if u_hit.is_none() {
1120 for (u, &(us, ue)) in merged.iter().enumerate() {
1121 if pre_b >= us && pre_b < ue {
1122 u_hit = Some(u);
1123 break;
1124 }
1125 }
1126 }
1127 let post_b = match u_hit {
1128 Some(u) => map_through_deletes(&merged, merged[u].0),
1129 None => map_through_deletes(&merged, pre_b),
1130 };
1131 points.push(post_b);
1132 }
1133
1134 let allowed = match self.max_length {
1135 Some(max) => max.saturating_sub(total),
1136 None => usize::MAX,
1137 };
1138 let ins = BufferEngine::fit_prefix(text, allowed);
1139 if ins.is_empty() {
1140 source.commit(&mut st.engine);
1141 let new_len = st.engine.len_chars();
1142 for (i, c) in st.carets.iter_mut().enumerate() {
1143 c.offset = st.engine.to_chars(points[i]).min(new_len);
1144 c.anchor = None;
1145 }
1146 dedupe_carets(&mut st.carets);
1147 st.goal_x = None;
1148 Self::reset_escalate(st);
1149 return;
1150 }
1151
1152 let mut uniq = points.clone();
1153 uniq.sort_unstable();
1154 uniq.dedup();
1155
1156 let mut pcs: Vec<usize> = uniq.iter().map(|&p| st.engine.to_chars(p)).collect();
1157 pcs.sort_unstable();
1158 let ins_chars = ins.chars().count();
1159 let at: Vec<usize> = uniq.clone();
1160 st.engine.insert_at_many(at, ins);
1161 if let Some(max) = self.max_lines {
1162 st.engine.enforce_max_lines(max);
1163 }
1164 source.commit(&mut st.engine);
1165 let new_len = st.engine.len_chars();
1166
1167 let mut new_offsets = vec![0usize; st.carets.len()];
1168 for (i, &p) in points.iter().enumerate() {
1169 let pc = st.engine.to_chars(p);
1170 let below = pcs.iter().filter(|&&q| q < pc).count();
1171 new_offsets[i] = (pc + ins_chars * (1 + below)).min(new_len);
1172 }
1173 for (i, c) in st.carets.iter_mut().enumerate() {
1174 c.offset = new_offsets[i];
1175 c.anchor = None;
1176 }
1177 dedupe_carets(&mut st.carets);
1178 st.goal_x = None;
1179 Self::reset_escalate(st);
1180 });
1181 self.bump();
1182 }
1183
1184 fn delete_range_for(
1185 engine: &BufferEngine,
1186 caret_b: usize,
1187 back: bool,
1188 word: bool,
1189 ) -> (usize, usize) {
1190 let buf = engine.buffer();
1191 if back {
1192 let from_b = if word {
1193 sequoia_buffer::word_start_before(buf, caret_b)
1194 } else {
1195 sequoia_buffer::prev_grapheme(buf, caret_b)
1196 };
1197 (from_b, caret_b)
1198 } else {
1199 let to_b = if word {
1200 sequoia_buffer::word_end_after(buf, caret_b)
1201 } else {
1202 sequoia_buffer::next_grapheme(buf, caret_b)
1203 };
1204 (caret_b, to_b)
1205 }
1206 }
1207
1208 fn range_styled(engine: &BufferEngine, s: usize, e: usize) -> bool {
1209 s < e
1210 && engine
1211 .styled_buffer()
1212 .spans()
1213 .iter()
1214 .any(|sp| sp.start < e && s < sp.end)
1215 }
1216
1217 fn backspace(&self, word: bool) {
1218 if !self.editable {
1219 return;
1220 }
1221 if self.delete_selection() {
1222 return;
1223 }
1224 let source = self.source.clone();
1225
1226 if self.with_state(|s| s.carets.len() <= 1) {
1227 if !word {
1228 let stripped = self.with_state(|s| {
1229 source.ensure(&mut s.engine);
1230 let len = s.engine.len_chars();
1231 let caret = Self::main_of(s).offset.min(len);
1232 let caret_b = s.engine.to_bytes(caret);
1233 let total_b = s.engine.to_bytes(len);
1234 if caret == 0 {
1235 return false;
1236 }
1237 let from_b = sequoia_buffer::prev_grapheme(s.engine.buffer(), caret_b);
1238 if from_b == caret_b || !Self::range_styled(&s.engine, from_b, caret_b) {
1239 return false;
1240 }
1241 let next_styled = if caret_b < total_b {
1242 let to_b = sequoia_buffer::next_grapheme(s.engine.buffer(), caret_b);
1243 Self::range_styled(&s.engine, caret_b, to_b)
1244 } else {
1245 false
1246 };
1247 if next_styled {
1248 return false;
1249 }
1250 s.engine.clear_attr(from_b..caret_b);
1251 source.commit(&mut s.engine);
1252
1253 Self::main_mut(s).offset = s.engine.to_chars(caret_b).min(s.engine.len_chars());
1254 Self::main_mut(s).anchor = None;
1255 s.goal_x = None;
1256 Self::reset_escalate(s);
1257 true
1258 });
1259 if stripped {
1260 self.bump();
1261 return;
1262 }
1263 }
1264 self.with_state(|s| {
1265 source.ensure(&mut s.engine);
1266 let len = s.engine.len_chars();
1267 let caret = Self::main_of(s).offset.min(len);
1268 if caret == 0 {
1269 return;
1270 }
1271 let caret_b = s.engine.to_bytes(caret);
1272 let (from_b, _) = Self::delete_range_for(&s.engine, caret_b, true, word);
1273 s.engine.delete(from_b..caret_b);
1274 source.commit(&mut s.engine);
1275 let offset = s.engine.to_chars(from_b);
1276 let main = Self::main_mut(s);
1277 main.offset = offset;
1278 main.anchor = None;
1279 s.goal_x = None;
1280 Self::reset_escalate(s);
1281 });
1282 self.bump();
1283 return;
1284 }
1285
1286 self.with_state(|s| {
1287 source.ensure(&mut s.engine);
1288 let len = s.engine.len_chars();
1289 let total_b = s.engine.to_bytes(len);
1290
1291 let pre_bytes: Vec<usize> = s
1292 .carets
1293 .iter()
1294 .map(|c| s.engine.to_bytes(c.offset.min(len)))
1295 .collect();
1296 let mut stripped_bytes: Vec<usize> = Vec::new();
1297 if !word {
1298 for c in s.carets.iter() {
1299 debug_assert!(c.selected().is_none());
1300 let caret = c.offset.min(len);
1301 if caret == 0 {
1302 continue;
1303 }
1304 let caret_b = s.engine.to_bytes(caret);
1305 let from_b = sequoia_buffer::prev_grapheme(s.engine.buffer(), caret_b);
1306 if from_b == caret_b || !Self::range_styled(&s.engine, from_b, caret_b) {
1307 continue;
1308 }
1309 let next_styled = if caret_b < total_b {
1310 let to_b = sequoia_buffer::next_grapheme(s.engine.buffer(), caret_b);
1311 Self::range_styled(&s.engine, caret_b, to_b)
1312 } else {
1313 false
1314 };
1315 if next_styled {
1316 continue;
1317 }
1318 s.engine.clear_attr(from_b..caret_b);
1319 stripped_bytes.push(caret_b);
1320 }
1321 if !stripped_bytes.is_empty() {
1322 source.commit(&mut s.engine);
1323 }
1324 }
1325
1326 let mut ranges: Vec<(usize, usize)> = Vec::new();
1327 let mut owners: Vec<usize> = Vec::new();
1328 for (i, c) in s.carets.iter().enumerate() {
1329 let caret = c.offset.min(len);
1330 let caret_b = s.engine.to_bytes(caret);
1331 if stripped_bytes.contains(&caret_b) {
1332 continue;
1333 }
1334 if caret == 0 {
1335 continue;
1336 }
1337 let (from_b, to_b) = Self::delete_range_for(&s.engine, caret_b, true, word);
1338 if from_b < to_b {
1339 ranges.push((from_b, to_b));
1340 owners.push(i);
1341 }
1342 }
1343 if ranges.is_empty() {
1344 let new_len = s.engine.len_chars();
1345 for c in s.carets.iter_mut() {
1346 c.offset = c.offset.min(new_len);
1347 c.anchor = None;
1348 }
1349 Self::reset_escalate(s);
1350 return;
1351 }
1352 let raw = ranges.clone();
1353 let (merged, owner) = merge_byte_ranges(&raw);
1354 let dranges: Vec<std::ops::Range<usize>> = merged.iter().map(|&(a, b)| a..b).collect();
1355 s.engine.delete_many(dranges);
1356 source.commit(&mut s.engine);
1357 let new_len = s.engine.len_chars();
1358
1359 let mut new_offsets = vec![0usize; s.carets.len()];
1360 let mut touched = vec![false; s.carets.len()];
1361 for (k, &i) in owners.iter().enumerate() {
1362 let (us, _) = merged[owner[k]];
1363 new_offsets[i] = s
1364 .engine
1365 .to_chars(map_through_deletes(&merged, us))
1366 .min(new_len);
1367 touched[i] = true;
1368 }
1369 for (i, c) in s.carets.iter_mut().enumerate() {
1370 if !touched[i] {
1371 let pb = pre_bytes[i];
1372 c.offset = s
1373 .engine
1374 .to_chars(map_through_deletes(&merged, pb))
1375 .min(new_len);
1376 } else {
1377 c.offset = new_offsets[i];
1378 }
1379 c.anchor = None;
1380 }
1381 s.goal_x = None;
1382 Self::reset_escalate(s);
1383 dedupe_carets(&mut s.carets);
1384 });
1385 self.bump();
1386 }
1387
1388 fn delete(&self, word: bool) {
1389 if !self.editable {
1390 return;
1391 }
1392 if self.delete_selection() {
1393 return;
1394 }
1395 let source = self.source.clone();
1396 if self.with_state(|s| s.carets.len() <= 1) {
1397 self.with_state(|s| {
1398 source.ensure(&mut s.engine);
1399 let len = s.engine.len_chars();
1400 let caret = Self::main_of(s).offset.min(len);
1401 if caret >= len {
1402 return;
1403 }
1404 let caret_b = s.engine.to_bytes(caret);
1405 let (_, to_b) = Self::delete_range_for(&s.engine, caret_b, false, word);
1406 s.engine.delete(caret_b..to_b);
1407 source.commit(&mut s.engine);
1408 let offset = s.engine.to_chars(caret_b);
1409 let main = Self::main_mut(s);
1410 main.offset = offset;
1411 main.anchor = None;
1412 s.goal_x = None;
1413 Self::reset_escalate(s);
1414 });
1415 self.bump();
1416 return;
1417 }
1418
1419 self.with_state(|s| {
1420 source.ensure(&mut s.engine);
1421 let len = s.engine.len_chars();
1422 let pre_bytes: Vec<usize> = s
1423 .carets
1424 .iter()
1425 .map(|c| s.engine.to_bytes(c.offset.min(len)))
1426 .collect();
1427 let mut ranges: Vec<(usize, usize)> = Vec::new();
1428 let mut owners: Vec<usize> = Vec::new();
1429 for (i, &cb) in pre_bytes.iter().enumerate() {
1430 let caret = s.carets[i].offset.min(len);
1431 if caret >= len {
1432 continue;
1433 }
1434 let (_, to_b) = Self::delete_range_for(&s.engine, cb, false, word);
1435 if cb < to_b {
1436 ranges.push((cb, to_b));
1437 owners.push(i);
1438 }
1439 }
1440 if ranges.is_empty() {
1441 Self::reset_escalate(s);
1442 return;
1443 }
1444 let (merged, owner) = merge_byte_ranges(&ranges);
1445 let dranges: Vec<std::ops::Range<usize>> = merged.iter().map(|&(a, b)| a..b).collect();
1446 s.engine.delete_many(dranges);
1447 source.commit(&mut s.engine);
1448 let new_len = s.engine.len_chars();
1449 let mut new_offsets = vec![0usize; s.carets.len()];
1450 let mut touched = vec![false; s.carets.len()];
1451 for (k, &i) in owners.iter().enumerate() {
1452 let (us, _) = merged[owner[k]];
1453 new_offsets[i] = s
1454 .engine
1455 .to_chars(map_through_deletes(&merged, us))
1456 .min(new_len);
1457 touched[i] = true;
1458 }
1459 for (i, c) in s.carets.iter_mut().enumerate() {
1460 if touched[i] {
1461 c.offset = new_offsets[i];
1462 } else {
1463 c.offset = s
1464 .engine
1465 .to_chars(map_through_deletes(&merged, pre_bytes[i]))
1466 .min(new_len);
1467 }
1468 c.anchor = None;
1469 }
1470 s.goal_x = None;
1471 Self::reset_escalate(s);
1472 dedupe_carets(&mut s.carets);
1473 });
1474 self.bump();
1475 }
1476
1477 fn move_caret(&self, delta: isize, extend: bool) {
1478 let source = self.source.clone();
1479 self.with_state(|s| {
1480 source.ensure(&mut s.engine);
1481 let len = s.engine.len_chars();
1482 for c in s.carets.iter_mut() {
1483 let mut caret_b = s.engine.to_bytes(c.offset.min(len));
1484 let steps = delta.unsigned_abs().max(1);
1485 for _ in 0..steps {
1486 let b = if delta < 0 {
1487 sequoia_buffer::prev_grapheme(s.engine.buffer(), caret_b)
1488 } else {
1489 sequoia_buffer::next_grapheme(s.engine.buffer(), caret_b)
1490 };
1491 if b == caret_b {
1492 break;
1493 }
1494 caret_b = b;
1495 }
1496 c.move_to(s.engine.to_chars(caret_b), extend);
1497 }
1498 Self::reset_escalate(s);
1499 s.goal_x = None;
1500 });
1501 self.bump();
1502 }
1503
1504 fn move_word(&self, dir: isize, extend: bool) {
1505 let source = self.source.clone();
1506 self.with_state(|s| {
1507 source.ensure(&mut s.engine);
1508 let len = s.engine.len_chars();
1509 for c in s.carets.iter_mut() {
1510 let caret_b = s.engine.to_bytes(c.offset.min(len));
1511 let next_b = if dir < 0 {
1512 sequoia_buffer::word_start_before(s.engine.buffer(), caret_b)
1513 } else {
1514 sequoia_buffer::word_end_after(s.engine.buffer(), caret_b)
1515 };
1516 c.move_to(s.engine.to_chars(next_b), extend);
1517 }
1518 Self::reset_escalate(s);
1519 s.goal_x = None;
1520 });
1521 self.bump();
1522 }
1523
1524 fn move_home(&self, extend: bool, doc: bool) {
1525 let source = self.source.clone();
1526 self.with_state(|s| {
1527 source.ensure(&mut s.engine);
1528 let len = s.engine.len_chars();
1529 for c in s.carets.iter_mut() {
1530 let next = if doc {
1531 0
1532 } else {
1533 let caret_b = s.engine.to_bytes(c.offset.min(len));
1534 let home_b = sequoia_buffer::line_home(s.engine.buffer(), caret_b);
1535 s.engine.to_chars(home_b)
1536 };
1537 c.move_to(next, extend);
1538 }
1539 Self::reset_escalate(s);
1540 s.goal_x = None;
1541 });
1542 self.bump();
1543 }
1544
1545 fn move_end(&self, extend: bool, doc: bool) {
1546 let source = self.source.clone();
1547 self.with_state(|s| {
1548 source.ensure(&mut s.engine);
1549 let total = s.engine.len_chars();
1550 for c in s.carets.iter_mut() {
1551 let next = if doc {
1552 total
1553 } else {
1554 let caret_b = s.engine.to_bytes(c.offset.min(total));
1555 let end_b = sequoia_buffer::line_end(s.engine.buffer(), caret_b);
1556 s.engine.to_chars(end_b)
1557 };
1558 c.move_to(next, extend);
1559 }
1560 Self::reset_escalate(s);
1561 s.goal_x = None;
1562 });
1563 self.bump();
1564 }
1565
1566 fn row_at_y(line_h: f32, rows: usize, cy: f32) -> usize {
1567 if line_h <= 0.0 || rows == 0 {
1568 return 0;
1569 }
1570 ((cy / line_h).floor() as isize).clamp(0, rows.saturating_sub(1) as isize) as usize
1571 }
1572
1573 fn move_page(&self, dir: isize, extend: bool, viewport_h: f32, line_h: f32) {
1574 if viewport_h <= 0.0 || line_h <= 0.0 {
1575 for _ in 0..3 {
1576 self.step_vertical(dir, extend);
1577 }
1578 self.bump();
1579 return;
1580 }
1581
1582 let step = ((viewport_h / line_h).floor() as usize).max(1);
1583 for _ in 0..step {
1584 self.step_vertical(dir, extend);
1585 }
1586 self.with_state(|s| {
1587 let max_y = (s.content.height + self.pad_y * 2.0 - viewport_h).max(0.0);
1588 s.scroll.y = (s.scroll.y + dir as f32 * viewport_h).clamp(0.0, max_y);
1589 });
1590 self.bump();
1591 }
1592
1593 fn step_vertical(&self, dir: isize, extend: bool) {
1594 let source = self.source.clone();
1595 self.with_state(|s| {
1596 source.ensure(&mut s.engine);
1597 let len = s.engine.len_chars();
1598 for c in s.carets.iter_mut() {
1599 let caret_b = s.engine.to_bytes(c.offset.min(len));
1600 let (next_b, _) = sequoia_buffer::move_lines(s.engine.buffer(), caret_b, dir, None);
1601 if next_b == caret_b {
1602 continue;
1603 }
1604 c.move_to(s.engine.to_chars(next_b), extend);
1605 }
1606 Self::reset_escalate(s);
1607 });
1608 }
1609
1610 fn on_enter(&self) {
1611 if !self.editable {
1612 return;
1613 }
1614 if self.with_state(|s| s.carets.len() <= 1) {
1615 self.delete_selection();
1616 let source = self.source.clone();
1617 let indent = self.with_state(|s| {
1618 source.ensure(&mut s.engine);
1619 if !self.auto_indent {
1620 return String::new();
1621 }
1622 let caret_b = s
1623 .engine
1624 .to_bytes(Self::main_of(s).offset.min(s.engine.len_chars()));
1625 let lines = s.engine.buffer().lines();
1626 let (ls, le) = lines.line_range(lines.line_of(caret_b));
1627 let line_text = s.engine.buffer().substring(ls..le);
1628 line_text
1629 .chars()
1630 .take(leading_indent_len(&line_text))
1631 .collect()
1632 });
1633 self.insert_str(&format!("\n{indent}"));
1634 return;
1635 }
1636
1637 self.delete_selection();
1638 let source = self.source.clone();
1639 self.with_state(|s| {
1640 source.ensure(&mut s.engine);
1641 let total = s.engine.len_chars();
1642 let mut items: Vec<(usize, String)> = Vec::with_capacity(s.carets.len());
1643 for c in s.carets.iter() {
1644 let caret_b = s.engine.to_bytes(c.offset.min(total));
1645 let indent = if self.auto_indent {
1646 let lines = s.engine.buffer().lines();
1647 let (ls, le) = lines.line_range(lines.line_of(caret_b));
1648 let line_text = s.engine.buffer().substring(ls..le);
1649 line_text
1650 .chars()
1651 .take(leading_indent_len(&line_text))
1652 .collect::<String>()
1653 } else {
1654 String::new()
1655 };
1656 items.push((caret_b, indent));
1657 }
1658
1659 let mut uniq: Vec<usize> = items.iter().map(|(b, _)| *b).collect();
1660 uniq.sort_unstable();
1661 uniq.dedup();
1662 s.engine.insert_at_many(uniq.clone(), "\n");
1663 if let Some(max) = self.max_lines {
1664 s.engine.enforce_max_lines(max);
1665 }
1666
1667 let post_nl = |pb: usize| -> usize { pb + uniq.iter().filter(|&&q| q <= pb).count() };
1668
1669 let cur_chars = s.engine.len_chars();
1670 let indent_total: usize = items.iter().map(|(_, it)| it.chars().count()).sum();
1671 let allow_indent = match self.max_length {
1672 Some(max) => cur_chars + indent_total <= max,
1673 None => true,
1674 };
1675 let mut phase2: Vec<(usize, String)> = items
1676 .iter()
1677 .map(|(pb, indent)| (post_nl(*pb), indent.clone()))
1678 .filter(|(_, indent)| !indent.is_empty())
1679 .collect();
1680 if !allow_indent {
1681 phase2.clear();
1682 }
1683 phase2.sort_by_key(|b| std::cmp::Reverse(b.0));
1684 let mut k = 0usize;
1685 while k < phase2.len() {
1686 let mut j = k + 1;
1687 while j < phase2.len() && phase2[j].1 == phase2[k].1 {
1688 j += 1;
1689 }
1690 let mut pts: Vec<usize> = phase2[k..j].iter().map(|(p, _)| *p).collect();
1691 pts.sort_unstable();
1692 pts.dedup();
1693 let text = phase2[k].1.clone();
1694 s.engine.insert_at_many(pts, &text);
1695 k = j;
1696 }
1697 if let Some(max) = self.max_lines {
1698 s.engine.enforce_max_lines(max);
1699 }
1700 source.commit(&mut s.engine);
1701
1702 let new_len = s.engine.len_chars();
1703 let mut finals: Vec<usize> = Vec::with_capacity(items.len());
1704 for (pb, indent) in &items {
1705 let q = post_nl(*pb);
1706 let own = if allow_indent { indent.len() } else { 0 };
1707 let below: usize = phase2
1708 .iter()
1709 .filter(|(p, _)| *p < q)
1710 .map(|(_, t)| t.len())
1711 .sum();
1712 finals.push(s.engine.to_chars(q + own + below).min(new_len));
1713 }
1714 for (c, f) in s.carets.iter_mut().zip(finals.iter()) {
1715 c.offset = *f;
1716 c.anchor = None;
1717 }
1718 dedupe_carets(&mut s.carets);
1719 Self::reset_escalate(s);
1720 s.goal_x = None;
1721 });
1722 self.bump();
1723 }
1724
1725 fn on_tab(&self, extend_shift: bool) {
1726 if !self.editable {
1727 return;
1728 }
1729 if extend_shift {
1730 self.outdent();
1731 } else if !self.indent_selection() {
1732 let indent = self.indent.clone();
1733 if self.with_state(|s| s.carets.len() <= 1) {
1734 self.insert_str(&indent);
1735 } else {
1736 let source = self.source.clone();
1737 self.with_state(|s| {
1738 source.ensure(&mut s.engine);
1739 let total = s.engine.len_chars();
1740 let mut pts: Vec<usize> = s
1741 .carets
1742 .iter()
1743 .map(|c| s.engine.to_bytes(c.offset.min(total)))
1744 .collect();
1745 pts.sort_unstable();
1746 pts.dedup();
1747 let allowed = match self.max_length {
1748 Some(max) => max.saturating_sub(s.engine.len_chars()),
1749 None => usize::MAX,
1750 };
1751 let ins = BufferEngine::fit_prefix(&indent, allowed / s.carets.len().max(1));
1752 if ins.is_empty() {
1753 return;
1754 }
1755
1756 let mut pcs: Vec<usize> = pts.iter().map(|&p| s.engine.to_chars(p)).collect();
1757 pcs.sort_unstable();
1758 let n = ins.chars().count();
1759
1760 let pre: Vec<usize> = s.carets.iter().map(|c| c.offset.min(total)).collect();
1761 s.engine.insert_at_many(pts, ins);
1762 if let Some(max) = self.max_lines {
1763 s.engine.enforce_max_lines(max);
1764 }
1765 source.commit(&mut s.engine);
1766 let new_len = s.engine.len_chars();
1767 for (c, &old) in s.carets.iter_mut().zip(pre.iter()) {
1768 let below = pcs.iter().filter(|&&q| q < old).count();
1769
1770 c.offset = (old + n * (1 + below)).min(new_len);
1771 c.anchor = None;
1772 }
1773 dedupe_carets(&mut s.carets);
1774 Self::reset_escalate(s);
1775 s.goal_x = None;
1776 });
1777 self.bump();
1778 }
1779 }
1780 }
1781
1782 fn indent_selection(&self) -> bool {
1783 if self.indent.is_empty() {
1784 return false;
1785 }
1786 let source = self.source.clone();
1787 let indent = self.indent.clone();
1788 let per = indent.len();
1789
1790 let take_block = self.with_state(|s| {
1791 source.ensure(&mut s.engine);
1792 let len = s.engine.len_chars();
1793 let buf = s.engine.buffer();
1794 let lines = buf.lines();
1795 let total_b = buf.len_bytes();
1796 let mut touched: std::collections::BTreeSet<usize> = std::collections::BTreeSet::new();
1797 let mut any_multi = false;
1798 for c in s.carets.iter() {
1799 match c.selected() {
1800 Some((a, b)) => {
1801 let (a, b) = (a.min(len), b.min(len));
1802 if a >= b {
1803 continue;
1804 }
1805 let (ab, bb) = (s.engine.to_bytes(a), s.engine.to_bytes(b));
1806 let l0 = lines.line_of(ab);
1807 let (mut l1, c1) = lines.line_col(bb.min(total_b));
1808 if c1 == 0 && l1 > l0 {
1809 l1 -= 1;
1810 }
1811 if l1 > l0 {
1812 any_multi = true;
1813 }
1814 for l in l0..=l1 {
1815 touched.insert(l);
1816 }
1817 }
1818 None => {
1819 let cb = s.engine.to_bytes(c.offset.min(len));
1820 touched.insert(lines.line_of(cb));
1821 }
1822 }
1823 }
1824 if touched.is_empty() {
1825 return false;
1826 }
1827
1828 if s.carets.len() <= 1 && !any_multi {
1829 return false;
1830 }
1831 true
1832 });
1833 if !take_block {
1834 return false;
1835 }
1836
1837 self.with_state(|s| {
1838 source.ensure(&mut s.engine);
1839 let buf = s.engine.buffer();
1840 let lines = buf.lines();
1841 let total_b = buf.len_bytes();
1842 let len = s.engine.len_chars();
1843
1844 let mut touched: std::collections::BTreeSet<usize> = std::collections::BTreeSet::new();
1845 for c in s.carets.iter() {
1846 match c.selected() {
1847 Some((a, b)) => {
1848 let (a, b) = (a.min(len), b.min(len));
1849 if a >= b {
1850 continue;
1851 }
1852 let (ab, bb) = (s.engine.to_bytes(a), s.engine.to_bytes(b));
1853 let l0 = lines.line_of(ab);
1854 let (mut l1, c1) = lines.line_col(bb.min(total_b));
1855 if c1 == 0 && l1 > l0 {
1856 l1 -= 1;
1857 }
1858 for l in l0..=l1 {
1859 touched.insert(l);
1860 }
1861 }
1862 None => {
1863 let cb = s.engine.to_bytes(c.offset.min(len));
1864 touched.insert(lines.line_of(cb));
1865 }
1866 }
1867 }
1868 let at: Vec<usize> = touched.iter().map(|&l| lines.line_start(l)).collect();
1869 let shift = |pos: usize| -> usize {
1870 let mut k = 0usize;
1871 for &l in touched.iter() {
1872 if lines.line_start(l) <= pos {
1873 k += 1;
1874 } else {
1875 break;
1876 }
1877 }
1878 pos + per * k
1879 };
1880
1881 let ends: Vec<(usize, usize)> = s
1882 .carets
1883 .iter()
1884 .map(|c| {
1885 let (a, b) = c.ends();
1886 let (a, b) = (a.min(len), b.min(len));
1887 (shift(s.engine.to_bytes(a)), shift(s.engine.to_bytes(b)))
1888 })
1889 .collect();
1890 s.engine.insert_at_many(at, &indent);
1891 if let Some(max) = self.max_lines {
1892 s.engine.enforce_max_lines(max);
1893 }
1894 source.commit(&mut s.engine);
1895 let new_len = s.engine.len_chars();
1896 for (c, (na, nb)) in s.carets.iter_mut().zip(ends.iter()) {
1897 let (na, nb) = (
1898 s.engine.to_chars(*na).min(new_len),
1899 s.engine.to_chars(*nb).min(new_len),
1900 );
1901
1902 let was_fwd = c.offset >= c.anchor.unwrap_or(c.offset);
1903 if na == nb {
1904 c.offset = na;
1905 c.anchor = None;
1906 } else if was_fwd {
1907 c.offset = nb;
1908 c.anchor = Some(na);
1909 } else {
1910 c.offset = na;
1911 c.anchor = Some(nb);
1912 }
1913 }
1914 dedupe_carets(&mut s.carets);
1915 s.goal_x = None;
1916 Self::reset_escalate(s);
1917 });
1918 self.bump();
1919 true
1920 }
1921
1922 fn outdent(&self) {
1923 let indent_w = self.indent.chars().count().max(1);
1924 let source = self.source.clone();
1925
1926 let take_block = self.with_state(|s| {
1927 source.ensure(&mut s.engine);
1928 let len = s.engine.len_chars();
1929 let buf = s.engine.buffer();
1930 let lines = buf.lines();
1931 let total_b = buf.len_bytes();
1932 let mut touched: std::collections::BTreeSet<usize> = std::collections::BTreeSet::new();
1933 let mut any_multi = false;
1934 for c in s.carets.iter() {
1935 match c.selected() {
1936 Some((a, b)) => {
1937 let (a, b) = (a.min(len), b.min(len));
1938 if a >= b {
1939 continue;
1940 }
1941 let (ab, bb) = (s.engine.to_bytes(a), s.engine.to_bytes(b));
1942 let l0 = lines.line_of(ab);
1943 let (mut l1, c1) = lines.line_col(bb.min(total_b));
1944 if c1 == 0 && l1 > l0 {
1945 l1 -= 1;
1946 }
1947 if l1 > l0 {
1948 any_multi = true;
1949 }
1950 for l in l0..=l1 {
1951 touched.insert(l);
1952 }
1953 }
1954 None => {
1955 let cb = s.engine.to_bytes(c.offset.min(len));
1956 touched.insert(lines.line_of(cb));
1957 }
1958 }
1959 }
1960 s.carets.len() > 1 || any_multi || touched.len() > 1
1961 });
1962 if take_block {
1963 self.with_state(|s| {
1964 source.ensure(&mut s.engine);
1965 let len = s.engine.len_chars();
1966 let buf = s.engine.buffer();
1967 let lines = buf.lines();
1968 let total_b = buf.len_bytes();
1969 let mut touched: std::collections::BTreeSet<usize> =
1970 std::collections::BTreeSet::new();
1971 for c in s.carets.iter() {
1972 match c.selected() {
1973 Some((a, b)) => {
1974 let (a, b) = (a.min(len), b.min(len));
1975 if a >= b {
1976 continue;
1977 }
1978 let (ab, bb) = (s.engine.to_bytes(a), s.engine.to_bytes(b));
1979 let l0 = lines.line_of(ab);
1980 let (mut l1, c1) = lines.line_col(bb.min(total_b));
1981 if c1 == 0 && l1 > l0 {
1982 l1 -= 1;
1983 }
1984 for l in l0..=l1 {
1985 touched.insert(l);
1986 }
1987 }
1988 None => {
1989 let cb = s.engine.to_bytes(c.offset.min(len));
1990 touched.insert(lines.line_of(cb));
1991 }
1992 }
1993 }
1994 let mut ranges = Vec::new();
1995 let mut take_of: std::collections::BTreeMap<usize, usize> =
1996 std::collections::BTreeMap::new();
1997 for &line in touched.iter() {
1998 let (ls, le) = lines.line_range(line);
1999 let head = buf.substring(ls..le.min(ls + indent_w + 1));
2000 let take = head
2001 .chars()
2002 .take_while(|&c| c == ' ' || c == '\t')
2003 .take(indent_w)
2004 .map(|c| c.len_utf8())
2005 .sum::<usize>();
2006 take_of.insert(line, take);
2007 if take > 0 {
2008 ranges.push(ls..ls + take);
2009 }
2010 }
2011
2012 let map = |pos: usize| -> usize {
2013 let (line, _) = lines.line_col(pos.min(total_b));
2014 let mut sub = 0usize;
2015 for (&l, &take) in take_of.iter() {
2016 if l > line {
2017 break;
2018 }
2019 if l < line || lines.line_start(l) + take <= pos {
2020 sub += take;
2021 }
2022 }
2023 pos.saturating_sub(sub)
2024 };
2025
2026 let ends: Vec<(usize, usize)> = s
2027 .carets
2028 .iter()
2029 .map(|c| {
2030 let (a, b) = c.ends();
2031 let (a, b) = (a.min(len), b.min(len));
2032 (map(s.engine.to_bytes(a)), map(s.engine.to_bytes(b)))
2033 })
2034 .collect();
2035 if !ranges.is_empty() {
2036 s.engine.delete_many(ranges);
2037 source.commit(&mut s.engine);
2038 }
2039 let new_len = s.engine.len_chars();
2040 for (c, (na, nb)) in s.carets.iter_mut().zip(ends.iter()) {
2041 let (na, nb) = (
2042 s.engine.to_chars(*na).min(new_len),
2043 s.engine.to_chars(*nb).min(new_len),
2044 );
2045 let was_fwd = c.offset >= c.anchor.unwrap_or(c.offset);
2046 if na == nb {
2047 c.offset = na;
2048 c.anchor = None;
2049 } else if was_fwd {
2050 c.offset = nb;
2051 c.anchor = Some(na);
2052 } else {
2053 c.offset = na;
2054 c.anchor = Some(nb);
2055 }
2056 }
2057 dedupe_carets(&mut s.carets);
2058 s.goal_x = None;
2059 Self::reset_escalate(s);
2060 });
2061 self.bump();
2062 return;
2063 }
2064
2065 let moved = self.with_state(|s| {
2066 source.ensure(&mut s.engine);
2067 let len = s.engine.len_chars();
2068 let caret = Self::main_of(s).offset.min(len);
2069 let caret_b = s.engine.to_bytes(caret);
2070 let lines = s.engine.buffer().lines();
2071 let start = lines.line_start(lines.line_of(caret_b));
2072 let prefix = s.engine.buffer().substring(start..caret_b);
2073 let mut strip = 0usize;
2074 for c in prefix.chars().rev().take(indent_w) {
2075 if c == ' ' || c == '\t' {
2076 strip += c.len_utf8();
2077 } else {
2078 break;
2079 }
2080 }
2081 if strip == 0 || caret_b == start {
2082 return None;
2083 }
2084 s.engine.delete(caret_b - strip..caret_b);
2085 source.commit(&mut s.engine);
2086 Some(s.engine.to_chars(caret_b - strip))
2087 });
2088 if let Some(caret) = moved {
2089 self.with_state(|s| {
2090 Self::main_mut(s).offset = caret;
2091 Self::main_mut(s).anchor = None;
2092 s.goal_x = None;
2093 Self::reset_escalate(s);
2094 });
2095 self.bump();
2096 }
2097 }
2098
2099 pub const LINK_PLACEHOLDER: &'static str = "https://example.com";
2100
2101 fn word_at(engine: &BufferEngine, text: &str, caret: usize) -> Option<(usize, usize)> {
2102 let chars: Vec<char> = text.chars().collect();
2103 let len = chars.len();
2104 let c = caret.min(len);
2105 let on_word = |i: usize| chars.get(i).is_some_and(|c| is_word_char(*c));
2106 if !(on_word(c) || (c > 0 && on_word(c - 1))) {
2107 return None;
2108 }
2109 let buf = engine.buffer();
2110 let b = engine.to_bytes(c);
2111 let sb = sequoia_buffer::word_start_before(buf, b);
2112 let eb = sequoia_buffer::word_end_after(buf, b);
2113 if sb >= eb {
2114 return None;
2115 }
2116 let (a, z) = (engine.to_chars(sb), engine.to_chars(eb));
2117 if a < z { Some((a, z)) } else { None }
2118 }
2119
2120 fn line_at(engine: &BufferEngine, len_chars: usize, caret: usize) -> (usize, usize) {
2121 let cb = engine.to_bytes(caret.min(len_chars));
2122 let lines = engine.buffer().lines();
2123 let (bs, be) = lines.line_range(lines.line_of(cb));
2124 let total_b = engine.to_bytes(len_chars);
2125 let s = engine.to_chars(bs);
2126 let e = if be < total_b {
2127 engine.to_chars(be) + 1
2128 } else {
2129 engine.to_chars(be)
2130 };
2131 (s, e.min(len_chars))
2132 }
2133
2134 fn toggle_flag(&self, flag: StyleFlag) {
2135 if !self.editable {
2136 return;
2137 }
2138 let source = self.source.clone();
2139 self.with_state(|s| {
2140 source.ensure(&mut s.engine);
2141 let len = s.engine.len_chars();
2142 let snapshot = s.engine.styled_buffer().to_string();
2143
2144 let mut ranges: Vec<(usize, usize)> = Vec::with_capacity(s.carets.len());
2145 for c in s.carets.iter() {
2146 let r = match c.selected() {
2147 Some((a, b)) => {
2148 let (a, b) = (a.min(len), b.min(len));
2149 if a < b { Some((a, b)) } else { None }
2150 }
2151 None => Self::word_at(&s.engine, &snapshot, c.offset),
2152 };
2153 if let Some(r) = r {
2154 ranges.push(r);
2155 }
2156 }
2157 if ranges.is_empty() {
2158 return;
2159 }
2160 for (a, b) in ranges {
2161 let sb = s.engine.to_bytes(a);
2162 let eb = s.engine.to_bytes(b);
2163 if sb >= eb {
2164 continue;
2165 }
2166 let spans: Vec<StyledSpan> = s.engine.styled_buffer().spans().to_vec();
2167
2168 let mut covered = sb;
2169 let mut all_set = true;
2170 for sp in spans.iter() {
2171 if sp.end <= covered {
2172 continue;
2173 }
2174 if sp.start > covered {
2175 all_set = false;
2176 break;
2177 }
2178 if !flag.get(&sp.attr) {
2179 all_set = false;
2180 break;
2181 }
2182 covered = covered.max(sp.end);
2183 if covered >= eb {
2184 break;
2185 }
2186 }
2187 if covered < eb {
2188 all_set = false;
2189 }
2190 if all_set {
2191 let pieces: Vec<(usize, usize, SpanAttr)> = spans
2192 .iter()
2193 .filter(|sp| sp.start < eb && sb < sp.end)
2194 .map(|sp| {
2195 let ps = sp.start.max(sb);
2196 let pe = sp.end.min(eb);
2197 let mut attr = sp.attr.clone();
2198 flag.set(&mut attr, false);
2199 (ps, pe, attr)
2200 })
2201 .collect();
2202 for (ps, pe, attr) in pieces {
2203 s.engine.set_attr(ps..pe, attr);
2204 }
2205 } else {
2206 let mut bounds = vec![sb, eb];
2207 for sp in spans.iter() {
2208 if sp.start > sb && sp.start < eb {
2209 bounds.push(sp.start);
2210 }
2211 if sp.end > sb && sp.end < eb {
2212 bounds.push(sp.end);
2213 }
2214 }
2215 bounds.sort_unstable();
2216 bounds.dedup();
2217 for w in bounds.windows(2) {
2218 let (ps, pe) = (w[0], w[1]);
2219 let mut attr = spans
2220 .iter()
2221 .find(|sp| sp.start <= ps && pe <= sp.end)
2222 .map(|sp| sp.attr.clone())
2223 .unwrap_or_else(SpanAttr::plain);
2224 flag.set(&mut attr, true);
2225 s.engine.set_attr(ps..pe, attr);
2226 }
2227 }
2228 }
2229 source.commit(&mut s.engine);
2230
2231 let new_len = s.engine.len_chars();
2232 for c in s.carets.iter_mut() {
2233 c.offset = c.offset.min(new_len);
2234 if let Some(an) = c.anchor {
2235 c.anchor = Some(an.min(new_len));
2236 if c.anchor == Some(c.offset) {
2237 c.anchor = None;
2238 }
2239 }
2240 }
2241 Self::reset_escalate(s);
2242 s.goal_x = None;
2243 });
2244 self.bump();
2245 }
2246
2247 pub fn toggle_bold(&self) {
2248 self.toggle_flag(StyleFlag::Bold);
2249 }
2250
2251 pub fn toggle_italic(&self) {
2252 self.toggle_flag(StyleFlag::Italic);
2253 }
2254
2255 pub fn toggle_underline(&self) {
2256 self.toggle_flag(StyleFlag::Underline);
2257 }
2258
2259 pub fn toggle_strike(&self) {
2260 self.toggle_flag(StyleFlag::Strike);
2261 }
2262
2263 pub fn apply_link(&self) {
2264 self.apply_link_url(Self::LINK_PLACEHOLDER);
2265 }
2266
2267 pub fn apply_link_url(&self, url: &str) {
2268 if !self.editable {
2269 return;
2270 }
2271 let source = self.source.clone();
2272 let url = url.to_string();
2273 self.with_state(|s| {
2274 source.ensure(&mut s.engine);
2275 let len = s.engine.len_chars();
2276 let snapshot = s.engine.styled_buffer().to_string();
2277 let mut ranges: Vec<(usize, usize)> = Vec::new();
2278 for c in s.carets.iter() {
2279 let r = match c.selected() {
2280 Some((a, b)) => {
2281 let (a, b) = (a.min(len), b.min(len));
2282 if a < b { Some((a, b)) } else { None }
2283 }
2284 None => Self::word_at(&s.engine, &snapshot, c.offset),
2285 };
2286 if let Some(r) = r {
2287 ranges.push(r);
2288 }
2289 }
2290 if ranges.is_empty() {
2291 return;
2292 }
2293 let mut mids: Vec<usize> = Vec::with_capacity(ranges.len());
2294 for (a, b) in &ranges {
2295 let sb = s.engine.to_bytes(*a);
2296 let eb = s.engine.to_bytes(*b);
2297 if sb < eb {
2298 s.engine.set_attr(sb..eb, SpanAttr::link(url.clone()));
2299 }
2300 mids.push(a + (b - a) / 2);
2301 }
2302 source.commit(&mut s.engine);
2303 let new_len = s.engine.len_chars();
2304 for (c, m) in s.carets.iter_mut().zip(mids.iter()) {
2305 c.offset = (*m).min(new_len);
2306 c.anchor = None;
2307 }
2308 dedupe_carets(&mut s.carets);
2309 Self::reset_escalate(s);
2310 s.goal_x = None;
2311 });
2312 self.bump();
2313 }
2314
2315 fn on_style_shortcut(&self, key: &KeyEvent) -> bool {
2316 let cmd =
2317 key.modifiers.contains(Modifiers::CONTROL) || key.modifiers.contains(Modifiers::SUPER);
2318 if !cmd || key.modifiers.contains(Modifiers::ALT) {
2319 return false;
2320 }
2321 let Some(c) = key.key.char().map(|c| c.to_ascii_lowercase()) else {
2322 return false;
2323 };
2324 match c {
2325 'b' => {
2326 if self.editable {
2327 self.toggle_bold();
2328 }
2329 true
2330 }
2331 'i' => {
2332 if self.editable {
2333 self.toggle_italic();
2334 }
2335 true
2336 }
2337 'u' => {
2338 if self.editable {
2339 self.toggle_underline();
2340 }
2341 true
2342 }
2343 'k' => {
2344 if self.editable {
2345 self.apply_link();
2346 }
2347 true
2348 }
2349 _ => false,
2350 }
2351 }
2352
2353 fn try_auto_close(&self, c: char) -> bool {
2354 let (open, close): (&str, &str) = match c {
2355 '*' => ("**", "**"),
2356 '_' => ("_", "_"),
2357 '`' => ("`", "`"),
2358 _ => return false,
2359 };
2360 if !self.editable {
2361 return false;
2362 }
2363 let source = self.source.clone();
2364 enum Role {
2365 Hop(usize),
2366 Wrap(usize, usize, usize),
2367 Plain(usize),
2368 }
2369 let plan = self.with_state(|s| {
2370 source.ensure(&mut s.engine);
2371 let len = s.engine.len_chars();
2372 let snapshot = s.engine.styled_buffer().to_string();
2373 let snap_chars: Vec<char> = snapshot.chars().collect();
2374 let mut roles: Vec<Role> = Vec::with_capacity(s.carets.len());
2375 for (i, caret) in s.carets.iter().enumerate() {
2376 if let Some((a, b)) = caret.selected() {
2377 let (a, b) = (a.min(len), b.min(len));
2378 if a < b {
2379 roles.push(Role::Wrap(i, a, b));
2380 continue;
2381 }
2382 }
2383 let p = caret.offset.min(len);
2384
2385 let tail: String = snap_chars[p..].iter().collect();
2386 if tail.starts_with(close) {
2387 roles.push(Role::Hop(i));
2388 continue;
2389 }
2390 match Self::word_at(&s.engine, &snapshot, p) {
2391 Some((w0, w1)) => roles.push(Role::Wrap(i, w0, w1)),
2392 None => roles.push(Role::Plain(i)),
2393 }
2394 }
2395 let _ = open;
2396 Some(roles)
2397 });
2398 let Some(roles) = plan else { return false };
2399 let has_wrap = roles.iter().any(|r| matches!(r, Role::Wrap(..)));
2400 let has_hop = roles.iter().any(|r| matches!(r, Role::Hop(_)));
2401 if !has_wrap && !has_hop {
2402 return false;
2403 }
2404 let single = c.to_string();
2405 self.with_state(|s| {
2406 source.ensure(&mut s.engine);
2407 let len = s.engine.len_chars();
2408 if !has_wrap {
2409 let snapshot = s.engine.styled_buffer().to_string();
2410 let snap_chars: Vec<char> = snapshot.chars().collect();
2411 for (i, caret) in s.carets.iter_mut().enumerate() {
2412 let still_hop = roles.iter().any(|r| matches!(r, Role::Hop(j) if *j == i));
2413 if !still_hop {
2414 continue;
2415 }
2416 let p = caret.offset.min(len);
2417 let tail: String = snap_chars[p..].iter().collect();
2418 let step = if tail.starts_with(close) {
2419 close.chars().count()
2420 } else {
2421 0
2422 };
2423 caret.offset = (p + step).min(len);
2424 caret.anchor = None;
2425 }
2426 Self::reset_escalate(s);
2427 s.goal_x = None;
2428 return;
2429 }
2430
2431 let mut pairs: Vec<(usize, String, usize)> = Vec::new();
2432
2433 let mut bases: Vec<(usize, usize)> = Vec::with_capacity(roles.len());
2434 for r in roles.iter() {
2435 match r {
2436 Role::Wrap(i, a, b) => {
2437 let ab = s.engine.to_bytes((*a).min(len));
2438 let bb = s.engine.to_bytes((*b).min(len));
2439 pairs.push((ab, open.to_string(), *a));
2440 pairs.push((bb, close.to_string(), *b));
2441 bases.push((*i, *b));
2442 }
2443 Role::Plain(i) => {
2444 let c = s.carets[*i].offset.min(len);
2445 pairs.push((s.engine.to_bytes(c), single.clone(), c));
2446 bases.push((*i, c));
2447 }
2448 Role::Hop(i) => {
2449 bases.push((*i, s.carets[*i].offset.min(len)));
2450 }
2451 }
2452 }
2453
2454 let added: usize = pairs.iter().map(|(_, t, _)| t.chars().count()).sum();
2455 if let Some(max) = self.max_length
2456 && s.engine.len_chars() + added > max
2457 {
2458 return;
2459 }
2460
2461 let mut byte_to_char: std::collections::HashMap<usize, usize> =
2462 std::collections::HashMap::new();
2463 for (pb, _, pc) in pairs.iter() {
2464 byte_to_char.entry(*pb).or_insert(*pc);
2465 }
2466
2467 let mut ordered = pairs.clone();
2468 ordered.sort_by_key(|b| std::cmp::Reverse(b.0));
2469
2470 let mut applied: Vec<(usize, usize)> = Vec::new();
2471 let mut used: std::collections::HashSet<usize> = std::collections::HashSet::new();
2472 let mut k = 0usize;
2473 while k < ordered.len() {
2474 let mut j = k + 1;
2475 while j < ordered.len() && ordered[j].1 == ordered[k].1 {
2476 j += 1;
2477 }
2478 let mut pts: Vec<usize> = ordered[k..j].iter().map(|(p, _, _)| *p).collect();
2479 pts.sort_unstable();
2480 pts.dedup();
2481 let fresh: Vec<usize> = pts.into_iter().filter(|p| used.insert(*p)).collect();
2482 if !fresh.is_empty() {
2483 let text = ordered[k].1.clone();
2484 let clen = text.chars().count();
2485 for p in fresh.iter() {
2486 if let Some(&pc) = byte_to_char.get(p) {
2487 applied.push((pc, clen));
2488 }
2489 }
2490 s.engine.insert_at_many(fresh, &text);
2491 }
2492 k = j;
2493 }
2494 if let Some(max) = self.max_lines {
2495 s.engine.enforce_max_lines(max);
2496 }
2497 source.commit(&mut s.engine);
2498
2499 applied.sort_unstable();
2500 let new_len = s.engine.len_chars();
2501 for (i, base_ch) in bases {
2502 let add: usize = applied
2503 .iter()
2504 .filter(|(pc, _)| *pc <= base_ch)
2505 .map(|(_, l)| l)
2506 .sum();
2507 s.carets[i].offset = (base_ch + add).min(new_len);
2508 s.carets[i].anchor = None;
2509 }
2510 dedupe_carets(&mut s.carets);
2511 Self::reset_escalate(s);
2512 s.goal_x = None;
2513 });
2514 self.bump();
2515 true
2516 }
2517
2518 fn undo(&self) {
2519 if !self.editable {
2520 return;
2521 }
2522 let source = self.source.clone();
2523 let restored = self.with_state(|s| {
2524 source.ensure(&mut s.engine);
2525 let text_before = s.engine.styled_buffer().to_string();
2526 match s.engine.undo() {
2527 Some((a, b)) => {
2528 source.commit(&mut s.engine);
2529 if s.engine.styled_buffer().to_string() == text_before {
2530 let len = s.engine.len_chars();
2531 for c in s.carets.iter_mut() {
2532 c.offset = c.offset.min(len);
2533 if let Some(an) = c.anchor {
2534 c.anchor = Some(an.min(len));
2535 if c.anchor == Some(c.offset) {
2536 c.anchor = None;
2537 }
2538 }
2539 }
2540 } else {
2541 let at = s.engine.to_chars(a).min(s.engine.len_chars());
2542 s.carets = vec![Caret::collapsed(at)];
2543 let _ = b;
2544 }
2545 s.goal_x = None;
2546 Self::reset_escalate(s);
2547 true
2548 }
2549 None => false,
2550 }
2551 });
2552 if restored {
2553 self.bump();
2554 }
2555 }
2556
2557 fn redo(&self) {
2558 if !self.editable {
2559 return;
2560 }
2561 let source = self.source.clone();
2562 let restored = self.with_state(|s| {
2563 source.ensure(&mut s.engine);
2564 let text_before = s.engine.styled_buffer().to_string();
2565 match s.engine.redo() {
2566 Some((a, b)) => {
2567 source.commit(&mut s.engine);
2568 if s.engine.styled_buffer().to_string() == text_before {
2569 let len = s.engine.len_chars();
2570 for c in s.carets.iter_mut() {
2571 c.offset = c.offset.min(len);
2572 if let Some(an) = c.anchor {
2573 c.anchor = Some(an.min(len));
2574 if c.anchor == Some(c.offset) {
2575 c.anchor = None;
2576 }
2577 }
2578 }
2579 let _ = a;
2580 } else {
2581 let at = s.engine.to_chars(b).min(s.engine.len_chars());
2582 s.carets = vec![Caret::collapsed(at)];
2583 }
2584 s.goal_x = None;
2585 Self::reset_escalate(s);
2586 true
2587 }
2588 None => false,
2589 }
2590 });
2591 if restored {
2592 self.bump();
2593 }
2594 }
2595
2596 fn escalate_select(&self) {
2597 let source = self.source.clone();
2598 self.with_state(|s| {
2599 source.ensure(&mut s.engine);
2600 let len = s.engine.len_chars();
2601 if s.carets.is_empty() {
2602 s.carets.push(Caret::default());
2603 }
2604 let snapshot = s.engine.styled_buffer().to_string();
2605 let main = s.carets[0];
2606 let cur_sel = main.selected().map(|(a, b)| (a.min(len), b.min(len)));
2607 let (ns, ne, level) = match s.escalate {
2608 0 => match cur_sel {
2609 None => match Self::word_at(&s.engine, &snapshot, main.offset) {
2610 Some((w0, w1)) => (w0, w1, 1),
2611
2612 None => {
2613 let (l0, l1) = Self::line_at(&s.engine, len, main.offset);
2614 (l0, l1, 2)
2615 }
2616 },
2617 Some((a, b)) if a == b => match Self::word_at(&s.engine, &snapshot, a) {
2618 Some((w0, w1)) => (w0, w1, 1),
2619 None => {
2620 let (l0, l1) = Self::line_at(&s.engine, len, a);
2621 (l0, l1, 2)
2622 }
2623 },
2624
2625 Some(_) => (0, len, 3),
2626 },
2627 1 => {
2628 let (l0, l1) = Self::line_at(&s.engine, len, s.escalate_base);
2629 (l0, l1, 2)
2630 }
2631 _ => (0, len, 3),
2632 };
2633 let base = if s.escalate == 0 {
2634 main.offset.min(len)
2635 } else {
2636 s.escalate_base
2637 };
2638 s.carets = vec![Caret {
2639 offset: ne,
2640 anchor: if ns == ne { None } else { Some(ns) },
2641 }];
2642 s.escalate = level;
2643 s.escalate_base = base;
2644 s.goal_x = None;
2645 });
2646 self.bump();
2647 }
2648
2649 fn handle_click(&self, caret: usize, extend: bool, alt: bool, now_ms: u128) {
2650 const TRIPLE_MS: u128 = 500;
2651 let source = self.source.clone();
2652 if alt {
2653 self.with_state(|s| {
2654 source.ensure(&mut s.engine);
2655 let caret = caret.min(s.engine.len_chars());
2656 if let Some(idx) = s.carets.iter().position(|c| c.offset == caret) {
2657 if s.carets.len() > 1 {
2658 s.carets.remove(idx);
2659 if let Some(ad) = s.alt_drag {
2660 if ad == idx {
2661 s.alt_drag = None;
2662 } else if ad > idx {
2663 s.alt_drag = Some(ad - 1);
2664 }
2665 }
2666 } else {
2667 s.carets[0].move_to(caret, extend);
2668 }
2669 } else {
2670 s.carets.push(Caret::collapsed(caret));
2671 s.alt_drag = Some(s.carets.len() - 1);
2672 }
2673 Self::reset_escalate(s);
2674 s.goal_x = None;
2675 });
2676 self.bump();
2677 return;
2678 }
2679 if extend {
2680 self.set_caret_extend(caret, true);
2681 return;
2682 }
2683 self.with_state(|s| {
2684 source.ensure(&mut s.engine);
2685 let len = s.engine.len_chars();
2686 let caret = caret.min(len);
2687 let chained = match s.last_click_ms {
2688 Some(t) => now_ms.saturating_sub(t) <= TRIPLE_MS,
2689 None => false,
2690 };
2691
2692 s.click_count = if chained { s.click_count + 1 } else { 1 };
2693 s.last_click_ms = Some(now_ms);
2694 s.last_click_pos = caret;
2695 s.alt_drag = None;
2696 match s.click_count % 3 {
2697 1 => {
2698 s.carets = vec![Caret::collapsed(caret)];
2699 Self::reset_escalate(s);
2700 }
2701 2 => {
2702 let snapshot = s.engine.styled_buffer().to_string();
2703 match Self::word_at(&s.engine, &snapshot, caret) {
2704 Some((w0, w1)) => {
2705 s.carets = vec![Caret {
2706 offset: w1,
2707 anchor: Some(w0),
2708 }];
2709 s.escalate = 1;
2710 s.escalate_base = caret;
2711 }
2712 None => {
2713 s.carets = vec![Caret::collapsed(caret)];
2714 Self::reset_escalate(s);
2715 }
2716 }
2717 }
2718 _ => {
2719 let (l0, l1) = Self::line_at(&s.engine, len, caret);
2720 s.carets = vec![Caret {
2721 offset: l1,
2722 anchor: if l0 == l1 { None } else { Some(l0) },
2723 }];
2724 s.escalate = 2;
2725 s.escalate_base = caret;
2726 }
2727 }
2728 s.goal_x = None;
2729 });
2730 self.bump();
2731 }
2732
2733 fn on_key(&self, key: &KeyEvent, ctx: &mut EventCtx) {
2734 let extend = key.modifiers.contains(Modifiers::SHIFT);
2735 let ctrl =
2736 key.modifiers.contains(Modifiers::CONTROL) || key.modifiers.contains(Modifiers::SUPER);
2737 let alt = key.modifiers.contains(Modifiers::ALT);
2738 match key.key {
2739 Key::BACKSPACE => {
2740 if !ctrl {
2741 self.backspace(false);
2742 } else {
2743 self.backspace(true);
2744 }
2745 self.ensure_caret_visible_ctx(ctx);
2746 ctx.handled();
2747 }
2748 Key::DELETE => {
2749 if !ctrl {
2750 self.delete(false);
2751 } else {
2752 self.delete(true);
2753 }
2754 self.ensure_caret_visible_ctx(ctx);
2755 ctx.handled();
2756 }
2757 Key::ARROW_LEFT => {
2758 if ctrl && !alt {
2759 self.move_word(-1, extend);
2760 } else {
2761 self.move_caret(-1, extend);
2762 }
2763 self.ensure_caret_visible_ctx(ctx);
2764 ctx.handled();
2765 }
2766 Key::ARROW_RIGHT => {
2767 if ctrl && !alt {
2768 self.move_word(1, extend);
2769 } else {
2770 self.move_caret(1, extend);
2771 }
2772 self.ensure_caret_visible_ctx(ctx);
2773 ctx.handled();
2774 }
2775 Key::ARROW_UP => {
2776 self.move_vertical_ctx(-1, extend, ctx);
2777 self.ensure_caret_visible_ctx(ctx);
2778 ctx.handled();
2779 }
2780 Key::ARROW_DOWN => {
2781 self.move_vertical_ctx(1, extend, ctx);
2782 self.ensure_caret_visible_ctx(ctx);
2783 ctx.handled();
2784 }
2785 Key::HOME => {
2786 self.move_home(extend, ctrl);
2787 self.ensure_caret_visible_ctx(ctx);
2788 ctx.handled();
2789 }
2790 Key::END => {
2791 self.move_end(extend, ctrl);
2792 self.ensure_caret_visible_ctx(ctx);
2793 ctx.handled();
2794 }
2795 Key::PAGE_UP => {
2796 let vp = self.with_state(|s| s.viewport);
2797 self.move_page(-1, extend, vp.height, self.line_height());
2798 self.ensure_caret_visible_ctx(ctx);
2799 ctx.handled();
2800 }
2801 Key::PAGE_DOWN => {
2802 let vp = self.with_state(|s| s.viewport);
2803 self.move_page(1, extend, vp.height, self.line_height());
2804 self.ensure_caret_visible_ctx(ctx);
2805 ctx.handled();
2806 }
2807 Key::ENTER => {
2808 self.on_enter();
2809 self.ensure_caret_visible_ctx(ctx);
2810 ctx.handled();
2811 }
2812 Key::TAB => {
2813 self.on_tab(extend);
2814 self.ensure_caret_visible_ctx(ctx);
2815 ctx.handled();
2816 }
2817 Key::ESCAPE => {
2818 let had = self.with_state(|s| {
2819 let had = s.carets.iter().any(|c| c.anchor.is_some())
2820 || s.carets.len() > 1
2821 || !s.preedit.is_empty();
2822 s.carets.truncate(1);
2823 if s.carets.is_empty() {
2824 s.carets.push(Caret::default());
2825 }
2826 s.carets[0].anchor = None;
2827 s.preedit.clear();
2828 s.goal_x = None;
2829 Self::reset_escalate(s);
2830 had
2831 });
2832 if had {
2833 self.bump();
2834 }
2835 ctx.handled();
2836 }
2837 Key::SPACE => {
2838 if extend || ctrl || alt {
2839 if extend && !ctrl && !alt {
2840 self.move_caret(1, true);
2841 self.ensure_caret_visible_ctx(ctx);
2842 }
2843 ctx.handled();
2844 } else if self.editable {
2845 self.insert_str(" ");
2846 self.ensure_caret_visible_ctx(ctx);
2847 ctx.handled();
2848 } else {
2849 ctx.handled();
2850 }
2851 }
2852 other => {
2853 if let Some(c) = other.char()
2854 && !c.is_control()
2855 {
2856 if ctrl || alt {
2857 ctx.handled();
2858 return;
2859 }
2860 if self.editable {
2861 if !self.try_auto_close(c) {
2862 let mut s = String::new();
2863 s.push(c);
2864 self.insert_str(&s);
2865 }
2866 self.ensure_caret_visible_ctx(ctx);
2867 }
2868 ctx.handled();
2869 } else {
2870 ctx.handled();
2871 }
2872 }
2873 }
2874 }
2875
2876 fn move_vertical_ctx(&self, dir: isize, extend: bool, ctx: &mut EventCtx) {
2877 let chars = self.chars();
2878 let starts = self.starts(&chars);
2879
2880 let mains: Vec<usize> =
2881 self.with_state(|s| s.carets.iter().map(|c| c.offset.min(chars.len())).collect());
2882 let rows = starts.len();
2883 let theme = ctx.environment.get::<Theme>().unwrap_or_else(Theme::dark);
2884 let size = self.resolved_font_size(&theme);
2885 let weight = self.font_weight();
2886 let text = self.source.get_text();
2887 let lines = split_lines(&text);
2888 let main_goal = self.with_state(|s| s.goal_x);
2889 let mut main_goal_next = main_goal;
2890 let mut nexts: Vec<Option<usize>> = Vec::with_capacity(mains.len());
2891 for (idx, caret) in mains.iter().enumerate() {
2892 let (line, col) = line_col_of(&chars, &starts, *caret);
2893 let target = if dir < 0 {
2894 line.saturating_sub(dir.unsigned_abs())
2895 } else {
2896 (line + dir as usize).min(rows.saturating_sub(1))
2897 };
2898 if target == line {
2899 nexts.push(None);
2900 continue;
2901 }
2902 let bounds_cur =
2903 ctx.caret_positions(lines.get(line).copied().unwrap_or(""), size, weight);
2904
2905 let caret_goal = if idx == 0 {
2906 if let Some(g) = main_goal {
2907 g
2908 } else {
2909 let g = bounds_cur
2910 .get(col.min(bounds_cur.len().saturating_sub(1)))
2911 .copied()
2912 .unwrap_or(0.0);
2913 main_goal_next = Some(g);
2914 g
2915 }
2916 } else {
2917 bounds_cur
2918 .get(col.min(bounds_cur.len().saturating_sub(1)))
2919 .copied()
2920 .unwrap_or(0.0)
2921 };
2922 let bounds_target =
2923 ctx.caret_positions(lines.get(target).copied().unwrap_or(""), size, weight);
2924 let target_col = closest_index(&bounds_target, caret_goal);
2925 nexts.push(Some(
2926 caret_of_line_col(&chars, &starts, target, target_col).min(chars.len()),
2927 ));
2928 }
2929 if nexts.iter().all(|n| n.is_none()) {
2930 if mains.len() == 1 && !extend {
2931 self.with_state(|s| s.goal_x = None);
2932 }
2933 return;
2934 }
2935 self.with_state(|s| {
2936 for (c, next) in s.carets.iter_mut().zip(nexts.iter()) {
2937 if let Some(next) = next {
2938 c.move_to(*next, extend);
2939 }
2940 }
2941 s.goal_x = main_goal_next;
2942 Self::reset_escalate(s);
2943 });
2944 self.bump();
2945 }
2946
2947 fn ensure_caret_visible_ctx(&self, ctx: &mut EventCtx) {
2948 let theme = ctx.environment.get::<Theme>().unwrap_or_else(Theme::dark);
2949 let size = self.resolved_font_size(&theme);
2950 let weight = self.font_weight();
2951 let line_h = self.resolved_line_height(&theme);
2952 let text = self.source.get_text();
2953 let chars: Vec<char> = text.chars().collect();
2954 let starts = line_starts(&chars);
2955 let caret = self.caret().min(chars.len());
2956 let (line, col) = line_col_of(&chars, &starts, caret);
2957 let lines = split_lines(&text);
2958 let line_str = lines.get(line).copied().unwrap_or("");
2959 let bounds = ctx.caret_positions(line_str, size, weight);
2960 let x = bounds
2961 .get(col.min(bounds.len().saturating_sub(1)))
2962 .copied()
2963 .unwrap_or(0.0);
2964 self.ensure_visible(x, line, line_h);
2965 }
2966
2967 fn ensure_visible(&self, caret_x: f32, line: usize, line_h: f32) {
2968 let changed = self.with_state(|s| {
2969 if s.viewport.is_empty() || line_h <= 0.0 {
2970 return false;
2971 }
2972 let before = s.scroll;
2973 apply_ensure_visible(s, self.pad_x, self.pad_y, caret_x, line, line_h);
2974 s.scroll != before
2975 });
2976 if changed {
2977 let s = self.with_state(|s| s.scroll);
2978 if let Some(f) = &self.on_scroll {
2979 f(s);
2980 }
2981 self.bump();
2982 }
2983 }
2984
2985 fn on_ime(&self, ime: &ImeEvent) {
2986 if !self.editable {
2987 if ime == &ImeEvent::Disabled {
2988 let had = self.with_state(|s| {
2989 let had = !s.preedit.is_empty();
2990 s.preedit.clear();
2991 had
2992 });
2993 if had {
2994 self.bump();
2995 }
2996 }
2997 return;
2998 }
2999 match ime {
3000 ImeEvent::Enabled => {}
3001 ImeEvent::Preedit { value, .. } => {
3002 self.with_state(|s| s.preedit = value.clone());
3003 self.bump();
3004 }
3005 ImeEvent::Commit(value) => {
3006 self.with_state(|s| s.preedit.clear());
3007 self.insert_str(&sanitize_multiline(value));
3008 }
3009 ImeEvent::Disabled => {
3010 let had = self.with_state(|s| {
3011 let had = !s.preedit.is_empty();
3012 s.preedit.clear();
3013 had
3014 });
3015 if had {
3016 self.bump();
3017 }
3018 }
3019
3020 _ => {}
3021 }
3022 }
3023
3024 fn on_clipboard_key(&self, key: &KeyEvent, ctx: &mut EventCtx) -> bool {
3025 let cmd =
3026 key.modifiers.contains(Modifiers::CONTROL) || key.modifiers.contains(Modifiers::SUPER);
3027 if !cmd {
3028 return false;
3029 }
3030 let Some(c) = key.key.char().map(|c| c.to_ascii_lowercase()) else {
3031 return false;
3032 };
3033
3034 if c == 'z' && !key.modifiers.contains(Modifiers::SHIFT) {
3035 self.undo();
3036 return true;
3037 }
3038 if c == 'y' || (c == 'z' && key.modifiers.contains(Modifiers::SHIFT)) {
3039 self.redo();
3040 return true;
3041 }
3042 if !matches!(c, 'c' | 'x' | 'v' | 'a') {
3043 return false;
3044 }
3045 if c == 'a' {
3046 self.escalate_select();
3047 return true;
3048 }
3049 let Some(clip) = ctx.environment.get::<SharedClipboard>() else {
3050 return true;
3051 };
3052 match c {
3053 'c' => {
3054 if let Some(sel) = self.selected_text() {
3055 clip.0.set_text(&sel);
3056 } else {
3057 clip.0.set_text(&self.source.get_text());
3058 }
3059 }
3060 'x' => {
3061 if !self.editable {
3062 return true;
3063 }
3064 if let Some(sel) = self.selected_text() {
3065 clip.0.set_text(&sel);
3066 self.delete_selection();
3067 } else {
3068 clip.0.set_text(&self.source.get_text());
3069 self.source.set_text(String::new());
3070 self.with_state(|s| {
3071 s.carets = vec![Caret::collapsed(0)];
3072 s.goal_x = None;
3073 });
3074 self.bump();
3075 }
3076 }
3077 _ => {
3078 if !self.editable {
3079 return true;
3080 }
3081 if let Some(paste) = clip.0.get_text() {
3082 self.insert_str(&sanitize_multiline(&paste));
3083 }
3084 }
3085 }
3086 true
3087 }
3088
3089 fn hit_test_caret(&self, bounds: &[f32], x: f32) -> usize {
3090 if bounds.is_empty() {
3091 return 0;
3092 }
3093 if x >= bounds[bounds.len() - 1] {
3094 return bounds.len() - 1;
3095 }
3096 if x <= 0.0 {
3097 return 0;
3098 }
3099 closest_index(bounds, x)
3100 }
3101
3102 fn caret_from_point(
3103 &self,
3104 lines: &[&str],
3105 chars: &[char],
3106 starts: &[usize],
3107 line: usize,
3108 bounds: &[f32],
3109 cx: f32,
3110 ) -> usize {
3111 let _ = lines;
3112 let col = self.hit_test_caret(bounds, cx);
3113 caret_of_line_col(chars, starts, line, col)
3114 }
3115}
3116
3117fn closest_index(bounds: &[f32], x: f32) -> usize {
3118 let mut best = 0usize;
3119 let mut best_d = f32::MAX;
3120 for (i, &b) in bounds.iter().enumerate() {
3121 let d = (b - x).abs();
3122 if d < best_d {
3123 best_d = d;
3124 best = i;
3125 }
3126 }
3127 best
3128}
3129
3130fn clamp_scroll(
3131 scroll: Vec2,
3132 content_w: f32,
3133 content_h: f32,
3134 viewport: Size,
3135 pad: (f32, f32),
3136) -> Vec2 {
3137 let max_x = (content_w + pad.0 * 2.0 - viewport.width).max(0.0);
3138 let max_y = (content_h + pad.1 * 2.0 - viewport.height).max(0.0);
3139 Vec2::new(
3140 if viewport.width <= 0.0 {
3141 scroll.x.max(0.0)
3142 } else {
3143 scroll.x.clamp(0.0, max_x)
3144 },
3145 if viewport.height <= 0.0 {
3146 scroll.y.max(0.0)
3147 } else {
3148 scroll.y.clamp(0.0, max_y)
3149 },
3150 )
3151}
3152
3153fn apply_ensure_visible(
3154 s: &mut AreaState,
3155 pad_x: f32,
3156 pad_y: f32,
3157 caret_x: f32,
3158 line: usize,
3159 line_h: f32,
3160) {
3161 let vp = s.viewport;
3162 if vp.is_empty() {
3163 return;
3164 }
3165 let cx = pad_x + caret_x;
3166 if cx < s.scroll.x + SCROLL_MARGIN {
3167 s.scroll.x = (cx - SCROLL_MARGIN).max(0.0);
3168 } else if cx + CARET_W + SCROLL_MARGIN > s.scroll.x + vp.width {
3169 s.scroll.x = (cx + CARET_W + SCROLL_MARGIN - vp.width).max(0.0);
3170 }
3171 let top = pad_y + line as f32 * line_h;
3172 if top < s.scroll.y {
3173 s.scroll.y = top.max(0.0);
3174 } else if top + line_h > s.scroll.y + vp.height {
3175 s.scroll.y = (top + line_h - vp.height).max(0.0);
3176 }
3177}
3178
3179impl View for TextArea {
3180 fn body(&self) -> impl View + 'static {
3181 Unit
3182 }
3183
3184 fn is_leaf(&self) -> bool {
3185 true
3186 }
3187
3188 fn focusable(&self) -> bool {
3189 true
3190 }
3191
3192 fn accepts_tab(&self) -> bool {
3193 self.accept_tab
3194 }
3195
3196 fn has_visual_state(&self) -> bool {
3197 true
3198 }
3199
3200 fn clip_self(&self) -> bool {
3201 true
3202 }
3203
3204 fn clip_radius(&self) -> f32 {
3205 self.radius
3206 .or_else(|| self.appearance.and_then(|s| s.radius))
3207 .unwrap_or(3.0)
3208 .max(0.0)
3209 }
3210
3211 fn reuse_state(&self, old: &Self) {
3212 if !Rc::ptr_eq(&self.state, &old.state) {
3213 *self.state.borrow_mut() = old.state.borrow().clone();
3214 }
3215 self.revision.set(old.revision.get_untracked());
3216 }
3217
3218 fn keyed_children(&self) -> Option<Vec<(u64, AnyView)>> {
3219 self.source.track();
3220 let _ = self.revision.get();
3221
3222 Some(Vec::new())
3223 }
3224
3225 fn cursor_icon(&self, _state: VisualState) -> CursorIcon {
3226 CursorIcon::Text
3227 }
3228
3229 fn layout_style(&self) -> LayoutStyle {
3230 let mut s = self.style;
3231 s.margin = self.margin;
3232 s
3233 }
3234
3235 fn intrinsic_size(&self, ctx: &mut LayoutCtx) -> Option<Size> {
3236 let size = self
3237 .font_size
3238 .or_else(|| self.appearance.and_then(|s| s.font_size))
3239 .unwrap_or(14.0);
3240 let weight = self.font_weight();
3241 let line_h = self.line_height();
3242 let text = self.source.get_text();
3243 let lines = split_lines(&text);
3244 let mut w: f32 = 0.0;
3245 for line in &lines {
3246 w = w.max(ctx.text_width_weighted(line, size, weight));
3247 }
3248 if !self.placeholder.is_empty() && text.is_empty() {
3249 for line in split_lines(&self.placeholder) {
3250 w = w.max(ctx.text_width_weighted(line, size, weight));
3251 }
3252 }
3253 let rows = lines.len().max(self.min_lines).max(1) as f32;
3254 Some(Size::new(
3255 w + self.pad_x * 2.0,
3256 rows * line_h + self.pad_y * 2.0,
3257 ))
3258 }
3259
3260 fn on_event(&self, event: &Event, ctx: &mut EventCtx) {
3261 if ctx.phase != EventPhase::Target {
3262 if let Event::Pointer(PointerEvent::Wheel {
3263 delta, modifiers, ..
3264 }) = event
3265 && matches!(ctx.phase, EventPhase::Bubble)
3266 {
3267 self.on_wheel(*delta, *modifiers, ctx);
3268 }
3269 return;
3270 }
3271 match event {
3272 Event::Pointer(PointerEvent::Down {
3273 button: PointerButton::Left,
3274 pos,
3275 modifiers,
3276 ..
3277 }) => {
3278 self.begin_session();
3279 let theme = ctx.environment.get::<Theme>().unwrap_or_else(Theme::dark);
3280 let size = self.resolved_font_size(&theme);
3281 let weight = self.font_weight();
3282 let line_h = self.resolved_line_height(&theme);
3283 let text = self.source.get_text();
3284 let chars: Vec<char> = text.chars().collect();
3285 let starts = line_starts(&chars);
3286 let lines = split_lines(&text);
3287 let scroll = self.with_state(|s| s.scroll);
3288 let cx = pos.x - ctx.rect.left() - self.pad_x + scroll.x;
3289 let cy = pos.y - ctx.rect.top() - self.pad_y + scroll.y;
3290 let line = Self::row_at_y(line_h, lines.len(), cy);
3291 let bounds =
3292 ctx.caret_positions(lines.get(line).copied().unwrap_or(""), size, weight);
3293 let caret = self.caret_from_point(&lines, &chars, &starts, line, &bounds, cx);
3294 let extend = modifiers.contains(Modifiers::SHIFT);
3295 let alt = modifiers.contains(Modifiers::ALT);
3296 self.handle_click(caret, extend, alt, now_ms());
3297 self.ensure_caret_visible_ctx(ctx);
3298 self.bump();
3299 ctx.request_focus();
3300
3301 ctx.capture_pointer();
3302 ctx.handled();
3303 }
3304 Event::Pointer(PointerEvent::Move { pos, .. }) => {
3305 if ctx.state.pressed {
3306 let theme = ctx.environment.get::<Theme>().unwrap_or_else(Theme::dark);
3307 let size = self.resolved_font_size(&theme);
3308 let weight = self.font_weight();
3309 let line_h = self.resolved_line_height(&theme);
3310 let text = self.source.get_text();
3311 let chars: Vec<char> = text.chars().collect();
3312 let starts = line_starts(&chars);
3313 let lines = split_lines(&text);
3314 let scroll = self.with_state(|s| s.scroll);
3315 let cx = pos.x - ctx.rect.left() - self.pad_x + scroll.x;
3316 let cy = pos.y - ctx.rect.top() - self.pad_y + scroll.y;
3317 let line = Self::row_at_y(line_h, lines.len(), cy);
3318 let bounds =
3319 ctx.caret_positions(lines.get(line).copied().unwrap_or(""), size, weight);
3320 let caret = self.caret_from_point(&lines, &chars, &starts, line, &bounds, cx);
3321 let alt_drag = self.with_state(|s| s.alt_drag);
3322 match alt_drag {
3323 Some(idx) => self.with_state(|s| {
3324 if let Some(c) = s.carets.get_mut(idx) {
3325 c.move_to(caret, true);
3326 }
3327 s.goal_x = None;
3328 }),
3329 None => self.set_caret_extend(caret, true),
3330 }
3331 self.ensure_caret_visible_ctx(ctx);
3332 self.bump();
3333 ctx.handled();
3334 }
3335 }
3336 Event::Pointer(PointerEvent::Wheel {
3337 delta, modifiers, ..
3338 }) => {
3339 self.on_wheel(*delta, *modifiers, ctx);
3340 }
3341 Event::Key(key) => {
3342 if self.on_style_shortcut(key) {
3343 self.ensure_caret_visible_ctx(ctx);
3344 ctx.handled();
3345 } else if !self.on_clipboard_key(key, ctx) {
3346 self.on_key(key, ctx);
3347 } else {
3348 self.ensure_caret_visible_ctx(ctx);
3349 ctx.handled();
3350 }
3351 }
3352 Event::Ime(ime) => {
3353 self.on_ime(ime);
3354 ctx.handled();
3355 }
3356 _ => {}
3357 }
3358 }
3359
3360 fn paint_self(&self, ctx: &mut PaintCtx) {
3361 self.note_focus(ctx.state.focused);
3362 let rect = ctx.rect;
3363 if rect.size.width <= 0.0 || rect.size.height <= 0.0 {
3364 return;
3365 }
3366 let theme = ctx.theme();
3367 let size = self.resolved_font_size(&theme);
3368 let weight = self.font_weight();
3369 let line_h = self.resolved_line_height(&theme);
3370 if line_h <= 0.0 {
3371 return;
3372 }
3373 let text_color = self.resolved_text_color(&theme);
3374 let placeholder_color = self.resolved_placeholder_color(&theme);
3375 let background = self.resolved_background(&theme);
3376 let radius = self.resolved_radius(&theme);
3377
3378 self.with_state(|s| s.viewport = rect.size);
3379
3380 let text = self.source.get_text();
3381 let chars: Vec<char> = text.chars().collect();
3382 let starts = line_starts(&chars);
3383 let lines = split_lines(&text);
3384 let rows = lines.len();
3385
3386 let mut content_w: f32 = 0.0;
3387 for line in &lines {
3388 content_w = content_w.max(ctx.text_width_weighted(line, size, weight));
3389 }
3390 let content_h = rows as f32 * line_h;
3391
3392 let (caret, anchor, preedit) = self.with_state(|s| {
3393 let main = Self::main_of(s);
3394 (main.offset, main.anchor, s.preedit.clone())
3395 });
3396 let caret = caret.min(chars.len());
3397 let (caret_line, caret_col) = line_col_of(&chars, &starts, caret);
3398
3399 let caret_bounds =
3400 ctx.caret_positions(lines.get(caret_line).copied().unwrap_or(""), size, weight);
3401 let caret_x = caret_bounds
3402 .get(caret_col.min(caret_bounds.len().saturating_sub(1)))
3403 .copied()
3404 .unwrap_or(0.0);
3405 self.with_state(|s| {
3406 s.content = Size::new(content_w, content_h);
3407 s.scroll = clamp_scroll(
3408 s.scroll,
3409 content_w,
3410 content_h,
3411 rect.size,
3412 (self.pad_x, self.pad_y),
3413 );
3414 apply_ensure_visible(s, self.pad_x, self.pad_y, caret_x, caret_line, line_h);
3415 s.scroll = clamp_scroll(
3416 s.scroll,
3417 content_w,
3418 content_h,
3419 rect.size,
3420 (self.pad_x, self.pad_y),
3421 );
3422 });
3423 let scroll = self.with_state(|s| s.scroll);
3424
3425 if background.a > 0.0 {
3426 match self.resolved_border(&theme) {
3427 Some((w, color)) => {
3428 ctx.bordered_quad(rect, background, radius, w, color);
3429 }
3430 None => {
3431 ctx.quad(rect, background, radius);
3432 }
3433 }
3434 }
3435
3436 let ox = rect.left() + self.pad_x - scroll.x;
3437 let oy = rect.top() + self.pad_y - scroll.y;
3438 let baseline_at =
3439 |row: usize| -> f32 { oy + row as f32 * line_h + line_h * 0.5 + size * 0.35 };
3440 let top_at = |row: usize| -> f32 { oy + row as f32 * line_h };
3441
3442 let first_row = ((scroll.y - self.pad_y) / line_h).floor().max(0.0) as usize;
3443 let last_row = (((scroll.y - self.pad_y + rect.size.height) / line_h)
3444 .ceil()
3445 .max(0.0) as usize)
3446 .min(rows.saturating_sub(1));
3447
3448 if ctx.state.focused && self.highlight_current_line && anchor.is_none() {
3449 let color = self.current_line.unwrap_or(Color {
3450 r: 1.0,
3451 g: 1.0,
3452 b: 1.0,
3453 a: 0.05,
3454 });
3455 if color.a > 0.0 && caret_line >= first_row && caret_line <= last_row {
3456 ctx.quad(
3457 Rect::from_xywh(rect.left(), top_at(caret_line), rect.size.width, line_h),
3458 color,
3459 0.0,
3460 );
3461 }
3462 }
3463
3464 if let Some(a) = anchor {
3465 let (s0, s1) = (a.min(caret).min(chars.len()), a.max(caret).min(chars.len()));
3466 if s1 > s0 {
3467 for row in first_row..=last_row.min(rows.saturating_sub(1)) {
3468 let ls = starts[row];
3469 let le = ls + line_len(&chars, &starts, row);
3470 let line_end_nl = if row + 1 < rows { le + 1 } else { le };
3471 if s0 >= line_end_nl || s1 <= ls {
3472 continue;
3473 }
3474 let bounds =
3475 ctx.caret_positions(lines.get(row).copied().unwrap_or(""), size, weight);
3476 let col0 = s0.saturating_sub(ls).min(bounds.len().saturating_sub(1));
3477 let x0 = bounds.get(col0).copied().unwrap_or(0.0);
3478 let x1 = if s1 > le {
3479 (scroll.x + rect.size.width - self.pad_x).max(x0 + 4.0)
3480 } else {
3481 let col1 = s1.saturating_sub(ls).min(bounds.len().saturating_sub(1));
3482 bounds.get(col1).copied().unwrap_or(x0)
3483 };
3484 if x1 > x0 {
3485 ctx.quad(
3486 Rect::from_xywh(ox + x0, top_at(row), x1 - x0, line_h),
3487 theme.accent_pressed,
3488 0.0,
3489 );
3490 }
3491 }
3492 }
3493 }
3494
3495 if chars.is_empty() && preedit.is_empty() && !self.placeholder.is_empty() {
3496 for (row, line) in split_lines(&self.placeholder).iter().enumerate() {
3497 if row < first_row || row > last_row {
3498 continue;
3499 }
3500 ctx.draw_text_weighted(
3501 line,
3502 (ox, baseline_at(row)),
3503 size,
3504 weight,
3505 placeholder_color,
3506 );
3507 }
3508 } else {
3509 for row in first_row..=last_row.min(rows.saturating_sub(1)) {
3510 let line = lines.get(row).copied().unwrap_or("");
3511 if line.is_empty() {
3512 continue;
3513 }
3514 if row == caret_line && !preedit.is_empty() && ctx.state.focused {
3515 let before: String = line
3516 .chars()
3517 .take(caret_col.min(line.chars().count()))
3518 .collect();
3519 let after: String = line
3520 .chars()
3521 .skip(caret_col.min(line.chars().count()))
3522 .collect();
3523 let bx = ctx.text_width_weighted(&before, size, weight);
3524 ctx.draw_text_weighted(
3525 &before,
3526 (ox, baseline_at(row)),
3527 size,
3528 weight,
3529 text_color,
3530 );
3531 ctx.draw_text_weighted(
3532 &preedit,
3533 (ox + bx, baseline_at(row)),
3534 size,
3535 weight,
3536 text_color,
3537 );
3538 let pw = ctx.text_width_weighted(&preedit, size, weight);
3539 ctx.draw_text_weighted(
3540 &after,
3541 (ox + bx + pw, baseline_at(row)),
3542 size,
3543 weight,
3544 text_color,
3545 );
3546 ctx.quad(
3547 Rect::from_xywh(ox + bx, top_at(row) + line_h - 2.0, pw.max(1.0), 1.5),
3548 text_color,
3549 0.0,
3550 );
3551 } else {
3552 ctx.draw_text_weighted(line, (ox, baseline_at(row)), size, weight, text_color);
3553 }
3554 }
3555 }
3556
3557 if ctx.state.focused {
3558 let mut dx = caret_x;
3559 if !preedit.is_empty() && anchor.is_none() {
3560 let line = lines.get(caret_line).copied().unwrap_or("");
3561 let before: String = line
3562 .chars()
3563 .take(caret_col.min(line.chars().count()))
3564 .collect();
3565 dx += ctx.text_width_weighted(&preedit, size, weight);
3566 let _ = before;
3567 }
3568 ctx.quad(
3569 Rect::from_xywh(
3570 ox + dx,
3571 baseline_at(caret_line) - size * 0.8,
3572 CARET_W,
3573 size * 1.05,
3574 ),
3575 text_color,
3576 0.0,
3577 );
3578 }
3579 }
3580}
3581
3582impl TextArea {
3583 fn on_wheel(&self, delta: Vec2, modifiers: Modifiers, ctx: &mut EventCtx) {
3584 let (mut dx, mut dy) = (delta.x, delta.y);
3585 if modifiers.contains(Modifiers::SHIFT) {
3586 std::mem::swap(&mut dx, &mut dy);
3587 }
3588 let (cur, viewport, content) = self.with_state(|s| (s.scroll, s.viewport, s.content));
3589 let next = clamp_scroll(
3590 Vec2::new(cur.x - dx, cur.y - dy),
3591 content.width,
3592 content.height,
3593 viewport,
3594 self.pad(),
3595 );
3596 if next != cur {
3597 self.with_state(|s| s.scroll = next);
3598 if let Some(f) = &self.on_scroll {
3599 f(next);
3600 }
3601 self.bump();
3602 ctx.handled();
3603 }
3604 }
3605}
3606
3607fn sanitize_margin(v: f32) -> f32 {
3608 if v.is_finite() { v.max(0.0) } else { 0.0 }
3609}
3610
3611#[cfg(test)]
3612mod tests {
3613 use super::*;
3614
3615 fn chars_of(s: &str) -> Vec<char> {
3616 s.chars().collect()
3617 }
3618
3619 #[test]
3620 fn line_starts_cover_empty_and_trailing_newline() {
3621 assert_eq!(line_starts(&chars_of("")), vec![0]);
3622 assert_eq!(line_starts(&chars_of("ab")), vec![0]);
3623 assert_eq!(line_starts(&chars_of("a\nb")), vec![0, 2]);
3624 assert_eq!(line_starts(&chars_of("a\n")), vec![0, 2]);
3625 assert_eq!(line_starts(&chars_of("\n")), vec![0, 1]);
3626 }
3627
3628 #[test]
3629 fn caret_roundtrips_through_line_col() {
3630 let text = "ab\ncde\n\nf";
3631 let chars = chars_of(text);
3632 let starts = line_starts(&chars);
3633 assert_eq!(starts.len(), 4);
3634 for caret in 0..=chars.len() {
3635 let (line, col) = line_col_of(&chars, &starts, caret);
3636 assert_eq!(caret_of_line_col(&chars, &starts, line, col), caret);
3637 }
3638 assert_eq!(line_col_of(&chars, &starts, 0), (0, 0));
3639 assert_eq!(line_col_of(&chars, &starts, 2), (0, 2));
3640 assert_eq!(line_col_of(&chars, &starts, 3), (1, 0));
3641 assert_eq!(line_col_of(&chars, &starts, 7), (2, 0));
3642 assert_eq!(line_col_of(&chars, &starts, 8), (3, 0));
3643 }
3644
3645 #[test]
3646 fn sanitize_multiline_normalizes_newlines() {
3647 assert_eq!(sanitize_multiline("a\r\nb\rc"), "a\nb\nc");
3648 assert_eq!(sanitize_multiline("a\x01b\tc"), "ab\tc");
3649 }
3650
3651 #[test]
3652 fn leading_indent_counts_spaces_and_tabs() {
3653 assert_eq!(leading_indent_len(" foo"), 4);
3654 assert_eq!(leading_indent_len("\t\tfoo"), 2);
3655 assert_eq!(leading_indent_len("foo"), 0);
3656 }
3657}