Files
inkr/src/text_editor.rs

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::markdown::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, markdown: &dyn TextBuffer, _wrap_width: f32| {
let mut layout_job = highlighter.highlight(ui.style(), markdown.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)
}
}