integrated library code editor

This commit is contained in:
Penwing 2024-01-22 20:00:17 +01:00
parent c87ad99dcc
commit 6c9aac62bd
23 changed files with 2643 additions and 138 deletions

10
Cargo.lock generated
View file

@ -573,7 +573,6 @@ name = "calcifer"
version = "0.1.1" version = "0.1.1"
dependencies = [ dependencies = [
"eframe", "eframe",
"egui_code_editor",
"egui_extras", "egui_extras",
"env_logger", "env_logger",
"image", "image",
@ -939,15 +938,6 @@ dependencies = [
"winit", "winit",
] ]
[[package]]
name = "egui_code_editor"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3d41044cbf017fde94efde48b19637f63d94fa46dd6e41913cfeed74f023bc"
dependencies = [
"egui",
]
[[package]] [[package]]
name = "egui_extras" name = "egui_extras"
version = "0.25.0" version = "0.25.0"

View file

@ -13,7 +13,6 @@ env_logger = { version = "0.10.1", default-features = false, features = [
] } ] }
rfd = "0.12.1" rfd = "0.12.1"
egui_extras = "0.25.0" egui_extras = "0.25.0"
egui_code_editor = {version = "0.2.3"}
image = "0.24.8" image = "0.24.8"
serde = { version = "1.0.195", features = ["derive"] } serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.111" serde_json = "1.0.111"

View file

@ -1 +1 @@
{"tabs":["/home/penwing/Documents/projects/rust/calcifer/src/main.rs","/home/penwing/Documents/notes/victory2.txt","/home/penwing/Documents/projects/rust/calcifer/src/tools/search.rs"],"theme":6} {"tabs":["/home/penwing/Documents/projects/rust/calcifer/src/main.rs","/home/penwing/Documents/notes/victory2.txt","/home/penwing/Documents/projects/rust/calcifer/src/tools/search.rs"],"theme":5}

View file

@ -1,9 +1,13 @@
use eframe::egui; use eframe::egui;
use eframe::egui::{text::CCursor, text_edit::CCursorRange}; //use egui::{text::CCursor, text_edit::CCursorRange};
use std::{env, path::Path, path::PathBuf, cmp::max, io, fs, cmp::min}; use std::{env, path::Path, path::PathBuf, cmp::max, io, fs, cmp::min};
use crate::tools; use crate::tools;
use crate::tools::themes::CustomColorTheme; //use tools::themes::CustomColorTheme;
use egui_code_editor::CodeEditor;
pub mod code_editor;
use code_editor::CodeEditor;
use code_editor::themes::DEFAULT_THEMES;
impl super::Calcifer { impl super::Calcifer {
@ -18,8 +22,7 @@ impl super::Calcifer {
.show_ui(ui, |ui| { .show_ui(ui, |ui| {
ui.style_mut().wrap = Some(false); ui.style_mut().wrap = Some(false);
ui.set_min_width(60.0); ui.set_min_width(60.0);
for i in 0..CustomColorTheme::max() { for theme in DEFAULT_THEMES {
let theme = CustomColorTheme::from_index(i);
ui.selectable_value(&mut self.theme, theme, theme.name); ui.selectable_value(&mut self.theme, theme, theme.name);
} }
}); });
@ -134,6 +137,45 @@ impl super::Calcifer {
}); });
} }
fn draw_code_file(&mut self, ui: &mut egui::Ui) {
let current_tab = &mut self.tabs[self.selected_tab.to_index()];
let lines = current_tab.code.chars().filter(|&c| c == '\n').count() + 1;
//let scroll_area = egui::ScrollArea::vertical()
//.vertical_scroll_offset(current_tab.scroll_offset)
//.show(ui, |ui| {
let mut _output = CodeEditor::default()
.id_source("code editor")
.with_rows(max(80, lines))
.with_fontsize(14.0)
.with_theme(self.theme)
.with_syntax(tools::to_syntax(&current_tab.language))
.with_numlines(true)
.show(ui, &mut current_tab.code);
//if !self.search.result_selected {
//output.state.set_ccursor_range(Some(CCursorRange::two(
//CCursor::new(self.search.get_cursor_start()),
//CCursor::new(self.search.get_cursor_end()),
//)));
//println!("Changed Cursor : from {} to {}", self.search.get_cursor_start(), self.search.get_cursor_end());
//self.search.result_selected = true;
//}
//});
//ui.label(format!("Scroll : {}", scroll_area.state.offset.y.clone()));
if current_tab.history.len() < 1 {
current_tab.history.push(current_tab.code.clone());
}
if &current_tab.code != current_tab.history.last().expect("There should be an history") {
current_tab.history.push(current_tab.code.clone());
current_tab.saved = false;
if current_tab.history.len() > super::HISTORY_LENGTH {
current_tab.history.remove(0);
}
}
}
pub fn save_tab(&self) -> Option<PathBuf> { pub fn save_tab(&self) -> Option<PathBuf> {
if self.tabs[self.selected_tab.to_index()].path.file_name().expect("Could not get Tab Name").to_string_lossy().to_string() == "untitled" { if self.tabs[self.selected_tab.to_index()].path.file_name().expect("Could not get Tab Name").to_string_lossy().to_string() == "untitled" {
return self.save_tab_as(); return self.save_tab_as();
@ -178,7 +220,7 @@ impl super::Calcifer {
pub fn from_app_state(app_state: tools::AppState) -> Self { pub fn from_app_state(app_state: tools::AppState) -> Self {
let mut new = Self { let mut new = Self {
theme: tools::themes::CustomColorTheme::from_index(app_state.theme), theme: DEFAULT_THEMES[min(app_state.theme, DEFAULT_THEMES.len() - 1)],
tabs: Vec::new(), tabs: Vec::new(),
..Default::default() ..Default::default()
}; };
@ -197,7 +239,10 @@ impl super::Calcifer {
} }
pub fn save_state(&self) { pub fn save_state(&self) {
let state_theme = tools::themes::CustomColorTheme::to_index(self.theme); let mut state_theme : usize = 0;
if let Some(theme) = DEFAULT_THEMES.iter().position(|&r| r == self.theme) {
state_theme = theme;
}
let mut state_tabs = vec![]; let mut state_tabs = vec![];
@ -212,42 +257,6 @@ impl super::Calcifer {
let _ = tools::save_state(&app_state, super::SAVE_PATH); let _ = tools::save_state(&app_state, super::SAVE_PATH);
} }
fn draw_code_file(&mut self, ui: &mut egui::Ui) {
let current_tab = &mut self.tabs[self.selected_tab.to_index()];
let lines = current_tab.code.chars().filter(|&c| c == '\n').count() + 1;
egui::ScrollArea::vertical().show(ui, |ui| {
let mut output = CodeEditor::default()
.id_source("code editor")
.with_rows(max(80, lines))
.with_fontsize(14.0)
.with_theme(self.theme)
.with_syntax(tools::to_syntax(&current_tab.language))
.with_numlines(true)
.show(ui, &mut current_tab.code);
if !self.search.result_selected {
output.state.set_ccursor_range(Some(CCursorRange::two(
CCursor::new(self.search.get_cursor_start()),
CCursor::new(self.search.get_cursor_end()),
)));
println!("Changed Cursor : from {} to {}", self.search.get_cursor_start(), self.search.get_cursor_end());
self.search.result_selected = true;
}
});
if current_tab.history.len() < 1 {
current_tab.history.push(current_tab.code.clone());
}
if &current_tab.code != current_tab.history.last().expect("There should be an history") {
current_tab.history.push(current_tab.code.clone());
current_tab.saved = false;
if current_tab.history.len() > super::HISTORY_LENGTH {
current_tab.history.remove(0);
}
}
}
fn list_files(&mut self, ui: &mut egui::Ui, path: &Path) -> io::Result<()> { fn list_files(&mut self, ui: &mut egui::Ui, path: &Path) -> io::Result<()> {
if let Some(name) = path.file_name() { if let Some(name) = path.file_name() {
if path.is_dir() { if path.is_dir() {
@ -284,6 +293,7 @@ impl super::Calcifer {
language: path.to_str().unwrap().split('.').last().unwrap().into(), language: path.to_str().unwrap().split('.').last().unwrap().into(),
saved: true, saved: true,
history: vec![], history: vec![],
scroll_offset: 0.0,
}; };
self.tabs.push(new_tab); self.tabs.push(new_tab);

View file

@ -0,0 +1,244 @@
use super::syntax::{Syntax, TokenType, QUOTES, SEPARATORS};
use std::mem;
use super::CodeEditor;
use eframe::egui::text::LayoutJob;
use eframe::egui;
#[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
}
/// Syntax highlighting
pub fn highlight(&mut self, editor: &CodeEditor, 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::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_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
}
}
impl eframe::egui::util::cache::ComputerMut<(&CodeEditor, &str), LayoutJob> for Token {
fn compute(&mut self, (cache, text): (&CodeEditor, &str)) -> LayoutJob {
self.highlight(cache, text)
}
}
pub type HighlightCache = egui::util::cache::FrameCache<LayoutJob, Token>;
pub fn highlight(ctx: &egui::Context, cache: &CodeEditor, text: &str) -> LayoutJob {
ctx.memory_mut(|mem| mem.caches.cache::<HighlightCache>().get((cache, text)))
}
impl CodeEditor {
fn append(&self, job: &mut LayoutJob, token: &Token) {
job.append(token.buffer(), 0.0, self.format(token.ty()));
}
}

View file

@ -0,0 +1,228 @@
#![allow(dead_code)]
pub mod highlighting;
mod syntax;
pub mod themes;
use eframe::egui::widgets::text_edit::TextEditOutput;
//use eframe::egui::ScrollArea;
use eframe::egui;
use highlighting::highlight;
//pub use highlighting::Token;
use std::hash::{Hash, Hasher};
pub use syntax::{Syntax, TokenType};
pub use themes::ColorTheme;
//pub use themes::DEFAULT_THEMES;
#[derive(Clone, Debug, PartialEq)]
/// CodeEditor struct which stores settings for highlighting.
pub struct CodeEditor {
id: String,
theme: ColorTheme,
syntax: Syntax,
numlines: bool,
fontsize: f32,
rows: usize,
vscroll: bool,
stick_to_bottom: bool,
shrink: bool,
}
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);
}
}
impl Default for CodeEditor {
fn default() -> CodeEditor {
CodeEditor {
id: String::from("Code Editor"),
theme: ColorTheme::GRUVBOX,
syntax: Syntax::rust(),
numlines: true,
fontsize: 10.0,
rows: 10,
vscroll: true,
stick_to_bottom: false,
shrink: false,
}
}
}
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 }
}
/// Use custom syntax for highlighting
///
/// **Default: Rust**
pub fn with_syntax(self, syntax: Syntax) -> Self {
CodeEditor { syntax, ..self }
}
/// Turn on/off scrolling on the vertical axis.
///
/// **Default: true**
pub fn vscroll(self, vscroll: bool) -> Self {
CodeEditor { vscroll, ..self }
}
/// Should the containing area shrink if the content is small?
///
/// **Default: false**
pub fn auto_shrink(self, shrink: bool) -> Self {
CodeEditor { shrink, ..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
}
}
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)
}
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);
let max_indent = total.to_string().len();
let mut counter = (1..=total)
.map(|i| {
let label = i.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;
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 String) -> 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);
}
egui::ScrollArea::horizontal()
.id_source(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(true)
.desired_width(if self.shrink { 0.0 } else { f32::MAX })
.layouter(&mut layouter)
.show(ui);
text_edit_output = Some(output);
});
});
};
if self.vscroll {
egui::ScrollArea::vertical()
.id_source(format!("{}_outer_scroll", self.id))
.stick_to_bottom(self.stick_to_bottom)
.show(ui, code_editor);
} else {
code_editor(ui);
}
text_edit_output.expect("TextEditOutput should exist at this point")
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,21 @@
use super::Syntax;
use std::collections::BTreeSet;
impl Syntax {
pub fn lua() -> Syntax {
Syntax {
language: "Lua",
case_sensitive: true,
comment: "--",
comment_multiline: ["--[[", "]]"],
keywords: BTreeSet::from([
"and", "break", "do", "else", "elseif", "end", "for", "function", "if", "in",
"local", "not", "or", "repeat", "return", "then", "until", "while",
]),
types: BTreeSet::from([
"boolean", "number", "string", "function", "userdata", "thread", "table",
]),
special: BTreeSet::from(["false", "nil", "true"]),
}
}
}

