Implement theme selector in editor example

This commit is contained in:
Héctor Ramón Jiménez 2023-09-18 14:38:54 +02:00
parent 61ef8f3249
commit 8446fe6de5
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
3 changed files with 100 additions and 23 deletions

View file

@ -1,10 +1,9 @@
use crate::Color; use crate::Color;
use std::hash::Hash;
use std::ops::Range; use std::ops::Range;
pub trait Highlighter: 'static { pub trait Highlighter: 'static {
type Settings: Hash; type Settings: PartialEq + Clone;
type Highlight; type Highlight;
type Iterator<'a>: Iterator<Item = (Range<usize>, Self::Highlight)> type Iterator<'a>: Iterator<Item = (Range<usize>, Self::Highlight)>
@ -13,6 +12,8 @@ pub trait Highlighter: 'static {
fn new(settings: &Self::Settings) -> Self; fn new(settings: &Self::Settings) -> Self;
fn update(&mut self, new_settings: &Self::Settings);
fn change_line(&mut self, line: usize); fn change_line(&mut self, line: usize);
fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_>; fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_>;
@ -38,6 +39,8 @@ impl Highlighter for PlainText {
Self Self
} }
fn update(&mut self, _new_settings: &Self::Settings) {}
fn change_line(&mut self, _line: usize) {} fn change_line(&mut self, _line: usize) {}
fn highlight_line(&mut self, _line: &str) -> Self::Iterator<'_> { fn highlight_line(&mut self, _line: &str) -> Self::Iterator<'_> {

View file

@ -1,5 +1,5 @@
use iced::widget::{container, text_editor}; use iced::widget::{column, horizontal_space, pick_list, row, text_editor};
use iced::{Element, Font, Sandbox, Settings, Theme}; use iced::{Element, Font, Length, Sandbox, Settings, Theme};
use highlighter::Highlighter; use highlighter::Highlighter;
@ -9,11 +9,13 @@ pub fn main() -> iced::Result {
struct Editor { struct Editor {
content: text_editor::Content, content: text_editor::Content,
theme: highlighter::Theme,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum Message { enum Message {
Edit(text_editor::Action), Edit(text_editor::Action),
ThemeSelected(highlighter::Theme),
} }
impl Sandbox for Editor { impl Sandbox for Editor {
@ -21,9 +23,8 @@ impl Sandbox for Editor {
fn new() -> Self { fn new() -> Self {
Self { Self {
content: text_editor::Content::with(include_str!( content: text_editor::Content::with(include_str!("main.rs")),
"../../../README.md" theme: highlighter::Theme::SolarizedDark,
)),
} }
} }
@ -36,18 +37,33 @@ impl Sandbox for Editor {
Message::Edit(action) => { Message::Edit(action) => {
self.content.edit(action); self.content.edit(action);
} }
Message::ThemeSelected(theme) => {
self.theme = theme;
}
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<Message> {
container( column![
row![
horizontal_space(Length::Fill),
pick_list(
highlighter::Theme::ALL,
Some(self.theme),
Message::ThemeSelected
)
.padding([5, 10])
]
.spacing(10),
text_editor(&self.content) text_editor(&self.content)
.on_edit(Message::Edit) .on_edit(Message::Edit)
.font(Font::with_name("Hasklug Nerd Font Mono")) .font(Font::with_name("Hasklug Nerd Font Mono"))
.highlight::<Highlighter>(highlighter::Settings { .highlight::<Highlighter>(highlighter::Settings {
token: String::from("md"), theme: self.theme,
extension: String::from("rs"),
}), }),
) ]
.spacing(10)
.padding(20) .padding(20)
.into() .into()
} }
@ -60,21 +76,52 @@ impl Sandbox for Editor {
mod highlighter { mod highlighter {
use iced::advanced::text::highlighter; use iced::advanced::text::highlighter;
use iced::widget::text_editor; use iced::widget::text_editor;
use iced::{Color, Font, Theme}; use iced::{Color, Font};
use std::ops::Range; use std::ops::Range;
use syntect::highlighting; use syntect::highlighting;
use syntect::parsing::{self, SyntaxReference}; use syntect::parsing::{self, SyntaxReference};
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, PartialEq)]
pub struct Settings { pub struct Settings {
pub token: String, pub theme: Theme,
pub extension: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Theme {
SolarizedDark,
InspiredGitHub,
Base16Mocha,
}
impl Theme {
pub const ALL: &[Self] =
&[Self::SolarizedDark, Self::InspiredGitHub, Self::Base16Mocha];
fn key(&self) -> &'static str {
match self {
Theme::InspiredGitHub => "InspiredGitHub",
Theme::Base16Mocha => "base16-mocha.dark",
Theme::SolarizedDark => "Solarized (dark)",
}
}
}
impl std::fmt::Display for Theme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Theme::InspiredGitHub => write!(f, "Inspired GitHub"),
Theme::Base16Mocha => write!(f, "Mocha"),
Theme::SolarizedDark => write!(f, "Solarized Dark"),
}
}
} }
pub struct Highlight(highlighting::StyleModifier); pub struct Highlight(highlighting::StyleModifier);
impl text_editor::Highlight for Highlight { impl text_editor::Highlight for Highlight {
fn format(&self, _theme: &Theme) -> highlighter::Format<Font> { fn format(&self, _theme: &iced::Theme) -> highlighter::Format<Font> {
highlighter::Format { highlighter::Format {
color: self.0.foreground.map(|color| { color: self.0.foreground.map(|color| {
Color::from_rgba8( Color::from_rgba8(
@ -92,8 +139,8 @@ mod highlighter {
pub struct Highlighter { pub struct Highlighter {
syntaxes: parsing::SyntaxSet, syntaxes: parsing::SyntaxSet,
syntax: SyntaxReference, syntax: SyntaxReference,
caches: Vec<(parsing::ParseState, parsing::ScopeStack)>,
theme: highlighting::Theme, theme: highlighting::Theme,
caches: Vec<(parsing::ParseState, parsing::ScopeStack)>,
current_line: usize, current_line: usize,
} }
@ -110,26 +157,42 @@ mod highlighter {
let syntaxes = parsing::SyntaxSet::load_defaults_nonewlines(); let syntaxes = parsing::SyntaxSet::load_defaults_nonewlines();
let syntax = syntaxes let syntax = syntaxes
.find_syntax_by_token(&settings.token) .find_syntax_by_token(&settings.extension)
.unwrap_or_else(|| syntaxes.find_syntax_plain_text()); .unwrap_or_else(|| syntaxes.find_syntax_plain_text());
let theme = highlighting::ThemeSet::load_defaults()
.themes
.remove(settings.theme.key())
.unwrap();
let parser = parsing::ParseState::new(syntax); let parser = parsing::ParseState::new(syntax);
let stack = parsing::ScopeStack::new(); let stack = parsing::ScopeStack::new();
let theme = highlighting::ThemeSet::load_defaults()
.themes
.remove("base16-mocha.dark")
.unwrap();
Highlighter { Highlighter {
syntax: syntax.clone(), syntax: syntax.clone(),
syntaxes, syntaxes,
caches: vec![(parser, stack)],
theme, theme,
caches: vec![(parser, stack)],
current_line: 0, current_line: 0,
} }
} }
fn update(&mut self, new_settings: &Self::Settings) {
self.syntax = self
.syntaxes
.find_syntax_by_token(&new_settings.extension)
.unwrap_or_else(|| self.syntaxes.find_syntax_plain_text())
.clone();
self.theme = highlighting::ThemeSet::load_defaults()
.themes
.remove(new_settings.theme.key())
.unwrap();
// Restart the highlighter
self.change_line(0);
}
fn change_line(&mut self, line: usize) { fn change_line(&mut self, line: usize) {
let snapshot = line / LINES_PER_SNAPSHOT; let snapshot = line / LINES_PER_SNAPSHOT;

View file

@ -193,11 +193,12 @@ where
} }
} }
struct State<Highlighter> { struct State<Highlighter: text::Highlighter> {
is_focused: bool, is_focused: bool,
last_click: Option<mouse::Click>, last_click: Option<mouse::Click>,
drag_click: Option<mouse::click::Kind>, drag_click: Option<mouse::click::Kind>,
highlighter: RefCell<Highlighter>, highlighter: RefCell<Highlighter>,
highlighter_settings: Highlighter::Settings,
} }
impl<'a, Highlighter, Message, Renderer> Widget<Message, Renderer> impl<'a, Highlighter, Message, Renderer> Widget<Message, Renderer>
@ -220,6 +221,7 @@ where
highlighter: RefCell::new(Highlighter::new( highlighter: RefCell::new(Highlighter::new(
&self.highlighter_settings, &self.highlighter_settings,
)), )),
highlighter_settings: self.highlighter_settings.clone(),
}) })
} }
@ -240,6 +242,15 @@ where
let mut internal = self.content.0.borrow_mut(); let mut internal = self.content.0.borrow_mut();
let state = tree.state.downcast_mut::<State<Highlighter>>(); let state = tree.state.downcast_mut::<State<Highlighter>>();
if state.highlighter_settings != self.highlighter_settings {
state
.highlighter
.borrow_mut()
.update(&self.highlighter_settings);
state.highlighter_settings = self.highlighter_settings.clone();
}
internal.editor.update( internal.editor.update(
limits.pad(self.padding).max(), limits.pad(self.padding).max(),
self.font.unwrap_or_else(|| renderer.default_font()), self.font.unwrap_or_else(|| renderer.default_font()),