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