View file

@ -0,0 +1,196 @@
#![allow(dead_code)]
pub mod asm;
pub mod lua;
pub mod python;
pub mod rust;
pub mod shell;
pub mod sql;
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,
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::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 keywords: BTreeSet<&'static str>,
pub types: BTreeSet<&'static str>,
pub special: BTreeSet<&'static str>,
}
impl Default for Syntax {
fn default() -> Self {
Syntax::rust()
}
}
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_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_keyword(&self, word: &str) -> bool {
if self.case_sensitive {
self.keywords.contains(&word)
} else {
self.keywords.contains(word.to_ascii_uppercase().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_uppercase().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_uppercase().as_str())
}
}
}
impl Syntax {
pub fn simple(comment: &'static str) -> Self {
Syntax {
language: "",
case_sensitive: false,
comment,
comment_multiline: [comment; 2],
keywords: BTreeSet::new(),
types: BTreeSet::new(),
special: BTreeSet::new(),
}
}
}

View file

@ -0,0 +1,36 @@
use super::Syntax;
use std::collections::BTreeSet;
impl Syntax {
pub fn python() -> Syntax {
Syntax {
language: "Python",
case_sensitive: true,
comment: "#",
comment_multiline: [r#"'''"#, r#"'''"#],
keywords: BTreeSet::from([
"and", "as", "assert", "break", "class", "continue", "def", "del", "elif", "else",
"except", "finally", "for", "from", "global", "if", "import", "in", "is", "lambda",
"nonlocal", "not", "or", "pass", "raise", "return", "try", "while", "with",
"yield",
]),
types: BTreeSet::from([
"bool",
"int",
"float",
"complex",
"str",
"list",
"tuple",
"range",
"bytes",
"bytearray",
"memoryview",
"dict",
"set",
"frozenset",
]),
special: BTreeSet::from(["False", "None", "True"]),
}
}
}

View file

@ -0,0 +1,87 @@
use super::Syntax;
use std::collections::BTreeSet;
impl Syntax {
pub fn rust() -> Self {
Syntax {
language: "Rust",
case_sensitive: true,
comment: "//",
comment_multiline: ["/*", "*/"],
keywords: BTreeSet::from([
"as", "break", "const", "continue", "crate", "else", "enum", "extern", "fn", "for",
"if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
"return", "self", "struct", "super", "trait", "type", "use", "where", "while",
"async", "await", "abstract", "become", "box", "do", "final", "macro", "override",
"priv", "typeof", "unsized", "virtual", "yield", "try", "unsafe", "dyn",
]),
types: BTreeSet::from([
"Option",
"Result",
"Error",
"Box",
"Cow",
// Primitives
"bool",
"i8",
"u8",
"i16",
"u16",
"i32",
"u32",
"i64",
"u64",
"i128",
"u128",
"isize",
"usize",
"f32",
"f64",
"char",
"str",
"String",
// STD Collections
"Vec",
"BTreeMap",
"BTreeSet",
"BTreeMap",
"BTreeSet",
"VecDeque",
"BinaryHeap",
"LinkedList",
// RC
"Rc",
"Weak",
"LazyCell",
"SyncUnsafeCell",
"BorrowErrorl",
"BorrowMutErrorl",
"Celll",
"OnceCelll",
"Refl",
"RefCelll",
"RefMutl",
"UnsafeCell",
"Exclusive",
"LazyLock",
// ARC
"Arc",
"Barrier",
"BarrierWaitResult",
"Condvar",
"Mutex",
"MutexGuard",
"Once",
"OnceLock",
"OnceState",
"PoisonError",
"RwLock",
"RwLockReadGuard",
"RwLockWriteGuard",
"WaitTimeoutResult",
"Weak",
]),
special: BTreeSet::from(["Self", "static", "true", "false"]),
}
}
}

View file

@ -0,0 +1,40 @@
use super::Syntax;
use std::collections::BTreeSet;
impl Syntax {
pub fn shell() -> Self {
Syntax {
language: "Shell",
case_sensitive: true,
comment: "#",
keywords: BTreeSet::from([
"echo", "read", "set", "unset", "readonly", "shift", "export", "if", "fi", "else",
"while", "do", "done", "for", "until", "case", "esac", "break", "continue", "exit",
"return", "trap", "wait", "eval", "exec", "ulimit", "umask",
]),
comment_multiline: [": '", "'"],
types: BTreeSet::from([
"ENV",
"HOME",
"IFS",
"LANG",
"LC_ALL",
"LC_COLLATE",
"LC_CTYPE",
"LC_MESSAGES",
"LINENO",
"NLSPATH",
"PATH",
"PPID",
"PS1",
"PS2",
"PS4",
"PWD",
]),
special: BTreeSet::from([
"alias", "bg", "cd", "command", "false", "fc", "fg", "getopts", "jobs", "kill",
"newgrp", "pwd", "read", "true", "umask", "unalias", "wait",
]),
}
}
}

View file

@ -0,0 +1,137 @@
use super::Syntax;
use std::collections::BTreeSet;
impl Syntax {
pub fn sql() -> Self {
Syntax {
language: "SQL",
case_sensitive: false,
comment: "--",
comment_multiline: ["/*", "*/"],
keywords: BTreeSet::from([
"ADD",
"ALL",
"ALTER",
"AND",
"ANY",
"AS",
"ASC",
"BACKUP",
"BETWEEN",
"CASE",
"CHECK",
"COLUMN",
"CONSTRAINT",
"CREATE",
"INDEX",
"OR",
"REPLACE",
"VIEW",
"PROCEDURE",
"UNIQUE",
"DEFAULT",
"DELETE",
"DESC",
"DISTINCT",
"DROP",
"PRIMARY",
"FOREIGN",
"EXEC",
"EXISTS",
"FROM",
"FULL",
"OUTER",
"JOIN",
"GROUP",
"BY",
"HAVING",
"IN",
"INNER",
"INSERT",
"INTO",
"SELECT",
"IS",
"KEY",
"NOT",
"NULL",
"LEFT",
"LIKE",
"LIMIT",
"ORDER",
"A",
"RIGHT",
"ROWNUM",
"TOP",
"TOPDOWN", //FIXME
"TRUNCATE",
"UNION",
"UPDATE",
"VALUES",
"WHERE",
"WITH",
]),
types: BTreeSet::from([
"BOOL",
"INTEGER",
"SMALLINT",
"BIGINT",
"REAL",
"DOUBLEPRECISION",
"VARCHAR",
"NUMBER",
"CHAR",
"TEXT",
"DATE",
"TIMESTAMP",
"UUID",
"BYTEA",
"LOB",
"BLOB",
"CLOB",
"BIGINT",
"NUMERIC",
"BIT",
"SMALLINT",
"DECIMAL",
"SMALLMONEY",
"INT",
"INT4",
"INT8",
"INT16",
"INT32",
"INT64",
"INT128",
"TINYINT",
"MONEY",
"FLOAT",
"REAL",
"DATE",
"DATETIMEOFFSET",
"DATETIME2",
"SMALLDATETIME",
"DATETIME",
"TIME",
"CHAR",
"VARCHAR",
"VARCHAR2",
"TEXT",
"NCHAR",
"NVARCHAR",
"NTEXT",
"BINARY",
"VARBINARY",
"IMAGE",
"ROWVERSION",
"HIERARCHYID",
"UNIQUEIDENTIFIER",
"SQL_VARIANT",
"XML",
"XMLTYPE",
"TABLE",
"SET",
"DATABASE",
]),
special: BTreeSet::from(["PUBLIC"]),
}
}
}

View file

@ -0,0 +1,65 @@
use super::*;
#[test]
fn numeric_float() {
assert_eq!(
Token::default().tokens(&Syntax::default(), "3.14"),
[Token::new(TokenType::Numeric(true), "3.14")]
);
}
#[test]
fn numeric_float_desription() {
assert_eq!(
Token::default().tokens(&Syntax::default(), "3.14_f32"),
[
Token::new(TokenType::Numeric(true), "3.14"),
Token::new(TokenType::Numeric(true), "_"),
Token::new(TokenType::Numeric(true), "f32")
]
);
}
#[test]
fn numeric_float_second_dot() {
assert_eq!(
Token::default().tokens(&Syntax::default(), "3.14.032"),
[
Token::new(TokenType::Numeric(true), "3.14"),
Token::new(TokenType::Punctuation('.'), "."),
Token::new(TokenType::Numeric(false), "032")
]
);
}
#[test]
fn simple_rust() {
let syntax = Syntax::rust();
let input = vec![
Token::new(TokenType::Keyword, "fn"),
Token::new(TokenType::Whitespace(' '), " "),
Token::new(TokenType::Function, "function"),
Token::new(TokenType::Punctuation('('), "("),
Token::new(TokenType::Str('\"'), "\"String\""),
Token::new(TokenType::Punctuation(')'), ")"),
Token::new(TokenType::Punctuation('{'), "{"),
Token::new(TokenType::Whitespace('\n'), "\n"),
Token::new(TokenType::Whitespace('\t'), "\t"),
Token::new(TokenType::Keyword, "let"),
Token::new(TokenType::Whitespace(' '), " "),
Token::new(TokenType::Literal, "x_0"),
Token::new(TokenType::Punctuation(':'), ":"),
Token::new(TokenType::Whitespace(' '), " "),
Token::new(TokenType::Type, "f32"),
Token::new(TokenType::Whitespace(' '), " "),
Token::new(TokenType::Punctuation('='), "="),
Token::new(TokenType::Numeric(true), "13.34"),
Token::new(TokenType::Punctuation(';'), ";"),
Token::new(TokenType::Whitespace('\n'), "\n"),
Token::new(TokenType::Punctuation('}'), "}"),
];
let str = input.iter().map(|h| h.buffer()).collect::<String>();
let output = Token::default().tokens(&syntax, &str);
println!("{str}");
assert_eq!(input, output);
}

View file

@ -0,0 +1,57 @@
use super::ColorTheme;
impl ColorTheme {
/// Author: André Sá <enkodr@outlook.com>
///
/// Based on the AYU theme colors from <https://github.com/dempfi/ayu>
pub const AYU: ColorTheme = ColorTheme {
name: "Ayu",
dark: false,
bg: "#fafafa",
cursor: "#5c6166", // foreground
selection: "#fa8d3e", // orange
comments: "#828c9a", // gray
functions: "#ffaa33", // yellow
keywords: "#fa8d3e", // orange
literals: "#5c6166", // foreground
numerics: "#a37acc", // magenta
punctuation: "#5c6166", // foreground
strs: "#86b300", // green
types: "#399ee6", // blue
special: "#f07171", // red
};
pub const AYU_MIRAGE: ColorTheme = ColorTheme {
name: "Ayu Mirage",
dark: true,
bg: "#1f2430",
cursor: "#cccac2", // foreground
selection: "#ffad66", // orange
comments: "#565b66", // gray
functions: "#ffcc77", // yellow
keywords: "#ffad66", // orange
literals: "#cccac2", // foreground
numerics: "#dfbfff", // magenta
punctuation: "#cccac2", // foreground
strs: "#d5ff80", // green
types: "#73d0ff", // blue
special: "#f28779", // red
};
pub const AYU_DARK: ColorTheme = ColorTheme {
name: "Ayu Dark",
dark: true,
bg: "#0f1419",
cursor: "#bfbdb6", // foreground
selection: "#ffad66", // orange
comments: "#5c6773", // gray
functions: "#e6b450", // yellow
keywords: "#ffad66", // orange
literals: "#bfbdb6", // foreground
numerics: "#dfbfff", // magenta
punctuation: "#bfbdb6", // foreground
strs: "#aad94c", // green
types: "#59c2ff", // blue
special: "#f28779", // red
};
}

View file

@ -0,0 +1,39 @@
use super::ColorTheme;
impl ColorTheme {
/// Original Author: sainnhe <https://github.com/sainnhe/sonokai>
/// Modified by p4ymak <https://github.com/p4ymak>
pub const FIRE: ColorTheme = ColorTheme {
name: "Fire",
dark: true,
bg: "#242424",
cursor: "#dadada", // foreground
selection: "#444852", // dunno
comments: "#656565", // dark_gray
functions: "#ffad69", // light orange
keywords: "#48b1a7", // mid green
literals: "#d2d2d3", //
numerics: "#ff7b4f", // orange
punctuation: "#989898", // gray
strs: "#cbd5a1", // light_green
types: "#038e83", // dark_green
special: "#48b1a7", // mid green
};
pub const ASH: ColorTheme = ColorTheme {
name: "Ash",
dark: true,
bg: "#101010",
cursor: "#eaeaea",
selection: "#505050",
comments: "#656565",
functions: "#a0a0a0",
keywords: "#848484",
literals: "#d2d2d2",
numerics: "#d2d2d2",
punctuation: "#848484",
strs: "#a0a0a0",
types: "#c6c6c6",
special: "#848484",
};
}

View file

@ -0,0 +1,39 @@
use super::ColorTheme;
impl ColorTheme {
/// Author : OwOSwordsman <owoswordsman@gmail.com>
/// An unofficial GitHub theme, generated using colors from: <https://primer.style/primitives/colors>
pub const GITHUB_DARK: ColorTheme = ColorTheme {
name: "Github Dark",
dark: true,
bg: "#0d1117", // default
cursor: "#d29922", // attention.fg
selection: "#0c2d6b", // scale.blue.8
comments: "#8b949e", // fg.muted
functions: "#d2a8ff", // scale.purple.2
keywords: "#ff7b72", // scale.red.3
literals: "#c9d1d9", // fg.default
numerics: "#79c0ff", // scale.blue.2
punctuation: "#c9d1d9", // fg.default
strs: "#a5d6ff", // scale.blue.1
types: "#ffa657", // scale.orange.2
special: "#a5d6ff", // scale.blue.1
};
pub const GITHUB_LIGHT: ColorTheme = ColorTheme {
name: "Github Light",
dark: false,
bg: "#ffffff", // default
cursor: "#000000", // invert
selection: "#0550ae", // scale.blue.6
comments: "#57606a", // fg.muted
functions: "#8250df", // done.fg
keywords: "#cf222e", // scale.red.5
literals: "#24292f", // fg.default
numerics: "#0550ae", // scale.blue.6
punctuation: "#24292f", // fg.default
strs: "#0a3069", // scale.blue.8
types: "#953800", // scale.orange.6
special: "#a475f9", // scale.purple.4
};
}

View file

@ -0,0 +1,41 @@
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 GRUVBOX: ColorTheme = ColorTheme {
name: "Gruvbox",
dark: true,
bg: "#282828",
cursor: "#a89984", // fg4
selection: "#504945", // bg2
comments: "#928374", // gray1
functions: "#b8bb26", // green1
keywords: "#fb4934", // red1
literals: "#ebdbb2", // fg1
numerics: "#d3869b", // purple1
punctuation: "#fe8019", // orange1
strs: "#8ec07c", // aqua1
types: "#fabd2f", // yellow1
special: "#83a598", // blue1
};
pub const GRUVBOX_DARK: ColorTheme = ColorTheme::GRUVBOX;
pub const GRUVBOX_LIGHT: ColorTheme = ColorTheme {
name: "Gruvbox Light",
dark: false,
bg: "#fbf1c7",
cursor: "#7c6f64", // fg4
selection: "#b57614", // yellow1
comments: "#7c6f64", // gray1
functions: "#79740e", // green1
keywords: "#9d0006", // red1
literals: "#282828", // fg1
numerics: "#8f3f71", // purple1
punctuation: "#af3a03", // orange1
strs: "#427b58", // aqua1
types: "#b57614", // yellow1
special: "#af3a03", // orange1
};
}

