merge commit
This commit is contained in:
-1
Submodule dsa_editor deleted from 1566784e20
Generated
+3985
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
@@ -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)))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user