use std::{ convert::Infallible, fmt::{self, Display}, iter::repeat_n, }; use egui::{ Color32, InputState, Key, Modifiers, TextBuffer, TextEdit, Ui, Vec2, text::CCursorRange, }; use crate::easy_mark::MemoizedHighlighter; #[derive(Default, serde::Deserialize, serde::Serialize)] pub struct MdTextEdit { pub text: String, #[serde(skip)] highlighter: MemoizedHighlighter, #[serde(skip)] focused: bool, #[serde(skip)] cursor: Option, } pub struct MdTextEditOutput { pub changed: bool, } impl MdTextEdit { pub fn new() -> Self { MdTextEdit::default() } pub fn from_text(text: String) -> Self { MdTextEdit { text, ..Default::default() } } pub fn ui(&mut self, ui: &mut Ui) -> MdTextEditOutput { let Self { text, highlighter, focused, cursor, } = self; let w = ui.available_width(); let mut layouter = |ui: &egui::Ui, easymark: &dyn TextBuffer, _wrap_width: f32| { let mut layout_job = highlighter.highlight(ui.style(), easymark.as_str(), *cursor); layout_job.wrap.max_width = w - 10.0; ui.fonts(|f| f.layout_job(layout_job)) }; if *focused { ui.input_mut(|input| { handle_tab_input(text, input, *cursor); }); } let text_edit = TextEdit::multiline(text) .layouter(&mut layouter) .background_color(Color32::TRANSPARENT) .desired_rows(1) .min_size(Vec2::new(w, 0.0)) .lock_focus(true) .show(ui); *focused = text_edit.response.has_focus(); if *cursor != text_edit.cursor_range { *cursor = text_edit.cursor_range; //ui.ctx().request_repaint(); } MdTextEditOutput { changed: text_edit.response.changed(), } } } fn handle_tab_input( text: &mut String, input: &mut InputState, cursor: Option, ) -> Option { let break_if_not = || (); let cursor = cursor.and_then(|c| c.single())?; let do_unindent = input.consume_key(Modifiers::SHIFT, Key::Tab); let do_indent = input.consume_key(Modifiers::NONE, Key::Tab); (do_unindent || do_indent).then(break_if_not)?; let row_n = text .chars() .take(cursor.index) .filter(|&c| c == '\n') .count(); let row = text.lines().nth(row_n)?; let (indent, content) = row.split_once("- ")?; indent.trim().is_empty().then(break_if_not)?; let indents = indent.chars().count() / 2; let indents = if do_indent { indents + 1 } else if indents == 0 { return None; } else { indents.saturating_sub(1) }; *text = text .lines() .take(row_n) .flat_map(|line| [line, "\n"]) .chain(repeat_n(" ", indents)) .chain([format!("- {content}\n").as_str()]) .chain(text.lines().skip(row_n + 1).flat_map(|line| [line, "\n"])) .collect(); None } impl Display for MdTextEdit { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.text) } }