integrated library code editor
This commit is contained in:
parent
c87ad99dcc
commit
6c9aac62bd
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -573,7 +573,6 @@ name = "calcifer"
|
|||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui_code_editor",
|
||||
"egui_extras",
|
||||
"env_logger",
|
||||
"image",
|
||||
|
@ -939,15 +938,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "egui_extras"
|
||||
version = "0.25.0"
|
||||
|
|
|
@ -13,7 +13,6 @@ env_logger = { version = "0.10.1", default-features = false, features = [
|
|||
] }
|
||||
rfd = "0.12.1"
|
||||
egui_extras = "0.25.0"
|
||||
egui_code_editor = {version = "0.2.3"}
|
||||
image = "0.24.8"
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
serde_json = "1.0.111"
|
||||
|
|
|
@ -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}
|
|
@ -1,9 +1,13 @@
|
|||
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 crate::tools;
|
||||
use crate::tools::themes::CustomColorTheme;
|
||||
use egui_code_editor::CodeEditor;
|
||||
//use tools::themes::CustomColorTheme;
|
||||
|
||||
pub mod code_editor;
|
||||
use code_editor::CodeEditor;
|
||||
use code_editor::themes::DEFAULT_THEMES;
|
||||
|
||||
|
||||
|
||||
impl super::Calcifer {
|
||||
|
@ -18,8 +22,7 @@ impl super::Calcifer {
|
|||
.show_ui(ui, |ui| {
|
||||
ui.style_mut().wrap = Some(false);
|
||||
ui.set_min_width(60.0);
|
||||
for i in 0..CustomColorTheme::max() {
|
||||
let theme = CustomColorTheme::from_index(i);
|
||||
for theme in DEFAULT_THEMES {
|
||||
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(¤t_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 ¤t_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> {
|
||||
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();
|
||||
|
@ -178,7 +220,7 @@ impl super::Calcifer {
|
|||
|
||||
pub fn from_app_state(app_state: tools::AppState) -> 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(),
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -197,7 +239,10 @@ impl super::Calcifer {
|
|||
}
|
||||
|
||||
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![];
|
||||
|
||||
|
@ -212,42 +257,6 @@ impl super::Calcifer {
|
|||
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(¤t_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 ¤t_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<()> {
|
||||
if let Some(name) = path.file_name() {
|
||||
if path.is_dir() {
|
||||
|
@ -284,6 +293,7 @@ impl super::Calcifer {
|
|||
language: path.to_str().unwrap().split('.').last().unwrap().into(),
|
||||
saved: true,
|
||||
history: vec![],
|
||||
scroll_offset: 0.0,
|
||||
};
|
||||
self.tabs.push(new_tab);
|
||||
|
||||
|
|
244
src/calcifer/code_editor/highlighting.rs
Normal file
244
src/calcifer/code_editor/highlighting.rs
Normal 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()));
|
||||
}
|
||||
}
|
228
src/calcifer/code_editor/mod.rs
Normal file
228
src/calcifer/code_editor/mod.rs
Normal 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")
|
||||
}
|
||||
}
|
1181
src/calcifer/code_editor/syntax/asm.rs
Normal file
1181
src/calcifer/code_editor/syntax/asm.rs
Normal file
File diff suppressed because it is too large
Load diff
21
src/calcifer/code_editor/syntax/lua.rs
Normal file
21
src/calcifer/code_editor/syntax/lua.rs
Normal 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"]),
|
||||
}
|
||||
}
|
||||
}
|
196
src/calcifer/code_editor/syntax/mod.rs
Normal file
196
src/calcifer/code_editor/syntax/mod.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
36
src/calcifer/code_editor/syntax/python.rs
Normal file
36
src/calcifer/code_editor/syntax/python.rs
Normal 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"]),
|
||||
}
|
||||
}
|
||||
}
|
87
src/calcifer/code_editor/syntax/rust.rs
Normal file
87
src/calcifer/code_editor/syntax/rust.rs
Normal 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"]),
|
||||
}
|
||||
}
|
||||
}
|
40
src/calcifer/code_editor/syntax/shell.rs
Normal file
40
src/calcifer/code_editor/syntax/shell.rs
Normal 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",
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
137
src/calcifer/code_editor/syntax/sql.rs
Normal file
137
src/calcifer/code_editor/syntax/sql.rs
Normal 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"]),
|
||||
}
|
||||
}
|
||||
}
|
65
src/calcifer/code_editor/tests.rs
Normal file
65
src/calcifer/code_editor/tests.rs
Normal 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);
|
||||
}
|
57
src/calcifer/code_editor/themes/ayu.rs
Normal file
57
src/calcifer/code_editor/themes/ayu.rs
Normal 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
|
||||
};
|
||||
}
|
39
src/calcifer/code_editor/themes/fantasy.rs
Normal file
39
src/calcifer/code_editor/themes/fantasy.rs
Normal 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",
|
||||
};
|
||||
}
|
39
src/calcifer/code_editor/themes/github.rs
Normal file
39
src/calcifer/code_editor/themes/github.rs
Normal 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
|
||||
};
|
||||
}
|
41
src/calcifer/code_editor/themes/gruvbox.rs
Normal file
41
src/calcifer/code_editor/themes/gruvbox.rs
Normal 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
|
||||
};
|
||||
}
|
148
src/calcifer/code_editor/themes/mod.rs
Normal file
148
src/calcifer/code_editor/themes/mod.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
22
src/calcifer/code_editor/themes/sonokai.rs
Normal file
22
src/calcifer/code_editor/themes/sonokai.rs
Normal 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
|
||||
};
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
|
||||
mod tools;
|
||||
mod calcifer;
|
||||
|
||||
use eframe::egui;
|
||||
use egui_code_editor::ColorTheme;
|
||||
use calcifer::code_editor::ColorTheme;
|
||||
use std::{path::Path, sync::Arc};
|
||||
use tools::Demo;
|
||||
use calcifer::code_editor::themes::DEFAULT_THEMES;
|
||||
|
||||
const TERMINAL_HEIGHT : f32 = 200.0;
|
||||
const RED : egui::Color32 = egui::Color32::from_rgb(235, 108, 99);
|
||||
|
@ -68,7 +68,7 @@ impl Default for Calcifer {
|
|||
command: String::new(),
|
||||
command_history: Vec::new(),
|
||||
|
||||
theme: tools::themes::CustomColorTheme::fire(),
|
||||
theme: DEFAULT_THEMES[0],
|
||||
|
||||
search: tools::search::SearchWindow::default(),
|
||||
searching: false,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
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 serde::{Serialize, Deserialize};
|
||||
|
||||
pub mod themes;
|
||||
//pub mod themes;
|
||||
pub mod search;
|
||||
|
||||
|
||||
|
@ -77,6 +77,7 @@ pub struct Tab {
|
|||
pub language : String,
|
||||
pub saved : bool,
|
||||
pub history : Vec<String>,
|
||||
pub scroll_offset : f32,
|
||||
}
|
||||
|
||||
impl Default for Tab {
|
||||
|
@ -87,6 +88,7 @@ impl Default for Tab {
|
|||
language: "rs".into(),
|
||||
saved: false,
|
||||
history: vec![],
|
||||
scroll_offset: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue