1mod boxes;
4mod cache;
5mod tokens;
6mod undo;
7
8#[cfg(test)]
9mod tests;
10
11use std::collections::BTreeSet;
12use std::fmt;
13
14use mathtex_editor_core::{
15 CaretPath, Command, Dir, Document, Editor, ExitDir, HostBoxEntry, HostBoxPolicy, MenuView, Point, Repair,
16 Selection, Side,
17};
18use mathtex_editor_keymap::{KeyInput, Keymap};
19use mathtex_engine::font::FontLoader;
20use mathtex_engine::{HostBoxes, MathMode, TypesetError, Typesetter};
21use mathtex_ir::Length;
22
23use crate::boxes::SessionBoxes;
24pub use crate::cache::{RenderCache, View};
25pub use crate::tokens::{TokenError, TokenRegistry};
26pub use crate::undo::{DEFAULT_UNDO_LIMIT, UndoStack};
27
28#[derive(Debug, Clone, Default, PartialEq, Eq)]
30#[non_exhaustive]
31pub struct Update {
32 pub changed: bool,
34 pub exit: Option<ExitDir>,
36 pub close: bool,
38 pub entered_host_box: Option<HostBoxEntry>,
40 pub needs_redraw: bool,
42 pub reminted: Vec<(u32, u32)>,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct ClipboardData {
49 pub json: String,
51 pub tex: String,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum PasteError {
58 Json(String),
60 Empty,
62 Tokens(TokenError),
64}
65
66impl fmt::Display for PasteError {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 match self {
69 PasteError::Json(e) => write!(f, "not a document: {e}"),
70 PasteError::Empty => write!(f, "the document is empty"),
71 PasteError::Tokens(e) => e.fmt(f),
72 }
73 }
74}
75
76impl std::error::Error for PasteError {}
77
78#[derive(PartialEq)]
80struct Visible {
81 revision: u64,
82 cursor: CaretPath,
83 selection: Option<Selection>,
84 menu: Option<MenuView>,
85}
86
87pub struct Session<L: FontLoader> {
89 editor: Editor,
90 keymap: Keymap,
91 typesetter: Typesetter<L>,
92 mode: MathMode,
93 policy: HostBoxPolicy,
95 cache: RenderCache,
96 history: UndoStack,
97 tokens: TokenRegistry,
98 clipboard: Option<Document>,
99 boxes: SessionBoxes,
100}
101
102impl<L: FontLoader> fmt::Debug for Session<L> {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 f.debug_struct("Session")
105 .field("revision", &self.editor.revision())
106 .field("typesetter", &self.typesetter)
107 .field("mode", &self.mode)
108 .finish_non_exhaustive()
109 }
110}
111
112impl<L: FontLoader> Session<L> {
113 pub fn new(typesetter: Typesetter<L>) -> Self {
115 Self {
116 editor: Editor::new(),
117 keymap: Keymap::new(),
118 typesetter,
119 mode: MathMode::Display,
120 policy: HostBoxPolicy::Skip,
121 cache: RenderCache::new(),
122 history: UndoStack::default(),
123 tokens: TokenRegistry::new(),
124 clipboard: None,
125 boxes: SessionBoxes::default(),
126 }
127 }
128
129 pub fn load(&mut self, mut doc: Document) -> Vec<Repair> {
131 let repairs = doc.repair();
132 if let Ok(mut editor) = Editor::from_document(&doc) {
134 editor.set_host_box_policy(self.policy);
135 self.editor = editor;
136 }
137 self.tokens.reserve_document(&doc);
138 self.keymap.reset();
139 self.history.clear();
140 self.cache.invalidate();
141 repairs
142 }
143
144 pub fn editor(&self) -> &Editor {
146 &self.editor
147 }
148
149 pub fn keymap(&self) -> &Keymap {
151 &self.keymap
152 }
153
154 pub fn keymap_mut(&mut self) -> &mut Keymap {
156 &mut self.keymap
157 }
158
159 pub fn typesetter(&self) -> &Typesetter<L> {
161 &self.typesetter
162 }
163
164 pub fn history(&self) -> &UndoStack {
166 &self.history
167 }
168
169 pub fn set_undo_limit(&mut self, limit: usize) {
171 self.history.set_limit(limit);
172 }
173
174 pub fn set_math_mode(&mut self, mode: MathMode) {
176 self.mode = mode;
177 self.cache.invalidate();
178 }
179
180 pub fn set_host_box_policy(&mut self, policy: HostBoxPolicy) {
182 self.policy = policy;
183 self.editor.set_host_box_policy(policy);
184 }
185
186 pub fn key(&mut self, input: &KeyInput) -> Update {
188 let ctx = self.editor.input_context();
189 let commands = self.keymap.map_key(input, &ctx);
190 self.run(commands)
191 }
192
193 pub fn text(&mut self, s: &str) -> Update {
195 let ctx = self.editor.input_context();
196 let commands = self.keymap.map_text(s, &ctx);
197 self.run(commands)
198 }
199
200 pub fn pointer(&mut self, at: Point, extend: bool) -> Update {
202 self.keymap.reset();
203 let hit = self.cache.hit_test(&self.editor, &mut self.typesetter, self.mode, &self.boxes, at);
204 match hit {
205 Ok(Some(path)) if extend => self.run(vec![Command::ExtendTo(path)]),
206 Ok(Some(path)) => self.run(vec![Command::MoveTo(path)]),
207 _ => Update::default(),
208 }
209 }
210
211 pub fn command(&mut self, cmd: Command) -> Update {
213 self.keymap.reset();
214 match &cmd {
215 Command::InsertHostBox(token) => {
216 let _ = self.tokens.reserve(*token);
217 }
218 Command::InsertDocument(doc) => self.tokens.reserve_document(doc),
219 _ => {}
220 }
221 self.run(vec![cmd])
222 }
223
224 pub fn commit_word(&mut self, word: &str) -> Update {
226 let Some(commands) = self.keymap.commands_for_word(word) else {
227 return Update::default();
228 };
229 self.keymap.reset();
230 self.run(commands)
231 }
232
233 pub fn step_over_host_box(&mut self, entry: HostBoxEntry) -> Update {
235 let dir = match entry.side {
236 Side::Before => Dir::Right,
237 Side::After => Dir::Left,
238 };
239 self.editor.set_host_box_policy(HostBoxPolicy::Skip);
240 let update = self.command(Command::Move(dir));
241 self.editor.set_host_box_policy(self.policy);
242 update
243 }
244
245 pub fn undo(&mut self) -> Update {
247 self.keymap.reset();
248 let Some(target) = self.history.undo(self.editor.snapshot()) else {
249 return Update::default();
250 };
251 self.restore(&target)
252 }
253
254 pub fn redo(&mut self) -> Update {
256 self.keymap.reset();
257 let Some(target) = self.history.redo(self.editor.snapshot()) else {
258 return Update::default();
259 };
260 self.restore(&target)
261 }
262
263 fn restore(&mut self, target: &mathtex_editor_core::Snapshot) -> Update {
264 let changed = self.editor.restore(target).is_ok();
266 Update { changed, needs_redraw: changed, ..Update::default() }
267 }
268
269 pub fn copy(&mut self) -> Option<ClipboardData> {
271 let doc = self.editor.selection_document()?;
272 let tex = self.editor.selection_tex()?;
273 let json = serde_json::to_string(&doc).ok()?;
274 self.clipboard = Some(doc);
275 Some(ClipboardData { json, tex })
276 }
277
278 pub fn cut(&mut self) -> Option<(ClipboardData, Update)> {
280 let data = self.copy()?;
281 let update = self.command(Command::DeleteBackward);
282 Some((data, update))
283 }
284
285 pub fn clipboard(&self) -> Option<&Document> {
287 self.clipboard.as_ref()
288 }
289
290 pub fn paste_json(&mut self, json: &str) -> Result<Update, PasteError> {
292 let doc: Document = serde_json::from_str(json).map_err(|e| PasteError::Json(e.to_string()))?;
293 if doc.is_empty() {
294 return Err(PasteError::Empty);
295 }
296 self.paste_document(doc).map_err(PasteError::Tokens)
297 }
298
299 pub fn paste_internal(&mut self) -> Result<Update, TokenError> {
301 match self.clipboard.clone() {
302 Some(doc) => self.paste_document(doc),
303 None => Ok(Update::default()),
304 }
305 }
306
307 pub fn paste_text(&mut self, s: &str) -> Update {
309 self.text(s)
310 }
311
312 fn paste_document(&mut self, mut doc: Document) -> Result<Update, TokenError> {
313 let reminted = self.tokens.remint(&mut doc)?;
314 for &(old, new) in &reminted {
315 self.boxes.copy_size(old, new);
316 }
317 self.keymap.reset();
318 let mut update = self.run(vec![Command::InsertDocument(doc)]);
319 if update.changed {
320 update.reminted = reminted;
321 }
322 Ok(update)
323 }
324
325 pub fn insert_host_box(&mut self) -> Result<(u32, Update), TokenError> {
327 let token = self.tokens.mint()?;
328 Ok((token, self.command(Command::InsertHostBox(token))))
329 }
330
331 pub fn mint_host_token(&mut self) -> Result<u32, TokenError> {
333 self.tokens.mint()
334 }
335
336 pub fn set_host_box_size(&mut self, token: u32, width: Length, height: Length, depth: Length) -> Result<(), TokenError> {
338 self.tokens.reserve(token)?;
339 self.boxes.set_size(token, width, height, depth);
340 self.cache.invalidate();
341 Ok(())
342 }
343
344 pub fn set_host_boxes(&mut self, provider: Box<dyn HostBoxes>) {
346 self.boxes.set_provider(Some(provider));
347 self.cache.invalidate();
348 }
349
350 pub fn host_boxes_changed(&mut self) {
352 self.cache.invalidate();
353 }
354
355 pub fn live_host_tokens(&mut self) -> BTreeSet<u32> {
357 let live = TokenRegistry::live(&self.editor, &self.history, self.clipboard.as_ref());
358 self.boxes.retain(&live);
359 live
360 }
361
362 pub fn view(&mut self) -> Result<&View, TypesetError> {
364 self.cache.view(&self.editor, &mut self.typesetter, self.mode, &self.boxes)
365 }
366
367 pub fn preview_word(&mut self, word: &str) -> Option<Result<View, TypesetError>> {
369 let commands = self.keymap.commands_for_word(word)?;
370 let mut scratch = Editor::new();
371 for cmd in commands {
372 let _ = scratch.exec(cmd);
373 }
374 let mut cache = RenderCache::new();
375 Some(cache.view(&scratch, &mut self.typesetter, self.mode, &self.boxes).cloned())
376 }
377
378 fn run(&mut self, commands: Vec<Command>) -> Update {
380 if commands.is_empty() {
381 return Update::default();
382 }
383 let before = self.editor.snapshot();
384 let seen = self.visible();
385 let mut update = Update::default();
386 for cmd in commands {
387 let out = self.editor.exec(cmd);
388 update.changed |= out.changed;
389 update.close |= out.close;
390 update.exit = update.exit.or(out.exit);
391 update.entered_host_box = update.entered_host_box.or(out.entered_host_box);
392 }
393 if update.changed {
394 self.history.record(before);
395 }
396 update.needs_redraw = update.changed || self.visible() != seen;
397 update
398 }
399
400 fn visible(&self) -> Visible {
401 Visible {
402 revision: self.editor.revision(),
403 cursor: self.editor.cursor(),
404 selection: self.editor.selection(),
405 menu: self.editor.menu(),
406 }
407 }
408}