1use std::{borrow::Cow, fmt::Debug, ops::Range, rc::Rc};
2
3use crate::{
4 peniko::Color,
5 peniko::color::palette,
6 reactive::{RwSignal, Scope},
7 text::{Attrs, AttrsList, FamilyOwned, Stretch, Weight},
8 views::EditorCustomStyle,
9};
10use floem_editor_core::{
11 buffer::rope_text::{RopeText, RopeTextVal},
12 command::EditCommand,
13 cursor::Cursor,
14 editor::EditType,
15 indent::IndentStyle,
16 mode::MotionMode,
17 register::{Clipboard, Register},
18 selection::Selection,
19 word::WordCursor,
20};
21use floem_reactive::SignalGet;
22use lapce_xi_rope::Rope;
23#[cfg(feature = "serde")]
24use serde::{Deserialize, Serialize};
25use ui_events::keyboard::Modifiers;
26
27use super::{
28 Editor, EditorStyle,
29 actions::CommonAction,
30 command::{Command, CommandExecuted},
31 gutter::GutterClass,
32 id::EditorId,
33 layout::TextLayoutLine,
34 normal_compute_screen_lines,
35 phantom_text::{PhantomText, PhantomTextKind, PhantomTextLine},
36 view::{ScreenLines, ScreenLinesBase},
37};
38
39pub struct SystemClipboard;
41
42impl Default for SystemClipboard {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48impl SystemClipboard {
49 pub fn new() -> Self {
50 Self
51 }
52
53 #[cfg(windows)]
54 pub fn get_file_list() -> Option<Vec<std::path::PathBuf>> {
55 crate::Clipboard::get_file_list().ok()
56 }
57}
58
59impl Clipboard for SystemClipboard {
60 fn get_string(&mut self) -> Option<String> {
61 crate::Clipboard::get_contents().ok()
62 }
63
64 fn put_string(&mut self, s: impl AsRef<str>) {
65 let _ = crate::Clipboard::set_contents(s.as_ref().to_string());
66 }
67}
68
69#[derive(Clone)]
70pub struct Preedit {
71 pub text: String,
72 pub cursor: Option<(usize, usize)>,
73 pub offset: usize,
74}
75
76#[derive(Debug, Clone)]
80pub struct PreeditData {
81 pub preedit: RwSignal<Option<Preedit>>,
82}
83impl PreeditData {
84 pub fn new(cx: Scope) -> PreeditData {
85 PreeditData {
86 preedit: cx.create_rw_signal(None),
87 }
88 }
89}
90
91pub trait Document: DocumentPhantom + ::std::any::Any {
93 fn text(&self) -> Rope;
98
99 fn rope_text(&self) -> RopeTextVal {
100 RopeTextVal::new(self.text())
101 }
102
103 fn cache_rev(&self) -> RwSignal<u64>;
104
105 fn find_unmatched(&self, offset: usize, previous: bool, ch: char) -> usize {
110 let text = self.text();
111 let mut cursor = WordCursor::new(&text, offset);
112 let new_offset = if previous {
113 cursor.previous_unmatched(ch)
114 } else {
115 cursor.next_unmatched(ch)
116 };
117
118 new_offset.unwrap_or(offset)
119 }
120
121 fn find_matching_pair(&self, offset: usize) -> usize {
125 WordCursor::new(&self.text(), offset)
126 .match_pairs()
127 .unwrap_or(offset)
128 }
129
130 fn preedit(&self) -> PreeditData;
131
132 fn preedit_phantom(&self, under_line: Option<Color>, line: usize) -> Option<PhantomText> {
135 let preedit = self.preedit().preedit.get_untracked()?;
136
137 let rope_text = self.rope_text();
138
139 let (ime_line, col) = rope_text.offset_to_line_col(preedit.offset);
140
141 if line != ime_line {
142 return None;
143 }
144
145 Some(PhantomText {
146 kind: PhantomTextKind::Ime,
147 text: preedit.text,
148 affinity: None,
149 col,
150 font_size: None,
151 fg: None,
152 bg: None,
153 under_line,
154 })
155 }
156
157 fn compute_screen_lines(
163 &self,
164 editor: &Editor,
165 base: RwSignal<ScreenLinesBase>,
166 ) -> ScreenLines {
167 normal_compute_screen_lines(editor, base)
168 }
169
170 fn run_command(
175 &self,
176 ed: &Editor,
177 cmd: &Command,
178 count: Option<usize>,
179 modifiers: Modifiers,
180 ) -> CommandExecuted;
181
182 fn receive_char(&self, ed: &Editor, c: &str);
183
184 fn edit_single(&self, selection: Selection, content: &str, edit_type: EditType) {
187 let mut iter = std::iter::once((selection, content));
188 self.edit(&mut iter, edit_type);
189 }
190
191 fn edit(&self, iter: &mut dyn Iterator<Item = (Selection, &str)>, edit_type: EditType);
209}
210
211pub trait DocumentPhantom {
212 fn phantom_text(&self, edid: EditorId, styling: &EditorStyle, line: usize) -> PhantomTextLine;
213
214 fn before_phantom_col(
217 &self,
218 edid: EditorId,
219 styling: &EditorStyle,
220 line: usize,
221 col: usize,
222 ) -> usize {
223 let phantom = self.phantom_text(edid, styling, line);
224 phantom.before_col(col)
225 }
226
227 fn has_multiline_phantom(&self, _edid: EditorId, _styling: &EditorStyle) -> bool {
228 true
229 }
230}
231
232#[derive(Debug, Default, Clone, Copy, PartialEq)]
233pub enum WrapMethod {
234 None,
235 #[default]
236 EditorWidth,
237 WrapColumn {
238 col: usize,
239 },
240 WrapWidth {
241 width: f32,
242 },
243}
244impl std::fmt::Display for WrapMethod {
245 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
246 match self {
247 WrapMethod::None => f.write_str("None"),
248 WrapMethod::EditorWidth => f.write_str("Editor Width"),
249 WrapMethod::WrapColumn { col } => f.write_fmt(format_args!("Wrap at Column {col}")),
250 WrapMethod::WrapWidth { width } => f.write_fmt(format_args!("Wrap Width {width}")),
251 }
252 }
253}
254impl WrapMethod {
255 pub fn is_none(&self) -> bool {
256 matches!(self, WrapMethod::None)
257 }
258
259 pub fn is_constant(&self) -> bool {
260 matches!(
261 self,
262 WrapMethod::None | WrapMethod::WrapColumn { .. } | WrapMethod::WrapWidth { .. }
263 )
264 }
265}
266
267#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
268#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
269#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
270pub enum RenderWhitespace {
271 #[default]
272 None,
273 All,
274 Boundary,
275 Trailing,
276}
277impl std::fmt::Display for RenderWhitespace {
278 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279 f.write_fmt(format_args!("{self:?}"))
280 }
281}
282
283pub trait Styling {
298 fn id(&self) -> u64;
302
303 fn font_size(&self, _edid: EditorId, _line: usize) -> usize {
304 16
305 }
306
307 fn line_height(&self, edid: EditorId, line: usize) -> f32 {
308 let font_size = self.font_size(edid, line) as f32;
309 (1.5 * font_size).round().max(font_size)
310 }
311
312 fn font_family(&self, _edid: EditorId, _line: usize) -> Cow<'_, [FamilyOwned]> {
313 Cow::Borrowed(&[FamilyOwned::SansSerif])
314 }
315
316 fn weight(&self, _edid: EditorId, _line: usize) -> Weight {
317 Weight::NORMAL
318 }
319
320 fn italic_style(&self, _edid: EditorId, _line: usize) -> crate::text::Style {
322 crate::text::Style::Normal
323 }
324
325 fn stretch(&self, _edid: EditorId, _line: usize) -> Stretch {
326 Stretch::Normal
327 }
328
329 fn indent_line(&self, _edid: EditorId, line: usize, _line_content: &str) -> usize {
332 line
333 }
334
335 fn tab_width(&self, _edid: EditorId, _line: usize) -> usize {
336 4
337 }
338
339 fn atomic_soft_tabs(&self, _edid: EditorId, _line: usize) -> bool {
341 false
342 }
343
344 fn apply_attr_styles(
349 &self,
350 _edid: EditorId,
351 _style: &EditorStyle,
352 _line: usize,
353 _default: Attrs,
354 _attrs: &mut AttrsList,
355 ) {
356 }
357
358 fn apply_layout_styles(
359 &self,
360 _edid: EditorId,
361 _style: &EditorStyle,
362 _line: usize,
363 _layout_line: &mut TextLayoutLine,
364 ) {
365 }
366
367 fn paint_caret(&self, _edid: EditorId, _line: usize) -> bool {
372 true
373 }
374}
375
376pub fn default_light_theme(mut style: EditorCustomStyle) -> EditorCustomStyle {
377 let fg = Color::from_rgb8(0x38, 0x3A, 0x42);
378 let bg = Color::from_rgb8(0xFA, 0xFA, 0xFA);
379 let blue = Color::from_rgb8(0x40, 0x78, 0xF2);
380 let grey = Color::from_rgb8(0xE5, 0xE5, 0xE6);
381 let _scroll_bar = Color::from_rgba8(0xB4, 0xB4, 0xB4, 0xBB);
382 let dim = Color::from_rgb8(0xA0, 0xA1, 0xA7);
383 let cursor = Color::from_rgb8(0x52, 0x6F, 0xFF);
384 let current_line = Color::from_rgb8(0xF2, 0xF2, 0xF2);
385 let _dropdown_shadow = Color::from_rgb8(0xB4, 0xB4, 0xB4);
386 let _link = blue;
387 let _sticky_header_background = bg;
388
389 style.0 = style
390 .0
391 .color(fg)
392 .background(bg)
393 .class(GutterClass, |s| s.background(bg));
394
395 style
396 .gutter_dim_color(dim)
397 .cursor_color(cursor)
398 .selection_color(grey)
399 .current_line_color(current_line)
400 .visible_whitespace(grey)
401 .preedit_underline_color(fg)
402 .indent_guide_color(grey)
403 .gutter_current_color(current_line)
404}
405
406pub fn default_dark_color(mut style: EditorCustomStyle) -> EditorCustomStyle {
407 let fg = Color::from_rgb8(0xAB, 0xB2, 0xBF);
408 let bg = Color::from_rgb8(0x28, 0x2C, 0x34);
409 let blue = Color::from_rgb8(0x61, 0xAF, 0xEF);
410 let grey = Color::from_rgb8(0x3E, 0x44, 0x51);
411 let _scroll_bar = Color::from_rgba8(0x3E, 0x44, 0x51, 0xBB);
412 let dim = Color::from_rgb8(0x5C, 0x63, 0x70);
413 let cursor = Color::from_rgb8(0x52, 0x8B, 0xFF);
414 let current_line = Color::from_rgb8(0x2C, 0x31, 0x3c);
415 let _dropdown_shadow = palette::css::BLACK;
416 let _link = blue;
417 let _sticky_header_background = bg;
418
419 style.0 = style
420 .0
421 .color(fg)
422 .background(bg)
423 .class(GutterClass, |s| s.background(bg));
424
425 style
426 .gutter_dim_color(dim)
427 .cursor_color(cursor)
428 .selection_color(grey)
429 .current_line_color(current_line)
430 .visible_whitespace(grey)
431 .preedit_underline_color(fg)
432 .indent_guide_color(grey)
433 .gutter_current_color(current_line)
434}
435
436pub type DocumentRef = Rc<dyn Document>;
437
438pub struct ExtCmdDocument<D, F> {
440 pub doc: D,
441 pub handler: F,
446}
447impl<D: Document, F: Fn(&Editor, &Command, Option<usize>, Modifiers) -> CommandExecuted + 'static>
448 ExtCmdDocument<D, F>
449{
450 pub fn new(doc: D, handler: F) -> ExtCmdDocument<D, F> {
451 ExtCmdDocument { doc, handler }
452 }
453}
454impl<D, F> Document for ExtCmdDocument<D, F>
457where
458 D: Document,
459 F: Fn(&Editor, &Command, Option<usize>, Modifiers) -> CommandExecuted + 'static,
460{
461 fn text(&self) -> Rope {
462 self.doc.text()
463 }
464
465 fn rope_text(&self) -> RopeTextVal {
466 self.doc.rope_text()
467 }
468
469 fn cache_rev(&self) -> RwSignal<u64> {
470 self.doc.cache_rev()
471 }
472
473 fn find_unmatched(&self, offset: usize, previous: bool, ch: char) -> usize {
474 self.doc.find_unmatched(offset, previous, ch)
475 }
476
477 fn find_matching_pair(&self, offset: usize) -> usize {
478 self.doc.find_matching_pair(offset)
479 }
480
481 fn preedit(&self) -> PreeditData {
482 self.doc.preedit()
483 }
484
485 fn preedit_phantom(&self, under_line: Option<Color>, line: usize) -> Option<PhantomText> {
486 self.doc.preedit_phantom(under_line, line)
487 }
488
489 fn compute_screen_lines(
490 &self,
491 editor: &Editor,
492 base: RwSignal<ScreenLinesBase>,
493 ) -> ScreenLines {
494 self.doc.compute_screen_lines(editor, base)
495 }
496
497 fn run_command(
498 &self,
499 ed: &Editor,
500 cmd: &Command,
501 count: Option<usize>,
502 modifiers: Modifiers,
503 ) -> CommandExecuted {
504 if (self.handler)(ed, cmd, count, modifiers) == CommandExecuted::Yes {
505 return CommandExecuted::Yes;
506 }
507
508 self.doc.run_command(ed, cmd, count, modifiers)
509 }
510
511 fn receive_char(&self, ed: &Editor, c: &str) {
512 self.doc.receive_char(ed, c)
513 }
514
515 fn edit_single(&self, selection: Selection, content: &str, edit_type: EditType) {
516 self.doc.edit_single(selection, content, edit_type)
517 }
518
519 fn edit(&self, iter: &mut dyn Iterator<Item = (Selection, &str)>, edit_type: EditType) {
520 self.doc.edit(iter, edit_type)
521 }
522}
523impl<D, F> DocumentPhantom for ExtCmdDocument<D, F>
524where
525 D: Document,
526 F: Fn(&Editor, &Command, Option<usize>, Modifiers) -> CommandExecuted,
527{
528 fn phantom_text(&self, edid: EditorId, styling: &EditorStyle, line: usize) -> PhantomTextLine {
529 self.doc.phantom_text(edid, styling, line)
530 }
531
532 fn has_multiline_phantom(&self, edid: EditorId, styling: &EditorStyle) -> bool {
533 self.doc.has_multiline_phantom(edid, styling)
534 }
535
536 fn before_phantom_col(
537 &self,
538 edid: EditorId,
539 styling: &EditorStyle,
540 line: usize,
541 col: usize,
542 ) -> usize {
543 self.doc.before_phantom_col(edid, styling, line, col)
544 }
545}
546impl<D, F> CommonAction for ExtCmdDocument<D, F>
547where
548 D: Document + CommonAction,
549 F: Fn(&Editor, &Command, Option<usize>, Modifiers) -> CommandExecuted,
550{
551 fn exec_motion_mode(
552 &self,
553 ed: &Editor,
554 cursor: &mut Cursor,
555 motion_mode: MotionMode,
556 range: Range<usize>,
557 is_vertical: bool,
558 register: &mut Register,
559 ) {
560 self.doc
561 .exec_motion_mode(ed, cursor, motion_mode, range, is_vertical, register)
562 }
563
564 fn do_edit(
565 &self,
566 ed: &Editor,
567 cursor: &mut Cursor,
568 cmd: &EditCommand,
569 modal: bool,
570 register: &mut Register,
571 smart_tab: bool,
572 ) -> bool {
573 self.doc
574 .do_edit(ed, cursor, cmd, modal, register, smart_tab)
575 }
576}
577
578pub const SCALE_OR_SIZE_LIMIT: f32 = 5.0;
579
580#[derive(Debug, Clone)]
581pub struct SimpleStyling {
582 id: u64,
583 font_size: usize,
584 line_height: f32,
587 font_family: Vec<FamilyOwned>,
588 weight: Weight,
589 italic_style: crate::text::Style,
590 stretch: Stretch,
591 tab_width: usize,
592 atomic_soft_tabs: bool,
593}
594impl SimpleStyling {
595 pub fn builder() -> SimpleStylingBuilder {
596 SimpleStylingBuilder::default()
597 }
598
599 pub fn new() -> Self {
600 Self::default()
601 }
602}
603impl SimpleStyling {
604 pub fn increment_id(&mut self) {
605 self.id += 1;
606 }
607
608 pub fn set_font_size(&mut self, font_size: usize) {
609 self.font_size = font_size;
610 self.increment_id();
611 }
612
613 pub fn set_line_height(&mut self, line_height: f32) {
614 self.line_height = line_height;
615 self.increment_id();
616 }
617
618 pub fn set_font_family(&mut self, font_family: Vec<FamilyOwned>) {
619 self.font_family = font_family;
620 self.increment_id();
621 }
622
623 pub fn set_weight(&mut self, weight: Weight) {
624 self.weight = weight;
625 self.increment_id();
626 }
627
628 pub fn set_italic_style(&mut self, italic_style: crate::text::Style) {
629 self.italic_style = italic_style;
630 self.increment_id();
631 }
632
633 pub fn set_stretch(&mut self, stretch: Stretch) {
634 self.stretch = stretch;
635 self.increment_id();
636 }
637
638 pub fn set_tab_width(&mut self, tab_width: usize) {
639 self.tab_width = tab_width;
640 self.increment_id();
641 }
642
643 pub fn set_atomic_soft_tabs(&mut self, atomic_soft_tabs: bool) {
644 self.atomic_soft_tabs = atomic_soft_tabs;
645 self.increment_id();
646 }
647}
648impl Default for SimpleStyling {
649 fn default() -> Self {
650 SimpleStyling {
651 id: 0,
652 font_size: 16,
653 line_height: 1.5,
654 font_family: vec![FamilyOwned::SansSerif],
655 weight: Weight::NORMAL,
656 italic_style: crate::text::Style::Normal,
657 stretch: Stretch::Normal,
658 tab_width: 4,
659 atomic_soft_tabs: false,
660 }
661 }
662}
663impl Styling for SimpleStyling {
664 fn id(&self) -> u64 {
665 0
666 }
667
668 fn font_size(&self, _edid: EditorId, _line: usize) -> usize {
669 self.font_size
670 }
671
672 fn line_height(&self, _edid: EditorId, _line: usize) -> f32 {
673 let line_height = if self.line_height < SCALE_OR_SIZE_LIMIT {
674 self.line_height * self.font_size as f32
675 } else {
676 self.line_height
677 };
678
679 (line_height.round() as usize).max(self.font_size) as f32
681 }
682
683 fn font_family(&self, _edid: EditorId, _line: usize) -> Cow<'_, [FamilyOwned]> {
684 Cow::Borrowed(&self.font_family)
685 }
686
687 fn weight(&self, _edid: EditorId, _line: usize) -> Weight {
688 self.weight
689 }
690
691 fn italic_style(&self, _edid: EditorId, _line: usize) -> crate::text::Style {
692 self.italic_style
693 }
694
695 fn stretch(&self, _edid: EditorId, _line: usize) -> Stretch {
696 self.stretch
697 }
698
699 fn tab_width(&self, _edid: EditorId, _line: usize) -> usize {
700 self.tab_width
701 }
702
703 fn atomic_soft_tabs(&self, _edid: EditorId, _line: usize) -> bool {
704 self.atomic_soft_tabs
705 }
706
707 fn apply_attr_styles(
708 &self,
709 _edid: EditorId,
710 _style: &EditorStyle,
711 _line: usize,
712 _default: Attrs,
713 _attrs: &mut AttrsList,
714 ) {
715 }
716
717 fn apply_layout_styles(
718 &self,
719 _edid: EditorId,
720 _style: &EditorStyle,
721 _line: usize,
722 _layout_line: &mut TextLayoutLine,
723 ) {
724 }
725}
726
727#[derive(Default, Clone)]
728pub struct SimpleStylingBuilder {
729 font_size: Option<usize>,
730 line_height: Option<f32>,
731 font_family: Option<Vec<FamilyOwned>>,
732 weight: Option<Weight>,
733 italic_style: Option<crate::text::Style>,
734 stretch: Option<Stretch>,
735 indent_style: Option<IndentStyle>,
736 tab_width: Option<usize>,
737 atomic_soft_tabs: Option<bool>,
738 wrap: Option<WrapMethod>,
739}
740impl SimpleStylingBuilder {
741 pub fn font_size(&mut self, font_size: usize) -> &mut Self {
744 self.font_size = Some(font_size);
745 self
746 }
747
748 pub fn line_height(&mut self, line_height: f32) -> &mut Self {
751 self.line_height = Some(line_height);
752 self
753 }
754
755 pub fn font_family(&mut self, font_family: Vec<FamilyOwned>) -> &mut Self {
758 self.font_family = Some(font_family);
759 self
760 }
761
762 pub fn weight(&mut self, weight: Weight) -> &mut Self {
765 self.weight = Some(weight);
766 self
767 }
768
769 pub fn italic_style(&mut self, italic_style: crate::text::Style) -> &mut Self {
772 self.italic_style = Some(italic_style);
773 self
774 }
775
776 pub fn stretch(&mut self, stretch: Stretch) -> &mut Self {
779 self.stretch = Some(stretch);
780 self
781 }
782
783 pub fn indent_style(&mut self, indent_style: IndentStyle) -> &mut Self {
786 self.indent_style = Some(indent_style);
787 self
788 }
789
790 pub fn tab_width(&mut self, tab_width: usize) -> &mut Self {
793 self.tab_width = Some(tab_width);
794 self
795 }
796
797 pub fn atomic_soft_tabs(&mut self, atomic_soft_tabs: bool) -> &mut Self {
800 self.atomic_soft_tabs = Some(atomic_soft_tabs);
801 self
802 }
803
804 pub fn wrap(&mut self, wrap: WrapMethod) -> &mut Self {
807 self.wrap = Some(wrap);
808 self
809 }
810
811 pub fn build(&self) -> SimpleStyling {
813 let default = SimpleStyling::new();
814 SimpleStyling {
815 id: 0,
816 font_size: self.font_size.unwrap_or(default.font_size),
817 line_height: self.line_height.unwrap_or(default.line_height),
818 font_family: self.font_family.clone().unwrap_or(default.font_family),
819 weight: self.weight.unwrap_or(default.weight),
820 italic_style: self.italic_style.unwrap_or(default.italic_style),
821 stretch: self.stretch.unwrap_or(default.stretch),
822 tab_width: self.tab_width.unwrap_or(default.tab_width),
823 atomic_soft_tabs: self.atomic_soft_tabs.unwrap_or(default.atomic_soft_tabs),
824 }
825 }
826}