mirror of
https://github.com/Noratrieb/game-wip-dontplay.git
synced 2026-01-17 12:55:02 +01:00
vendor
This commit is contained in:
parent
12163d1338
commit
550b1644cb
363 changed files with 84081 additions and 16 deletions
506
egui/crates/egui_demo_lib/src/syntax_highlighting.rs
Normal file
506
egui/crates/egui_demo_lib/src/syntax_highlighting.rs
Normal file
|
|
@ -0,0 +1,506 @@
|
|||
use egui::text::LayoutJob;
|
||||
|
||||
/// View some code with syntax highlighting and selection.
|
||||
pub fn code_view_ui(ui: &mut egui::Ui, mut code: &str) {
|
||||
let language = "rs";
|
||||
let theme = CodeTheme::from_memory(ui.ctx());
|
||||
|
||||
let mut layouter = |ui: &egui::Ui, string: &str, _wrap_width: f32| {
|
||||
let layout_job = highlight(ui.ctx(), &theme, string, language);
|
||||
// layout_job.wrap.max_width = wrap_width; // no wrapping
|
||||
ui.fonts(|f| f.layout_job(layout_job))
|
||||
};
|
||||
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut code)
|
||||
.font(egui::TextStyle::Monospace) // for cursor height
|
||||
.code_editor()
|
||||
.desired_rows(1)
|
||||
.lock_focus(true)
|
||||
.layouter(&mut layouter),
|
||||
);
|
||||
}
|
||||
|
||||
/// Memoized Code highlighting
|
||||
pub fn highlight(ctx: &egui::Context, theme: &CodeTheme, code: &str, language: &str) -> LayoutJob {
|
||||
impl egui::util::cache::ComputerMut<(&CodeTheme, &str, &str), LayoutJob> for Highlighter {
|
||||
fn compute(&mut self, (theme, code, lang): (&CodeTheme, &str, &str)) -> LayoutJob {
|
||||
self.highlight(theme, code, lang)
|
||||
}
|
||||
}
|
||||
|
||||
type HighlightCache = egui::util::cache::FrameCache<LayoutJob, Highlighter>;
|
||||
|
||||
ctx.memory_mut(|mem| {
|
||||
mem.caches
|
||||
.cache::<HighlightCache>()
|
||||
.get((theme, code, language))
|
||||
})
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(not(feature = "syntect"))]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[derive(enum_map::Enum)]
|
||||
enum TokenType {
|
||||
Comment,
|
||||
Keyword,
|
||||
Literal,
|
||||
StringLiteral,
|
||||
Punctuation,
|
||||
Whitespace,
|
||||
}
|
||||
|
||||
#[cfg(feature = "syntect")]
|
||||
#[derive(Clone, Copy, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
enum SyntectTheme {
|
||||
Base16EightiesDark,
|
||||
Base16MochaDark,
|
||||
Base16OceanDark,
|
||||
Base16OceanLight,
|
||||
InspiredGitHub,
|
||||
SolarizedDark,
|
||||
SolarizedLight,
|
||||
}
|
||||
|
||||
#[cfg(feature = "syntect")]
|
||||
impl SyntectTheme {
|
||||
fn all() -> impl ExactSizeIterator<Item = Self> {
|
||||
[
|
||||
Self::Base16EightiesDark,
|
||||
Self::Base16MochaDark,
|
||||
Self::Base16OceanDark,
|
||||
Self::Base16OceanLight,
|
||||
Self::InspiredGitHub,
|
||||
Self::SolarizedDark,
|
||||
Self::SolarizedLight,
|
||||
]
|
||||
.iter()
|
||||
.copied()
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Base16EightiesDark => "Base16 Eighties (dark)",
|
||||
Self::Base16MochaDark => "Base16 Mocha (dark)",
|
||||
Self::Base16OceanDark => "Base16 Ocean (dark)",
|
||||
Self::Base16OceanLight => "Base16 Ocean (light)",
|
||||
Self::InspiredGitHub => "InspiredGitHub (light)",
|
||||
Self::SolarizedDark => "Solarized (dark)",
|
||||
Self::SolarizedLight => "Solarized (light)",
|
||||
}
|
||||
}
|
||||
|
||||
fn syntect_key_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Base16EightiesDark => "base16-eighties.dark",
|
||||
Self::Base16MochaDark => "base16-mocha.dark",
|
||||
Self::Base16OceanDark => "base16-ocean.dark",
|
||||
Self::Base16OceanLight => "base16-ocean.light",
|
||||
Self::InspiredGitHub => "InspiredGitHub",
|
||||
Self::SolarizedDark => "Solarized (dark)",
|
||||
Self::SolarizedLight => "Solarized (light)",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_dark(&self) -> bool {
|
||||
match self {
|
||||
Self::Base16EightiesDark
|
||||
| Self::Base16MochaDark
|
||||
| Self::Base16OceanDark
|
||||
| Self::SolarizedDark => true,
|
||||
|
||||
Self::Base16OceanLight | Self::InspiredGitHub | Self::SolarizedLight => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct CodeTheme {
|
||||
dark_mode: bool,
|
||||
|
||||
#[cfg(feature = "syntect")]
|
||||
syntect_theme: SyntectTheme,
|
||||
|
||||
#[cfg(not(feature = "syntect"))]
|
||||
formats: enum_map::EnumMap<TokenType, egui::TextFormat>,
|
||||
}
|
||||
|
||||
impl Default for CodeTheme {
|
||||
fn default() -> Self {
|
||||
Self::dark()
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeTheme {
|
||||
pub fn from_style(style: &egui::Style) -> Self {
|
||||
if style.visuals.dark_mode {
|
||||
Self::dark()
|
||||
} else {
|
||||
Self::light()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_memory(ctx: &egui::Context) -> Self {
|
||||
if ctx.style().visuals.dark_mode {
|
||||
ctx.data_mut(|d| {
|
||||
d.get_persisted(egui::Id::new("dark"))
|
||||
.unwrap_or_else(CodeTheme::dark)
|
||||
})
|
||||
} else {
|
||||
ctx.data_mut(|d| {
|
||||
d.get_persisted(egui::Id::new("light"))
|
||||
.unwrap_or_else(CodeTheme::light)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store_in_memory(self, ctx: &egui::Context) {
|
||||
if self.dark_mode {
|
||||
ctx.data_mut(|d| d.insert_persisted(egui::Id::new("dark"), self));
|
||||
} else {
|
||||
ctx.data_mut(|d| d.insert_persisted(egui::Id::new("light"), self));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "syntect")]
|
||||
impl CodeTheme {
|
||||
pub fn dark() -> Self {
|
||||
Self {
|
||||
dark_mode: true,
|
||||
syntect_theme: SyntectTheme::Base16MochaDark,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn light() -> Self {
|
||||
Self {
|
||||
dark_mode: false,
|
||||
syntect_theme: SyntectTheme::SolarizedLight,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
egui::widgets::global_dark_light_mode_buttons(ui);
|
||||
|
||||
for theme in SyntectTheme::all() {
|
||||
if theme.is_dark() == self.dark_mode {
|
||||
ui.radio_value(&mut self.syntect_theme, theme, theme.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "syntect"))]
|
||||
impl CodeTheme {
|
||||
pub fn dark() -> Self {
|
||||
let font_id = egui::FontId::monospace(10.0);
|
||||
use egui::{Color32, TextFormat};
|
||||
Self {
|
||||
dark_mode: true,
|
||||
formats: enum_map::enum_map![
|
||||
TokenType::Comment => TextFormat::simple(font_id.clone(), Color32::from_gray(120)),
|
||||
TokenType::Keyword => TextFormat::simple(font_id.clone(), Color32::from_rgb(255, 100, 100)),
|
||||
TokenType::Literal => TextFormat::simple(font_id.clone(), Color32::from_rgb(87, 165, 171)),
|
||||
TokenType::StringLiteral => TextFormat::simple(font_id.clone(), Color32::from_rgb(109, 147, 226)),
|
||||
TokenType::Punctuation => TextFormat::simple(font_id.clone(), Color32::LIGHT_GRAY),
|
||||
TokenType::Whitespace => TextFormat::simple(font_id.clone(), Color32::TRANSPARENT),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn light() -> Self {
|
||||
let font_id = egui::FontId::monospace(10.0);
|
||||
use egui::{Color32, TextFormat};
|
||||
Self {
|
||||
dark_mode: false,
|
||||
#[cfg(not(feature = "syntect"))]
|
||||
formats: enum_map::enum_map![
|
||||
TokenType::Comment => TextFormat::simple(font_id.clone(), Color32::GRAY),
|
||||
TokenType::Keyword => TextFormat::simple(font_id.clone(), Color32::from_rgb(235, 0, 0)),
|
||||
TokenType::Literal => TextFormat::simple(font_id.clone(), Color32::from_rgb(153, 134, 255)),
|
||||
TokenType::StringLiteral => TextFormat::simple(font_id.clone(), Color32::from_rgb(37, 203, 105)),
|
||||
TokenType::Punctuation => TextFormat::simple(font_id.clone(), Color32::DARK_GRAY),
|
||||
TokenType::Whitespace => TextFormat::simple(font_id.clone(), Color32::TRANSPARENT),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.horizontal_top(|ui| {
|
||||
let selected_id = egui::Id::null();
|
||||
let mut selected_tt: TokenType =
|
||||
ui.data_mut(|d| *d.get_persisted_mut_or(selected_id, TokenType::Comment));
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.set_width(150.0);
|
||||
egui::widgets::global_dark_light_mode_buttons(ui);
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.separator();
|
||||
ui.add_space(8.0);
|
||||
|
||||
ui.scope(|ui| {
|
||||
for (tt, tt_name) in [
|
||||
(TokenType::Comment, "// comment"),
|
||||
(TokenType::Keyword, "keyword"),
|
||||
(TokenType::Literal, "literal"),
|
||||
(TokenType::StringLiteral, "\"string literal\""),
|
||||
(TokenType::Punctuation, "punctuation ;"),
|
||||
// (TokenType::Whitespace, "whitespace"),
|
||||
] {
|
||||
let format = &mut self.formats[tt];
|
||||
ui.style_mut().override_font_id = Some(format.font_id.clone());
|
||||
ui.visuals_mut().override_text_color = Some(format.color);
|
||||
ui.radio_value(&mut selected_tt, tt, tt_name);
|
||||
}
|
||||
});
|
||||
|
||||
let reset_value = if self.dark_mode {
|
||||
CodeTheme::dark()
|
||||
} else {
|
||||
CodeTheme::light()
|
||||
};
|
||||
|
||||
if ui
|
||||
.add_enabled(*self != reset_value, egui::Button::new("Reset theme"))
|
||||
.clicked()
|
||||
{
|
||||
*self = reset_value;
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(16.0);
|
||||
|
||||
ui.data_mut(|d| d.insert_persisted(selected_id, selected_tt));
|
||||
|
||||
egui::Frame::group(ui.style())
|
||||
.inner_margin(egui::Vec2::splat(2.0))
|
||||
.show(ui, |ui| {
|
||||
// ui.group(|ui| {
|
||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
|
||||
ui.spacing_mut().slider_width = 128.0; // Controls color picker size
|
||||
egui::widgets::color_picker::color_picker_color32(
|
||||
ui,
|
||||
&mut self.formats[selected_tt].color,
|
||||
egui::color_picker::Alpha::Opaque,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(feature = "syntect")]
|
||||
struct Highlighter {
|
||||
ps: syntect::parsing::SyntaxSet,
|
||||
ts: syntect::highlighting::ThemeSet,
|
||||
}
|
||||
|
||||
#[cfg(feature = "syntect")]
|
||||
impl Default for Highlighter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ps: syntect::parsing::SyntaxSet::load_defaults_newlines(),
|
||||
ts: syntect::highlighting::ThemeSet::load_defaults(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "syntect")]
|
||||
impl Highlighter {
|
||||
#[allow(clippy::unused_self, clippy::unnecessary_wraps)]
|
||||
fn highlight(&self, theme: &CodeTheme, code: &str, lang: &str) -> LayoutJob {
|
||||
self.highlight_impl(theme, code, lang).unwrap_or_else(|| {
|
||||
// Fallback:
|
||||
LayoutJob::simple(
|
||||
code.into(),
|
||||
egui::FontId::monospace(12.0),
|
||||
if theme.dark_mode {
|
||||
egui::Color32::LIGHT_GRAY
|
||||
} else {
|
||||
egui::Color32::DARK_GRAY
|
||||
},
|
||||
f32::INFINITY,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn highlight_impl(&self, theme: &CodeTheme, text: &str, language: &str) -> Option<LayoutJob> {
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::highlighting::FontStyle;
|
||||
use syntect::util::LinesWithEndings;
|
||||
|
||||
let syntax = self
|
||||
.ps
|
||||
.find_syntax_by_name(language)
|
||||
.or_else(|| self.ps.find_syntax_by_extension(language))?;
|
||||
|
||||
let theme = theme.syntect_theme.syntect_key_name();
|
||||
let mut h = HighlightLines::new(syntax, &self.ts.themes[theme]);
|
||||
|
||||
use egui::text::{LayoutSection, TextFormat};
|
||||
|
||||
let mut job = LayoutJob {
|
||||
text: text.into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for line in LinesWithEndings::from(text) {
|
||||
for (style, range) in h.highlight_line(line, &self.ps).ok()? {
|
||||
let fg = style.foreground;
|
||||
let text_color = egui::Color32::from_rgb(fg.r, fg.g, fg.b);
|
||||
let italics = style.font_style.contains(FontStyle::ITALIC);
|
||||
let underline = style.font_style.contains(FontStyle::ITALIC);
|
||||
let underline = if underline {
|
||||
egui::Stroke::new(1.0, text_color)
|
||||
} else {
|
||||
egui::Stroke::NONE
|
||||
};
|
||||
job.sections.push(LayoutSection {
|
||||
leading_space: 0.0,
|
||||
byte_range: as_byte_range(text, range),
|
||||
format: TextFormat {
|
||||
font_id: egui::FontId::monospace(12.0),
|
||||
color: text_color,
|
||||
italics,
|
||||
underline,
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Some(job)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "syntect")]
|
||||
fn as_byte_range(whole: &str, range: &str) -> std::ops::Range<usize> {
|
||||
let whole_start = whole.as_ptr() as usize;
|
||||
let range_start = range.as_ptr() as usize;
|
||||
assert!(whole_start <= range_start);
|
||||
assert!(range_start + range.len() <= whole_start + whole.len());
|
||||
let offset = range_start - whole_start;
|
||||
offset..(offset + range.len())
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(not(feature = "syntect"))]
|
||||
#[derive(Default)]
|
||||
struct Highlighter {}
|
||||
|
||||
#[cfg(not(feature = "syntect"))]
|
||||
impl Highlighter {
|
||||
#[allow(clippy::unused_self, clippy::unnecessary_wraps)]
|
||||
fn highlight(&self, theme: &CodeTheme, mut text: &str, _language: &str) -> LayoutJob {
|
||||
// Extremely simple syntax highlighter for when we compile without syntect
|
||||
|
||||
let mut job = LayoutJob::default();
|
||||
|
||||
while !text.is_empty() {
|
||||
if text.starts_with("//") {
|
||||
let end = text.find('\n').unwrap_or(text.len());
|
||||
job.append(&text[..end], 0.0, theme.formats[TokenType::Comment].clone());
|
||||
text = &text[end..];
|
||||
} else if text.starts_with('"') {
|
||||
let end = text[1..]
|
||||
.find('"')
|
||||
.map(|i| i + 2)
|
||||
.or_else(|| text.find('\n'))
|
||||
.unwrap_or(text.len());
|
||||
job.append(
|
||||
&text[..end],
|
||||
0.0,
|
||||
theme.formats[TokenType::StringLiteral].clone(),
|
||||
);
|
||||
text = &text[end..];
|
||||
} else if text.starts_with(|c: char| c.is_ascii_alphanumeric()) {
|
||||
let end = text[1..]
|
||||
.find(|c: char| !c.is_ascii_alphanumeric())
|
||||
.map_or_else(|| text.len(), |i| i + 1);
|
||||
let word = &text[..end];
|
||||
let tt = if is_keyword(word) {
|
||||
TokenType::Keyword
|
||||
} else {
|
||||
TokenType::Literal
|
||||
};
|
||||
job.append(word, 0.0, theme.formats[tt].clone());
|
||||
text = &text[end..];
|
||||
} else if text.starts_with(|c: char| c.is_ascii_whitespace()) {
|
||||
let end = text[1..]
|
||||
.find(|c: char| !c.is_ascii_whitespace())
|
||||
.map_or_else(|| text.len(), |i| i + 1);
|
||||
job.append(
|
||||
&text[..end],
|
||||
0.0,
|
||||
theme.formats[TokenType::Whitespace].clone(),
|
||||
);
|
||||
text = &text[end..];
|
||||
} else {
|
||||
let mut it = text.char_indices();
|
||||
it.next();
|
||||
let end = it.next().map_or(text.len(), |(idx, _chr)| idx);
|
||||
job.append(
|
||||
&text[..end],
|
||||
0.0,
|
||||
theme.formats[TokenType::Punctuation].clone(),
|
||||
);
|
||||
text = &text[end..];
|
||||
}
|
||||
}
|
||||
|
||||
job
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "syntect"))]
|
||||
fn is_keyword(word: &str) -> bool {
|
||||
matches!(
|
||||
word,
|
||||
"as" | "async"
|
||||
| "await"
|
||||
| "break"
|
||||
| "const"
|
||||
| "continue"
|
||||
| "crate"
|
||||
| "dyn"
|
||||
| "else"
|
||||
| "enum"
|
||||
| "extern"
|
||||
| "false"
|
||||
| "fn"
|
||||
| "for"
|
||||
| "if"
|
||||
| "impl"
|
||||
| "in"
|
||||
| "let"
|
||||
| "loop"
|
||||
| "match"
|
||||
| "mod"
|
||||
| "move"
|
||||
| "mut"
|
||||
| "pub"
|
||||
| "ref"
|
||||
| "return"
|
||||
| "self"
|
||||
| "Self"
|
||||
| "static"
|
||||
| "struct"
|
||||
| "super"
|
||||
| "trait"
|
||||
| "true"
|
||||
| "type"
|
||||
| "unsafe"
|
||||
| "use"
|
||||
| "where"
|
||||
| "while"
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue