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