1mod dialog;
20mod edit;
21mod menu;
22mod source;
23mod toolbar;
24mod view;
25
26pub use view::{MarkdownEditor, markdown_editor};
27
28use crate::doc::{Block, BlockKind, ColumnAlign, Container, Document, Leaf, Pos, Selection, Style};
29use crate::extension::{BlockRenderer, Frame, Rectangle, RenderRequest};
30use crate::features::Features;
31use crate::fonts::Fonts;
32use crate::highlight::{self, COMMON_LANGUAGES, CodeHighlighter, HighlightRequest};
33use crate::i18n::Language;
34use crate::images::{
35 self, ImageCache, ImageId, ImageLoader, ImageRejection, ImageSettings, ImageSource, PastedImage,
36};
37use crate::layout::{Layout, Lookups, compute as compute_layout};
38use crate::markdown;
39use crate::page::Paper;
40use crate::style::EditorStyle;
41use crate::zoom::ZoomSettings;
42use iced::keyboard::{Key, Modifiers};
43use iced::mouse::ScrollDelta;
44use iced::widget::canvas;
45use iced::{Color, Point, Size, Subscription, Task};
46use std::cell::{Cell, RefCell};
47use std::collections::HashMap;
48use std::collections::hash_map::DefaultHasher;
49use std::hash::{Hash, Hasher};
50use std::path::PathBuf;
51use std::sync::Arc;
52use std::time::Duration;
53use web_time::Instant;
55
56pub(crate) const BLINK: Duration = Duration::from_millis(530);
57
58pub const DEFAULT_TAB_WIDTH: usize = 4;
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum Mode {
64 Wysiwyg,
66 Source,
68}
69
70#[derive(Debug, Clone)]
74pub struct Action(pub(crate) Inner);
75
76impl From<Command> for Action {
77 fn from(command: Command) -> Self {
78 Action(Inner::Command(command))
79 }
80}
81
82#[derive(Debug, Clone)]
83pub(crate) enum Inner {
84 Key(Key, Modifiers, Option<String>),
85 Modifiers(Modifiers),
86 Press {
88 x: f32,
89 y: f32,
90 clicks: u8,
91 },
92 Drag {
94 x: f32,
95 y: f32,
96 },
97 PressOutside,
98 ContextMenu {
101 at: Point,
102 x: f32,
103 y: f32,
104 },
105 Blink,
106 ScrollBy {
108 dx: f32,
109 dy: f32,
110 },
111 ScrollTo {
113 x: Option<f32>,
114 y: Option<f32>,
115 },
116 Wheel(ScrollDelta),
118 Command(Command),
119 LinkUrl(String),
120 LinkDialog,
122 LinkText(String),
124 LinkTab,
126 LinkApply,
127 LinkRemove,
128 LinkCancel,
129 Paste(Option<String>),
130 FileDropped(PathBuf),
132}
133
134#[derive(Debug, Clone, PartialEq)]
136pub enum Command {
137 Undo,
138 Redo,
139 ToggleBold,
140 ToggleItalic,
141 ToggleStrike,
142 ToggleCode,
143 SetBlock(BlockKind),
146 ToggleChecked,
148 SetCodeLanguage(Option<String>),
150 InsertRule,
151 InsertTable {
153 rows: usize,
154 columns: usize,
155 },
156 InsertRow {
158 below: bool,
159 },
160 InsertColumn {
162 right: bool,
163 },
164 DeleteRow,
166 DeleteColumn,
168 DeleteTable,
170 SetColumnAlign(ColumnAlign),
172 EditLink,
174 InsertText(String),
176 Indent,
178 Outdent,
180 SelectAll,
181 Copy,
182 Cut,
183 Paste,
185 InsertImage {
187 bytes: Vec<u8>,
188 name: Option<String>,
190 },
191 InsertImageFile(PathBuf),
193 InsertImageLink {
195 url: String,
196 alt: String,
197 },
198 ResolveImage {
200 id: ImageId,
201 url: String,
202 alt: String,
203 },
204 CancelImage {
206 id: ImageId,
207 },
208 RequestImage,
210 SetMode(Mode),
211 Focus,
212 Blur,
213 ZoomIn,
214 ZoomOut,
215 ResetZoom,
216 SetZoom(f32),
218 FitWidth,
220 FitPage,
222}
223
224#[derive(Debug, Clone, PartialEq)]
226pub enum Event {
227 Changed,
230 SelectionChanged,
232 ModeChanged(Mode),
233 ZoomChanged(f32),
235 Focused,
236 Blurred,
237 LinkClicked(String),
239 ImagePasted(PastedImage),
242 ImageRejected {
244 source: ImageSource,
245 reason: ImageRejection,
246 },
247 ImageRequested,
249}
250
251pub struct Output {
253 pub task: Task<Action>,
255 pub events: Vec<Event>,
257}
258
259#[derive(Debug, Clone, PartialEq)]
261pub struct Formatting {
262 pub bold: bool,
263 pub italic: bool,
264 pub strike: bool,
265 pub code: bool,
266 pub link: Option<String>,
267 pub block: BlockKind,
269 pub list: Option<bool>,
271 pub task: Option<bool>,
273 pub list_depth: usize,
275 pub quote: bool,
277 pub code_language: Option<String>,
279 pub table: bool,
281 pub column_align: Option<ColumnAlign>,
283}
284
285pub(crate) struct Snapshot {
286 pub doc: Document,
287 pub sel: Selection,
288}
289
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
291pub(crate) enum EditKind {
292 None,
293 Typing,
294 Delete,
295 Other,
296}
297
298pub struct EditorState {
300 pub(crate) doc: Document,
301 pub(crate) sel: Selection,
302 pub(crate) layout: RefCell<Layout>,
303 pub(crate) cache: RefCell<canvas::Cache>,
304 pub(crate) viewport: Cell<Size>,
305 pub(crate) style: EditorStyle,
306 pub(crate) paper: Paper,
307 pub(crate) zoom: f32,
308 pub(crate) zoom_settings: ZoomSettings,
309 pub(crate) tab_width: usize,
310 pub(crate) highlighter: Option<Arc<dyn CodeHighlighter>>,
311 pub(crate) highlight_cache: RefCell<HashMap<u64, Arc<Vec<Color>>>>,
312 pub(crate) features: Features,
313 pub(crate) language: Language,
314 pub(crate) renderers: Vec<Arc<dyn BlockRenderer>>,
315 pub(crate) renderer_languages: HashMap<String, usize>,
317 pub(crate) render_cache: RefCell<HashMap<u64, Option<Size>>>,
319 pub(crate) image_settings: ImageSettings,
320 pub(crate) image_base: Option<PathBuf>,
321 pub(crate) image_loader: Option<ImageLoader>,
322 pub(crate) image_cache: RefCell<ImageCache>,
323 pub(crate) pending_images: HashMap<ImageId, Arc<[u8]>>,
324 pub(crate) next_image_id: u64,
325 pub(crate) embedded: source::Embedded,
327 pub(crate) mode: Mode,
328 pub(crate) focused: bool,
329 pub(crate) caret_on: bool,
330 pub(crate) link_url: Option<String>,
331 pub(crate) link_range: Option<(Pos, Pos)>,
332 pub(crate) link_dialog: Option<dialog::LinkDialog>,
335 pub(crate) context_menu: Option<Point>,
337 pub(crate) fonts: Fonts,
338 pub(crate) pending: Option<Style>,
339 pub(crate) goal_x: Option<f32>,
340 pub(crate) undo: Vec<Snapshot>,
341 pub(crate) redo: Vec<Snapshot>,
342 pub(crate) last_edit: EditKind,
343 pub(crate) modifiers: Modifiers,
344 pub(crate) last_activity: Instant,
345 pub(crate) scroll_x: f32,
346 pub(crate) scroll_y: f32,
347 pub(crate) revision: u64,
348 pub(crate) dirty: bool,
349 pub(crate) pending_events: Vec<Event>,
350}
351
352impl Default for EditorState {
353 fn default() -> Self {
354 Self::new()
355 }
356}
357
358impl EditorState {
359 pub fn new() -> Self {
361 Self::with_markdown("")
362 }
363
364 pub fn with_markdown(markdown_text: &str) -> Self {
367 let state = Self {
368 doc: markdown::parse(markdown_text),
369 sel: Selection::caret(Pos::new(0, 0)),
370 layout: RefCell::new(Layout::default()),
371 cache: RefCell::new(canvas::Cache::new()),
372 viewport: Cell::new(Size::new(1000.0, 700.0)),
373 style: EditorStyle::default(),
374 paper: Paper::default(),
375 zoom: 1.0,
376 zoom_settings: ZoomSettings::default(),
377 tab_width: DEFAULT_TAB_WIDTH,
378 highlighter: None,
379 highlight_cache: RefCell::new(HashMap::new()),
380 features: Features::default(),
381 language: Language::default(),
382 renderers: Vec::new(),
383 renderer_languages: HashMap::new(),
384 render_cache: RefCell::new(HashMap::new()),
385 image_settings: ImageSettings::default(),
386 image_base: None,
387 image_loader: None,
388 image_cache: RefCell::new(ImageCache::default()),
389 pending_images: HashMap::new(),
390 next_image_id: 1,
391 embedded: source::Embedded::default(),
392 mode: Mode::Wysiwyg,
393 focused: false,
394 caret_on: true,
395 link_url: None,
396 link_range: None,
397 link_dialog: None,
398 context_menu: None,
399 fonts: Fonts::load(),
400 pending: None,
401 goal_x: None,
402 undo: Vec::new(),
403 redo: Vec::new(),
404 last_edit: EditKind::None,
405 modifiers: Modifiers::empty(),
406 last_activity: Instant::now(),
407 scroll_x: 0.0,
408 scroll_y: 0.0,
409 revision: 0,
410 dirty: false,
411 pending_events: Vec::new(),
412 };
413 state.relayout();
414 state
415 }
416
417 pub fn style(mut self, style: EditorStyle) -> Self {
419 self.set_style(style);
420 self
421 }
422
423 pub fn paper(mut self, paper: Paper) -> Self {
425 self.set_paper(paper);
426 self
427 }
428
429 pub fn zoom_settings(mut self, settings: ZoomSettings) -> Self {
431 self.set_zoom_settings(settings);
432 self
433 }
434
435 pub fn zoom(mut self, zoom: f32) -> Self {
437 self.set_zoom(zoom);
438 self
439 }
440
441 pub fn tab_width(mut self, spaces: usize) -> Self {
443 self.set_tab_width(spaces);
444 self
445 }
446
447 pub fn highlighter(mut self, highlighter: impl CodeHighlighter) -> Self {
449 self.set_highlighter(Some(Arc::new(highlighter)));
450 self
451 }
452
453 pub fn features(mut self, features: Features) -> Self {
456 self.set_features(features);
457 self
458 }
459
460 pub fn language(mut self, language: Language) -> Self {
462 self.set_language(language);
463 self
464 }
465
466 pub fn block_renderer(mut self, renderer: impl BlockRenderer) -> Self {
469 let mut renderers = self.renderers.clone();
470 renderers.push(Arc::new(renderer));
471 self.set_block_renderers(renderers);
472 self
473 }
474
475 pub fn images(mut self, settings: ImageSettings) -> Self {
477 self.set_image_settings(settings);
478 self
479 }
480
481 pub fn image_base(mut self, folder: impl Into<PathBuf>) -> Self {
483 self.set_image_base(Some(folder.into()));
484 self
485 }
486
487 pub fn image_loader(
489 mut self,
490 loader: impl Fn(&str) -> Option<Vec<u8>> + Send + Sync + 'static,
491 ) -> Self {
492 self.set_image_loader(Some(Arc::new(loader)));
493 self
494 }
495
496 pub fn mode(mut self, mode: Mode) -> Self {
498 self.set_mode(mode);
499 self
500 }
501
502 pub fn markdown(&self) -> String {
509 match self.mode {
510 Mode::Wysiwyg => markdown::serialize(&images::without_pending(&self.doc)),
511 Mode::Source => self
512 .embedded
513 .expand(&source::text(&images::without_pending(&self.doc))),
514 }
515 }
516
517 pub fn plain_text(&self) -> String {
519 self.doc.plain_text(self.doc.start(), self.doc.end())
520 }
521
522 pub fn selected_text(&self) -> Option<String> {
524 let (a, b) = self.sel.range();
525 (a != b).then(|| self.embedded.expand(&self.doc.plain_text(a, b)))
526 }
527
528 pub fn word_count(&self) -> usize {
529 self.doc.word_count()
530 }
531
532 pub fn current_mode(&self) -> Mode {
533 self.mode
534 }
535
536 pub fn current_style(&self) -> &EditorStyle {
537 &self.style
538 }
539
540 pub fn current_paper(&self) -> &Paper {
541 &self.paper
542 }
543
544 pub fn current_zoom(&self) -> f32 {
546 self.zoom
547 }
548
549 pub fn current_zoom_settings(&self) -> &ZoomSettings {
550 &self.zoom_settings
551 }
552
553 pub fn current_tab_width(&self) -> usize {
555 self.tab_width
556 }
557
558 pub fn current_image_settings(&self) -> &ImageSettings {
559 &self.image_settings
560 }
561
562 pub fn pending_images(&self) -> usize {
564 self.pending_images.len()
565 }
566
567 pub fn has_highlighter(&self) -> bool {
568 self.highlighter.is_some()
569 }
570
571 pub fn current_features(&self) -> Features {
572 self.features
573 }
574
575 pub fn current_language(&self) -> Language {
576 self.language
577 }
578
579 pub fn rendered_languages(&self) -> Vec<String> {
581 self.renderers
582 .iter()
583 .flat_map(|renderer| renderer.languages())
584 .collect()
585 }
586
587 pub fn code_languages(&self) -> Vec<String> {
590 let languages = self
591 .highlighter
592 .as_ref()
593 .map(|highlighter| highlighter.languages())
594 .unwrap_or_default();
595 let mut languages: Vec<String> = if languages.is_empty() {
596 COMMON_LANGUAGES
597 .iter()
598 .map(|name| name.to_string())
599 .collect()
600 } else {
601 languages
602 };
603 for language in self.rendered_languages() {
604 if !languages.contains(&language) {
605 languages.push(language);
606 }
607 }
608 languages
609 }
610
611 pub fn task_progress(&self) -> (usize, usize) {
613 let mut seen = Vec::new();
614 let mut checked = 0;
615 for block in &self.doc.blocks {
616 for container in &block.containers {
617 if let Container::Item {
618 id,
619 task: Some(done),
620 ..
621 } = container
622 {
623 if !seen.contains(id) {
624 seen.push(*id);
625 if *done {
626 checked += 1;
627 }
628 }
629 }
630 }
631 }
632 (checked, seen.len())
633 }
634
635 pub fn page_count(&self) -> usize {
637 self.layout.borrow().pages.len().max(1)
638 }
639
640 pub fn current_page(&self) -> usize {
642 self.layout.borrow().page_of(self.sel.head) + 1
643 }
644
645 pub fn is_focused(&self) -> bool {
646 self.focused
647 }
648
649 pub fn is_dirty(&self) -> bool {
651 self.dirty
652 }
653
654 pub fn can_undo(&self) -> bool {
655 !self.undo.is_empty()
656 }
657
658 pub fn can_redo(&self) -> bool {
659 !self.redo.is_empty()
660 }
661
662 pub fn formatting(&self) -> Formatting {
664 let (a, b) = self.sel.range();
665 let style = if a == b {
666 self.typing_style()
667 } else {
668 self.doc.blocks[a.block]
669 .styles
670 .get(a.offset)
671 .cloned()
672 .unwrap_or_default()
673 };
674 let block = &self.doc.blocks[self.sel.head.block];
675 let leaf_kind = match block.leaf {
676 Leaf::Paragraph | Leaf::Line | Leaf::Cell(_) => BlockKind::Paragraph,
677 Leaf::Heading(level) => BlockKind::Heading(level),
678 Leaf::Code => BlockKind::Code,
679 Leaf::Html => BlockKind::Html,
680 Leaf::Rule => BlockKind::Rule,
681 };
682 let item = block
683 .containers
684 .iter()
685 .rev()
686 .find(|c| matches!(c, Container::Item { .. }));
687 Formatting {
688 bold: style.bold,
689 italic: style.italic,
690 strike: style.strike,
691 code: style.code,
692 link: style.link.map(|link| link.url.clone()),
693 block: leaf_kind,
694 list: item.and_then(Container::list).map(|list| list.ordered),
695 task: item.and_then(Container::task),
696 list_depth: block
697 .containers
698 .iter()
699 .filter(|c| c.list().is_some())
700 .count(),
701 quote: block.containers.iter().any(Container::is_quote),
702 code_language: if block.leaf == Leaf::Code {
703 block.language().map(str::to_string)
704 } else {
705 None
706 },
707 table: block.cell().is_some(),
708 column_align: block.cell().map(|cell| cell.align),
709 }
710 }
711
712 pub fn set_markdown(&mut self, markdown_text: &str) {
719 self.doc = match self.mode {
720 Mode::Wysiwyg => markdown::parse_with(markdown_text, self.features),
721 Mode::Source => source::document(&self.embedded.shorten(markdown_text)),
722 };
723 self.sel = Selection::caret(Pos::new(0, 0));
724 self.undo.clear();
725 self.redo.clear();
726 self.pending_images.clear();
727 self.reset_transient();
728 self.dirty = false;
729 self.revision += 1;
730 self.scroll_x = 0.0;
731 self.scroll_y = 0.0;
732 self.relayout();
733 }
734
735 pub fn mark_saved(&mut self) {
737 self.dirty = false;
738 }
739
740 pub fn set_style(&mut self, style: EditorStyle) {
741 self.style = style;
742 self.relayout();
743 }
744
745 pub fn set_paper(&mut self, paper: Paper) {
746 self.paper = paper;
747 self.relayout();
748 self.clamp_scroll();
749 }
750
751 pub fn set_zoom_settings(&mut self, settings: ZoomSettings) {
752 self.zoom_settings = settings;
753 self.zoom = self.zoom_settings.clamp(self.zoom);
754 self.relayout();
755 self.clamp_scroll();
756 }
757
758 pub fn set_zoom(&mut self, zoom: f32) {
760 self.zoom = self.zoom_settings.clamp(zoom);
761 self.relayout();
762 self.clamp_scroll();
763 }
764
765 pub fn set_tab_width(&mut self, spaces: usize) {
767 self.tab_width = spaces.min(16);
768 }
769
770 pub fn set_highlighter(&mut self, highlighter: Option<Arc<dyn CodeHighlighter>>) {
771 self.highlighter = highlighter;
772 self.highlight_cache.borrow_mut().clear();
773 self.relayout();
774 }
775
776 pub fn set_language(&mut self, language: Language) {
779 self.language = language;
780 }
781
782 pub fn set_features(&mut self, features: Features) {
786 if features == self.features {
787 return;
788 }
789 self.features = features;
790 if self.mode == Mode::Wysiwyg {
791 let text = markdown::serialize(&self.doc);
792 self.doc = markdown::parse_with(&text, features);
793 self.sel = Selection {
794 anchor: self.doc.clamp(self.sel.anchor),
795 head: self.doc.clamp(self.sel.head),
796 };
797 self.undo.clear();
798 self.redo.clear();
799 self.reset_transient();
800 }
801 self.relayout();
802 }
803
804 pub fn set_block_renderers(&mut self, renderers: Vec<Arc<dyn BlockRenderer>>) {
807 let mut languages = HashMap::new();
808 for (index, renderer) in renderers.iter().enumerate() {
809 for language in renderer.languages() {
810 languages.entry(language).or_insert(index);
811 }
812 }
813 self.renderers = renderers;
814 self.renderer_languages = languages;
815 self.render_cache.borrow_mut().clear();
816 self.relayout();
817 }
818
819 pub fn set_image_settings(&mut self, settings: ImageSettings) {
820 self.image_settings = settings;
821 }
822
823 pub fn set_image_base(&mut self, folder: Option<PathBuf>) {
824 self.image_base = folder;
825 self.reload_images();
826 }
827
828 pub fn set_image_loader(&mut self, loader: Option<ImageLoader>) {
829 self.image_loader = loader;
830 self.reload_images();
831 }
832
833 pub fn reload_images(&mut self) {
835 self.image_cache.borrow_mut().clear();
836 self.relayout();
837 }
838
839 pub fn set_mode(&mut self, mode: Mode) {
842 if mode == self.mode {
843 return;
844 }
845 self.doc = match mode {
846 Mode::Source => {
847 let text = markdown::serialize(&self.doc);
848 let text = self.embedded.shorten(&text);
849 source::document(&text)
850 }
851 Mode::Wysiwyg => {
852 let text = self.embedded.expand(&source::text(&self.doc));
853 markdown::parse_with(&text, self.features)
854 }
855 };
856 self.mode = mode;
857 self.sel = Selection::caret(Pos::new(0, 0));
858 self.undo.clear();
859 self.redo.clear();
860 self.reset_transient();
861 self.highlight_cache.borrow_mut().clear();
862 self.scroll_x = 0.0;
863 self.scroll_y = 0.0;
864 self.relayout();
865 }
866
867 pub fn execute(&mut self, command: Command) -> Output {
869 self.update(Action(Inner::Command(command)))
870 }
871
872 pub fn update(&mut self, action: Action) -> Output {
874 let revision = self.revision;
875 let sel = self.sel;
876 let mode = self.mode;
877 let focused = self.focused;
878 let zoom = self.zoom;
879
880 let task = self.apply(action.0);
881
882 if self.sel.head.block != sel.head.block
884 && (self.renderer_for(sel.head.block).is_some()
885 || self.renderer_for(self.sel.head.block).is_some())
886 {
887 self.relayout();
888 }
889
890 let mut events = std::mem::take(&mut self.pending_events);
891 if self.focused != focused {
892 events.push(if self.focused {
893 Event::Focused
894 } else {
895 Event::Blurred
896 });
897 }
898 if self.mode != mode {
899 events.push(Event::ModeChanged(self.mode));
900 }
901 if (self.zoom - zoom).abs() > f32::EPSILON {
902 events.push(Event::ZoomChanged(self.zoom));
903 }
904 if self.revision != revision {
905 events.push(Event::Changed);
906 }
907 if self.sel != sel {
908 events.push(Event::SelectionChanged);
909 }
910
911 Output { task, events }
912 }
913
914 pub fn subscription(&self) -> Subscription<Action> {
917 let blink = if self.focused {
918 iced::time::every(BLINK).map(|_| Action(Inner::Blink))
919 } else {
920 Subscription::none()
921 };
922 let drops = if self.image_settings.enabled() && self.image_settings.drop {
923 iced::event::listen_with(file_dropped)
924 } else {
925 Subscription::none()
926 };
927 Subscription::batch([blink, drops])
928 }
929
930 pub(crate) fn expand_width(&self) -> usize {
935 if self.tab_width == 0 {
936 DEFAULT_TAB_WIDTH
937 } else {
938 self.tab_width
939 }
940 }
941
942 pub(crate) fn new_image_id(&mut self) -> ImageId {
943 let id = ImageId(self.next_image_id);
944 self.next_image_id += 1;
945 id
946 }
947
948 pub(crate) fn code_colors(&self, index: usize) -> Option<Arc<Vec<Color>>> {
950 let highlighter = self.highlighter.as_ref()?;
951 let block = self.doc.blocks.get(index)?;
952 if block.leaf != Leaf::Code || block.chars.is_empty() {
953 return None;
954 }
955 let language = block.language()?;
956 let palette = &self.style.palette;
957
958 let mut hasher = DefaultHasher::new();
959 language.hash(&mut hasher);
960 block.chars.hash(&mut hasher);
961 for channel in [palette.code_background, palette.code] {
962 channel.r.to_bits().hash(&mut hasher);
963 channel.g.to_bits().hash(&mut hasher);
964 channel.b.to_bits().hash(&mut hasher);
965 }
966 let key = hasher.finish();
967
968 if let Some(colors) = self.highlight_cache.borrow().get(&key) {
969 return Some(colors.clone());
970 }
971
972 let code: String = block.chars.iter().collect();
973 let spans = highlighter.highlight(&HighlightRequest {
974 language,
975 code: &code,
976 background: palette.code_background,
977 dark: highlight::is_dark(palette.code_background),
978 });
979
980 let len = block.chars.len();
981 let mut colors = vec![palette.code; len];
982 for span in spans {
983 let start = span.range.start.min(len);
984 let end = span.range.end.min(len);
985 for color in &mut colors[start..end.max(start)] {
986 *color = span.color;
987 }
988 }
989
990 let colors = Arc::new(colors);
991 let mut cache = self.highlight_cache.borrow_mut();
992 if cache.len() > 512 {
993 cache.clear();
994 }
995 cache.insert(key, colors.clone());
996 Some(colors)
997 }
998
999 pub(crate) fn renderer_for(&self, index: usize) -> Option<&Arc<dyn BlockRenderer>> {
1002 if self.mode != Mode::Wysiwyg || self.renderers.is_empty() {
1003 return None;
1004 }
1005 let block = self.doc.blocks.get(index)?;
1006 if block.leaf != Leaf::Code {
1007 return None;
1008 }
1009 let renderer = *self.renderer_languages.get(block.language()?)?;
1010 self.renderers.get(renderer)
1011 }
1012
1013 fn measure_block(&self, index: usize, width: f32, max_height: f32) -> Option<Size> {
1016 if index == self.sel.head.block {
1017 return None;
1018 }
1019 let renderer = self.renderer_for(index)?;
1020 let block = &self.doc.blocks[index];
1021 let code: String = block.chars.iter().collect();
1022
1023 let mut hasher = DefaultHasher::new();
1024 Arc::as_ptr(renderer).cast::<()>().hash(&mut hasher);
1025 block.info.hash(&mut hasher);
1026 code.hash(&mut hasher);
1027 for value in [width, max_height, self.zoom] {
1028 value.to_bits().hash(&mut hasher);
1029 }
1030 let key = hasher.finish();
1031 if let Some(size) = self.render_cache.borrow().get(&key) {
1032 return *size;
1033 }
1034
1035 let size = renderer.measure(&RenderRequest {
1036 language: block.language().unwrap_or(""),
1037 info: block.info.as_deref().unwrap_or(""),
1038 code: &code,
1039 width,
1040 max_height,
1041 zoom: self.zoom,
1042 style: &self.style,
1043 });
1044 let mut cache = self.render_cache.borrow_mut();
1045 if cache.len() > 512 {
1046 cache.clear();
1047 }
1048 cache.insert(key, size);
1049 size
1050 }
1051
1052 pub(crate) fn draw_block(
1055 &self,
1056 frame: &mut Frame,
1057 index: usize,
1058 bounds: Rectangle,
1059 available: Size,
1060 ) {
1061 let Some(renderer) = self.renderer_for(index) else {
1062 return;
1063 };
1064 let block = &self.doc.blocks[index];
1065 let code: String = block.chars.iter().collect();
1066 let request = RenderRequest {
1067 language: block.language().unwrap_or(""),
1068 info: block.info.as_deref().unwrap_or(""),
1069 code: &code,
1070 width: available.width,
1071 max_height: available.height,
1072 zoom: self.zoom,
1073 style: &self.style,
1074 };
1075 renderer.draw(frame, bounds, &request);
1076 }
1077
1078 pub(crate) fn relayout(&self) {
1079 let cache = &self.image_cache;
1080 let base = self.image_base.as_deref();
1081 let loader = self.image_loader.as_ref();
1082 let lookup = |url: &str| cache.borrow_mut().get(url, base, loader);
1083 let custom = |index: usize, width: f32, max_height: f32| {
1084 self.measure_block(index, width, max_height)
1085 };
1086 let layout = compute_layout(
1087 &self.doc,
1088 &self.fonts,
1089 &self.style,
1090 &self.paper,
1091 self.viewport.get(),
1092 self.zoom,
1093 Lookups {
1094 images: &lookup,
1095 custom: &custom,
1096 },
1097 );
1098 *self.layout.borrow_mut() = layout;
1099 self.cache.borrow_mut().clear();
1100 }
1101
1102 pub(crate) fn sync_viewport(&self, size: Size) {
1104 let current = self.viewport.get();
1105 if (current.width - size.width).abs() > 0.5 || (current.height - size.height).abs() > 0.5 {
1106 self.viewport.set(size);
1107 self.relayout();
1108 }
1109 }
1110
1111 pub(crate) fn max_scroll(&self) -> (f32, f32) {
1113 let layout = self.layout.borrow();
1114 let viewport = self.viewport.get();
1115 (
1116 (layout.width - viewport.width).max(0.0),
1117 (layout.height - viewport.height).max(0.0),
1118 )
1119 }
1120
1121 pub(crate) fn clamped_scroll(&self) -> (f32, f32) {
1123 let (max_x, max_y) = self.max_scroll();
1124 (
1125 self.scroll_x.clamp(0.0, max_x),
1126 self.scroll_y.clamp(0.0, max_y),
1127 )
1128 }
1129
1130 pub(crate) fn set_scroll(&mut self, x: f32, y: f32) {
1131 let (max_x, max_y) = self.max_scroll();
1132 let x = x.clamp(0.0, max_x);
1133 let y = y.clamp(0.0, max_y);
1134 if (x - self.scroll_x).abs() > 0.01 || (y - self.scroll_y).abs() > 0.01 {
1135 self.scroll_x = x;
1136 self.scroll_y = y;
1137 self.cache.borrow_mut().clear();
1138 }
1139 }
1140
1141 pub(crate) fn clamp_scroll(&mut self) {
1142 let (x, y) = (self.scroll_x, self.scroll_y);
1143 self.set_scroll(x, y);
1144 }
1145
1146 pub(crate) fn reset_transient(&mut self) {
1147 self.pending = None;
1148 self.goal_x = None;
1149 self.last_edit = EditKind::None;
1150 self.link_url = None;
1151 self.link_range = None;
1152 self.link_dialog = None;
1153 self.context_menu = None;
1154 }
1155
1156 pub(crate) fn touch(&mut self) {
1157 self.last_activity = Instant::now();
1158 self.caret_on = true;
1159 }
1160
1161 pub(crate) fn typing_style(&self) -> Style {
1162 match &self.pending {
1163 Some(style) => style.clone(),
1164 None => self.doc.typing_style(self.sel.range().0),
1165 }
1166 }
1167
1168 pub(crate) fn begin_edit(&mut self, kind: EditKind) {
1169 let coalesce =
1170 kind == self.last_edit && matches!(kind, EditKind::Typing | EditKind::Delete);
1171 if !coalesce {
1172 self.undo.push(Snapshot {
1173 doc: self.doc.clone(),
1174 sel: self.sel,
1175 });
1176 if self.undo.len() > 300 {
1177 self.undo.remove(0);
1178 }
1179 }
1180 self.redo.clear();
1181 self.last_edit = kind;
1182 }
1183
1184 pub(crate) fn after_edit(&mut self) -> Task<Action> {
1185 self.dirty = true;
1186 self.revision += 1;
1187 self.goal_x = None;
1188 self.relayout();
1189 self.touch();
1190 self.scroll_to_caret()
1191 }
1192
1193 pub(crate) fn scroll_to_caret(&mut self) -> Task<Action> {
1195 let caret = self.layout.borrow().caret(self.sel.head);
1196 let Some((x, y, height)) = caret else {
1197 return Task::none();
1198 };
1199 let viewport = self.viewport.get();
1200 let margin = 24.0;
1201 let (mut sx, mut sy) = (self.scroll_x, self.scroll_y);
1202
1203 if y < sy + margin {
1204 sy = y - 2.0 * margin;
1205 } else if y + height > sy + viewport.height - margin {
1206 sy = y + height - viewport.height + 2.0 * margin;
1207 }
1208 if !self.paper.is_fill() {
1209 if x < sx + margin {
1210 sx = x - 4.0 * margin;
1211 } else if x > sx + viewport.width - margin {
1212 sx = x - viewport.width + 4.0 * margin;
1213 }
1214 }
1215 self.set_scroll(sx, sy);
1216 Task::none()
1217 }
1218
1219 pub(crate) fn zoom_to(&mut self, zoom: f32) -> Task<Action> {
1221 let zoom = self.zoom_settings.clamp(zoom);
1222 if (zoom - self.zoom).abs() < 1.0e-4 {
1223 return Task::none();
1224 }
1225 let ratio = zoom / self.zoom;
1226 let viewport = self.viewport.get();
1227 let center_x = self.scroll_x + viewport.width / 2.0;
1228 let center_y = self.scroll_y + viewport.height / 2.0;
1229
1230 self.zoom = zoom;
1231 self.relayout();
1232 self.set_scroll(
1233 center_x * ratio - viewport.width / 2.0,
1234 center_y * ratio - viewport.height / 2.0,
1235 );
1236 Task::none()
1237 }
1238
1239 pub(crate) fn block_len(&self, index: usize) -> usize {
1240 self.doc.blocks.get(index).map(Block::len).unwrap_or(0)
1241 }
1242}
1243
1244fn file_dropped(
1246 event: iced::Event,
1247 _status: iced::event::Status,
1248 _window: iced::window::Id,
1249) -> Option<Action> {
1250 match event {
1251 iced::Event::Window(iced::window::Event::FileDropped(path)) => {
1252 Some(Action(Inner::FileDropped(path)))
1253 }
1254 _ => None,
1255 }
1256}