Skip to content

Commit

Permalink
Use Style's font size in egui_extras::syntax_highlighting (emilk#5090)
Browse files Browse the repository at this point in the history
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->
* Closes emilk#3549
* [X] I have followed the instructions in the PR template

The syntax highlighting font size was always hardcoded to 12 or 10
depending on what case it was hitting (so not consistent). This is
particularly noticeable when you increase the font size to something
larger for the rest of the ui.

With this the default monospace font size is used by default.

Since the issue is closely related to emilk#3549 I decided to implement the
ability to use override_font_id too.

## Visualized

Default monospace is set to 15 in all the pictures

Before/After without syntect:

![normal](https://github.com/user-attachments/assets/0d058720-47ff-49e7-af77-30d48f5e138c)


Before/after _with_ syntect:

![syntect](https://github.com/user-attachments/assets/e5c380fe-ced1-40ee-b4b1-c26cec18a840)

Font override after without/with syntect (monospace = 20):

![override](https://github.com/user-attachments/assets/efd1b759-3f97-4673-864a-5a18afc64099)

### Breaking changes

- `CodeTheme::dark` and `CodeTheme::light` takes in the font size
- `CodeTheme::from_memory` takes in `Style`
- `highlight` function takes in `Style`
  • Loading branch information
lampsitter authored and hacknus committed Oct 30, 2024
1 parent 1395aba commit 43e4d00
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 34 deletions.
6 changes: 5 additions & 1 deletion crates/egui_demo_app/src/apps/http_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,11 @@ fn syntax_highlighting(
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,
ctx,
&ctx.style(),
&theme,
text,
extension,
)))
}

Expand Down
12 changes: 9 additions & 3 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,8 @@ impl crate::View for CodeEditor {
});
}

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

let mut layouter = |ui: &egui::Ui, string: &str, wrap_width: f32| {
let mut layout_job =
egui_extras::syntax_highlighting::highlight(ui.ctx(), &theme, string, language);
let mut layout_job = egui_extras::syntax_highlighting::highlight(
ui.ctx(),
ui.style(),
&theme,
string,
language,
);
layout_job.wrap.max_width = wrap_width;
ui.fonts(|f| f.layout_job(layout_job))
};
Expand Down
3 changes: 2 additions & 1 deletion crates/egui_demo_lib/src/demo/code_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ impl crate::View for CodeExample {

ui.separator();

let mut theme = egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx());
let mut theme =
egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx(), ui.style());
ui.collapsing("Theme", |ui| {
theme.ui(ui);
theme.store_in_memory(ui.ctx());
Expand Down
2 changes: 1 addition & 1 deletion crates/egui_demo_lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub use rendering_test::ColorTest;
/// 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());
let theme = egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx(), ui.style());
egui_extras::syntax_highlighting::code_view_ui(ui, &theme, code, language);
}

Expand Down
129 changes: 101 additions & 28 deletions crates/egui_extras/src/syntax_highlighting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#![allow(clippy::mem_forget)] // False positive from enum_map macro

use egui::text::LayoutJob;
use egui::TextStyle;

/// View some code with syntax highlighting and selection.
pub fn code_view_ui(
Expand All @@ -14,29 +15,53 @@ pub fn code_view_ui(
code: &str,
language: &str,
) -> egui::Response {
let layout_job = highlight(ui.ctx(), theme, code, language);
let layout_job = highlight(ui.ctx(), ui.style(), theme, code, language);
ui.add(egui::Label::new(layout_job).selectable(true))
}

/// Add syntax highlighting to a code string.
///
/// The results are memoized, so you can call this every frame without performance penalty.
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)
pub fn highlight(
ctx: &egui::Context,
style: &egui::Style,
theme: &CodeTheme,
code: &str,
language: &str,
) -> LayoutJob {
// We take in both context and style so that in situations where ui is not available such as when
// performing it at a separate thread (ctx, ctx.style()) can be used and when ui is available
// (ui.ctx(), ui.style()) can be used

impl egui::util::cache::ComputerMut<(&egui::FontId, &CodeTheme, &str, &str), LayoutJob>
for Highlighter
{
fn compute(
&mut self,
(font_id, theme, code, lang): (&egui::FontId, &CodeTheme, &str, &str),
) -> LayoutJob {
self.highlight(font_id.clone(), theme, code, lang)
}
}

type HighlightCache = egui::util::cache::FrameCache<LayoutJob, Highlighter>;

let font_id = style
.override_font_id
.clone()
.unwrap_or_else(|| TextStyle::Monospace.resolve(style));

ctx.memory_mut(|mem| {
mem.caches
.cache::<HighlightCache>()
.get((theme, code, language))
.get((&font_id, theme, code, language))
})
}

fn monospace_font_size(style: &egui::Style) -> f32 {
TextStyle::Monospace.resolve(style).size
}

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

#[cfg(not(feature = "syntect"))]
Expand Down Expand Up @@ -128,47 +153,84 @@ pub struct CodeTheme {

#[cfg(feature = "syntect")]
syntect_theme: SyntectTheme,
#[cfg(feature = "syntect")]
font_id: egui::FontId,

#[cfg(not(feature = "syntect"))]
formats: enum_map::EnumMap<TokenType, egui::TextFormat>,
}

