Skip to content

Commit

Permalink
Add syntax highlighing feature to egui_extras (#3333)
Browse files Browse the repository at this point in the history
* Add syntax highlighing feature to egui_extras

Enable "syntect" feature for great syntax highlighting of any language.

If not a simple fallback is used that works fine for C++, Rust, Python

* Check --no-default-features of egui_extras on CI

* spelling

* Fix building egui_extras without additional features
  • Loading branch information
emilk authored Sep 13, 2023
1 parent 4b5146d commit 5e785ae
Show file tree
Hide file tree
Showing 15 changed files with 307 additions and 98 deletions.
13 changes: 8 additions & 5 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,22 @@ jobs:
run: cargo check --locked --all-features --all-targets

- name: check egui_extras --all-features
run: cargo check --locked --all-features --all-targets -p egui_extras
run: cargo check --locked --all-features -p egui_extras

- name: check default features
run: cargo check --locked --all-targets

- name: check --no-default-features
run: cargo check --locked --no-default-features --lib --all-targets

- name: check epaint --no-default-features
run: cargo check --locked --no-default-features --lib --all-targets -p epaint

- name: check eframe --no-default-features
run: cargo check --locked --no-default-features --features x11 --lib --all-targets -p eframe
run: cargo check --locked --no-default-features --features x11 --lib -p eframe

- name: check egui_extras --no-default-features
run: cargo check --locked --no-default-features --lib -p egui_extras

- name: check epaint --no-default-features
run: cargo check --locked --no-default-features --lib -p epaint

- name: Test doc-tests
run: cargo test --doc --all-features
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/egui/src/load/bytes_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ impl DefaultBytesLoader {
self.cache
.lock()
.entry(uri.into())
.or_insert_with_key(|uri| {
.or_insert_with_key(|_uri| {
let bytes: Bytes = bytes.into();

#[cfg(feature = "log")]
log::trace!("loaded {} bytes for uri {uri:?}", bytes.len());
log::trace!("loaded {} bytes for uri {_uri:?}", bytes.len());

bytes
});
Expand Down
2 changes: 1 addition & 1 deletion crates/egui_demo_app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ image_viewer = ["image", "egui_extras/all-loaders", "rfd"]
persistence = ["eframe/persistence", "egui/persistence", "serde"]
web_screen_reader = ["eframe/web_screen_reader"] # experimental
serde = ["dep:serde", "egui_demo_lib/serde", "egui/serde"]
syntax_highlighting = ["egui_demo_lib/syntax_highlighting"]
syntect = ["egui_demo_lib/syntect"]

glow = ["eframe/glow"]
wgpu = ["eframe/wgpu", "bytemuck"]
Expand Down
12 changes: 3 additions & 9 deletions crates/egui_demo_app/src/apps/http_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,25 +223,19 @@ fn selectable_text(ui: &mut egui::Ui, mut text: &str) {
// ----------------------------------------------------------------------------
// Syntax highlighting:

#[cfg(feature = "syntect")]
fn syntax_highlighting(
ctx: &egui::Context,
response: &ehttp::Response,
text: &str,
) -> Option<ColoredText> {
let extension_and_rest: Vec<&str> = response.url.rsplitn(2, '.').collect();
let extension = extension_and_rest.get(0)?;
let theme = crate::syntax_highlighting::CodeTheme::from_style(&ctx.style());
Some(ColoredText(crate::syntax_highlighting::highlight(
let extension = extension_and_rest.first()?;
let theme = egui_extras::syntax_highlighting::CodeTheme::from_style(&ctx.style());
Some(ColoredText(egui_extras::syntax_highlighting::highlight(
ctx, &theme, text, extension,
)))
}

#[cfg(not(feature = "syntect"))]
fn syntax_highlighting(_ctx: &egui::Context, _: &ehttp::Response, _: &str) -> Option<ColoredText> {
None
}

struct ColoredText(egui::text::LayoutJob);

impl ColoredText {
Expand Down
6 changes: 1 addition & 5 deletions crates/egui_demo_lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ chrono = ["egui_extras/datepicker", "dep:chrono"]
serde = ["egui/serde", "egui_plot/serde", "dep:serde"]

## Enable better syntax highlighting using [`syntect`](https://docs.rs/syntect).
syntax_highlighting = ["syntect"]
syntect = ["egui_extras/syntect"]


[dependencies]
Expand All @@ -37,7 +37,6 @@ egui_extras = { version = "0.22.0", path = "../egui_extras", features = [
"log",
] }
egui_plot = { version = "0.22.0", path = "../egui_plot" }
enum-map = { version = "2", features = ["serde"] }
log = { version = "0.4", features = ["std"] }
unicode_names2 = { version = "0.6.0", default-features = false }

Expand All @@ -46,9 +45,6 @@ chrono = { version = "0.4", optional = true, features = ["js-sys", "wasmbind"] }
## Enable this when generating docs.
document-features = { version = "0.2", optional = true }
serde = { version = "1", optional = true, features = ["derive"] }
syntect = { version = "5", optional = true, default-features = false, features = [
"default-fancy",
] }


[dev-dependencies]
Expand Down
3 changes: 1 addition & 2 deletions crates/egui_demo_lib/src/demo/about.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ impl super::View for About {
}

fn about_immediate_mode(ui: &mut egui::Ui) {
use crate::syntax_highlighting::code_view_ui;
ui.style_mut().spacing.interact_size.y = 0.0; // hack to make `horizontal_wrapped` work better with text.

ui.horizontal_wrapped(|ui| {
Expand All @@ -56,7 +55,7 @@ fn about_immediate_mode(ui: &mut egui::Ui) {
});

ui.add_space(8.0);
code_view_ui(
crate::rust_view_ui(
ui,
r#"
if ui.button("Save").clicked() {
Expand Down
4 changes: 2 additions & 2 deletions crates/egui_demo_lib/src/demo/code_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl super::View for CodeEditor {
});
}

let mut theme = crate::syntax_highlighting::CodeTheme::from_memory(ui.ctx());
let mut theme = egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx());
ui.collapsing("Theme", |ui| {
ui.group(|ui| {
theme.ui(ui);
Expand All @@ -77,7 +77,7 @@ impl super::View for CodeEditor {

let mut layouter = |ui: &egui::Ui, string: &str, wrap_width: f32| {
let mut layout_job =
crate::syntax_highlighting::highlight(ui.ctx(), &theme, string, language);
egui_extras::syntax_highlighting::highlight(ui.ctx(), &theme, string, language);
layout_job.wrap.max_width = wrap_width;
ui.fonts(|f| f.layout_job(layout_job))
};
Expand Down
12 changes: 5 additions & 7 deletions crates/egui_demo_lib/src/demo/code_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,11 @@ impl super::Demo for CodeExample {

impl super::View for CodeExample {
fn ui(&mut self, ui: &mut egui::Ui) {
use crate::syntax_highlighting::code_view_ui;

ui.vertical_centered(|ui| {
ui.add(crate::egui_github_link_file!());
});

code_view_ui(
crate::rust_view_ui(
ui,
r"
pub struct CodeExample {
Expand Down Expand Up @@ -117,15 +115,15 @@ impl CodeExample {
});
});

code_view_ui(ui, " }\n}");
crate::rust_view_ui(ui, " }\n}");

ui.separator();

code_view_ui(ui, &format!("{self:#?}"));
crate::rust_view_ui(ui, &format!("{self:#?}"));

ui.separator();

let mut theme = crate::syntax_highlighting::CodeTheme::from_memory(ui.ctx());
let mut theme = egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx());
ui.collapsing("Theme", |ui| {
theme.ui(ui);
theme.store_in_memory(ui.ctx());
Expand All @@ -135,7 +133,7 @@ impl CodeExample {

fn show_code(ui: &mut egui::Ui, code: &str) {
let code = remove_leading_indentation(code.trim_start_matches('\n'));
crate::syntax_highlighting::code_view_ui(ui, &code);
crate::rust_view_ui(ui, &code);
}

fn remove_leading_indentation(code: &str) -> String {
Expand Down
8 changes: 7 additions & 1 deletion crates/egui_demo_lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,17 @@
mod color_test;
mod demo;
pub mod easy_mark;
pub mod syntax_highlighting;

pub use color_test::ColorTest;
pub use demo::DemoWindows;

/// View some Rust code with syntax highlighting and selection.
pub(crate) fn rust_view_ui(ui: &mut egui::Ui, code: &str) {
let language = "rs";
let theme = egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx());
egui_extras::syntax_highlighting::code_view_ui(ui, &theme, code, language);
}

// ----------------------------------------------------------------------------

/// Create a [`Hyperlink`](egui::Hyperlink) to this egui source code file on github.
Expand Down
10 changes: 9 additions & 1 deletion crates/egui_extras/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,13 @@ puffin = ["dep:puffin", "egui/puffin"]
## Support loading svg images.
svg = ["resvg", "tiny-skia", "usvg"]

## Enable better syntax highlighting using [`syntect`](https://docs.rs/syntect).
syntect = ["dep:syntect"]


[dependencies]
egui = { version = "0.22.0", path = "../egui", default-features = false }

enum-map = { version = "2", features = ["serde"] }
serde = { version = "1", features = ["derive"] }

#! ### Optional dependencies
Expand Down Expand Up @@ -83,6 +87,10 @@ mime_guess = { version = "2.0.4", optional = true, default-features = false }

puffin = { version = "0.16", optional = true }

syntect = { version = "5", optional = true, default-features = false, features = [
"default-fancy",
] }

# svg feature
resvg = { version = "0.28", optional = true, default-features = false }
tiny-skia = { version = "0.8", optional = true, default-features = false } # must be updated in lock-step with resvg
Expand Down
2 changes: 2 additions & 0 deletions crates/egui_extras/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#[cfg(feature = "chrono")]
mod datepicker;

pub mod syntax_highlighting;

#[doc(hidden)]
pub mod image;
mod layout;
Expand Down
22 changes: 15 additions & 7 deletions crates/egui_extras/src/loaders/file_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,29 @@ impl BytesLoader for FileLoader {
.spawn({
let ctx = ctx.clone();
let cache = self.cache.clone();
let uri = uri.to_owned();
let _uri = uri.to_owned();
move || {
let result = match std::fs::read(&path) {
Ok(bytes) => Ok(File {
bytes: bytes.into(),
mime: mime_guess::from_path(&path)
Ok(bytes) => {
#[cfg(feature = "mime_guess")]
let mime = mime_guess::from_path(&path)
.first_raw()
.map(|v| v.to_owned()),
}),
.map(|v| v.to_owned());

#[cfg(not(feature = "mime_guess"))]
let mime = None;

Ok(File {
bytes: bytes.into(),
mime,
})
}
Err(err) => Err(err.to_string()),
};
let prev = cache.lock().insert(path, Poll::Ready(result));
assert!(matches!(prev, Some(Poll::Pending)));
ctx.request_repaint();
crate::log_trace!("finished loading {uri:?}");
crate::log_trace!("finished loading {_uri:?}");
}
})
.expect("failed to spawn thread");
Expand Down
2 changes: 1 addition & 1 deletion crates/egui_extras/src/sizing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ impl From<Vec<Size>> for Sizing {
#[test]
fn test_sizing() {
let sizing: Sizing = vec![].into();
assert_eq!(sizing.to_lengths(50.0, 0.0), vec![]);
assert_eq!(sizing.to_lengths(50.0, 0.0), Vec::<f32>::new());

let sizing: Sizing = vec![Size::remainder().at_least(20.0), Size::remainder()].into();
assert_eq!(sizing.to_lengths(50.0, 0.0), vec![25.0, 25.0]);
Expand Down
Loading

0 comments on commit 5e785ae

Please sign in to comment.