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