use iced::widget::{container, text_editor}; use iced::{Element, Font, Sandbox, Settings, Theme}; use highlighter::Highlighter; pub fn main() -> iced::Result { Editor::run(Settings::default()) } struct Editor { content: text_editor::Content, } #[derive(Debug, Clone)] enum Message { Edit(text_editor::Action), } impl Sandbox for Editor { type Message = Message; fn new() -> Self { Self { content: text_editor::Content::with(include_str!( "../../../README.md" )), } } fn title(&self) -> String { String::from("Editor - Iced") } fn update(&mut self, message: Message) { match message { Message::Edit(action) => { self.content.edit(action); } } } fn view(&self) -> Element { container( text_editor(&self.content) .on_edit(Message::Edit) .font(Font::with_name("Hasklug Nerd Font Mono")) .highlight::(highlighter::Settings { token: String::from("md"), }), ) .padding(20) .into() } fn theme(&self) -> Theme { Theme::Dark } } mod highlighter { use iced::advanced::text::highlighter; use iced::widget::text_editor; use iced::{Color, Font, Theme}; use std::ops::Range; use syntect::highlighting; use syntect::parsing::{self, SyntaxReference}; #[derive(Debug, Clone, Hash)] pub struct Settings { pub token: String, } pub struct Highlight(highlighting::StyleModifier); impl text_editor::Highlight for Highlight { fn format(&self, _theme: &Theme) -> highlighter::Format { highlighter::Format { color: self.0.foreground.map(|color| { Color::from_rgba8( color.r, color.g, color.b, color.a as f32 / 255.0, ) }), font: None, } } } pub struct Highlighter { syntaxes: parsing::SyntaxSet, syntax: SyntaxReference, caches: Vec<(parsing::ParseState, parsing::ScopeStack)>, theme: highlighting::Theme, current_line: usize, } const LINES_PER_SNAPSHOT: usize = 50; impl highlighter::Highlighter for Highlighter { type Settings = Settings; type Highlight = Highlight; type Iterator<'a> = Box, Self::Highlight)> + 'a>; fn new(settings: &Self::Settings) -> Self { let syntaxes = parsing::SyntaxSet::load_defaults_nonewlines(); let syntax = syntaxes .find_syntax_by_token(&settings.token) .unwrap_or_else(|| syntaxes.find_syntax_plain_text()); let parser = parsing::ParseState::new(&syntax); let stack = parsing::ScopeStack::new(); let theme = highlighting::ThemeSet::load_defaults() .themes .remove("base16-mocha.dark") .unwrap(); Highlighter { syntax: syntax.clone(), syntaxes, caches: vec![(parser, stack)], theme, current_line: 0, } } fn change_line(&mut self, line: usize) { let snapshot = line / LINES_PER_SNAPSHOT; if snapshot <= self.caches.len() { self.caches.truncate(snapshot); self.current_line = snapshot * LINES_PER_SNAPSHOT; } else { self.caches.truncate(1); self.current_line = 0; } let (parser, stack) = self.caches.last().cloned().unwrap_or_else(|| { ( parsing::ParseState::new(&self.syntax), parsing::ScopeStack::new(), ) }); self.caches.push((parser, stack)); } fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_> { if self.current_line / LINES_PER_SNAPSHOT >= self.caches.len() { let (parser, stack) = self.caches.last().expect("Caches must not be empty"); self.caches.push((parser.clone(), stack.clone())); } self.current_line += 1; let (parser, stack) = self.caches.last_mut().expect("Caches must not be empty"); let ops = parser.parse_line(line, &self.syntaxes).unwrap_or_default(); let highlighter = highlighting::Highlighter::new(&self.theme); Box::new( ScopeRangeIterator { ops, line_length: line.len(), index: 0, last_str_index: 0, } .filter_map(move |(range, scope)| { let _ = stack.apply(&scope); if range.is_empty() { None } else { Some(( range, Highlight( highlighter.style_mod_for_stack(&stack.scopes), ), )) } }), ) } fn current_line(&self) -> usize { self.current_line } } pub struct ScopeRangeIterator { ops: Vec<(usize, parsing::ScopeStackOp)>, line_length: usize, index: usize, last_str_index: usize, } impl Iterator for ScopeRangeIterator { type Item = (std::ops::Range, parsing::ScopeStackOp); fn next(&mut self) -> Option { if self.index > self.ops.len() { return None; } let next_str_i = if self.index == self.ops.len() { self.line_length } else { self.ops[self.index].0 }; let range = self.last_str_index..next_str_i; self.last_str_index = next_str_i; let op = if self.index == 0 { parsing::ScopeStackOp::Noop } else { self.ops[self.index - 1].1.clone() }; self.index += 1; Some((range, op)) } } }