impl Default for CodeTheme {
fn default() -> Self {
Self::dark()
Self::dark(12.0)
}
}

impl CodeTheme {
/// Selects either dark or light theme based on the given style.
pub fn from_style(style: &egui::Style) -> Self {
let font_id = style
.override_font_id
.clone()
.unwrap_or_else(|| TextStyle::Monospace.resolve(style));

if style.visuals.dark_mode {
Self::dark()
Self::dark_with_font_id(font_id)
} else {
Self::light()
Self::light_with_font_id(font_id)
}
}

/// ### Example
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// use egui_extras::syntax_highlighting::CodeTheme;
/// let theme = CodeTheme::dark(12.0);
/// # });
/// ```
pub fn dark(font_size: f32) -> Self {
Self::dark_with_font_id(egui::FontId::monospace(font_size))
}

/// ### Example
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// use egui_extras::syntax_highlighting::CodeTheme;
/// let theme = CodeTheme::light(12.0);
/// # });
/// ```
pub fn light(font_size: f32) -> Self {
Self::light_with_font_id(egui::FontId::monospace(font_size))
}

/// Load code theme from egui memory.
///
/// There is one dark and one light theme stored at any one time.
pub fn from_memory(ctx: &egui::Context) -> Self {
pub fn from_memory(ctx: &egui::Context, style: &egui::Style) -> Self {
#![allow(clippy::needless_return)]

let (id, default) = if ctx.style().visuals.dark_mode {
(egui::Id::new("dark"), Self::dark as fn() -> Self)
let (id, default) = if style.visuals.dark_mode {
(egui::Id::new("dark"), Self::dark as fn(f32) -> Self)
} else {
(egui::Id::new("light"), Self::light as fn() -> Self)
(egui::Id::new("light"), Self::light as fn(f32) -> Self)
};

#[cfg(feature = "serde")]
{
return ctx.data_mut(|d| d.get_persisted(id).unwrap_or_else(default));
return ctx.data_mut(|d| {
d.get_persisted(id)
.unwrap_or_else(|| default(monospace_font_size(style)))
});
}

#[cfg(not(feature = "serde"))]
{
return ctx.data_mut(|d| d.get_temp(id).unwrap_or_else(default));
return ctx.data_mut(|d| {
d.get_temp(id)
.unwrap_or_else(|| default(monospace_font_size(style)))
});
}
}

Expand All @@ -192,17 +254,19 @@ impl CodeTheme {

#[cfg(feature = "syntect")]
impl CodeTheme {
pub fn dark() -> Self {
fn dark_with_font_id(font_id: egui::FontId) -> Self {
Self {
dark_mode: true,
syntect_theme: SyntectTheme::Base16MochaDark,
font_id,
}
}

pub fn light() -> Self {
fn light_with_font_id(font_id: egui::FontId) -> Self {
Self {
dark_mode: false,
syntect_theme: SyntectTheme::SolarizedLight,
font_id,
}
}

Expand All @@ -220,8 +284,10 @@ impl CodeTheme {

#[cfg(not(feature = "syntect"))]
impl CodeTheme {
pub fn dark() -> Self {
let font_id = egui::FontId::monospace(10.0);
// The syntect version takes it by value. This could be avoided by specializing the from_style
// function, but at the cost of more code duplication.
#[allow(clippy::needless_pass_by_value)]
fn dark_with_font_id(font_id: egui::FontId) -> Self {
use egui::{Color32, TextFormat};
Self {
dark_mode: true,
Expand All @@ -236,8 +302,9 @@ impl CodeTheme {
}
}

pub fn light() -> Self {
let font_id = egui::FontId::monospace(10.0);
// The syntect version takes it by value
#[allow(clippy::needless_pass_by_value)]
fn light_with_font_id(font_id: egui::FontId) -> Self {
use egui::{Color32, TextFormat};
Self {
dark_mode: false,
Expand Down Expand Up @@ -291,9 +358,9 @@ impl CodeTheme {
});

let reset_value = if self.dark_mode {
Self::dark()
Self::dark(monospace_font_size(ui.style()))
} else {
Self::light()
Self::light(monospace_font_size(ui.style()))
};

if ui
Expand Down Expand Up @@ -348,12 +415,18 @@ impl Default for Highlighter {

impl Highlighter {
#[allow(clippy::unused_self, clippy::unnecessary_wraps)]
fn highlight(&self, theme: &CodeTheme, code: &str, lang: &str) -> LayoutJob {
fn highlight(
&self,
font_id: egui::FontId,
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),
font_id,
if theme.dark_mode {
egui::Color32::LIGHT_GRAY
} else {
Expand All @@ -377,8 +450,8 @@ impl Highlighter {
.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]);
let syn_theme = theme.syntect_theme.syntect_key_name();
let mut h = HighlightLines::new(syntax, &self.ts.themes[syn_theme]);

use egui::text::{LayoutSection, TextFormat};

Expand All @@ -402,7 +475,7 @@ impl Highlighter {
leading_space: 0.0,
byte_range: as_byte_range(text, range),
format: TextFormat {
font_id: egui::FontId::monospace(12.0),
font_id: theme.font_id.clone(),
color: text_color,
italics,
underline,
Expand Down

0 comments on commit 43e4d00

Please sign in to comment.