diff --git a/gpui/examples/text.rs b/gpui/examples/text.rs index 32086b7fda327b9a9d9f99ad7b52f3df7c114b93..6c82b2d88a3df891ce9ae9b120d0e24496f790fb 100644 --- a/gpui/examples/text.rs +++ b/gpui/examples/text.rs @@ -1,6 +1,7 @@ use gpui::{ color::Color, fonts::{Properties, Weight}, + text_layout::RunStyle, DebugContext, Element as _, Quad, }; use log::LevelFilter; @@ -55,31 +56,39 @@ impl gpui::Element for TextElement { ) -> Self::PaintState { let font_size = 12.; let family = cx.font_cache.load_family(&["SF Pro Display"]).unwrap(); - let normal = cx - .font_cache - .select_font(family, &Default::default()) - .unwrap(); - let bold = cx - .font_cache - .select_font( - family, - &Properties { - weight: Weight::BOLD, - ..Default::default() - }, - ) - .unwrap(); + let normal = RunStyle { + font_id: cx + .font_cache + .select_font(family, &Default::default()) + .unwrap(), + color: Color::default(), + underline: false, + }; + let bold = RunStyle { + font_id: cx + .font_cache + .select_font( + family, + &Properties { + weight: Weight::BOLD, + ..Default::default() + }, + ) + .unwrap(), + color: Color::default(), + underline: false, + }; let text = "Hello world!"; let line = cx.text_layout_cache.layout_str( text, font_size, &[ - (1, normal, Color::default()), - (1, bold, Color::default()), - (1, normal, Color::default()), - (1, bold, Color::default()), - (text.len() - 4, normal, Color::default()), + (1, normal.clone()), + (1, bold.clone()), + (1, normal.clone()), + (1, bold.clone()), + (text.len() - 4, normal.clone()), ], ); diff --git a/gpui/src/elements/label.rs b/gpui/src/elements/label.rs index 917281d8c64eefc5930a7b59c45aff018768d3a0..8dbc76a65b5c3106ca43c091ef698f351b6a37af 100644 --- a/gpui/src/elements/label.rs +++ b/gpui/src/elements/label.rs @@ -1,12 +1,11 @@ use crate::{ - color::Color, - fonts::{FontId, TextStyle}, + fonts::TextStyle, geometry::{ rect::RectF, vector::{vec2f, Vector2F}, }, json::{ToJson, Value}, - text_layout::Line, + text_layout::{Line, RunStyle}, DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, }; use serde::Deserialize; @@ -48,10 +47,17 @@ impl Label { self } - fn compute_runs(&self) -> SmallVec<[(usize, FontId, Color); 8]> { + fn compute_runs(&self) -> SmallVec<[(usize, RunStyle); 8]> { let font_id = self.style.text.font_id; if self.highlight_indices.is_empty() { - return smallvec![(self.text.len(), font_id, self.style.text.color)]; + return smallvec![( + self.text.len(), + RunStyle { + font_id, + color: self.style.text.color, + underline: false, + } + )]; } let highlight_font_id = self @@ -62,25 +68,31 @@ impl Label { let mut highlight_indices = self.highlight_indices.iter().copied().peekable(); let mut runs = SmallVec::new(); + let highlight_style = self + .style + .highlight_text + .as_ref() + .unwrap_or(&self.style.text); for (char_ix, c) in self.text.char_indices() { let mut font_id = font_id; let mut color = self.style.text.color; + let mut underline = self.style.text.underline; if let Some(highlight_ix) = highlight_indices.peek() { if char_ix == *highlight_ix { font_id = highlight_font_id; - color = self - .style - .highlight_text - .as_ref() - .unwrap_or(&self.style.text) - .color; + color = highlight_style.color; + underline = highlight_style.underline; highlight_indices.next(); } } - let push_new_run = if let Some((last_len, last_font_id, last_color)) = runs.last_mut() { - if font_id == *last_font_id && color == *last_color { + let last_run: Option<&mut (usize, RunStyle)> = runs.last_mut(); + let push_new_run = if let Some((last_len, last_style)) = last_run { + if font_id == last_style.font_id + && color == last_style.color + && underline == last_style.underline + { *last_len += c.len_utf8(); false } else { @@ -91,7 +103,14 @@ impl Label { }; if push_new_run { - runs.push((c.len_utf8(), font_id, color)); + runs.push(( + c.len_utf8(), + RunStyle { + font_id, + color, + underline, + }, + )); } } @@ -177,6 +196,7 @@ impl ToJson for LabelStyle { #[cfg(test)] mod tests { use super::*; + use crate::color::Color; use crate::fonts::{Properties as FontProperties, Weight}; #[crate::test(self)] @@ -185,6 +205,7 @@ mod tests { "Menlo", 12., Default::default(), + false, Color::black(), cx.font_cache(), ) @@ -193,6 +214,7 @@ mod tests { "Menlo", 12., *FontProperties::new().weight(Weight::BOLD), + false, Color::new(255, 0, 0, 255), cx.font_cache(), ) @@ -212,21 +234,27 @@ mod tests { ".αβγδε.ⓐⓑ".len(), ]); + let default_run_style = RunStyle { + font_id: default_style.font_id, + color: default_style.color, + underline: default_style.underline, + }; + let highlight_run_style = RunStyle { + font_id: highlight_style.font_id, + color: highlight_style.color, + underline: highlight_style.underline, + }; let runs = label.compute_runs(); assert_eq!( runs.as_slice(), &[ - (".α".len(), default_style.font_id, default_style.color), - ("βγ".len(), highlight_style.font_id, highlight_style.color), - ("δ".len(), default_style.font_id, default_style.color), - ("ε".len(), highlight_style.font_id, highlight_style.color), - (".ⓐ".len(), default_style.font_id, default_style.color), - ("ⓑⓒ".len(), highlight_style.font_id, highlight_style.color), - ( - "ⓓⓔ.abcde.".len(), - default_style.font_id, - default_style.color - ), + (".α".len(), default_run_style), + ("βγ".len(), highlight_run_style), + ("δ".len(), default_run_style), + ("ε".len(), highlight_run_style), + (".ⓐ".len(), default_run_style), + ("ⓑⓒ".len(), highlight_run_style), + ("ⓓⓔ.abcde.".len(), default_run_style), ] ); } diff --git a/gpui/src/elements/text.rs b/gpui/src/elements/text.rs index 400c9d599783f766ae9a894e00e709166ec503f6..623af72af6444892b30c75448eea17e379b14a02 100644 --- a/gpui/src/elements/text.rs +++ b/gpui/src/elements/text.rs @@ -52,7 +52,7 @@ impl Element for Text { let shaped_line = cx.text_layout_cache.layout_str( line, self.style.font_size, - &[(line.len(), font_id, self.style.color)], + &[(line.len(), self.style.to_run())], ); let wrap_boundaries = wrapper .wrap_shaped_line(line, &shaped_line, constraint.max.x()) diff --git a/gpui/src/fonts.rs b/gpui/src/fonts.rs index 3010e1ada054a0500734f78c402a81f64201a38a..96248c167577326cb74ed3ee6c1d7934f385f362 100644 --- a/gpui/src/fonts.rs +++ b/gpui/src/fonts.rs @@ -1,6 +1,7 @@ use crate::{ color::Color, json::{json, ToJson}, + text_layout::RunStyle, FontCache, }; use anyhow::anyhow; @@ -24,12 +25,14 @@ pub struct TextStyle { pub font_id: FontId, pub font_size: f32, pub font_properties: Properties, + pub underline: bool, } #[derive(Clone, Debug, Default)] pub struct HighlightStyle { pub color: Color, pub font_properties: Properties, + pub underline: bool, } #[allow(non_camel_case_types)] @@ -55,9 +58,11 @@ struct TextStyleJson { color: Color, family: String, weight: Option, + size: f32, #[serde(default)] italic: bool, - size: f32, + #[serde(default)] + underline: bool, } #[derive(Deserialize)] @@ -66,6 +71,8 @@ struct HighlightStyleJson { weight: Option, #[serde(default)] italic: bool, + #[serde(default)] + underline: bool, } impl TextStyle { @@ -73,6 +80,7 @@ impl TextStyle { font_family_name: impl Into>, font_size: f32, font_properties: Properties, + underline: bool, color: Color, font_cache: &FontCache, ) -> anyhow::Result { @@ -85,9 +93,18 @@ impl TextStyle { font_id, font_size, font_properties, + underline, }) } + pub fn to_run(&self) -> RunStyle { + RunStyle { + font_id: self.font_id, + color: self.color, + underline: self.underline, + } + } + fn from_json(json: TextStyleJson) -> anyhow::Result { FONT_CACHE.with(|font_cache| { if let Some(font_cache) = font_cache.borrow().as_ref() { @@ -96,6 +113,7 @@ impl TextStyle { json.family, json.size, font_properties, + json.underline, json.color, font_cache, ) @@ -114,6 +132,7 @@ impl HighlightStyle { Self { color: json.color, font_properties, + underline: json.underline, } } } @@ -123,6 +142,7 @@ impl From for HighlightStyle { Self { color, font_properties: Default::default(), + underline: false, } } } @@ -161,6 +181,7 @@ impl<'de> Deserialize<'de> for HighlightStyle { Ok(Self { color: serde_json::from_value(json).map_err(de::Error::custom)?, font_properties: Properties::new(), + underline: false, }) } } diff --git a/gpui/src/platform.rs b/gpui/src/platform.rs index ba237d47882b2c9fe7d57586b929b2df72729ce1..dec7db910388ef2bd32c4b54f0f1955f74779661 100644 --- a/gpui/src/platform.rs +++ b/gpui/src/platform.rs @@ -8,14 +8,13 @@ pub mod current { } use crate::{ - color::Color, executor, fonts::{FontId, GlyphId, Metrics as FontMetrics, Properties as FontProperties}, geometry::{ rect::{RectF, RectI}, vector::{vec2f, Vector2F}, }, - text_layout::LineLayout, + text_layout::{LineLayout, RunStyle}, AnyAction, ClipboardItem, Menu, Scene, }; use anyhow::Result; @@ -146,12 +145,7 @@ pub trait FontSystem: Send + Sync { subpixel_shift: Vector2F, scale_factor: f32, ) -> Option<(RectI, Vec)>; - fn layout_line( - &self, - text: &str, - font_size: f32, - runs: &[(usize, FontId, Color)], - ) -> LineLayout; + fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout; fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec; } diff --git a/gpui/src/platform/mac/fonts.rs b/gpui/src/platform/mac/fonts.rs index 4b2716c0442f4bc0f4413d2ec6be4aa209b55393..c01700ce22817b341dca6caf634490a3a0b24666 100644 --- a/gpui/src/platform/mac/fonts.rs +++ b/gpui/src/platform/mac/fonts.rs @@ -1,5 +1,4 @@ use crate::{ - color::Color, fonts::{FontId, GlyphId, Metrics, Properties}, geometry::{ rect::{RectF, RectI}, @@ -7,7 +6,7 @@ use crate::{ vector::{vec2f, vec2i, Vector2F}, }, platform, - text_layout::{Glyph, LineLayout, Run}, + text_layout::{Glyph, LineLayout, Run, RunStyle}, }; use cocoa::appkit::{CGFloat, CGPoint}; use core_foundation::{ @@ -87,12 +86,7 @@ impl platform::FontSystem for FontSystem { .rasterize_glyph(font_id, font_size, glyph_id, subpixel_shift, scale_factor) } - fn layout_line( - &self, - text: &str, - font_size: f32, - runs: &[(usize, FontId, Color)], - ) -> LineLayout { + fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout { self.0.read().layout_line(text, font_size, runs) } @@ -210,12 +204,7 @@ impl FontSystemState { } } - fn layout_line( - &self, - text: &str, - font_size: f32, - runs: &[(usize, FontId, Color)], - ) -> LineLayout { + fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout { let font_id_attr_name = CFString::from_static_string("zed_font_id"); // Construct the attributed string, converting UTF8 ranges to UTF16 ranges. @@ -227,20 +216,20 @@ impl FontSystemState { let last_run: RefCell> = Default::default(); let font_runs = runs .iter() - .filter_map(|(len, font_id, _)| { + .filter_map(|(len, style)| { let mut last_run = last_run.borrow_mut(); if let Some((last_len, last_font_id)) = last_run.as_mut() { - if font_id == last_font_id { + if style.font_id == *last_font_id { *last_len += *len; None } else { let result = (*last_len, *last_font_id); *last_len = *len; - *last_font_id = *font_id; + *last_font_id = style.font_id; Some(result) } } else { - *last_run = Some((*len, *font_id)); + *last_run = Some((*len, style.font_id)); None } }) @@ -415,9 +404,8 @@ extern "C" { #[cfg(test)] mod tests { - use crate::MutableAppContext; - use super::*; + use crate::MutableAppContext; use font_kit::properties::{Style, Weight}; use platform::FontSystem as _; @@ -426,13 +414,25 @@ mod tests { // This is failing intermittently on CI and we don't have time to figure it out let fonts = FontSystem::new(); let menlo = fonts.load_family("Menlo").unwrap(); - let menlo_regular = fonts.select_font(&menlo, &Properties::new()).unwrap(); - let menlo_italic = fonts - .select_font(&menlo, &Properties::new().style(Style::Italic)) - .unwrap(); - let menlo_bold = fonts - .select_font(&menlo, &Properties::new().weight(Weight::BOLD)) - .unwrap(); + let menlo_regular = RunStyle { + font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(), + color: Default::default(), + underline: false, + }; + let menlo_italic = RunStyle { + font_id: fonts + .select_font(&menlo, &Properties::new().style(Style::Italic)) + .unwrap(), + color: Default::default(), + underline: false, + }; + let menlo_bold = RunStyle { + font_id: fonts + .select_font(&menlo, &Properties::new().weight(Weight::BOLD)) + .unwrap(), + color: Default::default(), + underline: false, + }; assert_ne!(menlo_regular, menlo_italic); assert_ne!(menlo_regular, menlo_bold); assert_ne!(menlo_italic, menlo_bold); @@ -440,18 +440,14 @@ mod tests { let line = fonts.layout_line( "hello world", 16.0, - &[ - (2, menlo_bold, Default::default()), - (4, menlo_italic, Default::default()), - (5, menlo_regular, Default::default()), - ], + &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)], ); assert_eq!(line.runs.len(), 3); - assert_eq!(line.runs[0].font_id, menlo_bold); + assert_eq!(line.runs[0].font_id, menlo_bold.font_id); assert_eq!(line.runs[0].glyphs.len(), 2); - assert_eq!(line.runs[1].font_id, menlo_italic); + assert_eq!(line.runs[1].font_id, menlo_italic.font_id); assert_eq!(line.runs[1].glyphs.len(), 4); - assert_eq!(line.runs[2].font_id, menlo_regular); + assert_eq!(line.runs[2].font_id, menlo_regular.font_id); assert_eq!(line.runs[2].glyphs.len(), 5); } @@ -459,18 +455,26 @@ mod tests { fn test_glyph_offsets() -> anyhow::Result<()> { let fonts = FontSystem::new(); let zapfino = fonts.load_family("Zapfino")?; - let zapfino_regular = fonts.select_font(&zapfino, &Properties::new())?; + let zapfino_regular = RunStyle { + font_id: fonts.select_font(&zapfino, &Properties::new())?, + color: Default::default(), + underline: false, + }; let menlo = fonts.load_family("Menlo")?; - let menlo_regular = fonts.select_font(&menlo, &Properties::new())?; + let menlo_regular = RunStyle { + font_id: fonts.select_font(&menlo, &Properties::new())?, + color: Default::default(), + underline: false, + }; let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈"; let line = fonts.layout_line( text, 16.0, &[ - (9, zapfino_regular, Color::default()), - (13, menlo_regular, Color::default()), - (text.len() - 22, zapfino_regular, Color::default()), + (9, zapfino_regular), + (13, menlo_regular), + (text.len() - 22, zapfino_regular), ], ); assert_eq!( @@ -536,15 +540,19 @@ mod tests { fn test_layout_line_bom_char() { let fonts = FontSystem::new(); let font_ids = fonts.load_family("Helvetica").unwrap(); - let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap(); + let style = RunStyle { + font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(), + color: Default::default(), + underline: false, + }; let line = "\u{feff}"; - let layout = fonts.layout_line(line, 16., &[(line.len(), font_id, Default::default())]); + let layout = fonts.layout_line(line, 16., &[(line.len(), style)]); assert_eq!(layout.len, line.len()); assert!(layout.runs.is_empty()); let line = "a\u{feff}b"; - let layout = fonts.layout_line(line, 16., &[(line.len(), font_id, Default::default())]); + let layout = fonts.layout_line(line, 16., &[(line.len(), style)]); assert_eq!(layout.len, line.len()); assert_eq!(layout.runs.len(), 1); assert_eq!(layout.runs[0].glyphs.len(), 2); diff --git a/gpui/src/text_layout.rs b/gpui/src/text_layout.rs index d668966dcc0fd05a4fee96dcff67486f77bfbb76..1f710ac31173575c6f0dcd27e5abb15558a98138 100644 --- a/gpui/src/text_layout.rs +++ b/gpui/src/text_layout.rs @@ -24,6 +24,13 @@ pub struct TextLayoutCache { fonts: Arc, } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct RunStyle { + pub color: Color, + pub font_id: FontId, + pub underline: bool, +} + impl TextLayoutCache { pub fn new(fonts: Arc) -> Self { Self { @@ -44,7 +51,7 @@ impl TextLayoutCache { &'a self, text: &'a str, font_size: f32, - runs: &'a [(usize, FontId, Color)], + runs: &'a [(usize, RunStyle)], ) -> Line { let key = &CacheKeyRef { text, @@ -95,7 +102,7 @@ impl<'a> Hash for (dyn CacheKey + 'a) { struct CacheKeyValue { text: String, font_size: OrderedFloat, - runs: SmallVec<[(usize, FontId, Color); 1]>, + runs: SmallVec<[(usize, RunStyle); 1]>, } impl CacheKey for CacheKeyValue { @@ -120,11 +127,11 @@ impl<'a> Borrow for CacheKeyValue { } } -#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone)] struct CacheKeyRef<'a> { text: &'a str, font_size: OrderedFloat, - runs: &'a [(usize, FontId, Color)], + runs: &'a [(usize, RunStyle)], } impl<'a> CacheKey for CacheKeyRef<'a> { @@ -133,10 +140,34 @@ impl<'a> CacheKey for CacheKeyRef<'a> { } } +impl<'a> PartialEq for CacheKeyRef<'a> { + fn eq(&self, other: &Self) -> bool { + self.text == other.text + && self.font_size == other.font_size + && self.runs.len() == other.runs.len() + && self.runs.iter().zip(other.runs.iter()).all( + |((len_a, style_a), (len_b, style_b))| { + len_a == len_b && style_a.font_id == style_b.font_id + }, + ) + } +} + +impl<'a> Hash for CacheKeyRef<'a> { + fn hash(&self, state: &mut H) { + self.text.hash(state); + self.font_size.hash(state); + for (len, style_id) in self.runs { + len.hash(state); + style_id.font_id.hash(state); + } + } +} + #[derive(Default, Debug)] pub struct Line { layout: Arc, - color_runs: SmallVec<[(u32, Color); 32]>, + style_runs: SmallVec<[(u32, Color, bool); 32]>, } #[derive(Default, Debug)] @@ -163,12 +194,12 @@ pub struct Glyph { } impl Line { - fn new(layout: Arc, runs: &[(usize, FontId, Color)]) -> Self { - let mut color_runs = SmallVec::new(); - for (len, _, color) in runs { - color_runs.push((*len as u32, *color)); + fn new(layout: Arc, runs: &[(usize, RunStyle)]) -> Self { + let mut style_runs = SmallVec::new(); + for (len, style) in runs { + style_runs.push((*len as u32, style.color, style.underline)); } - Self { layout, color_runs } + Self { layout, style_runs } } pub fn runs(&self) -> &[Run] { @@ -213,11 +244,12 @@ impl Line { cx: &mut PaintContext, ) { let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.; - let baseline_origin = vec2f(0., padding_top + self.layout.ascent); + let baseline_offset = vec2f(0., padding_top + self.layout.ascent); - let mut color_runs = self.color_runs.iter(); - let mut color_end = 0; + let mut style_runs = self.style_runs.iter(); + let mut run_end = 0; let mut color = Color::black(); + let mut underline_start = None; for run in &self.layout.runs { let max_glyph_width = cx @@ -226,7 +258,7 @@ impl Line { .x(); for glyph in &run.glyphs { - let glyph_origin = origin + baseline_origin + glyph.position; + let glyph_origin = origin + baseline_offset + glyph.position; if glyph_origin.x() + max_glyph_width < visible_bounds.origin().x() { continue; @@ -235,12 +267,31 @@ impl Line { break; } - if glyph.index >= color_end { - if let Some(next_run) = color_runs.next() { - color_end += next_run.0 as usize; - color = next_run.1; + if glyph.index >= run_end { + if let Some((run_len, run_color, run_underlined)) = style_runs.next() { + if let Some(underline_origin) = underline_start { + if !*run_underlined || *run_color != color { + cx.scene.push_quad(scene::Quad { + bounds: RectF::from_points( + underline_origin, + glyph_origin + vec2f(0., 1.), + ), + background: Some(color), + border: Default::default(), + corner_radius: 0., + }); + underline_start = None; + } + } + + if *run_underlined { + underline_start.get_or_insert(glyph_origin); + } + + run_end += *run_len as usize; + color = *run_color; } else { - color_end = self.layout.len; + run_end = self.layout.len; color = Color::black(); } } @@ -253,6 +304,16 @@ impl Line { color, }); } + + if let Some(underline_start) = underline_start.take() { + let line_end = origin + baseline_offset + vec2f(self.layout.width, 0.); + cx.scene.push_quad(scene::Quad { + bounds: RectF::from_points(underline_start, line_end + vec2f(0., 1.)), + background: Some(color), + border: Default::default(), + corner_radius: 0., + }); + } } } @@ -268,7 +329,7 @@ impl Line { let baseline_origin = vec2f(0., padding_top + self.layout.ascent); let mut boundaries = boundaries.into_iter().peekable(); - let mut color_runs = self.color_runs.iter(); + let mut color_runs = self.style_runs.iter(); let mut color_end = 0; let mut color = Color::black(); @@ -519,7 +580,14 @@ impl LineWrapper { .layout_line( &c.to_string(), self.font_size, - &[(1, self.font_id, Default::default())], + &[( + 1, + RunStyle { + font_id: self.font_id, + color: Default::default(), + underline: false, + }, + )], ) .width } @@ -528,10 +596,7 @@ impl LineWrapper { #[cfg(test)] mod tests { use super::*; - use crate::{ - color::Color, - fonts::{Properties, Weight}, - }; + use crate::fonts::{Properties, Weight}; #[crate::test(self)] fn test_wrap_line(cx: &mut crate::MutableAppContext) { @@ -600,28 +665,30 @@ mod tests { let family = font_cache.load_family(&["Helvetica"]).unwrap(); let font_id = font_cache.select_font(family, &Default::default()).unwrap(); - let normal = font_cache.select_font(family, &Default::default()).unwrap(); - let bold = font_cache - .select_font( - family, - &Properties { - weight: Weight::BOLD, - ..Default::default() - }, - ) - .unwrap(); + let normal = RunStyle { + font_id, + color: Default::default(), + underline: false, + }; + let bold = RunStyle { + font_id: font_cache + .select_font( + family, + &Properties { + weight: Weight::BOLD, + ..Default::default() + }, + ) + .unwrap(), + color: Default::default(), + underline: false, + }; let text = "aa bbb cccc ddddd eeee"; let line = text_layout_cache.layout_str( text, 16.0, - &[ - (4, normal, Color::default()), - (5, bold, Color::default()), - (6, normal, Color::default()), - (1, bold, Color::default()), - (7, normal, Color::default()), - ], + &[(4, normal), (5, bold), (6, normal), (1, bold), (7, normal)], ); let mut wrapper = LineWrapper::new(font_id, 16., font_system); diff --git a/zed/assets/themes/_base.toml b/zed/assets/themes/_base.toml index 2d0cfd12369d270956908f8337f9dbce3bc50d23..67ed95f9662b1ca2bf8f1728106bf1ea1a2cf03d 100644 --- a/zed/assets/themes/_base.toml +++ b/zed/assets/themes/_base.toml @@ -2,7 +2,7 @@ background = "$surface.0" [workspace.tab] -text = "$text.2" +text = { extends = "$text.2", underline = true } padding = { left = 10, right = 10 } icon_close = "$text.0.color" icon_dirty = "$status.info" diff --git a/zed/assets/themes/dark.toml b/zed/assets/themes/dark.toml index 74688fdb0d13dd705a56b900ffe4f049458b41e5..6f7a8d6e80f432a9a766f6afac931855d78616c2 100644 --- a/zed/assets/themes/dark.toml +++ b/zed/assets/themes/dark.toml @@ -23,7 +23,7 @@ bad = "#b7372e" [syntax] keyword = { color = "#0086c0", weight = "bold" } -function = "#dcdcaa" +function = { color = "#dcdcaa", underline = true } string = "#cb8f77" type = "#4ec9b0" number = "#b5cea8" diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 2d4b763776d3cb3d1749cb294575e1167a178ae5..a19b63c5ce93ff7b57ac2190117ed41c562d9c21 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -17,10 +17,15 @@ pub use display_map::DisplayPoint; use display_map::*; pub use element::*; use gpui::{ - action, color::Color, font_cache::FamilyId, fonts::Properties as FontProperties, - geometry::vector::Vector2F, keymap::Binding, text_layout, AppContext, ClipboardItem, Element, - ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, RenderContext, Task, - TextLayoutCache, View, ViewContext, WeakViewHandle, + action, + color::Color, + font_cache::FamilyId, + fonts::Properties as FontProperties, + geometry::vector::Vector2F, + keymap::Binding, + text_layout::{self, RunStyle}, + AppContext, ClipboardItem, Element, ElementBox, Entity, FontCache, ModelHandle, + MutableAppContext, RenderContext, Task, TextLayoutCache, View, ViewContext, WeakViewHandle, }; use postage::watch; use serde::{Deserialize, Serialize}; @@ -2330,7 +2335,7 @@ impl Snapshot { pub fn line_height(&self, font_cache: &FontCache) -> f32 { let font_id = font_cache.default_font(self.font_family); - font_cache.line_height(font_id, self.font_size) + font_cache.line_height(font_id, self.font_size).ceil() } pub fn em_width(&self, font_cache: &FontCache) -> f32 { @@ -2355,7 +2360,14 @@ impl Snapshot { .layout_str( "1".repeat(digit_count).as_str(), font_size, - &[(digit_count, font_id, Color::black())], + &[( + digit_count, + RunStyle { + font_id, + color: Color::black(), + underline: false, + }, + )], ) .width()) } @@ -2392,7 +2404,14 @@ impl Snapshot { layouts.push(Some(layout_cache.layout_str( &line_number, self.font_size, - &[(line_number.len(), font_id, color)], + &[( + line_number.len(), + RunStyle { + font_id, + color, + underline: false, + }, + )], ))); } } @@ -2429,7 +2448,14 @@ impl Snapshot { layout_cache.layout_str( line, self.font_size, - &[(line.len(), font_id, style.placeholder_text.color)], + &[( + line.len(), + RunStyle { + font_id, + color: style.placeholder_text.color, + underline: false, + }, + )], ) }) .collect()); @@ -2485,7 +2511,14 @@ impl Snapshot { } line.push_str(line_chunk); - styles.push((line_chunk.len(), font_id, style.color)); + styles.push(( + line_chunk.len(), + RunStyle { + font_id, + color: style.color, + underline: style.underline, + }, + )); prev_font_id = font_id; prev_font_properties = style.font_properties; } @@ -2518,8 +2551,11 @@ impl Snapshot { self.font_size, &[( self.display_snapshot.line_len(row) as usize, - font_id, - Color::black(), + RunStyle { + font_id, + color: Color::black(), + underline: false, + }, )], )) } diff --git a/zed/src/theme.rs b/zed/src/theme.rs index e15070d81837fa4b9cbef50da934273c0aaf592b..c8b2c61388cd187f459a8f13c52358a5c139fabd 100644 --- a/zed/src/theme.rs +++ b/zed/src/theme.rs @@ -180,10 +180,12 @@ impl Default for EditorStyle { text: HighlightStyle { color: Color::from_u32(0xff0000ff), font_properties: Default::default(), + underline: false, }, placeholder_text: HighlightStyle { color: Color::from_u32(0x00ff00ff), font_properties: Default::default(), + underline: false, }, background: Default::default(), gutter_background: Default::default(),