Display squiggly underlines underneath text with diagnostics
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
52594fe250
commit
b9b255652f
7 changed files with 119 additions and 72 deletions
|
@ -8,7 +8,7 @@ use collections::{BTreeMap, HashMap};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
color::Color,
|
color::Color,
|
||||||
elements::layout_highlighted_chunks,
|
elements::layout_highlighted_chunks,
|
||||||
fonts::HighlightStyle,
|
fonts::{HighlightStyle, Underline},
|
||||||
geometry::{
|
geometry::{
|
||||||
rect::RectF,
|
rect::RectF,
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
|
@ -540,12 +540,12 @@ impl EditorElement {
|
||||||
.chunks(rows.clone(), Some(&style.syntax))
|
.chunks(rows.clone(), Some(&style.syntax))
|
||||||
.map(|chunk| {
|
.map(|chunk| {
|
||||||
let highlight = if let Some(severity) = chunk.diagnostic {
|
let highlight = if let Some(severity) = chunk.diagnostic {
|
||||||
let underline = Some(
|
let diagnostic_style = super::diagnostic_style(severity, true, style);
|
||||||
super::diagnostic_style(severity, true, style)
|
let underline = Some(Underline {
|
||||||
.message
|
color: diagnostic_style.message.text.color,
|
||||||
.text
|
thickness: 1.0.into(),
|
||||||
.color,
|
squiggly: true,
|
||||||
);
|
});
|
||||||
if let Some(mut highlight) = chunk.highlight_style {
|
if let Some(mut highlight) = chunk.highlight_style {
|
||||||
highlight.underline = underline;
|
highlight.underline = underline;
|
||||||
Some(highlight)
|
Some(highlight)
|
||||||
|
|
|
@ -10,6 +10,7 @@ pub use font_kit::{
|
||||||
metrics::Metrics,
|
metrics::Metrics,
|
||||||
properties::{Properties, Stretch, Style, Weight},
|
properties::{Properties, Stretch, Style, Weight},
|
||||||
};
|
};
|
||||||
|
use ordered_float::OrderedFloat;
|
||||||
use serde::{de, Deserialize};
|
use serde::{de, Deserialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{cell::RefCell, sync::Arc};
|
use std::{cell::RefCell, sync::Arc};
|
||||||
|
@ -27,14 +28,21 @@ pub struct TextStyle {
|
||||||
pub font_id: FontId,
|
pub font_id: FontId,
|
||||||
pub font_size: f32,
|
pub font_size: f32,
|
||||||
pub font_properties: Properties,
|
pub font_properties: Properties,
|
||||||
pub underline: Option<Color>,
|
pub underline: Option<Underline>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||||
pub struct HighlightStyle {
|
pub struct HighlightStyle {
|
||||||
pub color: Color,
|
pub color: Color,
|
||||||
pub font_properties: Properties,
|
pub font_properties: Properties,
|
||||||
pub underline: Option<Color>,
|
pub underline: Option<Underline>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub struct Underline {
|
||||||
|
pub color: Color,
|
||||||
|
pub thickness: OrderedFloat<f32>,
|
||||||
|
pub squiggly: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
|
@ -81,7 +89,14 @@ struct HighlightStyleJson {
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
enum UnderlineStyleJson {
|
enum UnderlineStyleJson {
|
||||||
Underlined(bool),
|
Underlined(bool),
|
||||||
UnderlinedWithColor(Color),
|
UnderlinedWithProperties {
|
||||||
|
#[serde(default)]
|
||||||
|
color: Option<Color>,
|
||||||
|
#[serde(default)]
|
||||||
|
thickness: Option<f32>,
|
||||||
|
#[serde(default)]
|
||||||
|
squiggly: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextStyle {
|
impl TextStyle {
|
||||||
|
@ -89,7 +104,7 @@ impl TextStyle {
|
||||||
font_family_name: impl Into<Arc<str>>,
|
font_family_name: impl Into<Arc<str>>,
|
||||||
font_size: f32,
|
font_size: f32,
|
||||||
font_properties: Properties,
|
font_properties: Properties,
|
||||||
underline: Option<Color>,
|
underline: Option<Underline>,
|
||||||
color: Color,
|
color: Color,
|
||||||
font_cache: &FontCache,
|
font_cache: &FontCache,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
|
@ -276,11 +291,23 @@ impl<'de> Deserialize<'de> for HighlightStyle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn underline_from_json(json: UnderlineStyleJson, text_color: Color) -> Option<Color> {
|
fn underline_from_json(json: UnderlineStyleJson, text_color: Color) -> Option<Underline> {
|
||||||
match json {
|
match json {
|
||||||
UnderlineStyleJson::Underlined(false) => None,
|
UnderlineStyleJson::Underlined(false) => None,
|
||||||
UnderlineStyleJson::Underlined(true) => Some(text_color),
|
UnderlineStyleJson::Underlined(true) => Some(Underline {
|
||||||
UnderlineStyleJson::UnderlinedWithColor(color) => Some(color),
|
color: text_color,
|
||||||
|
thickness: 1.0.into(),
|
||||||
|
squiggly: false,
|
||||||
|
}),
|
||||||
|
UnderlineStyleJson::UnderlinedWithProperties {
|
||||||
|
color,
|
||||||
|
thickness,
|
||||||
|
squiggly,
|
||||||
|
} => Some(Underline {
|
||||||
|
color: color.unwrap_or(text_color),
|
||||||
|
thickness: thickness.unwrap_or(1.).into(),
|
||||||
|
squiggly,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
vector::{vec2f, vec2i, Vector2F},
|
vector::{vec2f, vec2i, Vector2F},
|
||||||
},
|
},
|
||||||
platform,
|
platform,
|
||||||
scene::{Glyph, Icon, Image, Layer, Quad, Scene, Shadow},
|
scene::{Glyph, Icon, Image, Layer, Quad, Scene, Shadow, Underline},
|
||||||
};
|
};
|
||||||
use cocoa::foundation::NSUInteger;
|
use cocoa::foundation::NSUInteger;
|
||||||
use metal::{MTLPixelFormat, MTLResourceOptions, NSRange};
|
use metal::{MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||||
|
@ -334,6 +334,13 @@ impl Renderer {
|
||||||
drawable_size,
|
drawable_size,
|
||||||
command_encoder,
|
command_encoder,
|
||||||
);
|
);
|
||||||
|
self.render_underlines(
|
||||||
|
layer.underlines(),
|
||||||
|
scale_factor,
|
||||||
|
offset,
|
||||||
|
drawable_size,
|
||||||
|
command_encoder,
|
||||||
|
);
|
||||||
self.render_sprites(
|
self.render_sprites(
|
||||||
layer.glyphs(),
|
layer.glyphs(),
|
||||||
layer.icons(),
|
layer.icons(),
|
||||||
|
@ -349,13 +356,6 @@ impl Renderer {
|
||||||
drawable_size,
|
drawable_size,
|
||||||
command_encoder,
|
command_encoder,
|
||||||
);
|
);
|
||||||
self.render_underlines(
|
|
||||||
layer.underlines(),
|
|
||||||
scale_factor,
|
|
||||||
offset,
|
|
||||||
drawable_size,
|
|
||||||
command_encoder,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
command_encoder.end_encoding();
|
command_encoder.end_encoding();
|
||||||
|
@ -834,7 +834,7 @@ impl Renderer {
|
||||||
|
|
||||||
fn render_underlines(
|
fn render_underlines(
|
||||||
&mut self,
|
&mut self,
|
||||||
underlines: &[Quad],
|
underlines: &[Underline],
|
||||||
scale_factor: f32,
|
scale_factor: f32,
|
||||||
offset: &mut usize,
|
offset: &mut usize,
|
||||||
drawable_size: Vector2F,
|
drawable_size: Vector2F,
|
||||||
|
@ -874,19 +874,22 @@ impl Renderer {
|
||||||
(self.instances.contents() as *mut u8).offset(*offset as isize)
|
(self.instances.contents() as *mut u8).offset(*offset as isize)
|
||||||
as *mut shaders::GPUIUnderline
|
as *mut shaders::GPUIUnderline
|
||||||
};
|
};
|
||||||
for (ix, quad) in underlines.iter().enumerate() {
|
for (ix, underline) in underlines.iter().enumerate() {
|
||||||
let bounds = quad.bounds * scale_factor;
|
let origin = underline.origin * scale_factor;
|
||||||
let shader_quad = shaders::GPUIUnderline {
|
let mut height = underline.thickness;
|
||||||
origin: bounds.origin().round().to_float2(),
|
if underline.squiggly {
|
||||||
size: bounds.size().round().to_float2(),
|
height *= 3.;
|
||||||
thickness: 1. * scale_factor,
|
}
|
||||||
color: quad
|
let size = vec2f(underline.width, height) * scale_factor;
|
||||||
.background
|
let shader_underline = shaders::GPUIUnderline {
|
||||||
.unwrap_or(Color::transparent_black())
|
origin: origin.round().to_float2(),
|
||||||
.to_uchar4(),
|
size: size.round().to_float2(),
|
||||||
|
thickness: underline.thickness * scale_factor,
|
||||||
|
color: underline.color.to_uchar4(),
|
||||||
|
squiggly: underline.squiggly as u8,
|
||||||
};
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
*(buffer_contents.offset(ix as isize)) = shader_quad;
|
*(buffer_contents.offset(ix as isize)) = shader_underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -118,4 +118,5 @@ typedef struct
|
||||||
vector_float2 size;
|
vector_float2 size;
|
||||||
float thickness;
|
float thickness;
|
||||||
vector_uchar4 color;
|
vector_uchar4 color;
|
||||||
|
uint8_t squiggly;
|
||||||
} GPUIUnderline;
|
} GPUIUnderline;
|
||||||
|
|
|
@ -311,6 +311,7 @@ struct UnderlineFragmentInput {
|
||||||
float2 size;
|
float2 size;
|
||||||
float thickness;
|
float thickness;
|
||||||
float4 color;
|
float4 color;
|
||||||
|
bool squiggly;
|
||||||
};
|
};
|
||||||
|
|
||||||
vertex UnderlineFragmentInput underline_vertex(
|
vertex UnderlineFragmentInput underline_vertex(
|
||||||
|
@ -331,16 +332,18 @@ vertex UnderlineFragmentInput underline_vertex(
|
||||||
underline.size,
|
underline.size,
|
||||||
underline.thickness,
|
underline.thickness,
|
||||||
coloru_to_colorf(underline.color),
|
coloru_to_colorf(underline.color),
|
||||||
|
underline.squiggly != 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment float4 underline_fragment(
|
fragment float4 underline_fragment(
|
||||||
UnderlineFragmentInput input [[stage_in]]
|
UnderlineFragmentInput input [[stage_in]]
|
||||||
) {
|
) {
|
||||||
|
if (input.squiggly) {
|
||||||
float half_thickness = input.thickness * 0.5;
|
float half_thickness = input.thickness * 0.5;
|
||||||
float2 st = ((input.position.xy - input.origin) / input.size.y) - float2(0., 0.5);
|
float2 st = ((input.position.xy - input.origin) / input.size.y) - float2(0., 0.5);
|
||||||
float frequency = M_PI_F * 0.75;
|
float frequency = (M_PI_F * (3. * input.thickness)) / 8.;
|
||||||
float amplitude = 0.3;
|
float amplitude = 1. / (2. * input.thickness);
|
||||||
float sine = sin(st.x * frequency) * amplitude;
|
float sine = sin(st.x * frequency) * amplitude;
|
||||||
float dSine = cos(st.x * frequency) * amplitude * frequency;
|
float dSine = cos(st.x * frequency) * amplitude * frequency;
|
||||||
float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
|
float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
|
||||||
|
@ -349,4 +352,7 @@ fragment float4 underline_fragment(
|
||||||
float distance_from_bottom_border = distance_in_pixels + half_thickness;
|
float distance_from_bottom_border = distance_in_pixels + half_thickness;
|
||||||
float alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border));
|
float alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border));
|
||||||
return input.color * float4(1., 1., 1., alpha);
|
return input.color * float4(1., 1., 1., alpha);
|
||||||
|
} else {
|
||||||
|
return input.color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ struct StackingContext {
|
||||||
pub struct Layer {
|
pub struct Layer {
|
||||||
clip_bounds: Option<RectF>,
|
clip_bounds: Option<RectF>,
|
||||||
quads: Vec<Quad>,
|
quads: Vec<Quad>,
|
||||||
underlines: Vec<Quad>,
|
underlines: Vec<Underline>,
|
||||||
images: Vec<Image>,
|
images: Vec<Image>,
|
||||||
shadows: Vec<Shadow>,
|
shadows: Vec<Shadow>,
|
||||||
glyphs: Vec<Glyph>,
|
glyphs: Vec<Glyph>,
|
||||||
|
@ -76,6 +76,15 @@ pub struct Border {
|
||||||
pub left: bool,
|
pub left: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default, Debug)]
|
||||||
|
pub struct Underline {
|
||||||
|
pub origin: Vector2F,
|
||||||
|
pub width: f32,
|
||||||
|
pub thickness: f32,
|
||||||
|
pub color: Color,
|
||||||
|
pub squiggly: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Border {
|
impl<'de> Deserialize<'de> for Border {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where
|
where
|
||||||
|
@ -183,7 +192,7 @@ impl Scene {
|
||||||
self.active_layer().push_image(image)
|
self.active_layer().push_image(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_underline(&mut self, underline: Quad) {
|
pub fn push_underline(&mut self, underline: Underline) {
|
||||||
self.active_layer().push_underline(underline)
|
self.active_layer().push_underline(underline)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,11 +286,11 @@ impl Layer {
|
||||||
self.quads.as_slice()
|
self.quads.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_underline(&mut self, underline: Quad) {
|
fn push_underline(&mut self, underline: Underline) {
|
||||||
self.underlines.push(underline);
|
self.underlines.push(underline);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn underlines(&self) -> &[Quad] {
|
pub fn underlines(&self) -> &[Underline] {
|
||||||
self.underlines.as_slice()
|
self.underlines.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
color::Color,
|
color::Color,
|
||||||
fonts::{FontId, GlyphId},
|
fonts::{FontId, GlyphId, Underline},
|
||||||
geometry::{
|
geometry::{
|
||||||
rect::RectF,
|
rect::RectF,
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
|
@ -28,7 +28,7 @@ pub struct TextLayoutCache {
|
||||||
pub struct RunStyle {
|
pub struct RunStyle {
|
||||||
pub color: Color,
|
pub color: Color,
|
||||||
pub font_id: FontId,
|
pub font_id: FontId,
|
||||||
pub underline: Option<Color>,
|
pub underline: Option<Underline>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextLayoutCache {
|
impl TextLayoutCache {
|
||||||
|
@ -167,7 +167,7 @@ impl<'a> Hash for CacheKeyRef<'a> {
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Line {
|
pub struct Line {
|
||||||
layout: Arc<LineLayout>,
|
layout: Arc<LineLayout>,
|
||||||
style_runs: SmallVec<[(u32, Color, Option<Color>); 32]>,
|
style_runs: SmallVec<[(u32, Color, Option<Underline>); 32]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
|
@ -265,14 +265,14 @@ impl Line {
|
||||||
|
|
||||||
let mut finished_underline = None;
|
let mut finished_underline = None;
|
||||||
if glyph.index >= run_end {
|
if glyph.index >= run_end {
|
||||||
if let Some((run_len, run_color, run_underline_color)) = style_runs.next() {
|
if let Some((run_len, run_color, run_underline)) = style_runs.next() {
|
||||||
if let Some((_, underline_color)) = underline {
|
if let Some((_, underline_style)) = underline {
|
||||||
if *run_underline_color != Some(underline_color) {
|
if *run_underline != Some(underline_style) {
|
||||||
finished_underline = underline.take();
|
finished_underline = underline.take();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(run_underline_color) = run_underline_color {
|
if let Some(run_underline) = run_underline {
|
||||||
underline.get_or_insert((glyph_origin, *run_underline_color));
|
underline.get_or_insert((glyph_origin, *run_underline));
|
||||||
}
|
}
|
||||||
|
|
||||||
run_end += *run_len as usize;
|
run_end += *run_len as usize;
|
||||||
|
@ -288,12 +288,13 @@ impl Line {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((underline_origin, underline_color)) = finished_underline {
|
if let Some((underline_origin, underline_style)) = finished_underline {
|
||||||
cx.scene.push_underline(scene::Quad {
|
cx.scene.push_underline(scene::Underline {
|
||||||
bounds: RectF::from_points(underline_origin, glyph_origin + vec2f(0., 3.)),
|
origin: underline_origin,
|
||||||
background: Some(underline_color),
|
width: glyph_origin.x() - underline_origin.x(),
|
||||||
border: Default::default(),
|
thickness: underline_style.thickness.into(),
|
||||||
corner_radius: 0.,
|
color: underline_style.color,
|
||||||
|
squiggly: underline_style.squiggly,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,14 +308,14 @@ impl Line {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((underline_start, underline_color)) = underline.take() {
|
if let Some((underline_start, underline_style)) = underline.take() {
|
||||||
let line_end = origin + baseline_offset + vec2f(self.layout.width, 0.);
|
let line_end_x = origin.x() + self.layout.width;
|
||||||
|
cx.scene.push_underline(scene::Underline {
|
||||||
cx.scene.push_underline(scene::Quad {
|
origin: underline_start,
|
||||||
bounds: RectF::from_points(underline_start, line_end + vec2f(0., 3.)),
|
width: line_end_x - underline_start.x(),
|
||||||
background: Some(underline_color),
|
color: underline_style.color,
|
||||||
border: Default::default(),
|
thickness: underline_style.thickness.into(),
|
||||||
corner_radius: 0.,
|
squiggly: underline_style.squiggly,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue