1use std::{collections::HashMap, ops::RangeInclusive, rc::Rc};
2
3use crate::{
4 Renderer,
5 action::{set_ime_allowed, set_ime_cursor_area},
6 context::{LayoutChanged, LayoutChangedListener, PaintCx, UpdateCx, VisualChanged},
7 event::{
8 CustomEvent, Event, EventPropagation, ImeEvent, PointerScrollEventExt, listener,
9 listener::UpdatePhaseBoxTreeCommit,
10 },
11 kurbo::{BezPath, Line, Point, Rect, Size, Vec2},
12 peniko::Color,
13 prelude::EventListenerTrait,
14 reactive::{Effect, Memo, RwSignal, Scope},
15 style::{CursorStyle, Style},
16 style_class,
17 taffy::tree::NodeId,
18 text::{Affinity, Attrs, AttrsList, TextLayout},
19 view::{FinalizeFn, IntoView, LayoutNodeCx, MeasureFn, View, ViewId},
20 views::{Decorators, Scroll, Stack, editor::keypress::KeypressKey},
21};
22use floem_editor_core::{
23 command::EditCommand,
24 cursor::{ColPosition, CursorAffinity, CursorMode},
25 mode::{Mode, VisualMode},
26};
27use floem_reactive::{SignalGet, SignalTrack, SignalUpdate, SignalWith};
28use peniko::Brush;
29use taffy::Overflow;
30use ui_events::{
31 keyboard::{Key, KeyboardEvent, Modifiers},
32 pointer::{PointerButton, PointerButtonEvent},
33};
34use winit::keyboard::NamedKey;
35
36use crate::views::editor::{
37 command::CommandExecuted,
38 gutter::editor_gutter_view,
39 layout::LineExtraStyle,
40 visual_line::{RVLine, VLineInfo},
41};
42
43use super::{CHAR_WIDTH, Editor, command::Command};
44
45#[derive(Clone, Copy, PartialEq, Eq)]
46pub enum DiffSectionKind {
47 NoCode,
48 Added,
49 Removed,
50}
51
52#[derive(Clone, PartialEq)]
53pub struct DiffSection {
54 pub y_idx: usize,
61 pub height: usize,
62 pub kind: DiffSectionKind,
63}
64
65#[derive(Clone, PartialEq)]
69pub struct ScreenLines {
70 pub lines: Rc<Vec<RVLine>>,
71 pub info: Rc<HashMap<RVLine, LineInfo>>,
74 pub diff_sections: Option<Rc<Vec<DiffSection>>>,
75 pub base: RwSignal<ScreenLinesBase>,
80}
81impl ScreenLines {
82 pub fn new(cx: Scope, viewport: Rect) -> ScreenLines {
83 ScreenLines {
84 lines: Default::default(),
85 info: Default::default(),
86 diff_sections: Default::default(),
87 base: cx.create_rw_signal(ScreenLinesBase {
88 active_viewport: viewport,
89 }),
90 }
91 }
92
93 pub fn is_empty(&self) -> bool {
94 self.lines.is_empty()
95 }
96
97 pub fn clear(&mut self, viewport: Rect) {
98 self.lines = Default::default();
99 self.info = Default::default();
100 self.diff_sections = Default::default();
101 self.base.set(ScreenLinesBase {
102 active_viewport: viewport,
103 });
104 }
105
106 pub fn info(&self, rvline: RVLine) -> Option<LineInfo> {
108 let info = self.info.get(&rvline)?;
109 let base = self.base.get();
110
111 Some(info.clone().with_base(base))
112 }
113
114 pub fn vline_info(&self, rvline: RVLine) -> Option<VLineInfo<()>> {
115 self.info.get(&rvline).map(|info| info.vline_info)
116 }
117
118 pub fn rvline_range(&self) -> Option<(RVLine, RVLine)> {
119 self.lines.first().copied().zip(self.lines.last().copied())
120 }
121
122 pub fn iter_line_info(&self) -> impl Iterator<Item = LineInfo> + '_ {
124 self.lines.iter().map(|rvline| self.info(*rvline).unwrap())
125 }
126
127 pub fn iter_line_info_r(
131 &self,
132 r: RangeInclusive<RVLine>,
133 ) -> impl Iterator<Item = LineInfo> + '_ {
134 let start_idx = self.lines.binary_search(r.start()).ok().or_else(|| {
138 if self.lines.first().map(|l| r.start() < l).unwrap_or(false) {
139 Some(0)
140 } else {
141 None
143 }
144 });
145
146 let end_idx = self.lines.binary_search(r.end()).ok().or_else(|| {
147 if self.lines.last().map(|l| r.end() > l).unwrap_or(false) {
148 Some(self.lines.len() - 1)
149 } else {
150 None
152 }
153 });
154
155 if let (Some(start_idx), Some(end_idx)) = (start_idx, end_idx) {
156 self.lines.get(start_idx..=end_idx)
157 } else {
158 self.lines.get(0..0)
160 }
161 .into_iter()
162 .flatten()
163 .copied()
164 .map(|rvline| self.info(rvline).unwrap())
165 }
166
167 pub fn iter_vline_info(&self) -> impl Iterator<Item = VLineInfo<()>> + '_ {
168 self.lines
169 .iter()
170 .map(|vline| &self.info[vline].vline_info)
171 .copied()
172 }
173
174 pub fn iter_vline_info_r(
175 &self,
176 r: RangeInclusive<RVLine>,
177 ) -> impl Iterator<Item = VLineInfo<()>> + '_ {
178 self.iter_line_info_r(r).map(|x| x.vline_info)
180 }
181
182 pub fn iter_lines(&self) -> impl Iterator<Item = usize> + '_ {
184 let start_vline = self.lines.first().copied().unwrap_or_default();
187 let end_vline = self.lines.last().copied().unwrap_or_default();
188
189 let start_line = self.info(start_vline).unwrap().vline_info.rvline.line;
190 let end_line = self.info(end_vline).unwrap().vline_info.rvline.line;
191
192 start_line..=end_line
193 }
194
195 pub fn iter_lines_y(&self) -> impl Iterator<Item = (usize, f64)> + '_ {
201 let mut last_line = None;
202 self.lines.iter().filter_map(move |vline| {
203 let info = self.info(*vline).unwrap();
204
205 let line = info.vline_info.rvline.line;
206
207 if last_line == Some(line) {
208 return None;
210 }
211
212 last_line = Some(line);
213
214 Some((line, info.y))
215 })
216 }
217
218 pub fn info_for_line(&self, line: usize) -> Option<LineInfo> {
220 self.info(self.first_rvline_for_line(line)?)
221 }
222
223 pub fn first_rvline_for_line(&self, line: usize) -> Option<RVLine> {
225 self.lines
226 .iter()
227 .find(|rvline| rvline.line == line)
228 .copied()
229 }
230
231 pub fn last_rvline_for_line(&self, line: usize) -> Option<RVLine> {
233 self.lines
234 .iter()
235 .rfind(|rvline| rvline.line == line)
236 .copied()
237 }
238
239 pub fn on_created_layout(&self, ed: &Editor, line: usize) -> bool {
244 if self.is_empty() {
247 return true;
248 }
249
250 let base = self.base.get_untracked();
251 let vp = ed.viewport.get_untracked();
252
253 let is_before = self
254 .iter_vline_info()
255 .next()
256 .map(|l| line < l.rvline.line)
257 .unwrap_or(false);
258
259 if is_before {
263 let line_height = f64::from(ed.line_height(0));
265
266 let layout = ed.text_layout_trigger(line, false);
273
274 let new_lines = layout.line_count() - 1;
276
277 let new_y0 = base.active_viewport.y0 + new_lines as f64 * line_height;
278 let new_y1 = new_y0 + vp.height();
279 let new_viewport = Rect::new(vp.x0, new_y0, vp.x1, new_y1);
280
281 Effect::batch(|| {
282 self.base.set(ScreenLinesBase {
283 active_viewport: new_viewport,
284 });
285 ed.viewport.set(new_viewport);
286 });
287
288 let _layout = ed.text_layout_trigger(line, true);
294
295 return false;
296 }
297
298 let is_after = self
299 .iter_vline_info()
300 .last()
301 .map(|l| line > l.rvline.line)
302 .unwrap_or(false);
303
304 if is_after {
307 return false;
308 }
309
310 true
314 }
315}
316
317#[derive(Debug, Clone, PartialEq)]
318pub struct ScreenLinesBase {
319 pub active_viewport: Rect,
324}
325
326#[derive(Debug, Clone, PartialEq)]
327pub struct LineInfo {
328 pub y: f64,
334 pub vline_y: f64,
336 pub vline_info: VLineInfo<()>,
337}
338
339impl LineInfo {
340 pub fn with_base(mut self, base: ScreenLinesBase) -> Self {
341 self.y += base.active_viewport.y0;
342 self.vline_y += base.active_viewport.y0;
343 self
344 }
345}
346
347pub struct EditorView {
348 id: ViewId,
349 editor: RwSignal<Editor>,
350 is_active: Memo<bool>,
351 inner_node: Option<NodeId>,
352}
353
354impl EditorView {
355 pub fn create_editor_layout_fn(editor: RwSignal<Editor>) -> Box<MeasureFn> {
357 Box::new(
358 move |known_dimensions, available_space, node_id, _style, measure_ctx| {
359 use taffy::*;
360 Effect::untrack(|| {
361 measure_ctx.needs_finalization(node_id);
363
364 let editor = editor.get_untracked();
365 let parent_size = editor.parent_size.get_untracked();
366 let screen_lines = editor.screen_lines.get_untracked();
367
368 let width_constraint: Option<f32> =
370 known_dimensions.width.or(match available_space.width {
371 AvailableSpace::Definite(w) => Some(w),
372 AvailableSpace::MinContent => {
373 Some(10.0)
375 }
376 AvailableSpace::MaxContent => {
377 None
379 }
380 });
381
382 for (line, _) in screen_lines.iter_lines_y() {
389 editor.text_layout(line);
390 }
391
392 let line_height = f64::from(editor.line_height(0));
394 let max_line_width = editor.max_line_width();
395
396 let width = if let Some(constraint) = width_constraint {
397 constraint as f64
398 } else {
399 max_line_width.max(parent_size.width())
401 };
402
403 let last_line_height = line_height * (editor.last_vline().get() + 1) as f64;
404 let height = last_line_height;
405
406 let margin_bottom =
407 if editor.es.with_untracked(|es| es.scroll_beyond_last_line()) {
408 parent_size.height().min(last_line_height) - line_height
409 } else {
410 0.0
411 };
412
413 Size {
414 width: width as f32,
415 height: known_dimensions
416 .height
417 .unwrap_or((height + margin_bottom) as f32),
418 }
419 })
420 },
421 )
422 }
423
424 pub fn create_editor_finalize_fn(editor: RwSignal<Editor>, view_id: ViewId) -> Box<FinalizeFn> {
425 Box::new(move |_node_id, _layout| {
426 let editor = editor.get_untracked();
427 if let Some(parent) = view_id.parent() {
429 let parent_size = parent.get_layout_rect();
430 if editor.parent_size.with_untracked(|ps| ps != &parent_size) {
431 editor.parent_size.set(parent_size);
432 }
433 }
434 })
435 }
436
437 fn set_taffy_layout(&mut self) {
438 let taffy_node = self.id.taffy_node();
439 let taffy = self.id.taffy();
440 let mut taffy = taffy.borrow_mut();
441
442 let editor_node = taffy
443 .new_leaf(taffy::Style {
444 ..taffy::Style::DEFAULT
445 })
446 .unwrap();
447
448 let layout_fn = Self::create_editor_layout_fn(self.editor);
449 let finalize_fn = Self::create_editor_finalize_fn(self.editor, self.id);
450
451 self.inner_node = Some(editor_node);
452
453 taffy
454 .set_node_context(
455 editor_node,
456 Some(LayoutNodeCx::Custom {
457 measure: layout_fn,
458 finalize: Some(finalize_fn),
459 }),
460 )
461 .unwrap();
462
463 taffy.set_children(taffy_node, &[editor_node]).unwrap();
464 }
465
466 #[allow(clippy::too_many_arguments)]
467 fn paint_normal_selection(
468 cx: &mut PaintCx,
469 ed: &Editor,
470 color: &Brush,
471 screen_lines: &ScreenLines,
472 start_offset: usize,
473 end_offset: usize,
474 affinity: CursorAffinity,
475 ) {
476 let (start_rvline, start_col) = ed.rvline_col_of_offset(start_offset, affinity);
478 let (end_rvline, end_col) = ed.rvline_col_of_offset(end_offset, affinity);
479
480 for LineInfo {
481 vline_y,
482 vline_info: info,
483 ..
484 } in screen_lines.iter_line_info_r(start_rvline..=end_rvline)
485 {
486 let rvline = info.rvline;
487 let line = rvline.line;
488
489 let left_col = if rvline == start_rvline {
490 start_col
491 } else {
492 ed.first_col(info)
493 };
494 let right_col = if rvline == end_rvline {
495 end_col
496 } else {
497 ed.last_col(info, true)
498 };
499
500 let line_height = f64::from(ed.line_height(line));
501
502 if left_col == right_col && info.line_count > 1 && left_col != ed.last_col(info, true) {
504 continue;
505 }
506
507 let left_affinity = if rvline == start_rvline && left_col == ed.last_col(info, true) {
509 CursorAffinity::Backward
510 } else {
511 CursorAffinity::Forward
512 };
513
514 let x0 = ed
515 .line_point_of_line_col(line, left_col, left_affinity, true)
516 .x;
517 let x1 = ed
518 .line_point_of_line_col(line, right_col, CursorAffinity::Backward, true)
519 .x;
520
521 let x1 = if rvline != end_rvline && rvline.line_index + 1 == info.line_count {
524 x1 + CHAR_WIDTH
525 } else {
526 x1
527 };
528
529 let (x0, width) = if info.is_empty_phantom() {
530 let text_layout = ed.text_layout(line);
531 let width = text_layout
532 .get_layout_x(rvline.line_index)
533 .map(|(_, x1)| x1)
534 .unwrap_or(0.0)
535 .into();
536 (0.0, width)
537 } else {
538 (x0, x1 - x0)
539 };
540
541 let rect = Rect::from_origin_size((x0, vline_y), (width, line_height));
542 cx.fill(&rect, color, 0.0);
543 }
544 }
545
546 #[allow(clippy::too_many_arguments)]
547 pub fn paint_linewise_selection(
548 cx: &mut PaintCx,
549 ed: &Editor,
550 color: &Brush,
551 screen_lines: &ScreenLines,
552 start_offset: usize,
553 end_offset: usize,
554 affinity: CursorAffinity,
555 ) {
556 let viewport = ed.viewport.get_untracked();
557
558 let (start_rvline, _) = ed.rvline_col_of_offset(start_offset, affinity);
559 let (end_rvline, _) = ed.rvline_col_of_offset(end_offset, affinity);
560 let start_rvline = screen_lines
562 .first_rvline_for_line(start_rvline.line)
563 .unwrap_or(start_rvline);
564 let end_rvline = screen_lines
565 .last_rvline_for_line(end_rvline.line)
566 .unwrap_or(end_rvline);
567
568 for LineInfo {
569 vline_info: info,
570 vline_y,
571 ..
572 } in screen_lines.iter_line_info_r(start_rvline..=end_rvline)
573 {
574 let rvline = info.rvline;
575 let line = rvline.line;
576
577 let right_col = ed.last_col(info, true);
579
580 let x1 = ed
582 .line_point_of_line_col(line, right_col, CursorAffinity::Backward, true)
583 .x
584 + CHAR_WIDTH;
585
586 let line_height = ed.line_height(line);
587 let rect = Rect::from_origin_size(
588 (viewport.x0, vline_y),
589 (x1 - viewport.x0, f64::from(line_height)),
590 );
591 cx.fill(&rect, color, 0.0);
592 }
593 }
594
595 #[allow(clippy::too_many_arguments)]
596 pub fn paint_blockwise_selection(
597 cx: &mut PaintCx,
598 ed: &Editor,
599 color: &Brush,
600 screen_lines: &ScreenLines,
601 start_offset: usize,
602 end_offset: usize,
603 affinity: CursorAffinity,
604 horiz: Option<ColPosition>,
605 ) {
606 let (start_rvline, start_col) = ed.rvline_col_of_offset(start_offset, affinity);
607 let (end_rvline, end_col) = ed.rvline_col_of_offset(end_offset, affinity);
608 let left_col = start_col.min(end_col);
609 let right_col = start_col.max(end_col) + 1;
610
611 let lines = screen_lines
612 .iter_line_info_r(start_rvline..=end_rvline)
613 .filter_map(|line_info| {
614 let max_col = ed.last_col(line_info.vline_info, true);
615 (max_col > left_col).then_some((line_info, max_col))
616 });
617
618 for (line_info, max_col) in lines {
619 let line = line_info.vline_info.rvline.line;
620 let right_col = if let Some(ColPosition::End) = horiz {
621 max_col
622 } else {
623 right_col.min(max_col)
624 };
625
626 let x0 = ed
628 .line_point_of_line_col(line, left_col, CursorAffinity::Forward, true)
629 .x;
630 let x1 = ed
631 .line_point_of_line_col(line, right_col, CursorAffinity::Backward, true)
632 .x;
633
634 let line_height = ed.line_height(line);
635 let rect =
636 Rect::from_origin_size((x0, line_info.vline_y), (x1 - x0, f64::from(line_height)));
637 cx.fill(&rect, color, 0.0);
638 }
639 }
640
641 fn paint_cursor(cx: &mut PaintCx, ed: &Editor, screen_lines: &ScreenLines) {
642 let cursor = ed.cursor;
643
644 let viewport = ed.viewport.get_untracked();
645
646 let current_line_color = ed.es.with_untracked(|es| es.current_line());
647
648 cursor.with_untracked(|cursor| {
649 let highlight_current_line = match cursor.mode {
650 CursorMode::Normal { offset: size, .. } => size == 0,
652 CursorMode::Insert(ref sel) => sel.is_caret(),
653 CursorMode::Visual { .. } => false,
654 };
655
656 if let Some(current_line_color) = current_line_color {
657 if highlight_current_line {
659 for (_, end, affinity) in cursor.regions_iter() {
660 let rvline = ed.rvline_of_offset(end, affinity);
662
663 if let Some(info) = screen_lines.info(rvline) {
664 let line_height = ed.line_height(info.vline_info.rvline.line);
665 let rect = Rect::from_origin_size(
666 (viewport.x0, info.vline_y),
667 (viewport.width(), f64::from(line_height)),
668 );
669
670 cx.fill(&rect, current_line_color, 0.0);
671 }
672 }
673 }
674 }
675
676 EditorView::paint_selection(cx, ed, screen_lines);
677 });
678 }
679
680 pub fn paint_selection(cx: &mut PaintCx, ed: &Editor, screen_lines: &ScreenLines) {
681 let cursor = ed.cursor;
682
683 let selection_color = ed.es.with_untracked(|es| es.selection());
684
685 cursor.with_untracked(|cursor| match cursor.mode {
686 CursorMode::Normal { .. } => {}
687 CursorMode::Visual {
688 start,
689 end,
690 mode: VisualMode::Normal,
691 affinity,
692 } => {
693 let start_offset = start.min(end);
694 let end_offset = ed.move_right(start.max(end), Mode::Insert, 1);
695
696 EditorView::paint_normal_selection(
697 cx,
698 ed,
699 &selection_color,
700 screen_lines,
701 start_offset,
702 end_offset,
703 affinity,
704 );
705 }
706 CursorMode::Visual {
707 start,
708 end,
709 mode: VisualMode::Linewise,
710 affinity,
711 } => {
712 EditorView::paint_linewise_selection(
713 cx,
714 ed,
715 &selection_color,
716 screen_lines,
717 start.min(end),
718 start.max(end),
719 affinity,
720 );
721 }
722 CursorMode::Visual {
723 start,
724 end,
725 mode: VisualMode::Blockwise,
726 affinity,
727 } => {
728 EditorView::paint_blockwise_selection(
729 cx,
730 ed,
731 &selection_color,
732 screen_lines,
733 start.min(end),
734 start.max(end),
735 affinity,
736 cursor.horiz,
737 );
738 }
739 CursorMode::Insert(_) => {
740 for (start, end, affinity) in
741 cursor.regions_iter().filter(|(start, end, _)| start != end)
742 {
743 EditorView::paint_normal_selection(
744 cx,
745 ed,
746 &selection_color,
747 screen_lines,
748 start.min(end),
749 start.max(end),
750 affinity,
751 );
752 }
753 }
754 });
755 }
756
757 fn paint_cursor_caret(
758 cx: &mut PaintCx,
759 ed: &Editor,
760 is_active: bool,
761 screen_lines: &ScreenLines,
762 ) {
763 let cursor = ed.cursor;
764 let hide_cursor = ed.cursor_info.hidden;
765 let caret_color = ed.es.with_untracked(|es| es.ed_caret());
766
767 if !is_active || hide_cursor.get_untracked() {
768 return;
769 }
770
771 cursor.with_untracked(|cursor| {
772 let style = ed.style();
773 let displaying_placeholder =
774 ed.text().is_empty() && ed.preedit().preedit.with_untracked(|p| p.is_none());
775
776 for (_, end, mut affinity) in cursor.regions_iter() {
777 if displaying_placeholder {
778 affinity = CursorAffinity::Backward;
779 }
780
781 let is_block = match cursor.mode {
782 CursorMode::Normal { .. } | CursorMode::Visual { .. } => true,
783 CursorMode::Insert(_) => false,
784 };
785 let LineRegion { x, width, rvline } = cursor_caret(ed, end, is_block, affinity);
786
787 if let Some(info) = screen_lines.info(rvline) {
788 if !style.paint_caret(ed.id(), rvline.line) {
789 continue;
790 }
791
792 let line_height = ed.line_height(info.vline_info.rvline.line);
793 let rect =
794 Rect::from_origin_size((x, info.vline_y), (width, f64::from(line_height)));
795 cx.fill(&rect, &caret_color, 0.0);
796 }
797 }
798 });
799 }
800
801 pub fn paint_wave_line(cx: &mut PaintCx, width: f64, point: Point, color: Color) {
802 let radius = 2.0;
803 let origin = Point::new(point.x, point.y + radius);
804 let mut path = BezPath::new();
805 path.move_to(origin);
806
807 let mut x = 0.0;
808 let mut direction = -1.0;
809 while x < width {
810 let point = origin + (x, 0.0);
811 let p1 = point + (radius, -radius * direction);
812 let p2 = point + (radius * 2.0, 0.0);
813 path.quad_to(p1, p2);
814 x += radius * 2.0;
815 direction *= -1.0;
816 }
817
818 cx.stroke(&path, color, &peniko::kurbo::Stroke::new(1.));
819 }
820
821 pub fn paint_extra_style(
822 cx: &mut PaintCx,
823 extra_styles: &[LineExtraStyle],
824 y: f64,
825 viewport: Rect,
826 ) {
827 for style in extra_styles {
828 let height = style.height;
829 if let Some(bg) = style.bg_color {
830 let width = style.width.unwrap_or_else(|| viewport.width());
831 let base = if style.width.is_none() {
832 viewport.x0
833 } else {
834 0.0
835 };
836 let x = style.x + base;
837 let y = y + style.y;
838 cx.fill(
839 &Rect::ZERO
840 .with_size(Size::new(width, height))
841 .with_origin(Point::new(x, y)),
842 bg,
843 0.0,
844 );
845 }
846
847 if let Some(color) = style.under_line {
848 let width = style.width.unwrap_or_else(|| viewport.width());
849 let base = if style.width.is_none() {
850 viewport.x0
851 } else {
852 0.0
853 };
854 let x = style.x + base;
855 let y = y + style.y + height;
856 cx.stroke(
857 &Line::new(Point::new(x, y), Point::new(x + width, y)),
858 color,
859 &peniko::kurbo::Stroke::new(1.),
860 );
861 }
862
863 if let Some(color) = style.wave_line {
864 let width = style.width.unwrap_or_else(|| viewport.width());
865 let y = y + style.y + height;
866 EditorView::paint_wave_line(cx, width, Point::new(style.x, y), color);
867 }
868 }
869 }
870
871 pub fn paint_text(
872 cx: &mut PaintCx,
873 view_id: Option<ViewId>,
874 ed: &Editor,
875 viewport: Rect,
876 is_active: bool,
877 screen_lines: &ScreenLines,
878 ) {
879 let edid = ed.id();
880 let style = ed.style();
881
882 let indent_unit = ed.es.with_untracked(|es| es.indent_style()).as_str();
884 let family = style.font_family(edid, 0);
886 let attrs = Attrs::new()
887 .family(&family)
888 .font_size(style.font_size(edid, 0) as f32);
889 let attrs_list = AttrsList::new(attrs);
890
891 let mut indent_text = TextLayout::new();
892 indent_text.set_text(&format!("{indent_unit}a"), attrs_list, None);
893 let indent_text_width = indent_text
894 .cursor_point(indent_unit.len(), Affinity::Upstream)
895 .x;
896
897 if ed.es.with(|s| s.show_indent_guide()) {
898 let indent_guide_color = ed.es.with_untracked(|es| es.indent_guide());
900 for (line, y) in screen_lines.iter_lines_y() {
901 let text_layout = ed.text_layout(line);
902 let line_height = f64::from(ed.line_height(line));
903 let mut x = 0.0;
904 while x + 1.0 < text_layout.indent {
905 cx.stroke(
906 &Line::new(Point::new(x, y), Point::new(x, y + line_height)),
907 indent_guide_color,
908 &peniko::kurbo::Stroke::new(1.),
909 );
910 x += indent_text_width;
911 }
912 }
913 }
914
915 let is_active = if let Some(view_id) = view_id {
916 is_active && cx.window_state.is_focused(view_id)
917 } else {
918 is_active
919 };
920 Self::paint_cursor_caret(cx, ed, is_active, screen_lines);
921
922 let whitespace_color = ed.es.with_untracked(|es| es.visible_whitespace());
927 let ws_attrs = Attrs::new()
928 .color(whitespace_color)
929 .family(&family)
930 .font_size(style.font_size(edid, 0) as f32);
931 let ws_attrs_list = AttrsList::new(ws_attrs);
932 let mut space_text = TextLayout::new();
933 space_text.set_text("·", ws_attrs_list.clone(), None);
934 let mut tab_text = TextLayout::new();
935 tab_text.set_text("→", ws_attrs_list, None);
936
937 for (line, y) in screen_lines.iter_lines_y() {
938 let text_layout = ed.text_layout(line);
939
940 EditorView::paint_extra_style(cx, &text_layout.extra_style, y, viewport);
941
942 if let Some(whitespaces) = &text_layout.whitespaces {
943 for (c, (x0, _x1)) in whitespaces.iter() {
944 match *c {
945 '\t' => {
946 tab_text.draw(cx, Point::new(*x0, y));
947 }
948 ' ' => {
949 space_text.draw(cx, Point::new(*x0, y));
950 }
951 _ => {}
952 }
953 }
954 }
955
956 text_layout.text.draw(cx, Point::new(0.0, y));
957 }
958 }
959}
960
961impl View for EditorView {
962 fn id(&self) -> ViewId {
963 self.id
964 }
965
966 fn style_pass(&mut self, cx: &mut crate::context::StyleCx<'_>) {
967 self.editor.with_untracked(|ed| {
968 ed.es.update(|s| {
969 if s.read(cx) {
970 ed.floem_style_id.update(|val| *val += 1);
971 cx.window_state.request_paint(self.id());
972 }
973 })
974 });
975 }
976
977 fn debug_name(&self) -> std::borrow::Cow<'static, str> {
978 "Editor View".into()
979 }
980
981 fn update(&mut self, _cx: &mut UpdateCx, state: Box<dyn std::any::Any>) {
982 if state.is::<SetEditorLayout>() {
983 let editor = self.editor.get_untracked();
984
985 let parent_size = editor.parent_size.get_untracked();
986
987 if self.inner_node.is_none() {
988 self.inner_node = Some(self.id.new_taffy_node());
989 }
990
991 let screen_lines = editor.screen_lines.get_untracked();
992 for (line, _) in screen_lines.iter_lines_y() {
993 editor.text_layout(line);
995 }
996
997 let inner_node = self.inner_node.unwrap();
998
999 let line_height = f64::from(editor.line_height(0));
1001
1002 let width = editor.max_line_width().max(parent_size.width());
1003 let last_line_height = line_height * (editor.last_vline().get() + 1) as f64;
1004 let height = last_line_height.max(parent_size.height());
1005
1006 let margin_bottom = if editor.es.with_untracked(|es| es.scroll_beyond_last_line()) {
1007 parent_size.height().min(last_line_height) - line_height
1008 } else {
1009 0.0
1010 };
1011
1012 let style = Style::new()
1013 .width(width)
1014 .height(height)
1015 .margin_bottom(margin_bottom)
1016 .to_taffy_style();
1017 let _ = self.id.taffy().borrow_mut().set_style(inner_node, style);
1018 self.id.request_layout();
1019 }
1020 }
1021
1022 fn event(&mut self, cx: &mut crate::event::EventCx) -> EventPropagation {
1023 if UpdatePhaseBoxTreeCommit::extract(&cx.event).is_some() {
1024 let editor = self.editor.get_untracked();
1025 let visual_rect = self.id.get_visual_rect();
1026 let layout_rect = self.id.get_visual_rect_no_clip();
1027 let viewport = Rect::from_origin_size(
1028 (
1029 (visual_rect.x0 - layout_rect.x0).max(0.0),
1030 (visual_rect.y0 - layout_rect.y0).max(0.0),
1031 ),
1032 visual_rect.size(),
1033 );
1034 if editor.viewport.with_untracked(|v| v != &viewport) {
1035 editor.viewport.set(viewport);
1036 }
1037 }
1038 if LayoutChangedListener::extract(&cx.event).is_some() {
1039 let editor = self.editor.get_untracked();
1040 if let Some(parent) = self.id.parent() {
1042 let parent_size = parent.get_layout_rect();
1043 if editor.parent_size.with_untracked(|ps| ps != &parent_size) {
1044 editor.parent_size.set(parent_size);
1045 }
1046 }
1047 }
1048 EventPropagation::Continue
1049 }
1050
1051 fn paint(&mut self, cx: &mut PaintCx) {
1052 let ed = self.editor.get_untracked();
1053 let viewport = ed.viewport.get_untracked();
1054
1055 let screen_lines = ed.screen_lines.get_untracked();
1064 EditorView::paint_cursor(cx, &ed, &screen_lines);
1065 let screen_lines = ed.screen_lines.get_untracked();
1066 EditorView::paint_text(
1067 cx,
1068 Some(self.id()),
1069 &ed,
1070 viewport,
1071 self.is_active.get_untracked(),
1072 &screen_lines,
1073 );
1074 }
1075}
1076
1077style_class!(pub EditorViewClass);
1078
1079#[derive(Clone, Copy, Debug)]
1080pub struct SetEditorLayout;
1081
1082pub fn editor_view(
1083 editor: RwSignal<Editor>,
1084 is_active: impl Fn(bool) -> bool + 'static + Copy,
1085) -> EditorView {
1086 let id = ViewId::new();
1087 id.register_listener(UpdatePhaseBoxTreeCommit::listener_key());
1088 let is_active = Scope::current().create_memo(move |_| is_active(true));
1089
1090 let ed = editor.get_untracked();
1091
1092 let doc = ed.doc;
1093 let style = ed.style;
1094 let lines = ed.screen_lines;
1095 Effect::new(move |_| {
1096 doc.track();
1097 style.track();
1098 lines.track();
1099 id.update_state(SetEditorLayout);
1101 });
1102
1103 let hide_cursor = ed.cursor_info.hidden;
1104 Effect::new(move |_| {
1105 hide_cursor.track();
1106 id.request_paint();
1107 });
1108
1109 let editor_window_origin = ed.window_origin;
1110 let cursor = ed.cursor;
1111 let cursor_memo =
1112 Scope::current().create_memo(move |_| cursor.with(|c| (c.is_insert(), c.offset())));
1113 let allows_ime = ed.ime_allowed;
1114 let editor_viewport = ed.viewport;
1115 let focused = ed.editor_view_focused_value;
1116 let prev_ime_area = ed.ime_cursor_area;
1117 let preedit = ed.preedit().preedit;
1118
1119 Effect::new(move |_| {
1120 if !is_active.get() {
1121 return;
1122 }
1123
1124 let (allowing_ime, offset) = cursor_memo.get();
1125 let focused = focused.get();
1126
1127 if allows_ime.get_untracked() != allowing_ime {
1129 allows_ime.set(allowing_ime);
1130
1131 if focused {
1132 set_ime_allowed(allowing_ime);
1133 }
1134 }
1135
1136 if !allowing_ime || !focused {
1137 return;
1139 }
1140
1141 preedit.with(|_| {});
1143
1144 let (point_above, _) = ed.points_of_offset(offset, CursorAffinity::Backward);
1145 let (point_above2, point_below) = ed.points_of_offset(offset, CursorAffinity::Forward);
1146
1147 let viewport = editor_viewport.get();
1148 let (min_x, max_x);
1149
1150 if point_above.y != point_above2.y {
1151 min_x = 0.0;
1153 max_x = viewport.x1 - viewport.x0;
1154 } else {
1155 min_x = point_above.x.min(point_above2.x);
1156 max_x = point_above.x.max(point_above2.x);
1157 }
1158
1159 let window_origin = editor_window_origin.get();
1160 let pos = window_origin + (min_x - viewport.x0, point_above.y - viewport.y0);
1161 let size = Size::new(max_x - min_x, point_below.y - point_above.y);
1162
1163 if prev_ime_area.get_untracked() != Some((pos, size)) {
1164 set_ime_cursor_area(pos, size);
1165 prev_ime_area.set(Some((pos, size)));
1166 }
1167 });
1168
1169 id.register_listener(LayoutChanged::listener_key());
1170
1171 let mut ed_view = EditorView {
1172 id,
1173 editor,
1174 is_active,
1175 inner_node: None,
1176 };
1177
1178 ed_view.set_taffy_layout();
1179
1180 ed_view
1181 .style(|s| s.keyboard_navigable())
1182 .on_event_cont(listener::FocusGained, move |_, _| {
1183 focused.set(true);
1184 prev_ime_area.set(None);
1185
1186 if allows_ime.get_untracked() {
1187 set_ime_allowed(true);
1188 }
1189 })
1190 .on_event_cont(listener::FocusLost, move |_, _| {
1191 focused.set(false);
1192 editor.with_untracked(|ed| ed.commit_preedit());
1193 set_ime_allowed(false);
1194 })
1195 .on_event(listener::ImePreedit, move |cx, _| {
1196 if !is_active.get_untracked() || !focused.get_untracked() {
1197 return EventPropagation::Continue;
1198 }
1199
1200 if let Event::Ime(ImeEvent::Preedit { text, cursor }) = &cx.event {
1201 editor.with_untracked(|ed| {
1202 if text.is_empty() {
1203 ed.clear_preedit();
1204 } else {
1205 ed.doc.with_untracked(|doc| {
1206 doc.run_command(
1207 ed,
1208 &Command::Edit(EditCommand::DeleteSelection),
1209 Some(1),
1210 Modifiers::empty(),
1211 );
1212 });
1213
1214 let offset = ed.cursor.with_untracked(|c| c.offset());
1215
1216 ed.cursor
1218 .update(|c| c.set_latest_affinity(CursorAffinity::Forward));
1219
1220 ed.set_preedit(text.clone(), *cursor, offset);
1221 }
1222 });
1223 }
1224 EventPropagation::Stop
1225 })
1226 .on_event(listener::ImeCommit, move |_cx, text| {
1227 if !is_active.get_untracked() || !focused.get_untracked() {
1228 return EventPropagation::Continue;
1229 }
1230
1231 editor.with_untracked(|ed| {
1232 ed.clear_preedit();
1233 ed.receive_char(text);
1234 });
1235 EventPropagation::Stop
1236 })
1237 .class(EditorViewClass)
1238}
1239
1240#[derive(Clone, Debug)]
1241pub struct LineRegion {
1242 pub x: f64,
1243 pub width: f64,
1244 pub rvline: RVLine,
1245}
1246
1247pub fn cursor_caret(
1249 ed: &Editor,
1250 offset: usize,
1251 block: bool,
1252 affinity: CursorAffinity,
1253) -> LineRegion {
1254 let info = ed.rvline_info_of_offset(offset, affinity);
1255 let (_, col) = ed.offset_to_line_col(offset);
1256 let after_last_char = col == ed.line_end_col(info.rvline.line, true);
1257
1258 let doc = ed.doc();
1259 let preedit_start = doc
1260 .preedit()
1261 .preedit
1262 .with_untracked(|preedit| {
1263 preedit.as_ref().and_then(|preedit| {
1264 let preedit_line = ed.line_of_offset(preedit.offset);
1265 preedit.cursor.map(|x| (preedit_line, x))
1266 })
1267 })
1268 .filter(|(preedit_line, _)| *preedit_line == info.rvline.line)
1269 .map(|(_, (start, _))| start);
1270
1271 let point = ed.line_point_of_line_col(info.rvline.line, col, affinity, false);
1272
1273 let rvline = if preedit_start.is_some() {
1274 let y = point.y;
1277
1278 let line_height = ed.line_height(info.rvline.line);
1280
1281 let line_index = (y / f64::from(line_height)).floor() as usize;
1282 RVLine::new(info.rvline.line, line_index)
1283 } else {
1284 info.rvline
1285 };
1286
1287 let x0 = point.x;
1288 if block {
1289 let x0 = ed
1290 .line_point_of_line_col(info.rvline.line, col, CursorAffinity::Forward, true)
1291 .x;
1292 let new_offset = ed.move_right(offset, Mode::Insert, 1);
1293 let (_, new_col) = ed.offset_to_line_col(new_offset);
1294 let width = if after_last_char {
1295 CHAR_WIDTH
1296 } else {
1297 let x1 = ed
1298 .line_point_of_line_col(info.rvline.line, new_col, CursorAffinity::Backward, true)
1299 .x;
1300 x1 - x0
1301 };
1302
1303 LineRegion {
1304 x: x0,
1305 width,
1306 rvline,
1307 }
1308 } else {
1309 LineRegion {
1310 x: x0 - 1.0,
1311 width: 2.0,
1312 rvline,
1313 }
1314 }
1315}
1316
1317pub fn editor_container_view(
1318 editor: RwSignal<Editor>,
1319 is_active: impl Fn(bool) -> bool + 'static + Copy,
1320 handle_key_event: impl Fn(KeypressKey) -> CommandExecuted + 'static,
1321) -> impl IntoView {
1322 Stack::new((
1323 editor_gutter(editor),
1324 editor_content(editor, is_active, handle_key_event),
1325 ))
1326 .style(|s| {
1327 s.absolute()
1328 .size_pct(100.0, 100.0)
1329 .overflow_x(Overflow::Clip)
1330 .overflow_y(Overflow::Clip)
1331 })
1332 .on_cleanup(move || {
1333 let editor = editor.get_untracked();
1335 editor.cx.get().dispose();
1336 })
1337}
1338
1339pub fn editor_gutter(editor: RwSignal<Editor>) -> impl IntoView {
1342 let ed = editor.get_untracked();
1343
1344 let scroll_delta = ed.scroll_delta;
1345
1346 let gutter_rect = RwSignal::new(Rect::ZERO);
1347
1348 editor_gutter_view(editor)
1349 .on_event_stop(
1350 LayoutChanged::listener(),
1351 move |_cx, LayoutChanged { new_box, .. }| {
1352 gutter_rect.set(*new_box);
1353 },
1354 )
1355 .on_event_stop(listener::PointerWheel, move |_cx, pse| {
1356 let line_height = ed.line_height(0) as f64;
1357 let view_size = ed.viewport.get_untracked().size();
1358 let delta =
1359 pse.resolve_to_points(Some(Size::new(line_height, line_height)), Some(view_size));
1360 scroll_delta.set(-delta);
1361 })
1362}
1363
1364fn editor_content(
1365 editor: RwSignal<Editor>,
1366 is_active: impl Fn(bool) -> bool + 'static + Copy,
1367 handle_key_event: impl Fn(KeypressKey) -> CommandExecuted + 'static,
1368) -> impl IntoView {
1369 let ed = editor.get_untracked();
1370 let cursor = ed.cursor;
1371 let scroll_delta = ed.scroll_delta;
1372 let scroll_to = ed.scroll_to;
1373 let window_origin = ed.window_origin;
1374 let viewport = ed.viewport;
1375
1376 Scroll::new({
1377 let editor_content_view =
1378 editor_view(editor, is_active).style(move |s| s.absolute().cursor(CursorStyle::Text));
1379
1380 let id = editor_content_view.id();
1381 ed.editor_view_id.set(Some(id));
1382
1383 editor_content_view
1384 .on_event_cont(listener::FocusGained, move |_, _| {
1385 editor.with_untracked(|ed| ed.editor_view_focused.notify())
1386 })
1387 .on_event_cont(listener::FocusLost, move |_, _| {
1388 editor.with_untracked(|ed| ed.editor_view_focus_lost.notify())
1389 })
1390 .on_event_cont(
1391 listener::PointerDown,
1392 move |cx,
1393 PointerButtonEvent {
1394 button,
1395 state,
1396 pointer,
1397 }| {
1398 if let Some(pointer_id) = pointer.pointer_id {
1399 cx.request_pointer_capture(pointer_id);
1400 }
1401 id.request_focus();
1402 id.request_paint();
1403 if pointer.is_primary_pointer() {
1404 editor.get_untracked().pointer_down_primary(state);
1405 } else if button.is_some_and(|b| b == PointerButton::Secondary) {
1406 editor.get_untracked().right_click(state);
1407 }
1408 },
1409 )
1410 .on_event_cont(listener::PointerMove, move |_cx, pu| {
1411 let editor = editor.get_untracked();
1412 if editor.active.get_untracked() {
1413 id.request_paint();
1414 }
1415 editor.pointer_move(&pu.current);
1416 })
1417 .on_event_cont(
1418 listener::PointerUp,
1419 move |_cx, PointerButtonEvent { state, .. }| {
1420 editor.get_untracked().pointer_up(state);
1421 },
1422 )
1423 .on_event(
1424 listener::KeyDown,
1425 move |cx, KeyboardEvent { key, modifiers, .. }| {
1426 if !cx.window_state.is_focused(id) {
1427 return EventPropagation::Continue;
1428 }
1429 if *key == Key::Named(NamedKey::Tab) {
1430 cx.prevent_default();
1431 }
1432 if handle_key_event(KeypressKey {
1433 key: key.clone(),
1434 modifiers: *modifiers,
1435 }) == CommandExecuted::Yes
1436 {
1437 cx.window_state.request_paint(cx.target);
1438 }
1439
1440 let mut mods = *modifiers;
1441 mods.set(Modifiers::SHIFT, false);
1442 mods.set(Modifiers::ALT, false);
1443 #[cfg(target_os = "macos")]
1444 mods.set(Modifiers::ALT, false);
1445
1446 if mods.is_empty()
1447 && let Key::Character(c) = &key
1448 {
1449 cx.window_state.request_paint(cx.target);
1450 editor.get_untracked().receive_char(c);
1451 }
1452 EventPropagation::Stop
1453 },
1454 )
1455 .style(|s| s.min_size_full())
1456 })
1457 .on_event_stop(VisualChanged::listener(), move |_cx, change| {
1458 window_origin.set(change.visual_window_origin());
1460 })
1461 .scroll_to(move || scroll_to.get().map(Vec2::to_point))
1462 .scroll_delta(move || scroll_delta.get())
1463 .ensure_visible(move || {
1464 let editor = editor.get_untracked();
1465 let cursor = cursor.get();
1466 let offset = cursor.offset();
1467 editor.doc.track();
1468 let LineRegion { x, width, rvline } =
1472 cursor_caret(&editor, offset, !cursor.is_insert(), cursor.affinity());
1473
1474 let line_height = f64::from(editor.line_height(0));
1476
1477 let vline = editor.vline_of_rvline(rvline);
1479 let rect =
1480 Rect::from_origin_size((x, vline.get() as f64 * line_height), (width, line_height))
1481 .inflate(10.0, 1.0);
1482
1483 let viewport = viewport.get_untracked();
1484 let smallest_distance = (viewport.y0 - rect.y0)
1485 .abs()
1486 .min((viewport.y1 - rect.y0).abs())
1487 .min((viewport.y0 - rect.y1).abs())
1488 .min((viewport.y1 - rect.y1).abs());
1489 let biggest_distance = (viewport.y0 - rect.y0)
1490 .abs()
1491 .max((viewport.y1 - rect.y0).abs())
1492 .max((viewport.y0 - rect.y1).abs())
1493 .max((viewport.y1 - rect.y1).abs());
1494 let jump_to_middle =
1495 biggest_distance > viewport.height() && smallest_distance > viewport.height() / 2.0;
1496
1497 if jump_to_middle {
1498 rect.inflate(0.0, viewport.height() / 2.0)
1499 } else {
1500 let mut rect = rect;
1501 let cursor_surrounding_lines = editor.es.with(|s| s.cursor_surrounding_lines()) as f64;
1502 rect.y0 -= cursor_surrounding_lines * line_height;
1503 rect.y1 += cursor_surrounding_lines * line_height;
1504 rect
1505 }
1506 })
1507 .style(|s| s.size_pct(100.0, 100.0))
1508}
1509
1510#[cfg(test)]
1511mod tests {
1512 use std::{collections::HashMap, rc::Rc};
1513
1514 use floem_reactive::RwSignal;
1515 use peniko::kurbo::Rect;
1516
1517 use crate::views::editor::{
1518 view::LineInfo,
1519 visual_line::{RVLine, VLineInfo},
1520 };
1521
1522 use super::{ScreenLines, ScreenLinesBase};
1523
1524 #[test]
1525 fn iter_line_info_range() {
1526 let lines = vec![
1527 RVLine::new(10, 0),
1528 RVLine::new(10, 1),
1529 RVLine::new(10, 2),
1530 RVLine::new(10, 3),
1531 ];
1532 let mut info = HashMap::new();
1533 for rv in lines.iter() {
1534 info.insert(
1535 *rv,
1536 LineInfo {
1537 y: 0.0,
1539 vline_y: 0.0,
1540 vline_info: VLineInfo::new(0..0, *rv, 4, ()),
1541 },
1542 );
1543 }
1544 let sl = ScreenLines {
1545 lines: Rc::new(lines),
1546 info: Rc::new(info),
1547 diff_sections: None,
1548 base: RwSignal::new(ScreenLinesBase {
1549 active_viewport: Rect::ZERO,
1550 }),
1551 };
1552
1553 assert_eq!(
1555 sl.iter_line_info_r(RVLine::new(0, 0)..=RVLine::new(1, 5))
1556 .collect::<Vec<_>>(),
1557 Vec::new()
1558 );
1559 assert_eq!(
1561 sl.iter_line_info_r(RVLine::new(10, 0)..=RVLine::new(10, 0))
1562 .count(),
1563 1
1564 );
1565 assert_eq!(
1567 sl.iter_line_info_r(RVLine::new(10, 0)..=RVLine::new(10, 2))
1568 .count(),
1569 3
1570 );
1571 assert_eq!(
1572 sl.iter_line_info_r(RVLine::new(10, 0)..=RVLine::new(10, 3))
1573 .count(),
1574 4
1575 );
1576 assert_eq!(
1578 sl.iter_line_info_r(RVLine::new(10, 0)..=RVLine::new(10, 5))
1579 .count(),
1580 4
1581 );
1582 assert_eq!(
1583 sl.iter_line_info_r(RVLine::new(0, 0)..=RVLine::new(10, 5))
1584 .count(),
1585 4
1586 );
1587 }
1588}