merge commit

This commit is contained in:
2025-06-17 23:50:16 +01:00
25 changed files with 5387 additions and 102 deletions
Submodule dsa_editor deleted from 1566784e20
+3985
View File
File diff suppressed because it is too large Load Diff
+23
View File
@@ -0,0 +1,23 @@
[package]
name = "dsa_editor"
version = "0.1.0"
edition = "2024"
description = "a fork of a code editor egui widget adapted to work with DSA syntax."
[dependencies]
egui = { version = "0.31", optional = true }
serde = { version = "1", optional = true }
[lib]
name = "dsa_editor"
path = "src/lib.rs"
[features]
default = ["egui", "editor"]
egui = ["dep:egui"]
editor = []
serde = ["dep:serde"]
[dev-dependencies]
eframe = "0.31"
colorful = "0.3"
+253
View File
@@ -0,0 +1,253 @@
#[cfg(feature = "editor")]
use super::Editor;
use super::syntax::{Syntax, TokenType, QUOTES, SEPARATORS};
use std::mem;
#[derive(Default, Debug, PartialEq, PartialOrd, Eq, Ord)]
/// Lexer and Token
pub struct Token {
ty: TokenType,
buffer: String,
}
impl Token {
pub fn new<S: Into<String>>(ty: TokenType, buffer: S) -> Self {
Token {
ty,
buffer: buffer.into(),
}
}
pub fn ty(&self) -> TokenType {
self.ty
}
pub fn buffer(&self) -> &str {
&self.buffer
}
fn first(&mut self, c: char, syntax: &Syntax) -> Option<Self> {
self.buffer.push(c);
let mut token = None;
self.ty = match c {
c if c.is_whitespace() => {
self.ty = TokenType::Whitespace(c);
token = self.drain(self.ty);
TokenType::Whitespace(c)
}
c if syntax.is_keyword(c.to_string().as_str()) => TokenType::Keyword,
c if syntax.is_type(c.to_string().as_str()) => TokenType::Type,
c if syntax.is_special(c.to_string().as_str()) => TokenType::Special,
c if syntax.comment == c.to_string().as_str() => TokenType::Comment(false),
c if syntax.comment_multiline[0] == c.to_string().as_str() => {
TokenType::Comment(true)
}
_ => TokenType::from(c),
};
token
}
fn drain(&mut self, ty: TokenType) -> Option<Self> {
let mut token = None;
if !self.buffer().is_empty() {
token = Some(Token {
buffer: mem::take(&mut self.buffer),
ty: self.ty,
});
}
self.ty = ty;
token
}
fn push_drain(&mut self, c: char, ty: TokenType) -> Option<Self> {
self.buffer.push(c);
self.drain(ty)
}
fn drain_push(&mut self, c: char, ty: TokenType) -> Option<Self> {
let token = self.drain(self.ty);
self.buffer.push(c);
self.ty = ty;
token
}
#[cfg(feature = "egui")]
/// Syntax highlighting
pub fn highlight<T: Editor>(&mut self, editor: &T, text: &str) -> LayoutJob {
*self = Token::default();
let mut job = LayoutJob::default();
for c in text.chars() {
for token in self.automata(c, editor.syntax()) {
editor.append(&mut job, &token);
}
}
editor.append(&mut job, self);
job
}
/// Lexer
pub fn tokens(&mut self, syntax: &Syntax, text: &str) -> Vec<Self> {
let mut tokens: Vec<Self> = text
.chars()
.flat_map(|c| self.automata(c, syntax))
.collect();
if !self.buffer.is_empty() {
tokens.push(mem::take(self));
}
tokens
}
fn automata(&mut self, c: char, syntax: &Syntax) -> Vec<Self> {
use TokenType as Ty;
let mut tokens = vec![];
match (self.ty, Ty::from(c)) {
(Ty::Comment(false), Ty::Whitespace('\n')) => {
self.buffer.push(c);
let n = self.buffer.pop();
tokens.extend(self.drain(Ty::Whitespace(c)));
if let Some(n) = n {
tokens.extend(self.push_drain(n, self.ty));
}
}
(Ty::Comment(false), _) => {
self.buffer.push(c);
}
(Ty::Comment(true), _) => {
self.buffer.push(c);
if self.buffer.ends_with(syntax.comment_multiline[1]) {
tokens.extend(self.drain(Ty::Unknown));
}
}
(Ty::Literal | Ty::Punctuation(_), Ty::Whitespace(_)) => {
tokens.extend(self.drain(Ty::Whitespace(c)));
tokens.extend(self.first(c, syntax));
}
(Ty::Hyperlink, Ty::Whitespace(_)) => {
tokens.extend(self.drain(Ty::Whitespace(c)));
tokens.extend(self.first(c, syntax));
}
(Ty::Hyperlink, _) => {
self.buffer.push(c);
}
(Ty::Literal, _) => match c {
c if c == '(' => {
self.ty = Ty::Function;
tokens.extend(self.drain(Ty::Punctuation(c)));
tokens.extend(self.push_drain(c, Ty::Unknown));
}
c if !c.is_alphanumeric() && !SEPARATORS.contains(&c) => {
tokens.extend(self.drain(self.ty));
self.buffer.push(c);
self.ty = if QUOTES.contains(&c) {
Ty::Str(c)
} else {
Ty::Punctuation(c)
};
}
_ => {
self.buffer.push(c);
self.ty = {
if self.buffer.starts_with(syntax.comment) {
Ty::Comment(false)
} else if self.buffer.starts_with(syntax.comment_multiline[0]) {
Ty::Comment(true)
} else if syntax.is_hyperlink(&self.buffer) {
Ty::Hyperlink
} else if syntax.is_keyword(&self.buffer) {
Ty::Keyword
} else if syntax.is_type(&self.buffer) {
Ty::Type
} else if syntax.is_special(&self.buffer) {
Ty::Special
} else {
Ty::Literal
}
};
}
},
(Ty::Numeric(false), Ty::Punctuation('.')) => {
self.buffer.push(c);
self.ty = Ty::Numeric(true);
}
(Ty::Numeric(_), Ty::Numeric(_)) => {
self.buffer.push(c);
}
(Ty::Numeric(_), Ty::Literal) => {
tokens.extend(self.drain(self.ty));
self.buffer.push(c);
}
(Ty::Numeric(_), _) | (Ty::Punctuation(_), Ty::Literal | Ty::Numeric(_)) => {
tokens.extend(self.drain(self.ty));
tokens.extend(self.first(c, syntax));
}
(Ty::Punctuation(_), Ty::Str(_)) => {
tokens.extend(self.drain_push(c, Ty::Str(c)));
}
(Ty::Punctuation(_), _) => {
if !(syntax.comment.starts_with(&self.buffer)
|| syntax.comment_multiline[0].starts_with(&self.buffer))
{
tokens.extend(self.drain(self.ty));
tokens.extend(self.first(c, syntax));
} else {
self.buffer.push(c);
if self.buffer.starts_with(syntax.comment) {
self.ty = Ty::Comment(false);
} else if self.buffer.starts_with(syntax.comment_multiline[0]) {
self.ty = Ty::Comment(true);
} else if let Some(c) = self.buffer.pop() {
tokens.extend(self.drain(Ty::Punctuation(c)));
tokens.extend(self.first(c, syntax));
}
}
}
(Ty::Str(q), _) => {
let control = self.buffer.ends_with('\\');
self.buffer.push(c);
if c == q && !control {
tokens.extend(self.drain(Ty::Unknown));
}
}
(Ty::Whitespace(_) | Ty::Unknown, _) => {
tokens.extend(self.first(c, syntax));
}
// Keyword, Type, Special
(_reserved, Ty::Literal | Ty::Numeric(_)) => {
self.buffer.push(c);
self.ty = if syntax.is_keyword(&self.buffer) {
Ty::Keyword
} else if syntax.is_type(&self.buffer) {
Ty::Type
} else if syntax.is_special(&self.buffer) {
Ty::Special
} else {
Ty::Literal
};
}
(reserved, _) => {
self.ty = reserved;
tokens.extend(self.drain(self.ty));
tokens.extend(self.first(c, syntax));
}
}
tokens
}
}
#[cfg(feature = "egui")]
use egui::text::LayoutJob;
#[cfg(feature = "egui")]
impl<T: Editor> egui::util::cache::ComputerMut<(&T, &str), LayoutJob> for Token {
fn compute(&mut self, (cache, text): (&T, &str)) -> LayoutJob {
self.highlight(cache, text)
}
}
#[cfg(feature = "egui")]
pub type HighlightCache = egui::util::cache::FrameCache<LayoutJob, Token>;
#[cfg(feature = "egui")]
pub fn highlight<T: Editor>(ctx: &egui::Context, cache: &T, text: &str) -> LayoutJob {
ctx.memory_mut(|mem| mem.caches.cache::<HighlightCache>().get((cache, text)))
}
+296
View File
@@ -0,0 +1,296 @@
pub mod highlighting;
mod syntax;
mod themes;
#[cfg(feature = "egui")]
use egui::text::LayoutJob;
#[cfg(feature = "egui")]
use egui::widgets::text_edit::TextEditOutput;
pub use highlighting::Token;
#[cfg(feature = "egui")]
use highlighting::highlight;
#[cfg(feature = "editor")]
use std::hash::{Hash, Hasher};
pub use syntax::{Syntax, TokenType};
pub use themes::ColorTheme;
pub use themes::DEFAULT_THEMES;
#[cfg(feature = "egui")]
pub trait Editor: Hash {
fn append(&self, job: &mut LayoutJob, token: &Token);
fn syntax(&self) -> &Syntax;
}
#[cfg(feature = "editor")]
#[derive(Clone, Debug, PartialEq)]
/// CodeEditor struct which stores settings for highlighting.
pub struct CodeEditor {
id: String,
theme: ColorTheme,
syntax: Syntax,
numlines: bool,
numlines_shift: isize,
numlines_only_natural: bool,
fontsize: f32,
rows: usize,
stick_to_bottom: bool,
desired_width: f32,
}
#[cfg(feature = "editor")]
impl Hash for CodeEditor {
fn hash<H: Hasher>(&self, state: &mut H) {
self.theme.hash(state);
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
(self.fontsize as u32).hash(state);
self.syntax.hash(state);
}
}
#[cfg(feature = "editor")]
impl Default for CodeEditor {
fn default() -> CodeEditor {
CodeEditor {
id: String::from("Code Editor"),
theme: ColorTheme::THEME,
syntax: Syntax::dsa(),
numlines: true,
numlines_shift: 0,
numlines_only_natural: false,
fontsize: 10.0,
rows: 10,
stick_to_bottom: false,
desired_width: f32::INFINITY,
}
}
}
#[cfg(feature = "editor")]
impl CodeEditor {
pub fn id_source(self, id_source: impl Into<String>) -> Self {
CodeEditor {
id: id_source.into(),
..self
}
}
/// Minimum number of rows to show.
///
/// **Default: 10**
pub fn with_rows(self, rows: usize) -> Self {
CodeEditor { rows, ..self }
}
/// Use custom Color Theme
///
/// **Default: Gruvbox**
pub fn with_theme(self, theme: ColorTheme) -> Self {
CodeEditor { theme, ..self }
}
/// Use custom font size
///
/// **Default: 10.0**
pub fn with_fontsize(self, fontsize: f32) -> Self {
CodeEditor { fontsize, ..self }
}
#[cfg(feature = "egui")]
/// Use UI font size
pub fn with_ui_fontsize(self, ui: &mut egui::Ui) -> Self {
CodeEditor {
fontsize: egui::TextStyle::Monospace.resolve(ui.style()).size,
..self
}
}
/// Show or hide lines numbering
///
/// **Default: true**
pub fn with_numlines(self, numlines: bool) -> Self {
CodeEditor { numlines, ..self }
}
/// Shift lines numbering by this value
///
/// **Default: 0**
pub fn with_numlines_shift(self, numlines_shift: isize) -> Self {
CodeEditor {
numlines_shift,
..self
}
}
/// Show lines numbering only above zero, useful for enabling numbering since nth row
///
/// **Default: false**
pub fn with_numlines_only_natural(self, numlines_only_natural: bool) -> Self {
CodeEditor {
numlines_only_natural,
..self
}
}
/// Use custom syntax for highlighting
///
/// **Default: Rust**
pub fn with_syntax(self, syntax: Syntax) -> Self {
CodeEditor { syntax, ..self }
}
/// Should the containing area shrink if the content is small?
///
/// **Default: false**
pub fn auto_shrink(self, shrink: bool) -> Self {
CodeEditor {
desired_width: if shrink { 0.0 } else { self.desired_width },
..self
}
}
/// Sets the desired width of the code editor
///
/// **Default: `f32::INFINITY`**
pub fn desired_width(self, width: f32) -> Self {
CodeEditor {
desired_width: width,
..self
}
}
/// Stick to bottom
/// The scroll handle will stick to the bottom position even while the content size
/// changes dynamically. This can be useful to simulate terminal UIs or log/info scrollers.
/// The scroll handle remains stuck until user manually changes position. Once "unstuck"
/// it will remain focused on whatever content viewport the user left it on. If the scroll
/// handle is dragged to the bottom it will again become stuck and remain there until manually
/// pulled from the end position.
///
/// **Default: false**
pub fn stick_to_bottom(self, stick_to_bottom: bool) -> Self {
CodeEditor {
stick_to_bottom,
..self
}
}
#[cfg(feature = "egui")]
pub fn format(&self, ty: TokenType) -> egui::text::TextFormat {
let font_id = egui::FontId::monospace(self.fontsize);
let color = self.theme.type_color(ty);
egui::text::TextFormat::simple(font_id, color)
}
#[cfg(feature = "egui")]
fn numlines_show(&self, ui: &mut egui::Ui, text: &str) {
let total = if text.ends_with('\n') || text.is_empty() {
text.lines().count() + 1
} else {
text.lines().count()
}
.max(self.rows) as isize;
let max_indent = total.to_string().len().max(
!self.numlines_only_natural as usize * self.numlines_shift.to_string().len(),
);
let mut counter = (1..=total)
.map(|i| {
let num = i + self.numlines_shift;
if num <= 0 && self.numlines_only_natural {
String::new()
} else {
let label = num.to_string();
format!(
"{}{label}",
" ".repeat(max_indent.saturating_sub(label.len()))
)
}
})
.collect::<Vec<String>>()
.join("\n");
#[allow(clippy::cast_precision_loss)]
let width = max_indent as f32
* self.fontsize
* 0.5
* !(total + self.numlines_shift <= 0 && self.numlines_only_natural) as u8
as f32;
let mut layouter = |ui: &egui::Ui, string: &str, _wrap_width: f32| {
let layout_job = egui::text::LayoutJob::single_section(
string.to_string(),
egui::TextFormat::simple(
egui::FontId::monospace(self.fontsize),
self.theme.type_color(TokenType::Comment(true)),
),
);
ui.fonts(|f| f.layout_job(layout_job))
};
ui.add(
egui::TextEdit::multiline(&mut counter)
.id_source(format!("{}_numlines", self.id))
.font(egui::TextStyle::Monospace)
.interactive(false)
.frame(false)
.desired_rows(self.rows)
.desired_width(width)
.layouter(&mut layouter),
);
}
#[cfg(feature = "egui")]
/// Show Code Editor
pub fn show(
&mut self,
ui: &mut egui::Ui,
text: &mut dyn egui::TextBuffer,
) -> TextEditOutput {
let mut text_edit_output: Option<TextEditOutput> = None;
let mut code_editor = |ui: &mut egui::Ui| {
ui.horizontal_top(|h| {
self.theme.modify_style(h, self.fontsize);
if self.numlines {
self.numlines_show(h, text.as_str());
}
egui::ScrollArea::horizontal()
.hscroll(true)
.id_salt(format!("{}_inner_scroll", self.id))
.show(h, |ui| {
let mut layouter =
|ui: &egui::Ui, string: &str, _wrap_width: f32| {
let layout_job = highlight(ui.ctx(), self, string);
ui.fonts(|f| f.layout_job(layout_job))
};
let output = egui::TextEdit::multiline(text)
.id_source(&self.id)
.lock_focus(true)
.desired_rows(self.rows)
.frame(false)
.desired_width(self.desired_width)
.layouter(&mut layouter)
.show(ui);
text_edit_output = Some(output);
});
});
};
egui::ScrollArea::vertical()
.id_salt(format!("{}_outer_scroll", self.id))
.stick_to_bottom(self.stick_to_bottom)
.show(ui, code_editor);
text_edit_output.expect("TextEditOutput should exist at this point")
}
}
#[cfg(feature = "editor")]
#[cfg(feature = "egui")]
impl Editor for CodeEditor {
fn append(&self, job: &mut LayoutJob, token: &Token) {
job.append(token.buffer(), 0.0, self.format(token.ty()));
}
fn syntax(&self) -> &Syntax {
&self.syntax
}
}
+29
View File
@@ -0,0 +1,29 @@
use super::Syntax;
use std::collections::BTreeSet;
impl Syntax {
pub fn dsa() -> Self {
Syntax {
language: "Assembly",
case_sensitive: false,
comment: "//",
comment_multiline: ["/*", "*/"],
hyperlinks: BTreeSet::from(["http"]),
keywords: BTreeSet::from([
"nop", "mov", "movs", "ldb", "ldbs", "ldh", "ldhs", "ldw", "stb", "sth",
"stw", "lli", "lui", "jmp", "jeq", "jne", "jgt", "jge", "jlt", "jle",
"cmp", "inc", "dec", "shl", "shr", "add", "sub", "and", "or", "not",
"xor", "nand", "nor", "xnor", "irt", "int", "hlt",
// pseduo-instructions
"db", "dh", "dw", "resb", "resh", "resw", "push", "pop", "lwi", "call",
"ret",
]),
types: BTreeSet::from(["ptr", "byte", "word", "dword", "qword"]),
special: BTreeSet::from([
"rg0", "rg1", "rg2", "rg3", "rg4", "rg5", "rg6", "rg7", "rg8", "rg9",
"rga", "rgb", "rgc", "rgd", "rge", "rgf", "acc", "spr", "bpr", "ret",
"idr", "mmr", "zero", "null",
]),
}
}
}
+204
View File
@@ -0,0 +1,204 @@
#![allow(dead_code)]
pub mod dsa;
use std::collections::BTreeSet;
use std::hash::{Hash, Hasher};
pub const SEPARATORS: [char; 1] = ['_'];
pub const QUOTES: [char; 3] = ['\'', '"', '`'];
type MultiLine = bool;
type Float = bool;
#[derive(Default, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum TokenType {
Comment(MultiLine),
Function,
Keyword,
Literal,
Hyperlink,
Numeric(Float),
Punctuation(char),
Special,
Str(char),
Type,
Whitespace(char),
#[default]
Unknown,
}
impl std::fmt::Debug for TokenType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut name = String::new();
match &self {
TokenType::Comment(multiline) => {
name.push_str("Comment");
{
if *multiline {
name.push_str(" MultiLine");
} else {
name.push_str(" SingleLine");
}
}
}
TokenType::Function => name.push_str("Function"),
TokenType::Keyword => name.push_str("Keyword"),
TokenType::Literal => name.push_str("Literal"),
TokenType::Hyperlink => name.push_str("Hyperlink"),
TokenType::Numeric(float) => {
name.push_str("Numeric");
if *float {
name.push_str(" Float");
} else {
name.push_str(" Integer");
}
}
TokenType::Punctuation(_) => name.push_str("Punctuation"),
TokenType::Special => name.push_str("Special"),
TokenType::Str(quote) => {
name.push_str("Str ");
name.push(*quote);
}
TokenType::Type => name.push_str("Type"),
TokenType::Whitespace(c) => {
name.push_str("Whitespace");
match c {
' ' => name.push_str(" Space"),
'\t' => name.push_str(" Tab"),
'\n' => name.push_str(" New Line"),
_ => (),
};
}
TokenType::Unknown => name.push_str("Unknown"),
};
write!(f, "{name}")
}
}
impl From<char> for TokenType {
fn from(c: char) -> Self {
match c {
c if c.is_whitespace() => TokenType::Whitespace(c),
c if QUOTES.contains(&c) => TokenType::Str(c),
c if c.is_numeric() => TokenType::Numeric(false),
c if c.is_alphabetic() || SEPARATORS.contains(&c) => TokenType::Literal,
c if c.is_ascii_punctuation() => TokenType::Punctuation(c),
_ => TokenType::Unknown,
}
}
}
#[derive(Clone, Debug, PartialEq)]
/// Rules for highlighting.
pub struct Syntax {
pub language: &'static str,
pub case_sensitive: bool,
pub comment: &'static str,
pub comment_multiline: [&'static str; 2],
pub hyperlinks: BTreeSet<&'static str>,
pub keywords: BTreeSet<&'static str>,
pub types: BTreeSet<&'static str>,
pub special: BTreeSet<&'static str>,
}
impl Default for Syntax {
fn default() -> Self {
Syntax::dsa()
}
}
impl Hash for Syntax {
fn hash<H: Hasher>(&self, state: &mut H) {
self.language.hash(state);
}
}
impl Syntax {
pub fn new(language: &'static str) -> Self {
Syntax {
language,
..Default::default()
}
}
pub fn with_case_sensitive(self, case_sensitive: bool) -> Self {
Syntax {
case_sensitive,
..self
}
}
pub fn with_comment(self, comment: &'static str) -> Self {
Syntax { comment, ..self }
}
pub fn with_comment_multiline(self, comment_multiline: [&'static str; 2]) -> Self {
Syntax {
comment_multiline,
..self
}
}
pub fn with_hyperlinks<T: Into<BTreeSet<&'static str>>>(self, hyperlinks: T) -> Self {
Syntax {
hyperlinks: hyperlinks.into(),
..self
}
}
pub fn with_keywords<T: Into<BTreeSet<&'static str>>>(self, keywords: T) -> Self {
Syntax {
keywords: keywords.into(),
..self
}
}
pub fn with_types<T: Into<BTreeSet<&'static str>>>(self, types: T) -> Self {
Syntax {
types: types.into(),
..self
}
}
pub fn with_special<T: Into<BTreeSet<&'static str>>>(self, special: T) -> Self {
Syntax {
special: special.into(),
..self
}
}
pub fn language(&self) -> &str {
self.language
}
pub fn comment(&self) -> &str {
self.comment
}
pub fn is_hyperlink(&self, word: &str) -> bool {
self.hyperlinks.contains(word.to_ascii_lowercase().as_str())
}
pub fn is_keyword(&self, word: &str) -> bool {
if self.case_sensitive {
self.keywords.contains(&word)
} else {
self.keywords.contains(word.to_ascii_lowercase().as_str())
}
}
pub fn is_type(&self, word: &str) -> bool {
if self.case_sensitive {
self.types.contains(&word)
} else {
self.types.contains(word.to_ascii_lowercase().as_str())
}
}
pub fn is_special(&self, word: &str) -> bool {
if self.case_sensitive {
self.special.contains(&word)
} else {
self.special.contains(word.to_ascii_lowercase().as_str())
}
}
}
impl Syntax {
pub fn simple(comment: &'static str) -> Self {
Syntax {
language: "",
case_sensitive: false,
comment,
comment_multiline: [comment; 2],
hyperlinks: BTreeSet::new(),
keywords: BTreeSet::new(),
types: BTreeSet::new(),
special: BTreeSet::new(),
}
}
}
+147
View File
@@ -0,0 +1,147 @@
#![allow(dead_code)]
pub mod theme;
use super::syntax::TokenType;
#[cfg(feature = "egui")]
use egui::Color32;
#[cfg(feature = "egui")]
pub const ERROR_COLOR: Color32 = Color32::from_rgb(255, 0, 255);
/// Array of default themes.
pub const DEFAULT_THEMES: [ColorTheme; 1] = [ColorTheme::THEME];
#[cfg(feature = "egui")]
fn color_from_hex(hex: &str) -> Option<Color32> {
if hex == "none" {
return Some(Color32::from_rgba_premultiplied(255, 0, 255, 0));
}
let rgb = (1..hex.len())
.step_by(2)
.filter_map(|i| u8::from_str_radix(&hex[i..i + 2], 16).ok())
.collect::<Vec<u8>>();
let color = Color32::from_rgb(*rgb.first()?, *rgb.get(1)?, *rgb.get(2)?);
Some(color)
}
#[derive(Hash, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
/// Colors in hexadecimal notation as used in HTML and CSS.
pub struct ColorTheme {
pub name: &'static str,
pub dark: bool,
pub bg: &'static str,
pub cursor: &'static str,
pub selection: &'static str,
pub comments: &'static str,
pub functions: &'static str,
pub keywords: &'static str,
pub literals: &'static str,
pub numerics: &'static str,
pub punctuation: &'static str,
pub strs: &'static str,
pub types: &'static str,
pub special: &'static str,
}
impl Default for ColorTheme {
fn default() -> Self {
ColorTheme::THEME
}
}
impl ColorTheme {
pub fn name(&self) -> &str {
self.name
}
pub fn is_dark(&self) -> bool {
self.dark
}
#[cfg(feature = "egui")]
pub fn bg(&self) -> Color32 {
color_from_hex(self.bg).unwrap_or(ERROR_COLOR)
}
#[cfg(feature = "egui")]
pub fn cursor(&self) -> Color32 {
color_from_hex(self.cursor).unwrap_or(ERROR_COLOR)
}
#[cfg(feature = "egui")]
pub fn selection(&self) -> Color32 {
color_from_hex(self.selection).unwrap_or(ERROR_COLOR)
}
#[cfg(feature = "egui")]
pub fn modify_style(&self, ui: &mut egui::Ui, fontsize: f32) {
let style = ui.style_mut();
style.visuals.widgets.noninteractive.bg_fill = self.bg();
style.visuals.window_fill = self.bg();
style.visuals.selection.stroke.color = self.cursor();
style.visuals.selection.bg_fill = self.selection();
style.visuals.extreme_bg_color = self.bg();
style.override_font_id = Some(egui::FontId::monospace(fontsize));
style.visuals.text_cursor.stroke.width = fontsize * 0.1;
}
pub const fn type_color_str(&self, ty: TokenType) -> &'static str {
match ty {
TokenType::Comment(_) => self.comments,
TokenType::Function => self.functions,
TokenType::Keyword => self.keywords,
TokenType::Literal => self.literals,
TokenType::Hyperlink => self.special,
TokenType::Numeric(_) => self.numerics,
TokenType::Punctuation(_) => self.punctuation,
TokenType::Special => self.special,
TokenType::Str(_) => self.strs,
TokenType::Type => self.types,
TokenType::Whitespace(_) | TokenType::Unknown => self.comments,
}
}
#[cfg(feature = "egui")]
pub fn type_color(&self, ty: TokenType) -> Color32 {
match ty {
TokenType::Comment(_) => color_from_hex(self.comments),
TokenType::Function => color_from_hex(self.functions),
TokenType::Keyword => color_from_hex(self.keywords),
TokenType::Literal => color_from_hex(self.literals),
TokenType::Hyperlink => color_from_hex(self.special),
TokenType::Numeric(_) => color_from_hex(self.numerics),
TokenType::Punctuation(_) => color_from_hex(self.punctuation),
TokenType::Special => color_from_hex(self.special),
TokenType::Str(_) => color_from_hex(self.strs),
TokenType::Type => color_from_hex(self.types),
TokenType::Whitespace(_) | TokenType::Unknown => {
color_from_hex(self.comments)
}
}
.unwrap_or(ERROR_COLOR)
}
pub fn monocolor(
dark: bool,
bg: &'static str,
fg: &'static str,
cursor: &'static str,
selection: &'static str,
) -> Self {
ColorTheme {
name: "monocolor",
dark,
bg,
cursor,
selection,
literals: fg,
numerics: fg,
keywords: fg,
functions: fg,
punctuation: fg,
types: fg,
strs: fg,
comments: fg,
special: fg,
}
}
}
+22
View File
@@ -0,0 +1,22 @@
use super::ColorTheme;
impl ColorTheme {
/// Author : Jakub Bartodziej <kubabartodziej@gmail.com>
/// Theme uses the gruvbox dark palette with standard contrast <https://github.com/morhetz/gruvbox>
pub const THEME: ColorTheme = ColorTheme {
name: "Theme",
dark: true,
bg: "#1b1b1b",
cursor: "#de5252", // fg4
selection: "#28323B", // bg2
comments: "#444444", // gray1
functions: "#7CCCC7", // green1
keywords: "#6C81E0", // red1
literals: "#A3ABFF", // fg1
numerics: "#8A46CF", // purple1
punctuation: "#99C9C9", // orange1
strs: "#618c84", // aqua1
types: "#B8B9D4", // yellow1
special: "#de5252", // blue1
};
}