View file

@ -0,0 +1,148 @@
#![allow(dead_code)]
pub mod ayu;
pub mod github;
pub mod gruvbox;
pub mod sonokai;
pub mod fantasy;
use super::syntax::TokenType;
use eframe::egui;
use egui::Color32;
pub const ERROR_COLOR: Color32 = Color32::from_rgb(255, 0, 255);
/// Array of default themes.
pub const DEFAULT_THEMES: [ColorTheme; 7] = [
ColorTheme::AYU_MIRAGE,
ColorTheme::AYU_DARK,
ColorTheme::GITHUB_DARK,
ColorTheme::GRUVBOX,
ColorTheme::SONOKAI,
ColorTheme::FIRE,
ColorTheme::ASH,
];
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::GRUVBOX
}
}
impl ColorTheme {
pub fn name(&self) -> &str {
self.name
}
pub fn is_dark(&self) -> bool {
self.dark
}
pub fn bg(&self) -> Color32 {
color_from_hex(self.bg).unwrap_or(ERROR_COLOR)
}
pub fn cursor(&self) -> Color32 {
color_from_hex(self.cursor).unwrap_or(ERROR_COLOR)
}
pub fn selection(&self) -> Color32 {
color_from_hex(self.selection).unwrap_or(ERROR_COLOR)
}
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.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::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,
}
}
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::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,
}
}
}

View file

@ -0,0 +1,22 @@
use super::ColorTheme;
impl ColorTheme {
/// Original Author: sainnhe <https://github.com/sainnhe/sonokai>
/// Modified by p4ymak <https://github.com/p4ymak>
pub const SONOKAI: ColorTheme = ColorTheme {
name: "Sonokai",
dark: true,
bg: "#2c2e34", // bg0
cursor: "#76cce0", // blue
selection: "#444852", // bg5
comments: "#7f8490", // gray
functions: "#9ed072", // green
keywords: "#fc5d7c", // red
literals: "#e2e2e3", // foreground
numerics: "#b39df3", // purple
punctuation: "#7f8490", // gray
strs: "#e7c664", // yellow
types: "#399ee6", // blue
special: "#f39660", // orange
};
}

View file

@ -1,11 +1,11 @@
mod tools; mod tools;
mod calcifer; mod calcifer;
use eframe::egui; use eframe::egui;
use egui_code_editor::ColorTheme; use calcifer::code_editor::ColorTheme;
use std::{path::Path, sync::Arc}; use std::{path::Path, sync::Arc};
use tools::Demo; use tools::Demo;
use calcifer::code_editor::themes::DEFAULT_THEMES;
const TERMINAL_HEIGHT : f32 = 200.0; const TERMINAL_HEIGHT : f32 = 200.0;
const RED : egui::Color32 = egui::Color32::from_rgb(235, 108, 99); const RED : egui::Color32 = egui::Color32::from_rgb(235, 108, 99);
@ -68,7 +68,7 @@ impl Default for Calcifer {
command: String::new(), command: String::new(),
command_history: Vec::new(), command_history: Vec::new(),
theme: tools::themes::CustomColorTheme::fire(), theme: DEFAULT_THEMES[0],
search: tools::search::SearchWindow::default(), search: tools::search::SearchWindow::default(),
searching: false, searching: false,

View file

@ -1,9 +1,9 @@
use std::{process::Command, cmp::Ordering, env, path::PathBuf, fs::read_to_string, fs::write}; use std::{process::Command, cmp::Ordering, env, path::PathBuf, fs::read_to_string, fs::write};
use egui_code_editor::Syntax; use crate::calcifer::code_editor::Syntax;
use eframe::egui; use eframe::egui;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
pub mod themes; //pub mod themes;
pub mod search; pub mod search;
@ -77,6 +77,7 @@ pub struct Tab {
pub language : String, pub language : String,
pub saved : bool, pub saved : bool,
pub history : Vec<String>, pub history : Vec<String>,
pub scroll_offset : f32,
} }
impl Default for Tab { impl Default for Tab {
@ -87,6 +88,7 @@ impl Default for Tab {
language: "rs".into(), language: "rs".into(),
saved: false, saved: false,
history: vec![], history: vec![],
scroll_offset: 0.0,
} }
} }
} }

