134 lines
3.2 KiB
Rust
134 lines
3.2 KiB
Rust
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<CCursorRange>,
|
|
}
|
|
|
|
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<CCursorRange>,
|
|
) -> Option<Infallible> {
|
|
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)
|
|
}
|
|
}
|