1mod color;
2mod decorations;
3mod layout;
4mod node;
5mod reconcile;
6mod virtual_text;
7
8#[cfg(feature = "syntax-syntect")]
9mod color_syntect;
10#[cfg(feature = "syntax-syntect")]
11mod syntect_document_formatter;
12
13pub(crate) use color::TextAreaColorCache;
14pub use color::{TextAreaColorInput, TextAreaColorLines, TextAreaColorStrategy};
15pub(crate) use decorations::{
16 TEXT_AREA_LAYER_PRIORITY_CURRENT_SEARCH, TEXT_AREA_LAYER_PRIORITY_SEARCH,
17 TEXT_AREA_LAYER_PRIORITY_SELECTION, TextAreaLayerKind, TextAreaRangeLayer,
18 TextAreaStyledSegment, public_decoration_layers_for_visible_range, resolve_text_area_spans,
19 segments_from_plain, segments_from_spans,
20};
21pub(crate) use layout::{
22 TextAreaGeometry, TextAreaVisualCache, TextAreaVisualKeyArgs, TextAreaVisualLine,
23 VirtualTextLayoutCtx, hash_peer_source_lines, layout_line_with_inline_virtual_text,
24 make_text_area_visual_key, text_area_auto_height_for_width, text_area_cursor_reserve,
25 text_area_pending_vim_search_row, text_area_total_gutter_width,
26 text_area_visual_line_for_cursor,
27};
28pub use layout::{measure_text_area, measure_text_area_constrained};
29pub use node::TextAreaNode;
30pub use reconcile::reconcile_text_area;
31pub(crate) use virtual_text::{
32 eol_virtual_texts_for_visual_line, inline_virtual_insertions_for_line,
33 inline_virtual_texts_for_visual_line, text_area_virtual_text_hash, virtual_text_content_width,
34};
35
36#[cfg(feature = "syntax-syntect")]
37pub use color_syntect::{SyntectStrategy, apply_syntect_strategy_app_theme, language_from_path};
38#[cfg(feature = "syntax-syntect")]
39pub use syntect_document_formatter::SyntectDocumentFormatter;
40mod metrics;
41mod sentinel;
42mod snapshot;
43mod vim_config;
44
45use std::collections::BTreeMap;
46use std::hash::Hash;
47use std::rc::Rc;
48use std::sync::Arc;
49
50use crate::animation::TransitionConfig;
51use crate::app::TextAreaNewlineBinding;
52use crate::callback::{Callback, KeyHandler};
53use crate::clipboard::ImageContent;
54use crate::core::element::{Element, ElementKind};
55use crate::core::event::MouseEvent;
56use crate::input::KeyBindings;
57use crate::style::{
58 BorderStyle, CaretShape, Color, LayoutConstraints, Length, Padding, ScrollbarConfig,
59 ScrollbarVariant, Span, Style, StyleSlot,
60};
61use crate::text::edit::TextEditEvent;
62use crate::text::editor::TextEditor;
63use crate::utils::text::SentinelInfo;
64use crate::widgets::scroll::{ScrollBehavior, ScrollEvent};
65
66#[allow(missing_docs)]
68#[derive(Clone, Debug, PartialEq)]
69pub struct TextAreaDecoration {
70 pub range: std::ops::Range<usize>,
71 pub style: Style,
72 pub priority: u16,
81 pub kind: TextAreaDecorationKind,
82}
83
84#[non_exhaustive]
86#[allow(missing_docs)]
87#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
88pub enum TextAreaDecorationKind {
89 Range,
90 WholeLine,
91 Underline,
92 #[deprecated(
93 note = "TextAreaDecorationKind::VirtualText is a reserved no-op; use TextArea::virtual_text with TextAreaVirtualText instead"
94 )]
95 VirtualText,
96}
97
98#[allow(missing_docs)]
100#[derive(Clone, Debug, PartialEq)]
101pub struct TextAreaGutterSign {
102 pub line: usize,
103 pub spans: Vec<Span>,
104}
105
106#[allow(missing_docs)]
107impl TextAreaGutterSign {
108 pub fn new(line: usize, spans: impl Into<Vec<Span>>) -> Self {
109 Self {
110 line,
111 spans: spans.into(),
112 }
113 }
114}
115
116#[allow(missing_docs)]
118#[derive(Clone, Debug, PartialEq)]
119pub struct TextAreaGutterColumn {
120 kind: TextAreaGutterColumnKind,
121 width: u16,
122}
123
124#[derive(Clone, Debug, PartialEq)]
125enum TextAreaGutterColumnKind {
126 LineNumbers(TextAreaLineNumberMode),
127 Custom(Arc<Vec<Vec<Span>>>),
128 Signs(Vec<TextAreaGutterSign>),
129}
130
131#[allow(missing_docs)]
132impl TextAreaGutterColumn {
133 pub fn line_numbers(mode: TextAreaLineNumberMode) -> Self {
134 Self {
135 kind: TextAreaGutterColumnKind::LineNumbers(mode),
136 width: 0,
137 }
138 }
139
140 pub fn custom(lines: Arc<Vec<Vec<Span>>>, width: u16) -> Self {
141 Self {
142 kind: TextAreaGutterColumnKind::Custom(lines),
143 width,
144 }
145 }
146
147 pub fn signs(signs: impl IntoIterator<Item = TextAreaGutterSign>) -> Self {
148 let signs: Vec<_> = signs.into_iter().collect();
149 let width = signs
150 .iter()
151 .flat_map(|s| s.spans.iter())
152 .map(|s| unicode_width::UnicodeWidthStr::width(s.content.as_ref()) as u16)
153 .max()
154 .unwrap_or(1)
155 .max(1);
156 Self {
157 kind: TextAreaGutterColumnKind::Signs(signs),
158 width,
159 }
160 }
161
162 pub fn width(mut self, width: u16) -> Self {
163 self.width = width;
164 self
165 }
166}
167
168#[allow(missing_docs)]
170#[derive(Clone, Debug, Default, PartialEq)]
171pub struct TextAreaGutter {
172 columns: Vec<TextAreaGutterColumn>,
173}
174
175#[allow(missing_docs)]
176impl TextAreaGutter {
177 pub fn new() -> Self {
178 Self::default()
179 }
180 pub fn line_numbers(mut self, mode: TextAreaLineNumberMode) -> Self {
181 self.columns.push(TextAreaGutterColumn::line_numbers(mode));
182 self
183 }
184 pub fn signs(mut self, signs: impl IntoIterator<Item = TextAreaGutterSign>) -> Self {
185 self.columns.push(TextAreaGutterColumn::signs(signs));
186 self
187 }
188 pub fn column(mut self, column: TextAreaGutterColumn) -> Self {
189 self.columns.push(column);
190 self
191 }
192}
193
194#[allow(missing_docs)]
196#[derive(Clone, Debug, PartialEq)]
197pub struct TextAreaStateChangeEvent {
198 pub reason: TextAreaStateChangeReason,
199 pub value: Arc<str>,
200 pub cursor: usize,
201 pub anchor: Option<usize>,
202 pub edit: Option<TextEditEvent>,
203 pub vim_mode: Option<TextAreaVimMode>,
204}
205
206#[non_exhaustive]
207#[allow(missing_docs)]
208#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
209pub enum TextAreaStateChangeReason {
210 Edit,
211 SelectionChange,
212 CursorMove,
213 Scroll,
214 VimModeChange,
215}
216
217#[derive(Clone)]
219pub struct TextArea {
220 pub(crate) value: Arc<str>,
221 pub(crate) cursor: usize, pub(crate) anchor: Option<usize>, pub(crate) placeholder: Option<Arc<str>>,
224 pub(crate) style: Style,
225 pub(crate) hover_style: StyleSlot,
226 pub(crate) focus_style: StyleSlot,
227 pub(crate) focus_content_style: Style,
228 pub(crate) hover_border_style: Option<BorderStyle>,
229 pub(crate) caret_shape: CaretShape,
230 pub(crate) caret_color: Option<Color>,
231 pub(crate) selection_style: StyleSlot,
232 pub(crate) unfocused_selection_style: StyleSlot,
233 pub(crate) show_selection_when_unfocused: bool,
235 pub(crate) placeholder_style: Style,
236 pub(crate) focus_placeholder_style: Style,
237 pub(crate) line_numbers: bool,
238 pub(crate) line_number_mode: TextAreaLineNumberMode,
239 pub(crate) line_number_style: Style,
240 pub(crate) min_line_number_width: u8,
241 pub(crate) wrap: bool,
242 pub(crate) color_strategy: Option<Rc<dyn TextAreaColorStrategy>>,
243 pub(crate) language: Option<Arc<str>>,
244 pub(crate) theme: Option<Arc<str>>,
245 pub(crate) border: bool,
246 pub(crate) border_style: BorderStyle,
247 pub(crate) padding: Padding,
248 pub(crate) width: Length,
249 pub(crate) height: Length,
250 pub(crate) scroll_offset: Option<usize>, pub(crate) scroll_to_line: Option<usize>,
253 pub(crate) scroll_behavior: ScrollBehavior,
254 pub(crate) scroll_wheel: bool,
255 pub(crate) scroll_wheel_multiplier: Option<u16>,
256 pub(crate) on_change: Option<Callback<TextAreaEvent>>,
257 pub(crate) on_edit: Option<Callback<TextEditEvent>>,
258 pub(crate) on_editor_state_change: Option<Callback<TextAreaStateChangeEvent>>,
259 pub(crate) on_scroll: Option<Callback<ScrollEvent>>,
260 pub(crate) on_scroll_to: Option<Callback<usize>>,
261 pub(crate) on_click: Option<Callback<MouseEvent>>,
262 pub(crate) on_key: Option<KeyHandler>,
263 pub(crate) key_interceptor: Option<KeyHandler>,
264 pub(crate) clear_bindings: Option<KeyBindings>,
265 pub(crate) vim_motions: bool,
266 pub(crate) vim_keymap: Option<TextAreaVimKeymap>,
267 pub(crate) vim_config: TextAreaVimConfig,
268 pub(crate) on_vim_mode_change: Option<Callback<TextAreaVimMode>>,
269 pub(crate) on_image_paste: Option<Callback<ImageContent>>,
270 pub(crate) on_text_paste: Option<Callback<TextAreaPasteEvent>>,
271 pub(crate) images: Vec<ImageContent>,
275 pub(crate) on_images_change: Option<Callback<Vec<ImageContent>>>,
276 pub(crate) image_mode: TextAreaImageMode,
277 pub(crate) image_placeholder: Arc<str>,
278 pub(crate) image_placeholder_style: Style,
279 pub(crate) image_placeholder_focus_style: Style,
280 pub(crate) image_placeholder_hover_style: Style,
281 pub(crate) disabled: bool,
282 pub(crate) disabled_style: Style,
283 pub(crate) read_only: bool,
284 pub(crate) focusable: bool,
285 pub(crate) newline_binding: Option<TextAreaNewlineBinding>,
286 pub(crate) tab_width: u8,
287 pub(crate) insert_tab: bool,
288 pub(crate) tab_stop: u8,
292 pub(crate) scrollbar: bool,
294 pub(crate) scrollbar_config: ScrollbarConfig,
295 pub(crate) h_scrollbar: bool,
296 pub(crate) h_scrollbar_variant: ScrollbarVariant,
297 pub(crate) h_scrollbar_thumb: Option<char>,
298 #[cfg(feature = "diff-view")]
299 pub(crate) pin_scrollbar_focus_style: bool,
300 pub(crate) gutter_lines: Option<Arc<Vec<Vec<crate::style::Span>>>>,
304 pub(crate) gutter_col_width: u16,
307 pub(crate) gutter_gap: u16,
309 pub(crate) gutter: Option<TextAreaGutter>,
310 pub(crate) peer_source_lines: Option<Arc<Vec<Arc<str>>>>,
312 #[cfg(feature = "diff-view")]
313 pub(crate) split_wrap_sync: Option<crate::widgets::diff_view::SharedSplitWrapSync>,
314 #[cfg(feature = "diff-view")]
315 pub(crate) split_wrap_side: Option<crate::widgets::diff_view::SplitPaneSide>,
316 #[cfg(feature = "diff-view")]
317 pub(crate) diff_context_separator_click:
318 Option<crate::widgets::diff_view::DiffContextSeparatorClickConfig>,
319 pub(crate) split_wrap_padding_gutter_style: Option<Style>,
321 pub(crate) split_wrap_padding_style: Option<Style>,
323 pub(crate) copy_excluded_bytes: Option<Arc<Vec<(usize, usize)>>>,
325 pub(crate) clipboard_transform: Option<TextAreaClipboardTransform>,
327 pub(crate) selection_excluded_lines: Option<Arc<Vec<usize>>>,
329 pub(crate) multi_click_select: bool,
331 pub(crate) triple_click_mode: crate::widgets::TripleClickSelectionMode,
333 pub(crate) sentinels: Vec<TextAreaSentinel>,
336 pub(crate) on_sentinels_change: Option<Callback<Vec<TextAreaSentinel>>>,
338 pub(crate) on_sentinel_event: Option<Callback<Vec<SentinelEvent>>>,
339 pub(crate) on_sentinel_click: Option<Callback<TextAreaSentinelClickEvent>>,
340 pub(crate) decorations: Vec<TextAreaDecoration>,
341 pub(crate) virtual_texts: Vec<TextAreaVirtualText>,
342}
343
344impl Default for TextArea {
345 fn default() -> Self {
346 Self {
347 value: "".into(),
348 cursor: 0,
349 anchor: None,
350 placeholder: None,
351 style: Style::default(),
352 hover_style: StyleSlot::Inherit,
353 focus_style: StyleSlot::Inherit,
354 focus_content_style: Style::default(),
355 hover_border_style: None,
356 caret_shape: CaretShape::default(),
357 caret_color: None,
358 selection_style: StyleSlot::Inherit,
359 unfocused_selection_style: StyleSlot::Inherit,
360 show_selection_when_unfocused: true,
361 placeholder_style: Style::default(),
362 focus_placeholder_style: Style::default(),
363 line_numbers: false,
364 line_number_mode: TextAreaLineNumberMode::default(),
365 line_number_style: Style::default(),
366 min_line_number_width: 0,
367 wrap: true,
368 color_strategy: None,
369 language: None,
370 theme: None,
371 border: true,
372 border_style: BorderStyle::Plain,
373 padding: Padding::default(),
374 width: Length::Flex(1),
375 height: Length::Flex(1),
376 scroll_offset: None,
377 scroll_to_line: None,
378 scroll_behavior: ScrollBehavior::Instant,
379 scroll_wheel: true,
380 scroll_wheel_multiplier: None,
381 on_change: None,
382 on_edit: None,
383 on_editor_state_change: None,
384 on_scroll: None,
385 on_scroll_to: None,
386 on_click: None,
387 on_key: None,
388 key_interceptor: None,
389 clear_bindings: None,
390 vim_motions: false,
391 vim_keymap: None,
392 vim_config: TextAreaVimConfig::default(),
393 on_vim_mode_change: None,
394 on_image_paste: None,
395 on_text_paste: None,
396 images: Vec::new(),
397 on_images_change: None,
398 image_mode: TextAreaImageMode::default(),
399 image_placeholder: "[Image]".into(),
400 image_placeholder_style: Style::default(),
401 image_placeholder_focus_style: Style::default(),
402 image_placeholder_hover_style: Style::default(),
403 disabled: false,
404 disabled_style: Style::default(),
405 read_only: false,
406 focusable: true,
407 newline_binding: None,
408 tab_width: 0,
409 insert_tab: false,
410 tab_stop: 8,
411 scrollbar: true,
412 scrollbar_config: ScrollbarConfig::default(),
413 h_scrollbar: false,
414 h_scrollbar_variant: ScrollbarVariant::default(),
415 h_scrollbar_thumb: None,
416 #[cfg(feature = "diff-view")]
417 pin_scrollbar_focus_style: false,
418 gutter_lines: None,
419 gutter_col_width: 0,
420 gutter_gap: 0,
421 gutter: None,
422 peer_source_lines: None,
423 #[cfg(feature = "diff-view")]
424 split_wrap_sync: None,
425 #[cfg(feature = "diff-view")]
426 split_wrap_side: None,
427 #[cfg(feature = "diff-view")]
428 diff_context_separator_click: None,
429 split_wrap_padding_gutter_style: None,
430 split_wrap_padding_style: None,
431 copy_excluded_bytes: None,
432 clipboard_transform: None,
433 selection_excluded_lines: None,
434 multi_click_select: true,
435 triple_click_mode: crate::widgets::TripleClickSelectionMode::Line,
436 sentinels: Vec::new(),
437 on_sentinels_change: None,
438 on_sentinel_event: None,
439 on_sentinel_click: None,
440 decorations: Vec::new(),
441 virtual_texts: Vec::new(),
442 }
443 }
444}
445
446impl TextArea {
447 pub fn new(value: impl Into<Arc<str>>) -> Self {
449 Self {
450 value: value.into(),
451 ..Self::default()
452 }
453 }
454
455 pub fn bound(state: &TextEditor) -> Self {
457 Self::new("").bind(state)
458 }
459
460 pub fn value(mut self, value: impl Into<Arc<str>>) -> Self {
462 self.value = value.into();
463 self
464 }
465
466 pub fn placeholder(mut self, placeholder: impl Into<Arc<str>>) -> Self {
468 self.placeholder = Some(placeholder.into());
469 self
470 }
471
472 pub fn cursor(mut self, cursor: usize) -> Self {
474 self.cursor = cursor;
475 self
476 }
477
478 pub fn anchor(mut self, anchor: Option<usize>) -> Self {
481 self.anchor = anchor;
482 self
483 }
484
485 pub fn bind(mut self, state: &TextEditor) -> Self {
487 self.value = state.text().into();
488 self.cursor = state.cursor();
489 self.anchor = state.anchor();
490 self
491 }
492
493 pub fn line_numbers(mut self, show: bool) -> Self {
495 self.line_numbers = show;
496 self.gutter = None;
497 self
498 }
499
500 pub fn line_number_mode(mut self, mode: TextAreaLineNumberMode) -> Self {
506 self.line_number_mode = mode;
507 self.gutter = None;
508 self
509 }
510
511 pub fn min_line_number_width(mut self, width: u8) -> Self {
513 self.min_line_number_width = width;
514 self
515 }
516
517 pub fn wrap(mut self, wrap: bool) -> Self {
519 self.wrap = wrap;
520 self
521 }
522
523 pub fn color_strategy(mut self, strategy: impl TextAreaColorStrategy + 'static) -> Self {
525 self.color_strategy = Some(Rc::new(strategy));
526 self
527 }
528
529 pub fn language(mut self, language: impl Into<Arc<str>>) -> Self {
531 self.language = Some(language.into());
532 self
533 }
534
535 #[cfg(feature = "syntax-syntect")]
542 pub fn language_from_path(self, path: impl AsRef<std::path::Path>) -> Self {
543 if let Some(lang) = crate::widgets::language_from_path(path) {
544 self.language(lang)
545 } else {
546 self
547 }
548 }
549
550 pub fn theme(mut self, theme: impl Into<Arc<str>>) -> Self {
552 self.theme = Some(theme.into());
553 self
554 }
555
556 #[cfg(feature = "syntax-syntect")]
558 pub fn with_syntax(self, language: impl Into<Arc<str>>, theme: impl Into<Arc<str>>) -> Self {
559 self.with_syntax_strategy(SyntectStrategy::default(), language, theme)
560 }
561
562 #[cfg(feature = "syntax-syntect")]
564 pub fn with_syntax_bg(self, language: impl Into<Arc<str>>, theme: impl Into<Arc<str>>) -> Self {
565 self.with_syntax_strategy(
566 SyntectStrategy::default().use_background(true),
567 language,
568 theme,
569 )
570 }
571
572 #[cfg(feature = "syntax-syntect")]
574 pub fn with_syntax_custom_theme(
575 self,
576 language: impl Into<Arc<str>>,
577 theme_name: impl Into<Arc<str>>,
578 tm_theme_xml: impl AsRef<str>,
579 ) -> crate::Result<Self> {
580 let theme_name = theme_name.into();
581 let strategy = SyntectStrategy::default().custom_theme(theme_name.clone(), tm_theme_xml)?;
582 Ok(self.with_syntax_strategy(strategy, language, theme_name))
583 }
584
585 #[cfg(feature = "syntax-syntect")]
587 pub fn with_syntax_custom_theme_bytes(
588 self,
589 language: impl Into<Arc<str>>,
590 theme_name: impl Into<Arc<str>>,
591 bytes: impl AsRef<[u8]>,
592 ) -> crate::Result<Self> {
593 let theme_name = theme_name.into();
594 let strategy = SyntectStrategy::default().custom_theme_bytes(theme_name.clone(), bytes)?;
595 Ok(self.with_syntax_strategy(strategy, language, theme_name))
596 }
597
598 #[cfg(feature = "syntax-syntect")]
600 pub fn with_syntax_custom_theme_from_file(
601 self,
602 language: impl Into<Arc<str>>,
603 theme_name: impl Into<Arc<str>>,
604 path: impl AsRef<std::path::Path>,
605 ) -> crate::Result<Self> {
606 let theme_name = theme_name.into();
607 let strategy =
608 SyntectStrategy::default().custom_theme_from_file(theme_name.clone(), path)?;
609 Ok(self.with_syntax_strategy(strategy, language, theme_name))
610 }
611
612 #[cfg(feature = "syntax-syntect")]
614 pub fn with_syntax_strategy(
615 self,
616 strategy: SyntectStrategy,
617 language: impl Into<Arc<str>>,
618 theme: impl Into<Arc<str>>,
619 ) -> Self {
620 self.color_strategy(strategy)
621 .language(language)
622 .theme(theme)
623 }
624
625 pub fn style(mut self, style: Style) -> Self {
627 self.style = style;
628 self
629 }
630
631 pub fn hover_style(mut self, style: Style) -> Self {
633 self.hover_style = StyleSlot::Replace(style);
634 self
635 }
636
637 pub fn extend_hover_style(mut self, style: Style) -> Self {
639 self.hover_style = StyleSlot::Extend(style);
640 self
641 }
642
643 pub fn inherit_hover_style(mut self) -> Self {
645 self.hover_style = StyleSlot::Inherit;
646 self
647 }
648
649 pub fn hover_style_slot(mut self, slot: StyleSlot) -> Self {
651 self.hover_style = slot;
652 self
653 }
654
655 pub fn focus_style(mut self, style: Style) -> Self {
657 self.focus_style = StyleSlot::Replace(style);
658 self
659 }
660
661 pub fn extend_focus_style(mut self, style: Style) -> Self {
663 self.focus_style = StyleSlot::Extend(style);
664 self
665 }
666
667 pub fn inherit_focus_style(mut self) -> Self {
669 self.focus_style = StyleSlot::Inherit;
670 self
671 }
672
673 pub fn focus_style_slot(mut self, slot: StyleSlot) -> Self {
675 self.focus_style = slot;
676 self
677 }
678
679 pub fn focus_content_style(mut self, style: Style) -> Self {
681 self.focus_content_style = style;
682 self
683 }
684
685 pub fn hover_border_style(mut self, border_style: BorderStyle) -> Self {
687 self.hover_border_style = Some(border_style);
688 self
689 }
690
691 pub fn caret_shape(mut self, shape: CaretShape) -> Self {
693 self.caret_shape = shape;
694 self
695 }
696
697 pub fn caret_color(mut self, color: Color) -> Self {
699 self.caret_color = Some(color);
700 self
701 }
702
703 pub fn selection_style(mut self, style: Style) -> Self {
705 self.selection_style = StyleSlot::Replace(style);
706 self
707 }
708
709 pub fn extend_selection_style(mut self, style: Style) -> Self {
711 self.selection_style = StyleSlot::Extend(style);
712 self
713 }
714
715 pub fn inherit_selection_style(mut self) -> Self {
717 self.selection_style = StyleSlot::Inherit;
718 self
719 }
720
721 pub fn selection_style_slot(mut self, slot: StyleSlot) -> Self {
723 self.selection_style = slot;
724 self
725 }
726
727 pub fn show_selection_when_unfocused(mut self, show: bool) -> Self {
733 self.show_selection_when_unfocused = show;
734 self
735 }
736
737 pub fn unfocused_selection_style(mut self, style: Style) -> Self {
739 self.unfocused_selection_style = StyleSlot::Replace(style);
740 self
741 }
742
743 pub fn inherit_unfocused_selection_style(mut self) -> Self {
745 self.unfocused_selection_style = StyleSlot::Inherit;
746 self
747 }
748
749 pub fn unfocused_selection_style_slot(mut self, slot: StyleSlot) -> Self {
751 self.unfocused_selection_style = slot;
752 self
753 }
754
755 pub fn placeholder_style(mut self, style: Style) -> Self {
757 self.placeholder_style = style;
758 self
759 }
760
761 pub fn focus_placeholder_style(mut self, style: Style) -> Self {
763 self.focus_placeholder_style = style;
764 self
765 }
766
767 pub fn line_number_style(mut self, style: Style) -> Self {
769 self.line_number_style = style;
770 self
771 }
772
773 pub fn border(mut self, border: bool) -> Self {
775 self.border = border;
776 self
777 }
778
779 pub fn border_style(mut self, style: BorderStyle) -> Self {
781 self.border_style = style;
782 self
783 }
784
785 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
787 self.padding = padding.into();
788 self
789 }
790
791 pub fn width(mut self, width: Length) -> Self {
793 self.width = width;
794 self
795 }
796
797 pub fn height(mut self, height: Length) -> Self {
799 self.height = height;
800 self
801 }
802
803 pub fn on_change(mut self, cb: Callback<TextAreaEvent>) -> Self {
805 self.on_change = Some(cb);
806 self
807 }
808
809 pub fn on_edit(mut self, cb: Callback<TextEditEvent>) -> Self {
811 self.on_edit = Some(cb);
812 self
813 }
814
815 pub fn on_editor_state_change(mut self, cb: Callback<TextAreaStateChangeEvent>) -> Self {
817 self.on_editor_state_change = Some(cb);
818 self
819 }
820
821 pub fn on_click(mut self, cb: Callback<MouseEvent>) -> Self {
823 self.on_click = Some(cb);
824 self
825 }
826
827 pub fn on_key(mut self, handler: KeyHandler) -> Self {
829 self.on_key = Some(handler);
830 self
831 }
832
833 pub fn key_interceptor(mut self, handler: KeyHandler) -> Self {
839 self.key_interceptor = Some(handler);
840 self
841 }
842
843 pub fn clear_bindings(mut self, bindings: KeyBindings) -> Self {
847 self.clear_bindings = Some(bindings);
848 self
849 }
850
851 pub fn vim_motions(mut self, enabled: bool) -> Self {
855 self.vim_motions = enabled;
856 self
857 }
858
859 pub fn vim_keymap(mut self, keymap: TextAreaVimKeymap) -> Self {
865 self.vim_keymap = Some(keymap);
866 self
867 }
868
869 pub fn vim_config(mut self, config: TextAreaVimConfig) -> Self {
872 self.vim_config = config;
873 self
874 }
875
876 pub fn vim_current_line_highlight(mut self, mode: TextAreaVimCurrentLineHighlight) -> Self {
882 self.vim_config.current_line_highlight = mode;
883 self
884 }
885
886 pub fn highlight_vim_current_line(mut self, enabled: bool) -> Self {
888 self.vim_config = self.vim_config.highlight_current_line(enabled);
889 self
890 }
891
892 pub fn on_vim_mode_change(mut self, cb: Callback<TextAreaVimMode>) -> Self {
894 self.on_vim_mode_change = Some(cb);
895 self
896 }
897
898 pub fn on_image_paste(mut self, cb: Callback<ImageContent>) -> Self {
900 self.on_image_paste = Some(cb);
901 self
902 }
903
904 pub fn images(mut self, images: Vec<ImageContent>) -> Self {
906 self.images = images;
907 self
908 }
909
910 pub fn on_images_change(mut self, cb: Callback<Vec<ImageContent>>) -> Self {
912 self.on_images_change = Some(cb);
913 self
914 }
915
916 pub fn image_mode(mut self, mode: TextAreaImageMode) -> Self {
918 self.image_mode = mode;
919 self
920 }
921
922 pub fn image_placeholder(mut self, label: impl Into<Arc<str>>) -> Self {
924 self.image_placeholder = label.into();
925 self
926 }
927
928 pub fn image_placeholder_style(mut self, style: Style) -> Self {
930 self.image_placeholder_style = style;
931 self
932 }
933
934 pub fn image_placeholder_focus_style(mut self, style: Style) -> Self {
936 self.image_placeholder_focus_style = style;
937 self
938 }
939
940 pub fn image_placeholder_hover_style(mut self, style: Style) -> Self {
942 self.image_placeholder_hover_style = style;
943 self
944 }
945
946 pub fn disabled(mut self, disabled: bool) -> Self {
948 self.disabled = disabled;
949 self
950 }
951
952 pub fn disabled_style(mut self, style: Style) -> Self {
954 self.disabled_style = style;
955 self
956 }
957
958 pub fn read_only(mut self, read_only: bool) -> Self {
960 self.read_only = read_only;
961 self
962 }
963
964 pub fn multi_click_select(mut self, enabled: bool) -> Self {
969 self.multi_click_select = enabled;
970 self
971 }
972
973 pub fn triple_click_mode(mut self, mode: crate::widgets::TripleClickSelectionMode) -> Self {
975 self.triple_click_mode = mode;
976 self
977 }
978
979 pub fn focusable(mut self, focusable: bool) -> Self {
981 self.focusable = focusable;
982 self
983 }
984
985 pub fn newline_binding(mut self, binding: TextAreaNewlineBinding) -> Self {
987 self.newline_binding = Some(binding);
988 self
989 }
990
991 pub fn tab_width(mut self, width: u8) -> Self {
994 self.tab_width = width;
995 self
996 }
997
998 pub fn insert_tab(mut self, insert_tab: bool) -> Self {
1000 self.insert_tab = insert_tab;
1001 self
1002 }
1003
1004 pub fn tab_stop(mut self, tab_stop: u8) -> Self {
1010 self.tab_stop = tab_stop;
1011 self
1012 }
1013
1014 pub fn scroll_offset(mut self, offset: usize) -> Self {
1016 self.scroll_offset = Some(offset);
1017 self
1018 }
1019
1020 pub fn scroll_to_line(mut self, line: usize) -> Self {
1026 self.scroll_to_line = Some(line);
1027 self
1028 }
1029
1030 pub fn scroll_behavior(mut self, behavior: ScrollBehavior) -> Self {
1035 self.scroll_behavior = behavior;
1036 self
1037 }
1038
1039 pub fn scroll_transition(mut self, transition: TransitionConfig) -> Self {
1041 self.scroll_behavior = ScrollBehavior::smooth(transition);
1042 self
1043 }
1044
1045 pub fn scroll_wheel(mut self, enabled: bool) -> Self {
1047 self.scroll_wheel = enabled;
1048 self
1049 }
1050
1051 pub fn scroll_wheel_multiplier(mut self, multiplier: u16) -> Self {
1053 self.scroll_wheel_multiplier = Some(multiplier.max(1));
1054 self
1055 }
1056
1057 pub fn on_scroll(mut self, cb: Callback<ScrollEvent>) -> Self {
1059 self.on_scroll = Some(cb);
1060 self
1061 }
1062
1063 pub fn on_scroll_to(mut self, cb: Callback<usize>) -> Self {
1065 self.on_scroll_to = Some(cb);
1066 self
1067 }
1068
1069 pub fn scrollbar(mut self, scrollbar: bool) -> Self {
1071 self.scrollbar = scrollbar;
1072 self
1073 }
1074
1075 pub fn scrollbar_config(mut self, config: ScrollbarConfig) -> Self {
1077 self.scrollbar_config = config;
1078 self
1079 }
1080
1081 pub fn gutter_lines(
1088 mut self,
1089 lines: Arc<Vec<Vec<crate::style::Span>>>,
1090 col_width: u16,
1091 ) -> Self {
1092 self.gutter_lines = Some(lines);
1093 self.gutter_col_width = col_width;
1094 self.gutter = None;
1095 self
1096 }
1097
1098 pub fn gutter(mut self, gutter: TextAreaGutter) -> Self {
1100 self.apply_gutter(&gutter);
1101 self.gutter = Some(gutter);
1102 self
1103 }
1104
1105 pub fn gutter_inset(mut self, inset: u16) -> Self {
1107 self.gutter_gap = inset;
1108 self
1109 }
1110
1111 fn apply_gutter(&mut self, gutter: &TextAreaGutter) {
1112 if gutter.columns.is_empty() {
1113 self.line_numbers = false;
1114 self.gutter_lines = None;
1115 self.gutter_col_width = 0;
1116 return;
1117 }
1118 if gutter.columns.len() == 1
1119 && let TextAreaGutterColumnKind::LineNumbers(mode) = &gutter.columns[0].kind
1120 {
1121 self.line_numbers = true;
1122 self.line_number_mode = *mode;
1123 self.gutter_lines = None;
1124 self.gutter_col_width = gutter.columns[0].width;
1125 return;
1126 }
1127
1128 let logical_lines = self
1129 .value
1130 .as_bytes()
1131 .iter()
1132 .filter(|&&b| b == b'\n')
1133 .count()
1134 + 1;
1135 let cursor = crate::utils::text::clamp_cursor(&self.value, self.cursor);
1136 let cursor_line = self.value[..cursor]
1137 .as_bytes()
1138 .iter()
1139 .filter(|&&b| b == b'\n')
1140 .count()
1141 + 1;
1142 let mut rows = vec![Vec::new(); logical_lines.max(1)];
1143 let mut total_width = 0u16;
1144 for (col_idx, column) in gutter.columns.iter().enumerate() {
1145 if col_idx > 0 {
1146 for row in &mut rows {
1147 row.push(Span::new(" "));
1148 }
1149 total_width = total_width.saturating_add(1);
1150 }
1151 let col_width = column_width(column, logical_lines, self.min_line_number_width);
1152 total_width = total_width.saturating_add(col_width);
1153 match &column.kind {
1154 TextAreaGutterColumnKind::LineNumbers(mode) => {
1155 for (idx, row) in rows.iter_mut().enumerate() {
1156 let line = idx + 1;
1157 let n = match mode {
1158 TextAreaLineNumberMode::Absolute => line,
1159 TextAreaLineNumberMode::Relative => {
1160 if line == cursor_line {
1161 line
1162 } else {
1163 line.abs_diff(cursor_line)
1164 }
1165 }
1166 };
1167 row.push(
1168 Span::new(format!(
1169 "{n:>width$} │",
1170 width = col_width.saturating_sub(2) as usize
1171 ))
1172 .style(self.line_number_style),
1173 );
1174 }
1175 }
1176 TextAreaGutterColumnKind::Custom(lines) => {
1177 for (idx, row) in rows.iter_mut().enumerate() {
1178 if let Some(spans) = lines.get(idx) {
1179 row.extend(spans.iter().cloned());
1180 }
1181 }
1182 }
1183 TextAreaGutterColumnKind::Signs(signs) => {
1184 let mut by_line: BTreeMap<usize, Vec<Span>> = BTreeMap::new();
1185 for sign in signs {
1186 by_line
1187 .entry(sign.line)
1188 .or_default()
1189 .extend(sign.spans.iter().cloned());
1190 }
1191 for (idx, row) in rows.iter_mut().enumerate() {
1192 if let Some(spans) = by_line.get(&idx) {
1193 row.extend(spans.iter().cloned());
1194 }
1195 }
1196 }
1197 }
1198 }
1199 self.line_numbers = false;
1200 self.gutter_lines = Some(Arc::new(rows));
1201 self.gutter_col_width = total_width;
1202 }
1203
1204 pub fn copy_excluded_bytes(mut self, ranges: Arc<Vec<(usize, usize)>>) -> Self {
1206 self.copy_excluded_bytes = Some(ranges);
1207 self
1208 }
1209
1210 pub fn clipboard_transform(mut self, transform: TextAreaClipboardTransform) -> Self {
1214 self.clipboard_transform = Some(transform);
1215 self
1216 }
1217
1218 pub fn selection_excluded_lines(mut self, lines: Arc<Vec<usize>>) -> Self {
1220 self.selection_excluded_lines = Some(lines);
1221 self
1222 }
1223
1224 pub fn h_scrollbar(mut self, h_scrollbar: bool) -> Self {
1226 self.h_scrollbar = h_scrollbar;
1227 self
1228 }
1229
1230 pub fn h_scrollbar_variant(mut self, style: ScrollbarVariant) -> Self {
1232 self.h_scrollbar_variant = style;
1233 self
1234 }
1235
1236 pub fn h_scrollbar_thumb(mut self, ch: char) -> Self {
1238 self.h_scrollbar_thumb = Some(ch);
1239 self
1240 }
1241
1242 pub fn sentinels(mut self, sentinels: Vec<TextAreaSentinel>) -> Self {
1244 self.sentinels = sentinels;
1245 self
1246 }
1247
1248 pub fn on_sentinels_change(mut self, cb: Callback<Vec<TextAreaSentinel>>) -> Self {
1250 self.on_sentinels_change = Some(cb);
1251 self
1252 }
1253
1254 pub fn on_sentinel_event(mut self, cb: Callback<Vec<SentinelEvent>>) -> Self {
1256 self.on_sentinel_event = Some(cb);
1257 self
1258 }
1259
1260 pub fn on_sentinel_click(mut self, cb: Callback<TextAreaSentinelClickEvent>) -> Self {
1262 self.on_sentinel_click = Some(cb);
1263 self
1264 }
1265
1266 pub fn decoration(mut self, decoration: TextAreaDecoration) -> Self {
1268 self.decorations.push(decoration);
1269 self
1270 }
1271
1272 pub fn decorations(
1274 mut self,
1275 decorations: impl IntoIterator<Item = TextAreaDecoration>,
1276 ) -> Self {
1277 self.decorations.extend(decorations);
1278 self
1279 }
1280
1281 pub fn virtual_text(mut self, virtual_text: TextAreaVirtualText) -> Self {
1283 self.virtual_texts.push(virtual_text);
1284 self
1285 }
1286
1287 pub fn virtual_texts(
1289 mut self,
1290 virtual_texts: impl IntoIterator<Item = TextAreaVirtualText>,
1291 ) -> Self {
1292 self.virtual_texts.extend(virtual_texts);
1293 self
1294 }
1295
1296 pub fn on_text_paste(mut self, cb: Callback<TextAreaPasteEvent>) -> Self {
1301 self.on_text_paste = Some(cb);
1302 self
1303 }
1304
1305 pub(crate) fn sentinel_info(&self) -> Option<SentinelInfo> {
1307 sentinel_info_for(
1308 self.image_mode,
1309 self.images.len(),
1310 &self.image_placeholder,
1311 &self.sentinels,
1312 )
1313 }
1314}
1315
1316fn column_width(
1317 column: &TextAreaGutterColumn,
1318 logical_lines: usize,
1319 min_line_number_width: u8,
1320) -> u16 {
1321 use unicode_width::UnicodeWidthStr;
1322 let measured = match &column.kind {
1323 TextAreaGutterColumnKind::LineNumbers(_) => logical_lines
1324 .max(1)
1325 .to_string()
1326 .len()
1327 .max(min_line_number_width as usize)
1328 .saturating_add(2) as u16,
1329 TextAreaGutterColumnKind::Custom(lines) => lines
1330 .iter()
1331 .map(|row| {
1332 row.iter()
1333 .map(|s| UnicodeWidthStr::width(s.content.as_ref()))
1334 .sum::<usize>()
1335 })
1336 .max()
1337 .unwrap_or(0) as u16,
1338 TextAreaGutterColumnKind::Signs(signs) => signs
1339 .iter()
1340 .flat_map(|s| s.spans.iter())
1341 .map(|s| UnicodeWidthStr::width(s.content.as_ref()))
1342 .max()
1343 .unwrap_or(1) as u16,
1344 };
1345 column.width.max(measured)
1346}
1347
1348impl From<TextArea> for Element {
1349 fn from(value: TextArea) -> Self {
1350 let mut min_w = value.padding.horizontal();
1351 let mut min_h = 1u16.saturating_add(value.padding.vertical());
1352 if value.border {
1353 min_w = min_w.saturating_add(2);
1354 min_h = min_h.saturating_add(2);
1355 }
1356 let layout = LayoutConstraints::default()
1357 .min_width(Length::Px(min_w))
1358 .min_height(Length::Px(min_h));
1359 Element::new(ElementKind::TextArea(Box::new(value))).with_layout(layout)
1360 }
1361}
1362
1363#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1365pub struct TextAreaEvent {
1366 pub value: Arc<str>,
1368 pub cursor: usize,
1370 pub anchor: Option<usize>,
1372}
1373
1374#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1376pub struct TextAreaPasteEvent {
1377 pub text: Arc<str>,
1379 pub cursor: usize,
1381 pub anchor: Option<usize>,
1383}
1384
1385impl TextAreaEvent {
1386 pub fn apply_to(&self, state: &mut TextEditor) {
1388 state.core.text = self.value.to_string();
1389 state.core.cursor = crate::utils::text::clamp_cursor(&state.core.text, self.cursor);
1390 state.core.anchor = self
1391 .anchor
1392 .map(|anchor| crate::utils::text::clamp_cursor(&state.core.text, anchor));
1393 }
1394}
1395
1396impl crate::layout::hash::LayoutHash for TextArea {
1397 fn layout_hash(
1398 &self,
1399 hasher: &mut impl std::hash::Hasher,
1400 _recurse: &dyn Fn(&Element) -> Option<u64>,
1401 ) -> Option<()> {
1402 use std::hash::Hash;
1403 self.width.hash(hasher);
1404 self.height.hash(hasher);
1405 self.wrap.hash(hasher);
1406 self.line_numbers.hash(hasher);
1407 self.line_number_mode.hash(hasher);
1408 self.min_line_number_width.hash(hasher);
1409 self.border.hash(hasher);
1410 self.padding.hash(hasher);
1411 self.scrollbar.hash(hasher);
1412 self.scrollbar_config.gap.hash(hasher);
1413 self.gutter_col_width.hash(hasher);
1414 self.gutter_gap.hash(hasher);
1415 self.gutter.as_ref().map(|g| g.columns.len()).hash(hasher);
1416 if let Some(peer_lines) = &self.peer_source_lines {
1417 peer_lines.len().hash(hasher);
1418 for line in peer_lines.iter() {
1419 line.as_ref().hash(hasher);
1420 }
1421 } else {
1422 0usize.hash(hasher);
1423 }
1424 #[cfg(feature = "diff-view")]
1425 if let Some(sync) = &self.split_wrap_sync {
1426 self.split_wrap_side.hash(hasher);
1427 self.split_wrap_side
1428 .and_then(|side| crate::widgets::diff_view::split_wrap_pane_widths(sync, side))
1429 .hash(hasher);
1430 crate::widgets::diff_view::split_wrap_scrollbar_cols_pair(sync).hash(hasher);
1431 crate::widgets::diff_view::split_wrap_layout_pass(sync).hash(hasher);
1432 }
1433 self.read_only.hash(hasher);
1434
1435 for s in &self.sentinels {
1436 s.label.hash(hasher);
1437 s.sentinel_id().hash(hasher);
1438 }
1439 self.images.len().hash(hasher);
1440 self.virtual_texts.hash(hasher);
1441
1442 let needs_content =
1443 matches!(self.width, Length::Auto) || matches!(self.height, Length::Auto);
1444 if needs_content {
1445 self.value.hash(hasher);
1446 }
1447 Some(())
1448 }
1449}
1450
1451pub use metrics::*;
1452pub use sentinel::*;
1453pub use snapshot::*;
1454pub use vim_config::*;
1455
1456pub(crate) use vim_config::TextAreaVimSearchFeedback;
1457
1458#[cfg(test)]
1459mod mod_tests;