View file

@ -1,77 +0,0 @@
use egui_code_editor::ColorTheme;
pub struct CustomColorTheme;
impl CustomColorTheme {
pub fn fire() -> ColorTheme {
let mut theme = ColorTheme::GRUVBOX; // Or any other theme you want to modify
theme.name = "Fire";
theme.dark = true;
theme.bg = "#242424";
theme.cursor = "#dadada"; // foreground
theme.selection = "#444852"; // dunno
theme.comments = "#656565"; // dark_gray
theme.functions = "#ffad69"; // light orange
theme.keywords = "#48b1a7"; // mid green
theme.literals = "#d2d2d3"; //
theme.numerics = "#ff7b4f"; // orange
theme.punctuation = "#989898"; // gray
theme.strs = "#cbd5a1"; // light_green
theme.types = "#038e83"; // dark_green
theme.special = "#48b1a7"; // mid green
theme
}
pub fn ash() -> ColorTheme {
let mut theme = ColorTheme::GRUVBOX; // Or any other theme you want to modify
theme.name = "Ash";
theme.dark = true;
theme.bg = "#101010";
theme.cursor = "#eaeaea"; // foreground
theme.selection = "#505050"; // bg5
theme.comments = "#656565"; // gray
theme.functions = "#a0a0a0"; // green
theme.keywords = "#848484"; // orange
theme.literals = "#d2d2d2"; // foreground
theme.numerics = "#d2d2d2"; // magenta
theme.punctuation = "#848484"; // foreground
theme.strs = "#a0a0a0"; // green
theme.types = "#c6c6c6";
theme.special = "#848484";
theme
}
pub fn from_index(n : usize) -> ColorTheme {
match n {
0 => ColorTheme::SONOKAI,
1 => ColorTheme::GRUVBOX,
2 => ColorTheme::GITHUB_DARK,
3 => ColorTheme::AYU_MIRAGE,
4 => ColorTheme::AYU_DARK,
5 => CustomColorTheme::ash(),
6 => CustomColorTheme::fire(),
_ => CustomColorTheme::ash(),
}
}
pub fn max() -> usize {
7
}
pub fn to_index(theme : ColorTheme) -> usize {
match theme.name {
"Sonokai" => 0,
"Gruvbox" => 1,
"Github Dark" => 2,
"Ayu Mirage" => 3,
"Ayu Dark" => 4,
"Ash" => 5,
"Fire" => 6,
_ => 0,
}
}
}