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}
93
94#[allow(missing_docs)]
96#[derive(Clone, Debug, PartialEq)]
97pub struct TextAreaGutterSign {
98 pub line: usize,
99 pub spans: Vec<Span>,
100}
101
102#[allow(missing_docs)]
103impl TextAreaGutterSign {
104 pub fn new(line: usize, spans: impl Into<Vec<Span>>) -> Self {
105 Self {
106 line,
107 spans: spans.into(),
108 }
109 }
110}
111
112#[allow(missing_docs)]
114#[derive(Clone, Debug, PartialEq)]
115pub struct TextAreaGutterColumn {
116 kind: TextAreaGutterColumnKind,
117 width: u16,
118}
119
120#[derive(Clone, Debug, PartialEq)]
121enum TextAreaGutterColumnKind {
122 LineNumbers(TextAreaLineNumberMode),
123 Custom(Arc<Vec<Vec<Span>>>),
124 Signs(Vec<TextAreaGutterSign>),
125}
126
127#[allow(missing_docs)]
128impl TextAreaGutterColumn {
129 pub fn line_numbers(mode: TextAreaLineNumberMode) -> Self {
130 Self {
131 kind: TextAreaGutterColumnKind::LineNumbers(mode),
132 width: 0,
133 }
134 }
135
136 pub fn custom(lines: Arc<Vec<Vec<Span>>>, width: u16) -> Self {
137 Self {
138 kind: TextAreaGutterColumnKind::Custom(lines),
139 width,
140 }
141 }
142
143 pub fn signs(signs: impl IntoIterator<Item = TextAreaGutterSign>) -> Self {
144 let signs: Vec<_> = signs.into_iter().collect();
145 let width = signs
146 .iter()
147 .flat_map(|s| s.spans.iter())
148 .map(|s| unicode_width::UnicodeWidthStr::width(s.content.as_ref()) as u16)
149 .max()
150 .unwrap_or(1)
151 .max(1);
152 Self {
153 kind: TextAreaGutterColumnKind::Signs(signs),
154 width,
155 }
156 }
157
158 pub fn width(mut self, width: u16) -> Self {
159 self.width = width;
160 self
161 }
162}
163
164#[allow(missing_docs)]
166#[derive(Clone, Debug, Default, PartialEq)]
167pub struct TextAreaGutter {
168 columns: Vec<TextAreaGutterColumn>,
169}
170
171#[allow(missing_docs)]
172impl TextAreaGutter {
173 pub fn new() -> Self {
174 Self::default()
175 }
176 pub fn line_numbers(mut self, mode: TextAreaLineNumberMode) -> Self {
177 self.columns.push(TextAreaGutterColumn::line_numbers(mode));
178 self
179 }
180 pub fn signs(mut self, signs: impl IntoIterator<Item = TextAreaGutterSign>) -> Self {
181 self.columns.push(TextAreaGutterColumn::signs(signs));
182 self
183 }
184 pub fn column(mut self, column: TextAreaGutterColumn) -> Self {
185 self.columns.push(column);
186 self
187 }
188}
189
190#[allow(missing_docs)]
192#[derive(Clone, Debug, PartialEq)]
193pub struct TextAreaStateChangeEvent {
194 pub reason: TextAreaStateChangeReason,
195 pub value: Arc<str>,
196 pub cursor: usize,
197 pub anchor: Option<usize>,
198 pub edit: Option<TextEditEvent>,
199 pub vim_mode: Option<TextAreaVimMode>,
200}
201
202#[non_exhaustive]
203#[allow(missing_docs)]
204#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
205pub enum TextAreaStateChangeReason {
206 Edit,
207 SelectionChange,
208 CursorMove,
209 Scroll,
210 VimModeChange,
211}
212
213#[derive(Clone)]
215pub struct TextArea {
216 pub(crate) value: Arc<str>,
217 pub(crate) cursor: usize, pub(crate) anchor: Option<usize>, pub(crate) placeholder: Option<Arc<str>>,
220 pub(crate) style: Style,
221 pub(crate) hover_style: StyleSlot,
222 pub(crate) focus_style: StyleSlot,
223 pub(crate) focus_content_style: Style,
224 pub(crate) hover_border_style: Option<BorderStyle>,
225 pub(crate) caret_shape: Option<CaretShape>,
226 pub(crate) caret_color: Option<Color>,
227 pub(crate) selection_style: StyleSlot,
228 pub(crate) unfocused_selection_style: StyleSlot,
229 pub(crate) show_selection_when_unfocused: bool,
231 pub(crate) placeholder_style: Style,
232 pub(crate) focus_placeholder_style: Style,
233 pub(crate) line_numbers: bool,
234 pub(crate) line_number_mode: TextAreaLineNumberMode,
235 pub(crate) line_number_style: Style,
236 pub(crate) min_line_number_width: u8,
237 pub(crate) wrap: bool,
238 pub(crate) color_strategy: Option<Rc<dyn TextAreaColorStrategy>>,
239 pub(crate) language: Option<Arc<str>>,
240 pub(crate) theme: Option<Arc<str>>,
241 pub(crate) border: bool,
242 pub(crate) border_style: BorderStyle,
243 pub(crate) padding: Padding,
244 pub(crate) width: Length,
245 pub(crate) height: Length,
246 pub(crate) scroll_offset: Option<usize>, pub(crate) scroll_to_line: Option<usize>,
249 pub(crate) scroll_behavior: ScrollBehavior,
250 pub(crate) scroll_wheel: bool,
251 pub(crate) scroll_wheel_multiplier: Option<u16>,
252 pub(crate) on_change: Option<Callback<TextAreaEvent>>,
253 pub(crate) on_edit: Option<Callback<TextEditEvent>>,
254 pub(crate) on_editor_state_change: Option<Callback<TextAreaStateChangeEvent>>,
255 pub(crate) on_scroll: Option<Callback<ScrollEvent>>,
256 pub(crate) on_scroll_to: Option<Callback<usize>>,
257 pub(crate) on_click: Option<Callback<MouseEvent>>,
258 pub(crate) on_key: Option<KeyHandler>,
259 pub(crate) key_interceptor: Option<KeyHandler>,
260 pub(crate) clear_bindings: Option<KeyBindings>,
261 pub(crate) vim_motions: bool,
262 pub(crate) vim_keymap: Option<TextAreaVimKeymap>,
263 pub(crate) vim_config: TextAreaVimConfig,
264 pub(crate) on_vim_mode_change: Option<Callback<TextAreaVimMode>>,
265 pub(crate) on_image_paste: Option<Callback<ImageContent>>,
266 pub(crate) on_text_paste: Option<Callback<TextAreaPasteEvent>>,
267 pub(crate) images: Vec<ImageContent>,
271 pub(crate) on_images_change: Option<Callback<Vec<ImageContent>>>,
272 pub(crate) image_mode: TextAreaImageMode,
273 pub(crate) image_placeholder: Arc<str>,
274 pub(crate) image_placeholder_style: Style,
275 pub(crate) image_placeholder_focus_style: Style,
276 pub(crate) image_placeholder_hover_style: Style,
277 pub(crate) disabled: bool,
278 pub(crate) disabled_style: Style,
279 pub(crate) read_only: bool,
280 pub(crate) focusable: bool,
281 pub(crate) tab_stop: bool,
282 pub(crate) on_focus: Option<Callback<()>>,
283 pub(crate) on_blur: Option<Callback<()>>,
284 pub(crate) newline_binding: Option<TextAreaNewlineBinding>,
285 pub(crate) tab_width: u8,
286 pub(crate) insert_tab: bool,
287 pub(crate) tab_display_width: u8,
291 pub(crate) scrollbar: bool,
293 pub(crate) scrollbar_config: ScrollbarConfig,
294 pub(crate) h_scrollbar: bool,
295 pub(crate) h_scrollbar_variant: ScrollbarVariant,
296 pub(crate) h_scrollbar_thumb: Option<char>,
297 #[cfg(feature = "diff-view")]
298 pub(crate) pin_scrollbar_focus_style: bool,
299 pub(crate) gutter_lines: Option<Arc<Vec<Vec<crate::style::Span>>>>,
303 pub(crate) gutter_col_width: u16,
306 pub(crate) gutter_gap: u16,
308 pub(crate) gutter: Option<TextAreaGutter>,
309 pub(crate) peer_source_lines: Option<Arc<Vec<Arc<str>>>>,
311 #[cfg(feature = "diff-view")]
312 pub(crate) split_wrap_sync: Option<crate::widgets::diff_view::SharedSplitWrapSync>,
313 #[cfg(feature = "diff-view")]
314 pub(crate) split_wrap_side: Option<crate::widgets::diff_view::SplitPaneSide>,
315 #[cfg(feature = "diff-view")]
316 pub(crate) diff_context_separator_click:
317 Option<crate::widgets::diff_view::DiffContextSeparatorClickConfig>,
318 pub(crate) split_wrap_padding_gutter_style: Option<Style>,
320 pub(crate) split_wrap_padding_style: Option<Style>,
322 pub(crate) copy_excluded_bytes: Option<Arc<Vec<(usize, usize)>>>,
324 pub(crate) clipboard_transform: Option<TextAreaClipboardTransform>,
326 pub(crate) selection_excluded_lines: Option<Arc<Vec<usize>>>,
328 pub(crate) multi_click_select: bool,
330 pub(crate) triple_click_mode: crate::widgets::TripleClickSelectionMode,
332 pub(crate) sentinels: Vec<TextAreaSentinel>,
335 pub(crate) on_sentinels_change: Option<Callback<Vec<TextAreaSentinel>>>,
337 pub(crate) on_sentinel_event: Option<Callback<Vec<SentinelEvent>>>,
338 pub(crate) on_sentinel_click: Option<Callback<TextAreaSentinelClickEvent>>,
339 pub(crate) decorations: Vec<TextAreaDecoration>,
340 pub(crate) virtual_texts: Vec<TextAreaVirtualText>,
341}
342
343impl Default for TextArea {
344 fn default() -> Self {
345 Self {
346 value: "".into(),
347 cursor: 0,
348 anchor: None,
349 placeholder: None,
350 style: Style::default(),
351 hover_style: StyleSlot::Inherit,
352 focus_style: StyleSlot::Inherit,
353 focus_content_style: Style::default(),
354 hover_border_style: None,
355 caret_shape: None,
356 caret_color: None,
357 selection_style: StyleSlot::Inherit,
358 unfocused_selection_style: StyleSlot::Inherit,
359 show_selection_when_unfocused: true,
360 placeholder_style: Style::default(),
361 focus_placeholder_style: Style::default(),
362 line_numbers: false,
363 line_number_mode: TextAreaLineNumberMode::default(),
364 line_number_style: Style::default(),
365 min_line_number_width: 0,
366 wrap: true,
367 color_strategy: None,
368 language: None,
369 theme: None,
370 border: true,
371 border_style: BorderStyle::Plain,
372 padding: Padding::default(),
373 width: Length::Flex(1),
374 height: Length::Flex(1),
375 scroll_offset: None,
376 scroll_to_line: None,
377 scroll_behavior: ScrollBehavior::Instant,
378 scroll_wheel: true,
379 scroll_wheel_multiplier: None,
380 on_change: None,
381 on_edit: None,
382 on_editor_state_change: None,
383 on_scroll: None,
384 on_scroll_to: None,
385 on_click: None,
386 on_key: None,
387 key_interceptor: None,
388 clear_bindings: None,
389 vim_motions: false,
390 vim_keymap: None,
391 vim_config: TextAreaVimConfig::default(),
392 on_vim_mode_change: None,
393 on_image_paste: None,
394 on_text_paste: None,
395 images: Vec::new(),
396 on_images_change: None,
397 image_mode: TextAreaImageMode::default(),
398 image_placeholder: "[Image]".into(),
399 image_placeholder_style: Style::default(),
400 image_placeholder_focus_style: Style::default(),
401 image_placeholder_hover_style: Style::default(),
402 disabled: false,
403 disabled_style: Style::default(),
404 read_only: false,
405 focusable: true,
406 tab_stop: true,
407 on_focus: None,
408 on_blur: None,
409 newline_binding: None,
410 tab_width: 0,
411 insert_tab: false,
412 tab_display_width: 8,
413 scrollbar: true,
414 scrollbar_config: ScrollbarConfig::default(),
415 h_scrollbar: false,
416 h_scrollbar_variant: ScrollbarVariant::default(),
417 h_scrollbar_thumb: None,
418 #[cfg(feature = "diff-view")]
419 pin_scrollbar_focus_style: false,
420 gutter_lines: None,
421 gutter_col_width: 0,
422 gutter_gap: 0,
423 gutter: None,
424 peer_source_lines: None,
425 #[cfg(feature = "diff-view")]
426 split_wrap_sync: None,
427 #[cfg(feature = "diff-view")]
428 split_wrap_side: None,
429 #[cfg(feature = "diff-view")]
430 diff_context_separator_click: None,
431 split_wrap_padding_gutter_style: None,
432 split_wrap_padding_style: None,
433 copy_excluded_bytes: None,
434 clipboard_transform: None,
435 selection_excluded_lines: None,
436 multi_click_select: true,
437 triple_click_mode: crate::widgets::TripleClickSelectionMode::Line,
438 sentinels: Vec::new(),
439 on_sentinels_change: None,
440 on_sentinel_event: None,
441 on_sentinel_click: None,
442 decorations: Vec::new(),
443 virtual_texts: Vec::new(),
444 }
445 }
446}
447
448impl TextArea {
449 pub fn new(value: impl Into<Arc<str>>) -> Self {
451 Self {
452 value: value.into(),
453 ..Self::default()
454 }
455 }
456
457 pub fn bound(state: &TextEditor) -> Self {
459 Self::new("").bind(state)
460 }
461
462 pub fn value(mut self, value: impl Into<Arc<str>>) -> Self {
464 self.value = value.into();
465 self
466 }
467
468 pub fn placeholder(mut self, placeholder: impl Into<Arc<str>>) -> Self {
470 self.placeholder = Some(placeholder.into());
471 self
472 }
473
474 pub fn cursor(mut self, cursor: usize) -> Self {
476 self.cursor = cursor;
477 self
478 }
479
480 pub fn anchor(mut self, anchor: Option<usize>) -> Self {
483 self.anchor = anchor;
484 self
485 }
486
487 pub fn bind(mut self, state: &TextEditor) -> Self {
489 self.value = state.text().into();
490 self.cursor = state.cursor();
491 self.anchor = state.anchor();
492 self
493 }
494
495 pub fn line_numbers(mut self, show: bool) -> Self {
497 self.line_numbers = show;
498 self.gutter = None;
499 self
500 }
501
502 pub fn line_number_mode(mut self, mode: TextAreaLineNumberMode) -> Self {
508 self.line_number_mode = mode;
509 self.gutter = None;
510 self
511 }
512
513 pub fn min_line_number_width(mut self, width: u8) -> Self {
515 self.min_line_number_width = width;
516 self
517 }
518
519 pub fn wrap(mut self, wrap: bool) -> Self {
521 self.wrap = wrap;
522 self
523 }
524
525 pub fn color_strategy(mut self, strategy: impl TextAreaColorStrategy + 'static) -> Self {
527 self.color_strategy = Some(Rc::new(strategy));
528 self
529 }
530
531 pub fn language(mut self, language: impl Into<Arc<str>>) -> Self {
533 self.language = Some(language.into());
534 self
535 }
536
537 #[cfg(feature = "syntax-syntect")]
544 pub fn language_from_path(self, path: impl AsRef<std::path::Path>) -> Self {
545 if let Some(lang) = crate::widgets::language_from_path(path) {
546 self.language(lang)
547 } else {
548 self
549 }
550 }
551
552 pub fn theme(mut self, theme: impl Into<Arc<str>>) -> Self {
554 self.theme = Some(theme.into());
555 self
556 }
557
558 #[cfg(feature = "syntax-syntect")]
560 pub fn with_syntax(self, language: impl Into<Arc<str>>, theme: impl Into<Arc<str>>) -> Self {
561 self.with_syntax_strategy(SyntectStrategy::default(), language, theme)
562 }
563
564 #[cfg(feature = "syntax-syntect")]
566 pub fn with_syntax_bg(self, language: impl Into<Arc<str>>, theme: impl Into<Arc<str>>) -> Self {
567 self.with_syntax_strategy(
568 SyntectStrategy::default().use_background(true),
569 language,
570 theme,
571 )
572 }
573
574 #[cfg(feature = "syntax-syntect")]
576 pub fn with_syntax_custom_theme(
577 self,
578 language: impl Into<Arc<str>>,
579 theme_name: impl Into<Arc<str>>,
580 tm_theme_xml: impl AsRef<str>,
581 ) -> crate::Result<Self> {
582 let theme_name = theme_name.into();
583 let strategy = SyntectStrategy::default().custom_theme(theme_name.clone(), tm_theme_xml)?;
584 Ok(self.with_syntax_strategy(strategy, language, theme_name))
585 }
586
587 #[cfg(feature = "syntax-syntect")]
589 pub fn with_syntax_custom_theme_bytes(
590 self,
591 language: impl Into<Arc<str>>,
592 theme_name: impl Into<Arc<str>>,
593 bytes: impl AsRef<[u8]>,
594 ) -> crate::Result<Self> {
595 let theme_name = theme_name.into();
596 let strategy = SyntectStrategy::default().custom_theme_bytes(theme_name.clone(), bytes)?;
597 Ok(self.with_syntax_strategy(strategy, language, theme_name))
598 }
599
600 #[cfg(feature = "syntax-syntect")]
602 pub fn with_syntax_custom_theme_from_file(
603 self,
604 language: impl Into<Arc<str>>,
605 theme_name: impl Into<Arc<str>>,
606 path: impl AsRef<std::path::Path>,
607 ) -> crate::Result<Self> {
608 let theme_name = theme_name.into();
609 let strategy =
610 SyntectStrategy::default().custom_theme_from_file(theme_name.clone(), path)?;
611 Ok(self.with_syntax_strategy(strategy, language, theme_name))
612 }
613
614 #[cfg(feature = "syntax-syntect")]
616 pub fn with_syntax_strategy(
617 self,
618 strategy: SyntectStrategy,
619 language: impl Into<Arc<str>>,
620 theme: impl Into<Arc<str>>,
621 ) -> Self {
622 self.color_strategy(strategy)
623 .language(language)
624 .theme(theme)
625 }
626
627 pub fn style(mut self, style: Style) -> Self {
629 self.style = style;
630 self
631 }
632
633 pub fn hover_style(mut self, style: Style) -> Self {
635 self.hover_style = StyleSlot::Replace(style);
636 self
637 }
638
639 pub fn extend_hover_style(mut self, style: Style) -> Self {
641 self.hover_style = StyleSlot::Extend(style);
642 self
643 }
644
645 pub fn inherit_hover_style(mut self) -> Self {
647 self.hover_style = StyleSlot::Inherit;
648 self
649 }
650
651 pub fn hover_style_slot(mut self, slot: StyleSlot) -> Self {
653 self.hover_style = slot;
654 self
655 }
656
657 pub fn focus_style(mut self, style: Style) -> Self {
659 self.focus_style = StyleSlot::Replace(style);
660 self
661 }
662
663 pub fn extend_focus_style(mut self, style: Style) -> Self {
665 self.focus_style = StyleSlot::Extend(style);
666 self
667 }
668
669 pub fn inherit_focus_style(mut self) -> Self {
671 self.focus_style = StyleSlot::Inherit;
672 self
673 }
674
675 pub fn focus_style_slot(mut self, slot: StyleSlot) -> Self {
677 self.focus_style = slot;
678 self
679 }
680
681 pub fn focus_content_style(mut self, style: Style) -> Self {
683 self.focus_content_style = style;
684 self
685 }
686
687 pub fn hover_border_style(mut self, border_style: BorderStyle) -> Self {
689 self.hover_border_style = Some(border_style);
690 self
691 }
692
693 pub fn caret_shape(mut self, shape: CaretShape) -> Self {
695 self.caret_shape = Some(shape);
696 self
697 }
698
699 pub fn caret_color(mut self, color: Color) -> Self {
701 self.caret_color = Some(color);
702 self
703 }
704
705 pub fn selection_style(mut self, style: Style) -> Self {
707 self.selection_style = StyleSlot::Replace(style);
708 self
709 }
710
711 pub fn extend_selection_style(mut self, style: Style) -> Self {
713 self.selection_style = StyleSlot::Extend(style);
714 self
715 }
716
717 pub fn inherit_selection_style(mut self) -> Self {
719 self.selection_style = StyleSlot::Inherit;
720 self
721 }
722
723 pub fn selection_style_slot(mut self, slot: StyleSlot) -> Self {
725 self.selection_style = slot;
726 self
727 }
728
729 pub fn show_selection_when_unfocused(mut self, show: bool) -> Self {
735 self.show_selection_when_unfocused = show;
736 self
737 }
738
739 pub fn unfocused_selection_style(mut self, style: Style) -> Self {
741 self.unfocused_selection_style = StyleSlot::Replace(style);
742 self
743 }
744
745 pub fn inherit_unfocused_selection_style(mut self) -> Self {
747 self.unfocused_selection_style = StyleSlot::Inherit;
748 self
749 }
750
751 pub fn unfocused_selection_style_slot(mut self, slot: StyleSlot) -> Self {
753 self.unfocused_selection_style = slot;
754 self
755 }
756
757 pub fn placeholder_style(mut self, style: Style) -> Self {
759 self.placeholder_style = style;
760 self
761 }
762
763 pub fn focus_placeholder_style(mut self, style: Style) -> Self {
765 self.focus_placeholder_style = style;
766 self
767 }
768
769 pub fn line_number_style(mut self, style: Style) -> Self {
771 self.line_number_style = style;
772 self
773 }
774
775 pub fn border(mut self, border: bool) -> Self {
777 self.border = border;
778 self
779 }
780
781 pub fn border_style(mut self, style: BorderStyle) -> Self {
783 self.border_style = style;
784 self
785 }
786
787 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
789 self.padding = padding.into();
790 self
791 }
792
793 pub fn width(mut self, width: Length) -> Self {
795 self.width = width;
796 self
797 }
798
799 pub fn height(mut self, height: Length) -> Self {
801 self.height = height;
802 self
803 }
804
805 pub fn on_change(mut self, cb: Callback<TextAreaEvent>) -> Self {
807 self.on_change = Some(cb);
808 self
809 }
810
811 pub fn on_edit(mut self, cb: Callback<TextEditEvent>) -> Self {
813 self.on_edit = Some(cb);
814 self
815 }
816
817 pub fn on_editor_state_change(mut self, cb: Callback<TextAreaStateChangeEvent>) -> Self {
819 self.on_editor_state_change = Some(cb);
820 self
821 }
822
823 pub fn on_click(mut self, cb: Callback<MouseEvent>) -> Self {
825 self.on_click = Some(cb);
826 self
827 }
828
829 pub fn on_key(mut self, handler: KeyHandler) -> Self {
831 self.on_key = Some(handler);
832 self
833 }
834
835 pub fn key_interceptor(mut self, handler: KeyHandler) -> Self {
841 self.key_interceptor = Some(handler);
842 self
843 }
844
845 pub fn clear_bindings(mut self, bindings: KeyBindings) -> Self {
849 self.clear_bindings = Some(bindings);
850 self
851 }
852
853 pub fn vim_motions(mut self, enabled: bool) -> Self {
857 self.vim_motions = enabled;
858 self
859 }
860
861 pub fn vim_keymap(mut self, keymap: TextAreaVimKeymap) -> Self {
867 self.vim_keymap = Some(keymap);
868 self
869 }
870
871 pub fn vim_config(mut self, config: TextAreaVimConfig) -> Self {
874 self.vim_config = config;
875 self
876 }
877
878 pub fn vim_current_line_highlight(mut self, mode: TextAreaVimCurrentLineHighlight) -> Self {
884 self.vim_config.current_line_highlight = mode;
885 self
886 }
887
888 pub fn highlight_vim_current_line(mut self, enabled: bool) -> Self {
890 self.vim_config = self.vim_config.highlight_current_line(enabled);
891 self
892 }
893
894 pub fn on_vim_mode_change(mut self, cb: Callback<TextAreaVimMode>) -> Self {
896 self.on_vim_mode_change = Some(cb);
897 self
898 }
899
900 pub fn on_image_paste(mut self, cb: Callback<ImageContent>) -> Self {
902 self.on_image_paste = Some(cb);
903 self
904 }
905
906 pub fn images(mut self, images: Vec<ImageContent>) -> Self {
908 self.images = images;
909 self
910 }
911
912 pub fn on_images_change(mut self, cb: Callback<Vec<ImageContent>>) -> Self {
914 self.on_images_change = Some(cb);
915 self
916 }
917
918 pub fn image_mode(mut self, mode: TextAreaImageMode) -> Self {
920 self.image_mode = mode;
921 self
922 }
923
924 pub fn image_placeholder(mut self, label: impl Into<Arc<str>>) -> Self {
926 self.image_placeholder = label.into();
927 self
928 }
929
930 pub fn image_placeholder_style(mut self, style: Style) -> Self {
932 self.image_placeholder_style = style;
933 self
934 }
935
936 pub fn image_placeholder_focus_style(mut self, style: Style) -> Self {
938 self.image_placeholder_focus_style = style;
939 self
940 }
941
942 pub fn image_placeholder_hover_style(mut self, style: Style) -> Self {
944 self.image_placeholder_hover_style = style;
945 self
946 }
947
948 pub fn disabled(mut self, disabled: bool) -> Self {
950 self.disabled = disabled;
951 self
952 }
953
954 pub fn disabled_style(mut self, style: Style) -> Self {
956 self.disabled_style = style;
957 self
958 }
959
960 pub fn read_only(mut self, read_only: bool) -> Self {
962 self.read_only = read_only;
963 self
964 }
965
966 pub fn multi_click_select(mut self, enabled: bool) -> Self {
971 self.multi_click_select = enabled;
972 self
973 }
974
975 pub fn triple_click_mode(mut self, mode: crate::widgets::TripleClickSelectionMode) -> Self {
977 self.triple_click_mode = mode;
978 self
979 }
980
981 pub fn focusable(mut self, focusable: bool) -> Self {
983 self.focusable = focusable;
984 self
985 }
986
987 pub fn on_focus(mut self, cb: Callback<()>) -> Self {
989 self.on_focus = Some(cb);
990 self
991 }
992
993 pub fn on_blur(mut self, cb: Callback<()>) -> Self {
995 self.on_blur = Some(cb);
996 self
997 }
998
999 pub fn newline_binding(mut self, binding: TextAreaNewlineBinding) -> Self {
1001 self.newline_binding = Some(binding);
1002 self
1003 }
1004
1005 pub fn tab_width(mut self, width: u8) -> Self {
1008 self.tab_width = width;
1009 self
1010 }
1011
1012 pub fn insert_tab(mut self, insert_tab: bool) -> Self {
1014 self.insert_tab = insert_tab;
1015 self
1016 }
1017
1018 pub fn tab_stop(mut self, tab_stop: bool) -> Self {
1020 self.tab_stop = tab_stop;
1021 self
1022 }
1023
1024 pub fn tab_display_width(mut self, width: u8) -> Self {
1026 self.tab_display_width = width;
1027 self
1028 }
1029
1030 pub fn scroll_offset(mut self, offset: usize) -> Self {
1032 self.scroll_offset = Some(offset);
1033 self
1034 }
1035
1036 pub fn scroll_to_line(mut self, line: usize) -> Self {
1042 self.scroll_to_line = Some(line);
1043 self
1044 }
1045
1046 pub fn scroll_behavior(mut self, behavior: ScrollBehavior) -> Self {
1051 self.scroll_behavior = behavior;
1052 self
1053 }
1054
1055 pub fn scroll_transition(mut self, transition: TransitionConfig) -> Self {
1057 self.scroll_behavior = ScrollBehavior::smooth(transition);
1058 self
1059 }
1060
1061 pub fn scroll_wheel(mut self, enabled: bool) -> Self {
1063 self.scroll_wheel = enabled;
1064 self
1065 }
1066
1067 pub fn scroll_wheel_multiplier(mut self, multiplier: u16) -> Self {
1069 self.scroll_wheel_multiplier = Some(multiplier.max(1));
1070 self
1071 }
1072
1073 pub fn on_scroll(mut self, cb: Callback<ScrollEvent>) -> Self {
1075 self.on_scroll = Some(cb);
1076 self
1077 }
1078
1079 pub fn on_scroll_to(mut self, cb: Callback<usize>) -> Self {
1081 self.on_scroll_to = Some(cb);
1082 self
1083 }
1084
1085 pub fn scrollbar(mut self, scrollbar: bool) -> Self {
1087 self.scrollbar = scrollbar;
1088 self
1089 }
1090
1091 pub fn scrollbar_config(mut self, config: ScrollbarConfig) -> Self {
1093 self.scrollbar_config = config;
1094 self
1095 }
1096
1097 pub fn gutter_lines(
1104 mut self,
1105 lines: Arc<Vec<Vec<crate::style::Span>>>,
1106 col_width: u16,
1107 ) -> Self {
1108 self.gutter_lines = Some(lines);
1109 self.gutter_col_width = col_width;
1110 self.gutter = None;
1111 self
1112 }
1113
1114 pub fn gutter(mut self, gutter: TextAreaGutter) -> Self {
1116 self.apply_gutter(&gutter);
1117 self.gutter = Some(gutter);
1118 self
1119 }
1120
1121 pub fn gutter_inset(mut self, inset: u16) -> Self {
1123 self.gutter_gap = inset;
1124 self
1125 }
1126
1127 fn apply_gutter(&mut self, gutter: &TextAreaGutter) {
1128 if gutter.columns.is_empty() {
1129 self.line_numbers = false;
1130 self.gutter_lines = None;
1131 self.gutter_col_width = 0;
1132 return;
1133 }
1134 if gutter.columns.len() == 1
1135 && let TextAreaGutterColumnKind::LineNumbers(mode) = &gutter.columns[0].kind
1136 {
1137 self.line_numbers = true;
1138 self.line_number_mode = *mode;
1139 self.gutter_lines = None;
1140 self.gutter_col_width = gutter.columns[0].width;
1141 return;
1142 }
1143
1144 let logical_lines = self
1145 .value
1146 .as_bytes()
1147 .iter()
1148 .filter(|&&b| b == b'\n')
1149 .count()
1150 + 1;
1151 let cursor = crate::utils::text::clamp_cursor(&self.value, self.cursor);
1152 let cursor_line = self.value[..cursor]
1153 .as_bytes()
1154 .iter()
1155 .filter(|&&b| b == b'\n')
1156 .count()
1157 + 1;
1158 let mut rows = vec![Vec::new(); logical_lines.max(1)];
1159 let mut total_width = 0u16;
1160 for (col_idx, column) in gutter.columns.iter().enumerate() {
1161 if col_idx > 0 {
1162 for row in &mut rows {
1163 row.push(Span::new(" "));
1164 }
1165 total_width = total_width.saturating_add(1);
1166 }
1167 let col_width = column_width(column, logical_lines, self.min_line_number_width);
1168 total_width = total_width.saturating_add(col_width);
1169 match &column.kind {
1170 TextAreaGutterColumnKind::LineNumbers(mode) => {
1171 for (idx, row) in rows.iter_mut().enumerate() {
1172 let line = idx + 1;
1173 let n = match mode {
1174 TextAreaLineNumberMode::Absolute => line,
1175 TextAreaLineNumberMode::Relative => {
1176 if line == cursor_line {
1177 line
1178 } else {
1179 line.abs_diff(cursor_line)
1180 }
1181 }
1182 };
1183 row.push(
1184 Span::new(format!(
1185 "{n:>width$} │",
1186 width = col_width.saturating_sub(2) as usize
1187 ))
1188 .style(self.line_number_style),
1189 );
1190 }
1191 }
1192 TextAreaGutterColumnKind::Custom(lines) => {
1193 for (idx, row) in rows.iter_mut().enumerate() {
1194 if let Some(spans) = lines.get(idx) {
1195 row.extend(spans.iter().cloned());
1196 }
1197 }
1198 }
1199 TextAreaGutterColumnKind::Signs(signs) => {
1200 let mut by_line: BTreeMap<usize, Vec<Span>> = BTreeMap::new();
1201 for sign in signs {
1202 by_line
1203 .entry(sign.line)
1204 .or_default()
1205 .extend(sign.spans.iter().cloned());
1206 }
1207 for (idx, row) in rows.iter_mut().enumerate() {
1208 if let Some(spans) = by_line.get(&idx) {
1209 row.extend(spans.iter().cloned());
1210 }
1211 }
1212 }
1213 }
1214 }
1215 self.line_numbers = false;
1216 self.gutter_lines = Some(Arc::new(rows));
1217 self.gutter_col_width = total_width;
1218 }
1219
1220 pub fn copy_excluded_bytes(mut self, ranges: Arc<Vec<(usize, usize)>>) -> Self {
1222 self.copy_excluded_bytes = Some(ranges);
1223 self
1224 }
1225
1226 pub fn clipboard_transform(mut self, transform: TextAreaClipboardTransform) -> Self {
1230 self.clipboard_transform = Some(transform);
1231 self
1232 }
1233
1234 pub fn selection_excluded_lines(mut self, lines: Arc<Vec<usize>>) -> Self {
1236 self.selection_excluded_lines = Some(lines);
1237 self
1238 }
1239
1240 pub fn h_scrollbar(mut self, h_scrollbar: bool) -> Self {
1242 self.h_scrollbar = h_scrollbar;
1243 self
1244 }
1245
1246 pub fn h_scrollbar_variant(mut self, style: ScrollbarVariant) -> Self {
1248 self.h_scrollbar_variant = style;
1249 self
1250 }
1251
1252 pub fn h_scrollbar_thumb(mut self, ch: char) -> Self {
1254 self.h_scrollbar_thumb = Some(ch);
1255 self
1256 }
1257
1258 pub fn sentinels(mut self, sentinels: Vec<TextAreaSentinel>) -> Self {
1260 self.sentinels = sentinels;
1261 self
1262 }
1263
1264 pub fn on_sentinels_change(mut self, cb: Callback<Vec<TextAreaSentinel>>) -> Self {
1266 self.on_sentinels_change = Some(cb);
1267 self
1268 }
1269
1270 pub fn on_sentinel_event(mut self, cb: Callback<Vec<SentinelEvent>>) -> Self {
1272 self.on_sentinel_event = Some(cb);
1273 self
1274 }
1275
1276 pub fn on_sentinel_click(mut self, cb: Callback<TextAreaSentinelClickEvent>) -> Self {
1278 self.on_sentinel_click = Some(cb);
1279 self
1280 }
1281
1282 pub fn decoration(mut self, decoration: TextAreaDecoration) -> Self {
1284 self.decorations.push(decoration);
1285 self
1286 }
1287
1288 pub fn decorations(
1290 mut self,
1291 decorations: impl IntoIterator<Item = TextAreaDecoration>,
1292 ) -> Self {
1293 self.decorations.extend(decorations);
1294 self
1295 }
1296
1297 pub fn virtual_text(mut self, virtual_text: TextAreaVirtualText) -> Self {
1299 self.virtual_texts.push(virtual_text);
1300 self
1301 }
1302
1303 pub fn virtual_texts(
1305 mut self,
1306 virtual_texts: impl IntoIterator<Item = TextAreaVirtualText>,
1307 ) -> Self {
1308 self.virtual_texts.extend(virtual_texts);
1309 self
1310 }
1311
1312 pub fn on_text_paste(mut self, cb: Callback<TextAreaPasteEvent>) -> Self {
1317 self.on_text_paste = Some(cb);
1318 self
1319 }
1320
1321 pub(crate) fn sentinel_info(&self) -> Option<SentinelInfo> {
1323 sentinel_info_for(
1324 self.image_mode,
1325 self.images.len(),
1326 &self.image_placeholder,
1327 &self.sentinels,
1328 )
1329 }
1330}
1331
1332fn column_width(
1333 column: &TextAreaGutterColumn,
1334 logical_lines: usize,
1335 min_line_number_width: u8,
1336) -> u16 {
1337 use unicode_width::UnicodeWidthStr;
1338 let measured = match &column.kind {
1339 TextAreaGutterColumnKind::LineNumbers(_) => logical_lines
1340 .max(1)
1341 .to_string()
1342 .len()
1343 .max(min_line_number_width as usize)
1344 .saturating_add(2) as u16,
1345 TextAreaGutterColumnKind::Custom(lines) => lines
1346 .iter()
1347 .map(|row| {
1348 row.iter()
1349 .map(|s| UnicodeWidthStr::width(s.content.as_ref()))
1350 .sum::<usize>()
1351 })
1352 .max()
1353 .unwrap_or(0) as u16,
1354 TextAreaGutterColumnKind::Signs(signs) => signs
1355 .iter()
1356 .flat_map(|s| s.spans.iter())
1357 .map(|s| UnicodeWidthStr::width(s.content.as_ref()))
1358 .max()
1359 .unwrap_or(1) as u16,
1360 };
1361 column.width.max(measured)
1362}
1363
1364impl From<TextArea> for Element {
1365 fn from(value: TextArea) -> Self {
1366 let mut min_w = value.padding.horizontal();
1367 let mut min_h = 1u16.saturating_add(value.padding.vertical());
1368 if value.border {
1369 min_w = min_w.saturating_add(2);
1370 min_h = min_h.saturating_add(2);
1371 }
1372 let layout = LayoutConstraints::default()
1373 .min_width(Length::Px(min_w))
1374 .min_height(Length::Px(min_h));
1375 Element::new(ElementKind::TextArea(Box::new(value))).with_layout(layout)
1376 }
1377}
1378
1379#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1381pub struct TextAreaEvent {
1382 pub value: Arc<str>,
1384 pub cursor: usize,
1386 pub anchor: Option<usize>,
1388}
1389
1390#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1392pub struct TextAreaPasteEvent {
1393 pub text: Arc<str>,
1395 pub cursor: usize,
1397 pub anchor: Option<usize>,
1399}
1400
1401impl TextAreaEvent {
1402 pub fn apply_to(&self, state: &mut TextEditor) {
1404 state.core.text = self.value.to_string();
1405 state.core.cursor = crate::utils::text::clamp_cursor(&state.core.text, self.cursor);
1406 state.core.anchor = self
1407 .anchor
1408 .map(|anchor| crate::utils::text::clamp_cursor(&state.core.text, anchor));
1409 }
1410}
1411
1412impl crate::layout::hash::LayoutHash for TextArea {
1413 fn layout_hash(
1414 &self,
1415 hasher: &mut impl std::hash::Hasher,
1416 _recurse: &dyn Fn(&Element) -> Option<u64>,
1417 ) -> Option<()> {
1418 use std::hash::Hash;
1419 self.width.hash(hasher);
1420 self.height.hash(hasher);
1421 self.wrap.hash(hasher);
1422 self.line_numbers.hash(hasher);
1423 self.line_number_mode.hash(hasher);
1424 self.min_line_number_width.hash(hasher);
1425 self.border.hash(hasher);
1426 self.padding.hash(hasher);
1427 self.scrollbar.hash(hasher);
1428 self.scrollbar_config.gap.hash(hasher);
1429 self.gutter_col_width.hash(hasher);
1430 self.gutter_gap.hash(hasher);
1431 self.gutter.as_ref().map(|g| g.columns.len()).hash(hasher);
1432 if let Some(peer_lines) = &self.peer_source_lines {
1433 peer_lines.len().hash(hasher);
1434 for line in peer_lines.iter() {
1435 line.as_ref().hash(hasher);
1436 }
1437 } else {
1438 0usize.hash(hasher);
1439 }
1440 #[cfg(feature = "diff-view")]
1441 if let Some(sync) = &self.split_wrap_sync {
1442 self.split_wrap_side.hash(hasher);
1443 self.split_wrap_side
1444 .and_then(|side| crate::widgets::diff_view::split_wrap_pane_widths(sync, side))
1445 .hash(hasher);
1446 crate::widgets::diff_view::split_wrap_scrollbar_cols_pair(sync).hash(hasher);
1447 crate::widgets::diff_view::split_wrap_layout_pass(sync).hash(hasher);
1448 }
1449 self.read_only.hash(hasher);
1450 if self.wrap && !self.read_only {
1451 self.cursor.hash(hasher);
1452 }
1453
1454 for s in &self.sentinels {
1455 s.label.hash(hasher);
1456 s.sentinel_id().hash(hasher);
1457 }
1458 self.images.len().hash(hasher);
1459 self.virtual_texts.hash(hasher);
1460
1461 let needs_content =
1462 matches!(self.width, Length::Auto) || matches!(self.height, Length::Auto);
1463 if needs_content {
1464 self.value.hash(hasher);
1465 }
1466 Some(())
1467 }
1468}
1469
1470pub use metrics::*;
1471pub use sentinel::*;
1472pub use snapshot::*;
1473pub use vim_config::*;
1474
1475pub(crate) use vim_config::TextAreaVimSearchFeedback;
1476
1477#[cfg(test)]
1478mod mod_tests;