1use std::{collections::HashMap, ops::RangeInclusive, rc::Rc};
2
3use crate::{
4 Renderer,
5 action::{set_ime_allowed, set_ime_cursor_area},
6 context::{LayoutCx, PaintCx, UpdateCx},
7 event::{Event, EventListener, EventPropagation},
8 kurbo::{BezPath, Line, Point, Rect, Size, Vec2},
9 peniko::Color,
10 reactive::{Effect, Memo, RwSignal, Scope},
11 style::{CursorStyle, Style},
12 style_class,
13 taffy::tree::NodeId,
14 text::{Attrs, AttrsList, TextLayout},
15 view::ViewId,
16 view::{IntoView, View},
17 views::{Decorators, Scroll, Stack, editor::keypress::KeypressKey},
18};
19use floem_editor_core::{
20 command::EditCommand,
21 cursor::{ColPosition, CursorAffinity, CursorMode},
22 mode::{Mode, VisualMode},
23};
24use floem_reactive::{SignalGet, SignalTrack, SignalUpdate, SignalWith};
25use ui_events::{
26 keyboard::{Key, KeyState, KeyboardEvent, Modifiers},
27 pointer::{PointerButton, PointerButtonEvent, PointerEvent},
28};
29
30use crate::views::editor::{
31 command::CommandExecuted,
32 gutter::editor_gutter_view,
33 layout::LineExtraStyle,
34 visual_line::{RVLine, VLineInfo},
35};
36
37use super::{CHAR_WIDTH, Editor, command::Command};
38
39#[derive(Clone, Copy, PartialEq, Eq)]
40pub enum DiffSectionKind {
41 NoCode,
42 Added,
43 Removed,
44}
45
46#[derive(Clone, PartialEq)]
47pub struct DiffSection {
48 pub y_idx: usize,
55 pub height: usize,
56 pub kind: DiffSectionKind,
57}
58
59#[derive(Clone, PartialEq)]
63pub struct ScreenLines {
64 pub lines: Rc<Vec<RVLine>>,
65 pub info: Rc<HashMap<RVLine, LineInfo>>,
68 pub diff_sections: Option<Rc<Vec<DiffSection>>>,
69 pub base: RwSignal<ScreenLinesBase>,
74}
75impl ScreenLines {
76 pub fn new(cx: Scope, viewport: Rect) -> ScreenLines {
77 ScreenLines {
78 lines: Default::default(),
79 info: Default::default(),
80 diff_sections: Default::default(),
81 base: cx.create_rw_signal(ScreenLinesBase {
82 active_viewport: viewport,
83 }),
84 }
85 }
86
87 pub fn is_empty(&self) -> bool {
88 self.lines.is_empty()
89 }
90
91 pub fn clear(&mut self, viewport: Rect) {
92 self.lines = Default::default();
93 self.info = Default::default();
94 self.diff_sections = Default::default();
95 self.base.set(ScreenLinesBase {
96 active_viewport: viewport,
97 });
98 }
99
100 pub fn info(&self, rvline: RVLine) -> Option<LineInfo> {
102 let info = self.info.get(&rvline)?;
103 let base = self.base.get();
104
105 Some(info.clone().with_base(base))
106 }
107
108 pub fn vline_info(&self, rvline: RVLine) -> Option<VLineInfo<()>> {
109 self.info.get(&rvline).map(|info| info.vline_info)
110 }
111
112 pub fn rvline_range(&self) -> Option<(RVLine, RVLine)> {
113 self.lines.first().copied().zip(self.lines.last().copied())
114 }
115
116 pub fn iter_line_info(&self) -> impl Iterator<Item = LineInfo> + '_ {
118 self.lines.iter().map(|rvline| self.info(*rvline).unwrap())
119 }
120
121 pub fn iter_line_info_r(
125 &self,
126 r: RangeInclusive<RVLine>,
127 ) -> impl Iterator<Item = LineInfo> + '_ {
128 let start_idx = self.lines.binary_search(r.start()).ok().or_else(|| {
132 if self.lines.first().map(|l| r.start() < l).unwrap_or(false) {
133 Some(0)
134 } else {
135 None
137 }
138 });
139
140 let end_idx = self.lines.binary_search(r.end()).ok().or_else(|| {
141 if self.lines.last().map(|l| r.end() > l).unwrap_or(false) {
142 Some(self.lines.len() - 1)
143 } else {
144 None
146 }
147 });
148
149 if let (Some(start_idx), Some(end_idx)) = (start_idx, end_idx) {
150 self.lines.get(start_idx..=end_idx)
151 } else {
152 self.lines.get(0..0)
154 }
155 .into_iter()
156 .flatten()
157 .copied()
158 .map(|rvline| self.info(rvline).unwrap())
159 }
160
161 pub fn iter_vline_info(&self) -> impl Iterator<Item = VLineInfo<()>> + '_ {
162 self.lines
163 .iter()
164 .map(|vline| &self.info[vline].vline_info)
165 .copied()
166 }
167
168 pub fn iter_vline_info_r(
169 &self,
170 r: RangeInclusive<RVLine>,
171 ) -> impl Iterator<Item = VLineInfo<()>> + '_ {
172 self.iter_line_info_r(r).map(|x| x.vline_info)
174 }
175
176 pub fn iter_lines(&self) -> impl Iterator<Item = usize> + '_ {
178 let start_vline = self.lines.first().copied().unwrap_or_default();
181 let end_vline = self.lines.last().copied().unwrap_or_default();
182
183 let start_line = self.info(start_vline).unwrap().vline_info.rvline.line;
184 let end_line = self.info(end_vline).unwrap().vline_info.rvline.line;
185
186 start_line..=end_line
187 }
188
189 pub fn iter_lines_y(&self) -> impl Iterator<Item = (usize, f64)> + '_ {
195 let mut last_line = None;
196 self.lines.iter().filter_map(move |vline| {
197 let info = self.info(*vline).unwrap();
198
199 let line = info.vline_info.rvline.line;
200
201 if last_line == Some(line) {
202 return None;
204 }
205
206 last_line = Some(line);
207
208 Some((line, info.y))
209 })
210 }
211
212 pub fn info_for_line(&self, line: usize) -> Option<LineInfo> {
214 self.info(self.first_rvline_for_line(line)?)
215 }
216
217 pub fn first_rvline_for_line(&self, line: usize) -> Option<RVLine> {
219 self.lines
220 .iter()
221 .find(|rvline| rvline.line == line)
222 .copied()
223 }
224
225 pub fn last_rvline_for_line(&self, line: usize) -> Option<RVLine> {
227 self.lines
228 .iter()
229 .rfind(|rvline| rvline.line == line)
230 .copied()
231 }
232
233 pub fn on_created_layout(&self, ed: &Editor, line: usize) -> bool {
238 if self.is_empty() {
241 return true;
242 }
243
244 let base = self.base.get_untracked();
245 let vp = ed.viewport.get_untracked();
246
247 let is_before = self
248 .iter_vline_info()
249 .next()
250 .map(|l| line < l.rvline.line)
251 .unwrap_or(false);
252
253 if is_before {
257 let line_height = f64::from(ed.line_height(0));
259
260 let layout = ed.text_layout_trigger(line, false);
267
268 let new_lines = layout.line_count() - 1;
270
271 let new_y0 = base.active_viewport.y0 + new_lines as f64 * line_height;
272 let new_y1 = new_y0 + vp.height();
273 let new_viewport = Rect::new(vp.x0, new_y0, vp.x1, new_y1);
274
275 Effect::batch(|| {
276 self.base.set(ScreenLinesBase {
277 active_viewport: new_viewport,
278 });
279 ed.viewport.set(new_viewport);
280 });
281
282 let _layout = ed.text_layout_trigger(line, true);
288
289 return false;
290 }
291
292 let is_after = self
293 .iter_vline_info()
294 .last()
295 .map(|l| line > l.rvline.line)
296 .unwrap_or(false);
297
298 if is_after {
301 return false;
302 }
303
304 true
308 }
309}
310
311#[derive(Debug, Clone, PartialEq)]
312pub struct ScreenLinesBase {
313 pub active_viewport: Rect,
318}
319
320#[derive(Debug, Clone, PartialEq)]
321pub struct LineInfo {
322 pub y: f64,
328 pub vline_y: f64,
330 pub vline_info: VLineInfo<()>,
331}
332
333impl LineInfo {
334 pub fn with_base(mut self, base: ScreenLinesBase) -> Self {
335 self.y += base.active_viewport.y0;
336 self.vline_y += base.active_viewport.y0;
337 self
338 }
339}
340
341pub struct EditorView {
342 id: ViewId,
343 editor: RwSignal<Editor>,
344 is_active: Memo<bool>,
345 inner_node: Option<NodeId>,
346}
347
348impl EditorView {
349 #[allow(clippy::too_many_arguments)]
350 fn paint_normal_selection(
351 cx: &mut PaintCx,
352 ed: &Editor,
353 color: Color,
354 screen_lines: &ScreenLines,
355 start_offset: usize,
356 end_offset: usize,
357 affinity: CursorAffinity,
358 ) {
359 let (start_rvline, start_col) = ed.rvline_col_of_offset(start_offset, affinity);
361 let (end_rvline, end_col) = ed.rvline_col_of_offset(end_offset, affinity);
362
363 for LineInfo {
364 vline_y,
365 vline_info: info,
366 ..
367 } in screen_lines.iter_line_info_r(start_rvline..=end_rvline)
368 {
369 let rvline = info.rvline;
370 let line = rvline.line;
371
372 let left_col = if rvline == start_rvline {
373 start_col
374 } else {
375 ed.first_col(info)
376 };
377 let right_col = if rvline == end_rvline {
378 end_col
379 } else {
380 ed.last_col(info, true)
381 };
382
383 let line_height = f64::from(ed.line_height(line));
384
385 if left_col == right_col && info.line_count > 1 && left_col != ed.last_col(info, true) {
387 continue;
388 }
389
390 let left_affinity = if rvline == start_rvline && left_col == ed.last_col(info, true) {
392 CursorAffinity::Backward
393 } else {
394 CursorAffinity::Forward
395 };
396
397 let x0 = ed
398 .line_point_of_line_col(line, left_col, left_affinity, true)
399 .x;
400 let x1 = ed
401 .line_point_of_line_col(line, right_col, CursorAffinity::Backward, true)
402 .x;
403
404 let x1 = if rvline != end_rvline && rvline.line_index + 1 == info.line_count {
407 x1 + CHAR_WIDTH
408 } else {
409 x1
410 };
411
412 let (x0, width) = if info.is_empty_phantom() {
413 let text_layout = ed.text_layout(line);
414 let width = text_layout
415 .get_layout_x(rvline.line_index)
416 .map(|(_, x1)| x1)
417 .unwrap_or(0.0)
418 .into();
419 (0.0, width)
420 } else {
421 (x0, x1 - x0)
422 };
423
424 let rect = Rect::from_origin_size((x0, vline_y), (width, line_height));
425 cx.fill(&rect, color, 0.0);
426 }
427 }
428
429 #[allow(clippy::too_many_arguments)]
430 pub fn paint_linewise_selection(
431 cx: &mut PaintCx,
432 ed: &Editor,
433 color: Color,
434 screen_lines: &ScreenLines,
435 start_offset: usize,
436 end_offset: usize,
437 affinity: CursorAffinity,
438 ) {
439 let viewport = ed.viewport.get_untracked();
440
441 let (start_rvline, _) = ed.rvline_col_of_offset(start_offset, affinity);
442 let (end_rvline, _) = ed.rvline_col_of_offset(end_offset, affinity);
443 let start_rvline = screen_lines
445 .first_rvline_for_line(start_rvline.line)
446 .unwrap_or(start_rvline);
447 let end_rvline = screen_lines
448 .last_rvline_for_line(end_rvline.line)
449 .unwrap_or(end_rvline);
450
451 for LineInfo {
452 vline_info: info,
453 vline_y,
454 ..
455 } in screen_lines.iter_line_info_r(start_rvline..=end_rvline)
456 {
457 let rvline = info.rvline;
458 let line = rvline.line;
459
460 let right_col = ed.last_col(info, true);
462
463 let x1 = ed
465 .line_point_of_line_col(line, right_col, CursorAffinity::Backward, true)
466 .x
467 + CHAR_WIDTH;
468
469 let line_height = ed.line_height(line);
470 let rect = Rect::from_origin_size(
471 (viewport.x0, vline_y),
472 (x1 - viewport.x0, f64::from(line_height)),
473 );
474 cx.fill(&rect, color, 0.0);
475 }
476 }
477
478 #[allow(clippy::too_many_arguments)]
479 pub fn paint_blockwise_selection(
480 cx: &mut PaintCx,
481 ed: &Editor,
482 color: Color,
483 screen_lines: &ScreenLines,
484 start_offset: usize,
485 end_offset: usize,
486 affinity: CursorAffinity,
487 horiz: Option<ColPosition>,
488 ) {
489 let (start_rvline, start_col) = ed.rvline_col_of_offset(start_offset, affinity);
490 let (end_rvline, end_col) = ed.rvline_col_of_offset(end_offset, affinity);
491 let left_col = start_col.min(end_col);
492 let right_col = start_col.max(end_col) + 1;
493
494 let lines = screen_lines
495 .iter_line_info_r(start_rvline..=end_rvline)
496 .filter_map(|line_info| {
497 let max_col = ed.last_col(line_info.vline_info, true);
498 (max_col > left_col).then_some((line_info, max_col))
499 });
500
501 for (line_info, max_col) in lines {
502 let line = line_info.vline_info.rvline.line;
503 let right_col = if let Some(ColPosition::End) = horiz {
504 max_col
505 } else {
506 right_col.min(max_col)
507 };
508
509 let x0 = ed
511 .line_point_of_line_col(line, left_col, CursorAffinity::Forward, true)
512 .x;
513 let x1 = ed
514 .line_point_of_line_col(line, right_col, CursorAffinity::Backward, true)
515 .x;
516
517 let line_height = ed.line_height(line);
518 let rect =
519 Rect::from_origin_size((x0, line_info.vline_y), (x1 - x0, f64::from(line_height)));
520 cx.fill(&rect, color, 0.0);
521 }
522 }
523
524 fn paint_cursor(cx: &mut PaintCx, ed: &Editor, screen_lines: &ScreenLines) {
525 let cursor = ed.cursor;
526
527 let viewport = ed.viewport.get_untracked();
528
529 let current_line_color = ed.es.with_untracked(|es| es.current_line());
530
531 cursor.with_untracked(|cursor| {
532 let highlight_current_line = match cursor.mode {
533 CursorMode::Normal { offset: size, .. } => size == 0,
535 CursorMode::Insert(ref sel) => sel.is_caret(),
536 CursorMode::Visual { .. } => false,
537 };
538
539 if let Some(current_line_color) = current_line_color {
540 if highlight_current_line {
542 for (_, end, affinity) in cursor.regions_iter() {
543 let rvline = ed.rvline_of_offset(end, affinity);
545
546 if let Some(info) = screen_lines.info(rvline) {
547 let line_height = ed.line_height(info.vline_info.rvline.line);
548 let rect = Rect::from_origin_size(
549 (viewport.x0, info.vline_y),
550 (viewport.width(), f64::from(line_height)),
551 );
552
553 cx.fill(&rect, current_line_color, 0.0);
554 }
555 }
556 }
557 }
558
559 EditorView::paint_selection(cx, ed, screen_lines);
560 });
561 }
562
563 pub fn paint_selection(cx: &mut PaintCx, ed: &Editor, screen_lines: &ScreenLines) {
564 let cursor = ed.cursor;
565
566 let selection_color = ed.es.with_untracked(|es| es.selection());
567
568 cursor.with_untracked(|cursor| match cursor.mode {
569 CursorMode::Normal { .. } => {}
570 CursorMode::Visual {
571 start,
572 end,
573 mode: VisualMode::Normal,
574 affinity,
575 } => {
576 let start_offset = start.min(end);
577 let end_offset = ed.move_right(start.max(end), Mode::Insert, 1);
578
579 EditorView::paint_normal_selection(
580 cx,
581 ed,
582 selection_color,
583 screen_lines,
584 start_offset,
585 end_offset,
586 affinity,
587 );
588 }
589 CursorMode::Visual {
590 start,
591 end,
592 mode: VisualMode::Linewise,
593 affinity,
594 } => {
595 EditorView::paint_linewise_selection(
596 cx,
597 ed,
598 selection_color,
599 screen_lines,
600 start.min(end),
601 start.max(end),
602 affinity,
603 );
604 }
605 CursorMode::Visual {
606 start,
607 end,
608 mode: VisualMode::Blockwise,
609 affinity,
610 } => {
611 EditorView::paint_blockwise_selection(
612 cx,
613 ed,
614 selection_color,
615 screen_lines,
616 start.min(end),
617 start.max(end),
618 affinity,
619 cursor.horiz,
620 );
621 }
622 CursorMode::Insert(_) => {
623 for (start, end, affinity) in
624 cursor.regions_iter().filter(|(start, end, _)| start != end)
625 {
626 EditorView::paint_normal_selection(
627 cx,
628 ed,
629 selection_color,
630 screen_lines,
631 start.min(end),
632 start.max(end),
633 affinity,
634 );
635 }
636 }
637 });
638 }
639
640 fn paint_cursor_caret(
641 cx: &mut PaintCx,
642 ed: &Editor,
643 is_active: bool,
644 screen_lines: &ScreenLines,
645 ) {
646 let cursor = ed.cursor;
647 let hide_cursor = ed.cursor_info.hidden;
648 let caret_color = ed.es.with_untracked(|es| es.ed_caret());
649
650 if !is_active || hide_cursor.get_untracked() {
651 return;
652 }
653
654 cursor.with_untracked(|cursor| {
655 let style = ed.style();
656 let displaying_placeholder =
657 ed.text().is_empty() && ed.preedit().preedit.with_untracked(|p| p.is_none());
658
659 for (_, end, mut affinity) in cursor.regions_iter() {
660 if displaying_placeholder {
661 affinity = CursorAffinity::Backward;
662 }
663
664 let is_block = match cursor.mode {
665 CursorMode::Normal { .. } | CursorMode::Visual { .. } => true,
666 CursorMode::Insert(_) => false,
667 };
668 let LineRegion { x, width, rvline } = cursor_caret(ed, end, is_block, affinity);
669
670 if let Some(info) = screen_lines.info(rvline) {
671 if !style.paint_caret(ed.id(), rvline.line) {
672 continue;
673 }
674
675 let line_height = ed.line_height(info.vline_info.rvline.line);
676 let rect =
677 Rect::from_origin_size((x, info.vline_y), (width, f64::from(line_height)));
678 cx.fill(&rect, &caret_color, 0.0);
679 }
680 }
681 });
682 }
683
684 pub fn paint_wave_line(cx: &mut PaintCx, width: f64, point: Point, color: Color) {
685 let radius = 2.0;
686 let origin = Point::new(point.x, point.y + radius);
687 let mut path = BezPath::new();
688 path.move_to(origin);
689
690 let mut x = 0.0;
691 let mut direction = -1.0;
692 while x < width {
693 let point = origin + (x, 0.0);
694 let p1 = point + (radius, -radius * direction);
695 let p2 = point + (radius * 2.0, 0.0);
696 path.quad_to(p1, p2);
697 x += radius * 2.0;
698 direction *= -1.0;
699 }
700
701 cx.stroke(&path, color, &peniko::kurbo::Stroke::new(1.));
702 }
703
704 pub fn paint_extra_style(
705 cx: &mut PaintCx,
706 extra_styles: &[LineExtraStyle],
707 y: f64,
708 viewport: Rect,
709 ) {
710 for style in extra_styles {
711 let height = style.height;
712 if let Some(bg) = style.bg_color {
713 let width = style.width.unwrap_or_else(|| viewport.width());
714 let base = if style.width.is_none() {
715 viewport.x0
716 } else {
717 0.0
718 };
719 let x = style.x + base;
720 let y = y + style.y;
721 cx.fill(
722 &Rect::ZERO
723 .with_size(Size::new(width, height))
724 .with_origin(Point::new(x, y)),
725 bg,
726 0.0,
727 );
728 }
729
730 if let Some(color) = style.under_line {
731 let width = style.width.unwrap_or_else(|| viewport.width());
732 let base = if style.width.is_none() {
733 viewport.x0
734 } else {
735 0.0
736 };
737 let x = style.x + base;
738 let y = y + style.y + height;
739 cx.stroke(
740 &Line::new(Point::new(x, y), Point::new(x + width, y)),
741 color,
742 &peniko::kurbo::Stroke::new(1.),
743 );
744 }
745
746 if let Some(color) = style.wave_line {
747 let width = style.width.unwrap_or_else(|| viewport.width());
748 let y = y + style.y + height;
749 EditorView::paint_wave_line(cx, width, Point::new(style.x, y), color);
750 }
751 }
752 }
753
754 pub fn paint_text(
755 cx: &mut PaintCx,
756 view_id: Option<ViewId>,
757 ed: &Editor,
758 viewport: Rect,
759 is_active: bool,
760 screen_lines: &ScreenLines,
761 ) {
762 let edid = ed.id();
763 let style = ed.style();
764
765 let indent_unit = ed.es.with_untracked(|es| es.indent_style()).as_str();
767 let family = style.font_family(edid, 0);
769 let attrs = Attrs::new()
770 .family(&family)
771 .font_size(style.font_size(edid, 0) as f32);
772 let attrs_list = AttrsList::new(attrs);
773
774 let mut indent_text = TextLayout::new();
775 indent_text.set_text(&format!("{indent_unit}a"), attrs_list, None);
776 let indent_text_width = indent_text.hit_position(indent_unit.len()).point.x;
777
778 if ed.es.with(|s| s.show_indent_guide()) {
779 let indent_guide_color = ed.es.with_untracked(|es| es.indent_guide());
781 for (line, y) in screen_lines.iter_lines_y() {
782 let text_layout = ed.text_layout(line);
783 let line_height = f64::from(ed.line_height(line));
784 let mut x = 0.0;
785 while x + 1.0 < text_layout.indent {
786 cx.stroke(
787 &Line::new(Point::new(x, y), Point::new(x, y + line_height)),
788 indent_guide_color,
789 &peniko::kurbo::Stroke::new(1.),
790 );
791 x += indent_text_width;
792 }
793 }
794 }
795
796 let is_active = if let Some(view_id) = view_id {
797 is_active && cx.window_state.is_focused(&view_id)
798 } else {
799 is_active
800 };
801 Self::paint_cursor_caret(cx, ed, is_active, screen_lines);
802
803 let whitespace_color = ed.es.with_untracked(|es| es.visible_whitespace());
808 let ws_attrs = Attrs::new()
809 .color(whitespace_color)
810 .family(&family)
811 .font_size(style.font_size(edid, 0) as f32);
812 let ws_attrs_list = AttrsList::new(ws_attrs);
813 let mut space_text = TextLayout::new();
814 space_text.set_text("·", ws_attrs_list.clone(), None);
815 let mut tab_text = TextLayout::new();
816 tab_text.set_text("→", ws_attrs_list, None);
817
818 for (line, y) in screen_lines.iter_lines_y() {
819 let text_layout = ed.text_layout(line);
820
821 EditorView::paint_extra_style(cx, &text_layout.extra_style, y, viewport);
822
823 if let Some(whitespaces) = &text_layout.whitespaces {
824 for (c, (x0, _x1)) in whitespaces.iter() {
825 match *c {
826 '\t' => {
827 cx.draw_text(&tab_text, Point::new(*x0, y));
828 }
829 ' ' => {
830 cx.draw_text(&space_text, Point::new(*x0, y));
831 }
832 _ => {}
833 }
834 }
835 }
836
837 cx.draw_text(&text_layout.text, Point::new(0.0, y));
838 }
839 }
840}
841
842impl View for EditorView {
843 fn id(&self) -> ViewId {
844 self.id
845 }
846
847 fn style_pass(&mut self, cx: &mut crate::context::StyleCx<'_>) {
848 self.editor.with_untracked(|ed| {
849 ed.es.update(|s| {
850 if s.read(cx) {
851 ed.floem_style_id.update(|val| *val += 1);
852 cx.window_state.request_paint(self.id());
853 }
854 })
855 });
856 }
857
858 fn debug_name(&self) -> std::borrow::Cow<'static, str> {
859 "Editor View".into()
860 }
861
862 fn update(&mut self, _cx: &mut UpdateCx, _state: Box<dyn std::any::Any>) {}
863
864 fn layout(&mut self, cx: &mut LayoutCx) -> crate::taffy::tree::NodeId {
865 cx.layout_node(self.id, true, |_cx| {
866 let editor = self.editor.get_untracked();
867
868 let parent_size = editor.parent_size.get_untracked();
869
870 if self.inner_node.is_none() {
871 self.inner_node = Some(self.id.new_taffy_node());
872 }
873
874 let screen_lines = editor.screen_lines.get_untracked();
875 for (line, _) in screen_lines.iter_lines_y() {
876 editor.text_layout(line);
878 }
879
880 let inner_node = self.inner_node.unwrap();
881
882 let line_height = f64::from(editor.line_height(0));
884
885 let width = editor.max_line_width().max(parent_size.width());
886 let last_line_height = line_height * (editor.last_vline().get() + 1) as f64;
887 let height = last_line_height.max(parent_size.height());
888
889 let margin_bottom = if editor.es.with_untracked(|es| es.scroll_beyond_last_line()) {
890 parent_size.height().min(last_line_height) - line_height
891 } else {
892 0.0
893 };
894
895 let style = Style::new()
896 .width(width)
897 .height(height)
898 .margin_bottom(margin_bottom)
899 .to_taffy_style();
900 let _ = self.id.taffy().borrow_mut().set_style(inner_node, style);
901
902 vec![inner_node]
903 })
904 }
905
906 fn compute_layout(&mut self, cx: &mut crate::context::ComputeLayoutCx) -> Option<Rect> {
907 let editor = self.editor.get_untracked();
908
909 let viewport = cx.current_viewport();
910 if editor.viewport.with_untracked(|v| v != &viewport) {
911 editor.viewport.set(viewport);
912 }
913
914 if let Some(parent) = self.id.parent() {
915 let parent_size = parent.layout_rect();
916 if editor.parent_size.with_untracked(|ps| ps != &parent_size) {
917 editor.parent_size.set(parent_size);
918 }
919 }
920 None
921 }
922
923 fn paint(&mut self, cx: &mut PaintCx) {
924 let ed = self.editor.get_untracked();
925 let viewport = ed.viewport.get_untracked();
926
927 let screen_lines = ed.screen_lines.get_untracked();
936 EditorView::paint_cursor(cx, &ed, &screen_lines);
937 let screen_lines = ed.screen_lines.get_untracked();
938 EditorView::paint_text(
939 cx,
940 Some(self.id()),
941 &ed,
942 viewport,
943 self.is_active.get_untracked(),
944 &screen_lines,
945 );
946 }
947}
948
949style_class!(pub EditorViewClass);
950
951pub fn editor_view(
952 editor: RwSignal<Editor>,
953 is_active: impl Fn(bool) -> bool + 'static + Copy,
954) -> EditorView {
955 let id = ViewId::new();
956 let is_active = Scope::current().create_memo(move |_| is_active(true));
957
958 let ed = editor.get_untracked();
959
960 let doc = ed.doc;
961 let style = ed.style;
962 let lines = ed.screen_lines;
963 Effect::new(move |_| {
964 doc.track();
965 style.track();
966 lines.track();
967 id.request_layout();
968 });
969
970 let hide_cursor = ed.cursor_info.hidden;
971 Effect::new(move |_| {
972 hide_cursor.track();
973 id.request_paint();
974 });
975
976 let editor_window_origin = ed.window_origin;
977 let cursor = ed.cursor;
978 let cursor_memo =
979 Scope::current().create_memo(move |_| cursor.with(|c| (c.is_insert(), c.offset())));
980 let allows_ime = ed.ime_allowed;
981 let editor_viewport = ed.viewport;
982 let focused = ed.editor_view_focused_value;
983 let prev_ime_area = ed.ime_cursor_area;
984 let preedit = ed.preedit().preedit;
985
986 Effect::new(move |_| {
987 if !is_active.get() {
988 return;
989 }
990
991 let (allowing_ime, offset) = cursor_memo.get();
992 let focused = focused.get();
993
994 if allows_ime.get_untracked() != allowing_ime {
996 allows_ime.set(allowing_ime);
997
998 if focused {
999 set_ime_allowed(allowing_ime);
1000 }
1001 }
1002
1003 if !allowing_ime || !focused {
1004 return;
1006 }
1007
1008 preedit.with(|_| {});
1010
1011 let (point_above, _) = ed.points_of_offset(offset, CursorAffinity::Backward);
1012 let (point_above2, point_below) = ed.points_of_offset(offset, CursorAffinity::Forward);
1013
1014 let viewport = editor_viewport.get();
1015 let (min_x, max_x);
1016
1017 if point_above.y != point_above2.y {
1018 min_x = 0.0;
1020 max_x = viewport.x1 - viewport.x0;
1021 } else {
1022 min_x = point_above.x.min(point_above2.x);
1023 max_x = point_above.x.max(point_above2.x);
1024 }
1025
1026 let window_origin = editor_window_origin.get();
1027 let pos = window_origin + (min_x - viewport.x0, point_above.y - viewport.y0);
1028 let size = Size::new(max_x - min_x, point_below.y - point_above.y);
1029
1030 if prev_ime_area.get_untracked() != Some((pos, size)) {
1031 set_ime_cursor_area(pos, size);
1032 prev_ime_area.set(Some((pos, size)));
1033 }
1034 });
1035
1036 EditorView {
1037 id,
1038 editor,
1039 is_active,
1040 inner_node: None,
1041 }
1042 .style(|s| s.focusable(true))
1043 .on_event_cont(EventListener::FocusGained, move |_| {
1044 focused.set(true);
1045 prev_ime_area.set(None);
1046
1047 if allows_ime.get_untracked() {
1048 set_ime_allowed(true);
1049 }
1050 })
1051 .on_event_cont(EventListener::FocusLost, move |_| {
1052 focused.set(false);
1053 editor.with_untracked(|ed| ed.commit_preedit());
1054 set_ime_allowed(false);
1055 })
1056 .on_event(EventListener::ImePreedit, move |event| {
1057 if !is_active.get_untracked() || !focused.get_untracked() {
1058 return EventPropagation::Continue;
1059 }
1060
1061 if let Event::ImePreedit { text, cursor } = event {
1062 editor.with_untracked(|ed| {
1063 if text.is_empty() {
1064 ed.clear_preedit();
1065 } else {
1066 ed.doc.with_untracked(|doc| {
1067 doc.run_command(
1068 ed,
1069 &Command::Edit(EditCommand::DeleteSelection),
1070 Some(1),
1071 Modifiers::empty(),
1072 );
1073 });
1074
1075 let offset = ed.cursor.with_untracked(|c| c.offset());
1076
1077 ed.cursor
1079 .update(|c| c.set_latest_affinity(CursorAffinity::Forward));
1080
1081 ed.set_preedit(text.clone(), *cursor, offset);
1082 }
1083 });
1084 }
1085 EventPropagation::Stop
1086 })
1087 .on_event(EventListener::ImeCommit, move |event| {
1088 if !is_active.get_untracked() || !focused.get_untracked() {
1089 return EventPropagation::Continue;
1090 }
1091
1092 if let Event::ImeCommit(text) = event {
1093 editor.with_untracked(|ed| {
1094 ed.clear_preedit();
1095 ed.receive_char(text);
1096 });
1097 }
1098 EventPropagation::Stop
1099 })
1100 .class(EditorViewClass)
1101}
1102
1103#[derive(Clone, Debug)]
1104pub struct LineRegion {
1105 pub x: f64,
1106 pub width: f64,
1107 pub rvline: RVLine,
1108}
1109
1110pub fn cursor_caret(
1112 ed: &Editor,
1113 offset: usize,
1114 block: bool,
1115 affinity: CursorAffinity,
1116) -> LineRegion {
1117 let info = ed.rvline_info_of_offset(offset, affinity);
1118 let (_, col) = ed.offset_to_line_col(offset);
1119 let after_last_char = col == ed.line_end_col(info.rvline.line, true);
1120
1121 let doc = ed.doc();
1122 let preedit_start = doc
1123 .preedit()
1124 .preedit
1125 .with_untracked(|preedit| {
1126 preedit.as_ref().and_then(|preedit| {
1127 let preedit_line = ed.line_of_offset(preedit.offset);
1128 preedit.cursor.map(|x| (preedit_line, x))
1129 })
1130 })
1131 .filter(|(preedit_line, _)| *preedit_line == info.rvline.line)
1132 .map(|(_, (start, _))| start);
1133
1134 let point = ed.line_point_of_line_col(info.rvline.line, col, affinity, false);
1135
1136 let rvline = if preedit_start.is_some() {
1137 let y = point.y;
1140
1141 let line_height = ed.line_height(info.rvline.line);
1143
1144 let line_index = (y / f64::from(line_height)).floor() as usize;
1145 RVLine::new(info.rvline.line, line_index)
1146 } else {
1147 info.rvline
1148 };
1149
1150 let x0 = point.x;
1151 if block {
1152 let x0 = ed
1153 .line_point_of_line_col(info.rvline.line, col, CursorAffinity::Forward, true)
1154 .x;
1155 let new_offset = ed.move_right(offset, Mode::Insert, 1);
1156 let (_, new_col) = ed.offset_to_line_col(new_offset);
1157 let width = if after_last_char {
1158 CHAR_WIDTH
1159 } else {
1160 let x1 = ed
1161 .line_point_of_line_col(info.rvline.line, new_col, CursorAffinity::Backward, true)
1162 .x;
1163 x1 - x0
1164 };
1165
1166 LineRegion {
1167 x: x0,
1168 width,
1169 rvline,
1170 }
1171 } else {
1172 LineRegion {
1173 x: x0 - 1.0,
1174 width: 2.0,
1175 rvline,
1176 }
1177 }
1178}
1179
1180pub fn editor_container_view(
1181 editor: RwSignal<Editor>,
1182 is_active: impl Fn(bool) -> bool + 'static + Copy,
1183 handle_key_event: impl Fn(KeypressKey) -> CommandExecuted + 'static,
1184) -> impl IntoView {
1185 Stack::new((
1186 editor_gutter(editor),
1187 editor_content(editor, is_active, handle_key_event),
1188 ))
1189 .style(|s| s.absolute().size_pct(100.0, 100.0))
1190 .on_cleanup(move || {
1191 let editor = editor.get_untracked();
1193 editor.cx.get().dispose();
1194 })
1195}
1196
1197pub fn editor_gutter(editor: RwSignal<Editor>) -> impl IntoView {
1200 let ed = editor.get_untracked();
1201
1202 let scroll_delta = ed.scroll_delta;
1203
1204 let gutter_rect = RwSignal::new(Rect::ZERO);
1205
1206 editor_gutter_view(editor)
1207 .on_resize(move |rect| {
1208 gutter_rect.set(rect);
1209 })
1210 .on_event_stop(EventListener::PointerWheel, move |event| {
1211 if let Some(vec2) = event.pixel_scroll_delta_vec2() {
1212 scroll_delta.set(vec2);
1213 }
1214 })
1215}
1216
1217fn editor_content(
1218 editor: RwSignal<Editor>,
1219 is_active: impl Fn(bool) -> bool + 'static + Copy,
1220 handle_key_event: impl Fn(KeypressKey) -> CommandExecuted + 'static,
1221) -> impl IntoView {
1222 let ed = editor.get_untracked();
1223 let cursor = ed.cursor;
1224 let scroll_delta = ed.scroll_delta;
1225 let scroll_to = ed.scroll_to;
1226 let window_origin = ed.window_origin;
1227 let viewport = ed.viewport;
1228
1229 Scroll::new({
1230 let editor_content_view =
1231 editor_view(editor, is_active).style(move |s| s.absolute().cursor(CursorStyle::Text));
1232
1233 let id = editor_content_view.id();
1234 ed.editor_view_id.set(Some(id));
1235
1236 editor_content_view
1237 .on_event_cont(EventListener::FocusGained, move |_| {
1238 editor.with_untracked(|ed| ed.editor_view_focused.notify())
1239 })
1240 .on_event_cont(EventListener::FocusLost, move |_| {
1241 editor.with_untracked(|ed| ed.editor_view_focus_lost.notify())
1242 })
1243 .on_event_cont(EventListener::PointerDown, move |event| {
1244 if let Event::Pointer(
1245 pointer_event @ PointerEvent::Down(PointerButtonEvent { state, button, .. }),
1246 ) = event
1247 {
1248 id.request_active();
1249 id.request_focus();
1250 if pointer_event.is_primary_pointer() {
1251 editor.get_untracked().pointer_down_primary(state);
1252 } else if button.is_some_and(|b| b == PointerButton::Secondary) {
1253 editor.get_untracked().right_click(state);
1254 }
1255 }
1256 })
1257 .on_event_cont(EventListener::PointerMove, move |event| {
1258 if let Event::Pointer(PointerEvent::Move(pu)) = event {
1259 editor.get_untracked().pointer_move(&pu.current);
1260 }
1261 })
1262 .on_event_cont(EventListener::PointerUp, move |event| {
1263 if let Event::Pointer(PointerEvent::Up(PointerButtonEvent { state, .. })) = event {
1264 editor.get_untracked().pointer_up(state);
1265 }
1266 })
1267 .on_event_stop(EventListener::KeyDown, move |event| {
1268 let Event::Key(
1269 key_event @ KeyboardEvent {
1270 state: KeyState::Down,
1271 ..
1272 },
1273 ) = event
1274 else {
1275 return;
1276 };
1277
1278 handle_key_event(KeypressKey {
1279 key: key_event.key.clone(),
1280 modifiers: key_event.modifiers,
1281 });
1282
1283 let mut mods = key_event.modifiers;
1284 mods.set(Modifiers::SHIFT, false);
1285 mods.set(Modifiers::ALT, false);
1286 #[cfg(target_os = "macos")]
1287 mods.set(Modifiers::ALT, false);
1288
1289 if mods.is_empty() {
1290 if let Key::Character(c) = &key_event.key {
1291 editor.get_untracked().receive_char(c);
1292 }
1293 }
1294 })
1295 .style(|s| s.min_size_full())
1296 })
1297 .on_move(move |point| {
1298 window_origin.set(point);
1299 })
1300 .scroll_to(move || scroll_to.get().map(Vec2::to_point))
1301 .scroll_delta(move || scroll_delta.get())
1302 .ensure_visible(move || {
1303 let editor = editor.get_untracked();
1304 let cursor = cursor.get();
1305 let offset = cursor.offset();
1306 editor.doc.track();
1307 let LineRegion { x, width, rvline } =
1311 cursor_caret(&editor, offset, !cursor.is_insert(), cursor.affinity());
1312
1313 let line_height = f64::from(editor.line_height(0));
1315
1316 let vline = editor.vline_of_rvline(rvline);
1318 let rect =
1319 Rect::from_origin_size((x, vline.get() as f64 * line_height), (width, line_height))
1320 .inflate(10.0, 1.0);
1321
1322 let viewport = viewport.get_untracked();
1323 let smallest_distance = (viewport.y0 - rect.y0)
1324 .abs()
1325 .min((viewport.y1 - rect.y0).abs())
1326 .min((viewport.y0 - rect.y1).abs())
1327 .min((viewport.y1 - rect.y1).abs());
1328 let biggest_distance = (viewport.y0 - rect.y0)
1329 .abs()
1330 .max((viewport.y1 - rect.y0).abs())
1331 .max((viewport.y0 - rect.y1).abs())
1332 .max((viewport.y1 - rect.y1).abs());
1333 let jump_to_middle =
1334 biggest_distance > viewport.height() && smallest_distance > viewport.height() / 2.0;
1335
1336 if jump_to_middle {
1337 rect.inflate(0.0, viewport.height() / 2.0)
1338 } else {
1339 let mut rect = rect;
1340 let cursor_surrounding_lines = editor.es.with(|s| s.cursor_surrounding_lines()) as f64;
1341 rect.y0 -= cursor_surrounding_lines * line_height;
1342 rect.y1 += cursor_surrounding_lines * line_height;
1343 rect
1344 }
1345 })
1346 .style(|s| s.size_pct(100.0, 100.0))
1347}
1348
1349#[cfg(test)]
1350mod tests {
1351 use std::{collections::HashMap, rc::Rc};
1352
1353 use floem_reactive::RwSignal;
1354 use peniko::kurbo::Rect;
1355
1356 use crate::views::editor::{
1357 view::LineInfo,
1358 visual_line::{RVLine, VLineInfo},
1359 };
1360
1361 use super::{ScreenLines, ScreenLinesBase};
1362
1363 #[test]
1364 fn iter_line_info_range() {
1365 let lines = vec![
1366 RVLine::new(10, 0),
1367 RVLine::new(10, 1),
1368 RVLine::new(10, 2),
1369 RVLine::new(10, 3),
1370 ];
1371 let mut info = HashMap::new();
1372 for rv in lines.iter() {
1373 info.insert(
1374 *rv,
1375 LineInfo {
1376 y: 0.0,
1378 vline_y: 0.0,
1379 vline_info: VLineInfo::new(0..0, *rv, 4, ()),
1380 },
1381 );
1382 }
1383 let sl = ScreenLines {
1384 lines: Rc::new(lines),
1385 info: Rc::new(info),
1386 diff_sections: None,
1387 base: RwSignal::new(ScreenLinesBase {
1388 active_viewport: Rect::ZERO,
1389 }),
1390 };
1391
1392 assert_eq!(
1394 sl.iter_line_info_r(RVLine::new(0, 0)..=RVLine::new(1, 5))
1395 .collect::<Vec<_>>(),
1396 Vec::new()
1397 );
1398 assert_eq!(
1400 sl.iter_line_info_r(RVLine::new(10, 0)..=RVLine::new(10, 0))
1401 .count(),
1402 1
1403 );
1404 assert_eq!(
1406 sl.iter_line_info_r(RVLine::new(10, 0)..=RVLine::new(10, 2))
1407 .count(),
1408 3
1409 );
1410 assert_eq!(
1411 sl.iter_line_info_r(RVLine::new(10, 0)..=RVLine::new(10, 3))
1412 .count(),
1413 4
1414 );
1415 assert_eq!(
1417 sl.iter_line_info_r(RVLine::new(10, 0)..=RVLine::new(10, 5))
1418 .count(),
1419 4
1420 );
1421 assert_eq!(
1422 sl.iter_line_info_r(RVLine::new(0, 0)..=RVLine::new(10, 5))
1423 .count(),
1424 4
1425 );
1426 }
1427}