#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use eframe::{self, CreationContext, egui};
use egui::TextEdit;
use egui_code_editor::{self, CodeEditor, ColorTheme, Completer, Syntax, highlighting::Token};
const THEMES: [ColorTheme; 8] = [
ColorTheme::AYU,
ColorTheme::AYU_MIRAGE,
ColorTheme::AYU_DARK,
ColorTheme::GITHUB_DARK,
ColorTheme::GITHUB_LIGHT,
ColorTheme::GRUVBOX,
ColorTheme::GRUVBOX_LIGHT,
ColorTheme::SONOKAI,
];
const SYNTAXES: [SyntaxDemo; 5] = [
SyntaxDemo::new(
"Lua",
r#"-- Binary Search
function binarySearch(list, value)
local function search(low, high)
if low > high then return false end
local mid = math.floor((low+high)/2)
if list[mid] > value then return search(low,mid-1) end
if list[mid] < value then return search(mid+1,high) end
return mid
end
return search(1,#list)
end"#,
),
SyntaxDemo::new(
"Python",
r#"from collections.abc import Iterable
from typing import Protocol
class Combiner(Protocol):
def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ...
def batch_proc(data: Iterable[bytes], cb_results: Combiner) -> bytes:
for item in data:
...
def good_cb(*vals: bytes, maxlen: int | None = None) -> list[bytes]:
...
"""
def bad_cb(*vals: bytes, maxitems: int | None) -> list[bytes]:
...
"""
batch_proc([], good_cb) # OK
batch_proc([], bad_cb) # Error! Argument 2 has incompatible type because of
# different name and kind in the callback"#,
),
SyntaxDemo::new(
"Rust",
r#"// Code Editor
CodeEditor::default()
.id_source("code editor")
.with_rows(12)
.with_fontsize(14.0)
.with_theme(self.theme)
.with_syntax(self.syntax.to_owned())
.with_numlines(true)
.vscroll(true)
.show(ui, &mut self.code);"#,
),
SyntaxDemo::new(
"Shell",
r#"#!/bin/bash
user=p4ymak
if grep $user /etc/passwd
then
echo "The user $user Exists"
fi"#,
),
SyntaxDemo::new(
"SQL",
r#"select now(); -- what time it is?
WITH employee_ranking AS (
SELECT
employee_id as real,
last_name,
first_name,
salary,
dept_id
RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) as ranking
FROM employee
)"#,
),
];
#[derive(Clone, Copy)]
struct SyntaxDemo {
name: &'static str,
example: &'static str,
}
impl SyntaxDemo {
const fn new(name: &'static str, example: &'static str) -> Self {
SyntaxDemo { name, example }
}
fn syntax(&self) -> Syntax {
match self.name {
"Assembly" => Syntax::asm(),
"Lua" => Syntax::lua(),
"Python" => Syntax::python(),
"Rust" => Syntax::rust()
.with_word_start(['#'])
.with_hyperlinks(["www", "http"]),
"Shell" => Syntax::shell(),
"SQL" => Syntax::sql(),
_ => Syntax::shell(),
}
}
}
fn main() -> Result<(), eframe::Error> {
#[cfg(debug_assertions)]
unsafe {
std::env::set_var("RUST_BACKTRACE", "1");
}
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_decorations(true)
.with_transparent(false)
.with_resizable(true)
.with_maximized(false)
.with_drag_and_drop(true)
.with_inner_size([900.0, 600.0])
.with_min_inner_size([280.0, 280.0]),
..Default::default()
};
eframe::run_native(
"Egui Code Editor Demo",
options,
Box::new(|cc| Ok(Box::new(CodeEditorDemo::new(cc)))),
)
}
#[derive(Default)]
struct CodeEditorDemo {
code: String,
text: String,
theme: ColorTheme,
syntax: Syntax,
completer: Completer,
example: bool,
shift: isize,
numlines_only_natural: bool,
}
impl CodeEditorDemo {
fn new(_cc: &CreationContext) -> Self {
let rust = SYNTAXES[2];
CodeEditorDemo {
code: rust.example.to_string(),
text: String::default(),
theme: ColorTheme::GRUVBOX,
syntax: rust.syntax(),
completer: Completer::new_with_syntax(&rust.syntax())
.with_auto_indent()
.with_user_words(),
example: true,
shift: 0,
numlines_only_natural: false,
}
}
}
impl eframe::App for CodeEditorDemo {
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
egui::Panel::left("theme_picker").show_inside(ui, |ui| {
ui.heading("Theme");
egui::ScrollArea::both().show(ui, |ui| {
for theme in THEMES.iter() {
if ui
.selectable_value(&mut self.theme, *theme, theme.name())
.clicked()
{
if theme.is_dark() {
ui.set_visuals(egui::Visuals::dark());
} else {
ui.set_visuals(egui::Visuals::light());
}
};
}
});
});
egui::Panel::right("syntax_picker").show_inside(ui, |ui| {
ui.horizontal(|h| {
h.heading("Syntax");
h.checkbox(&mut self.example, "Example");
});
egui::ScrollArea::both().show(ui, |ui| {
for syntax in SYNTAXES.iter() {
if ui
.selectable_label(self.syntax.language() == syntax.name, syntax.name)
.clicked()
{
self.syntax = syntax.syntax();
self.completer =
Completer::new_with_syntax(&syntax.syntax()).with_user_words();
if self.example {
self.code = syntax.example.to_string()
}
};
}
});
});
egui::CentralPanel::default().show_inside(ui, |ui| {
ui.horizontal(|h| {
h.label("Numbering Shift");
h.add(egui::DragValue::new(&mut self.shift));
h.checkbox(&mut self.numlines_only_natural, "Only Natural Numbering");
});
let mut editor = CodeEditor::default()
.id_source("code editor")
.with_rows(10)
.with_fontsize(14.0)
.with_theme(self.theme)
.with_numlines(true)
.with_numlines_shift(self.shift)
.with_numlines_only_natural(self.numlines_only_natural)
.hint_text("Hint text if Editor is empty")
.vscroll(true);
editor.show_with_completer(ui, &mut self.code, &self.syntax, &mut self.completer);
ui.separator();
ui.horizontal(|h| {
h.label("Auto-complete TextEdit::singleLine");
self.completer.show_on_text_widget(
h,
&Syntax::simple("#"),
&ColorTheme::default(),
|ui| {
TextEdit::singleline(&mut self.text)
.lock_focus(true)
.show(ui)
},
);
if h.button("add words").clicked() {
for word in self.text.split_whitespace() {
let word = word.replace(|c: char| !(c.is_alphanumeric() || c == '_'), "");
self.completer.push_word(&word);
}
}
});
ui.separator();
egui::ScrollArea::both()
.auto_shrink([false; 2])
.show(ui, |ui| {
for token in Token::default().tokens(&self.syntax, &self.code) {
ui.horizontal(|h| {
let fmt = editor.format_token(token.ty());
h.label(egui::text::LayoutJob::single_section(
format!("{:?}", token.ty()),
fmt,
));
h.label(token.buffer());
});
}
});
});
}
}