@ -6,6 +6,7 @@
#![ allow(clippy::mem_forget) ] // False positive from enum_map macro
#![ allow(clippy::mem_forget) ] // False positive from enum_map macro
use egui ::text ::LayoutJob ;
use egui ::text ::LayoutJob ;
use egui ::TextStyle ;
/// View some code with syntax highlighting and selection.
/// View some code with syntax highlighting and selection.
pub fn code_view_ui (
pub fn code_view_ui (
@ -14,29 +15,53 @@ pub fn code_view_ui(
code : & str ,
code : & str ,
language : & str ,
language : & str ,
) -> egui ::Response {
) -> 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 ) )
ui . add ( egui ::Label ::new ( layout_job ) . selectable ( true ) )
}
}
/// Add syntax highlighting to a code string.
/// Add syntax highlighting to a code string.
///
///
/// The results are memoized, so you can call this every frame without performance penalty.
/// 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 {
pub fn highlight (
impl egui ::util ::cache ::ComputerMut < ( & CodeTheme , & str , & str ) , LayoutJob > for Highlighter {
ctx : & egui ::Context ,
fn compute ( & mut self , ( theme , code , lang ) : ( & CodeTheme , & str , & str ) ) -> LayoutJob {
style : & egui ::Style ,
self . highlight ( theme , code , lang )
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 > ;
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 | {
ctx . memory_mut ( | mem | {
mem . caches
mem . caches
. cache ::< HighlightCache > ( )
. 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 " )) ]
#[ cfg(not(feature = " syntect " )) ]
@ -128,6 +153,8 @@ pub struct CodeTheme {
#[ cfg(feature = " syntect " ) ]
#[ cfg(feature = " syntect " ) ]
syntect_theme : SyntectTheme ,
syntect_theme : SyntectTheme ,
#[ cfg(feature = " syntect " ) ]
font_id : egui ::FontId ,
#[ cfg(not(feature = " syntect " )) ]
#[ cfg(not(feature = " syntect " )) ]
formats : enum_map ::EnumMap < TokenType , egui ::TextFormat > ,
formats : enum_map ::EnumMap < TokenType , egui ::TextFormat > ,
@ -135,40 +162,75 @@ pub struct CodeTheme {
impl Default for CodeTheme {
impl Default for CodeTheme {
fn default ( ) -> Self {
fn default ( ) -> Self {
Self ::dark ( )
Self ::dark ( 12.0 )
}
}
}
}
impl CodeTheme {
impl CodeTheme {
/// Selects either dark or light theme based on the given style.
/// Selects either dark or light theme based on the given style.
pub fn from_style ( style : & egui ::Style ) -> Self {
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 {
if style . visuals . dark_mode {
Self ::dark ( )
Self ::dark_with_font_id ( font_id )
} else {
} 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.
/// Load code theme from egui memory.
///
///
/// There is one dark and one light theme stored at any one time.
/// 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) ]
#![ allow(clippy::needless_return) ]
let ( id , default ) = if ctx . style ( ) . visuals . dark_mode {
let ( id , default ) = if style . visuals . dark_mode {
( egui ::Id ::new ( "dark" ) , Self ::dark as fn ( ) -> Self )
( egui ::Id ::new ( "dark" ) , Self ::dark as fn ( f32 ) -> Self )
} else {
} else {
( egui ::Id ::new ( "light" ) , Self ::light as fn ( ) -> Self )
( egui ::Id ::new ( "light" ) , Self ::light as fn ( f32 ) -> Self )
} ;
} ;
#[ cfg(feature = " serde " ) ]
#[ 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 " )) ]
#[ 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 ) ) )
} ) ;
}
}
}
}
@ -192,17 +254,19 @@ impl CodeTheme {
#[ cfg(feature = " syntect " ) ]
#[ cfg(feature = " syntect " ) ]
impl CodeTheme {
impl CodeTheme {
pub fn dark ( ) -> Self {
fn dark_with_font_id ( font_id : egui ::FontId ) -> Self {
Self {
Self {
dark_mode : true ,
dark_mode : true ,
syntect_theme : SyntectTheme ::Base16MochaDark ,
syntect_theme : SyntectTheme ::Base16MochaDark ,
font_id ,
}
}
}
}
pub fn light ( ) -> Self {
fn light_with_font_id ( font_id : egui ::FontId ) -> Self {
Self {
Self {
dark_mode : false ,
dark_mode : false ,
syntect_theme : SyntectTheme ::SolarizedLight ,
syntect_theme : SyntectTheme ::SolarizedLight ,
font_id ,
}
}
}
}
@ -220,8 +284,10 @@ impl CodeTheme {
#[ cfg(not(feature = " syntect " )) ]
#[ cfg(not(feature = " syntect " )) ]
impl CodeTheme {
impl CodeTheme {
pub fn dark ( ) -> Self {
// The syntect version takes it by value. This could be avoided by specializing the from_style
let font_id = egui ::FontId ::monospace ( 10.0 ) ;
// 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 } ;
use egui ::{ Color32 , TextFormat } ;
Self {
Self {
dark_mode : true ,
dark_mode : true ,
@ -236,8 +302,9 @@ impl CodeTheme {
}
}
}
}
pub fn light ( ) -> Self {
// The syntect version takes it by value
let font_id = egui ::FontId ::monospace ( 10.0 ) ;
#[ allow(clippy::needless_pass_by_value) ]
fn light_with_font_id ( font_id : egui ::FontId ) -> Self {
use egui ::{ Color32 , TextFormat } ;
use egui ::{ Color32 , TextFormat } ;
Self {
Self {
dark_mode : false ,
dark_mode : false ,
@ -291,9 +358,9 @@ impl CodeTheme {
} ) ;
} ) ;
let reset_value = if self . dark_mode {
let reset_value = if self . dark_mode {
Self ::dark ( )
Self ::dark ( monospace_font_size ( ui . style ( ) ) )
} else {
} else {
Self ::light ( )
Self ::light ( monospace_font_size ( ui . style ( ) ) )
} ;
} ;
if ui
if ui
@ -348,12 +415,18 @@ impl Default for Highlighter {
impl Highlighter {
impl Highlighter {
#[ allow(clippy::unused_self, clippy::unnecessary_wraps) ]
#[ 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 ( | | {
self . highlight_impl ( theme , code , lang ) . unwrap_or_else ( | | {
// Fallback:
// Fallback:
LayoutJob ::simple (
LayoutJob ::simple (
code . into ( ) ,
code . into ( ) ,
egui ::FontId ::monospace ( 12.0 ) ,
font_id ,
if theme . dark_mode {
if theme . dark_mode {
egui ::Color32 ::LIGHT_GRAY
egui ::Color32 ::LIGHT_GRAY
} else {
} else {
@ -377,8 +450,8 @@ impl Highlighter {
. find_syntax_by_name ( language )
. find_syntax_by_name ( language )
. or_else ( | | self . ps . find_syntax_by_extension ( language ) ) ? ;
. or_else ( | | self . ps . find_syntax_by_extension ( language ) ) ? ;
let theme = theme . syntect_theme . syntect_key_name ( ) ;
let syn_ theme = theme . syntect_theme . syntect_key_name ( ) ;
let mut h = HighlightLines ::new ( syntax , & self . ts . themes [ theme ] ) ;
let mut h = HighlightLines ::new ( syntax , & self . ts . themes [ syn_ theme] ) ;
use egui ::text ::{ LayoutSection , TextFormat } ;
use egui ::text ::{ LayoutSection , TextFormat } ;
@ -402,7 +475,7 @@ impl Highlighter {
leading_space : 0.0 ,
leading_space : 0.0 ,
byte_range : as_byte_range ( text , range ) ,
byte_range : as_byte_range ( text , range ) ,
format : TextFormat {
format : TextFormat {
font_id : egui ::FontId ::monospace ( 12.0 ) ,
font_id : theme . font_id . clone ( ) ,
color : text_color ,
color : text_color ,
italics ,
italics ,
underline ,
underline ,