Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle different underlining styles supported by neovim #1469

Merged
merged 11 commits into from
Aug 13, 2022
21 changes: 18 additions & 3 deletions src/bridge/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use log::debug;
use rmpv::Value;
use skia_safe::Color4f;

use crate::editor::{Colors, CursorMode, CursorShape, Style};
use crate::editor::{Colors, CursorMode, CursorShape, Style, UnderlineStyle};

#[derive(Clone, Debug)]
pub enum ParseError {
Expand Down Expand Up @@ -493,9 +493,24 @@ fn parse_style(style_map: Value) -> Result<Style> {
("strikethrough", Value::Boolean(strikethrough)) => {
style.strikethrough = strikethrough
}
("underline", Value::Boolean(underline)) => style.underline = underline,
("undercurl", Value::Boolean(undercurl)) => style.undercurl = undercurl,
("blend", Value::Integer(blend)) => style.blend = blend.as_u64().unwrap() as u8,

("underline", Value::Boolean(true)) => {
style.underline = Some(UnderlineStyle::Underline)
}
("undercurl", Value::Boolean(true)) => {
style.underline = Some(UnderlineStyle::UnderCurl)
}
("underdotted" | "underdot", Value::Boolean(true)) => {
style.underline = Some(UnderlineStyle::UnderDot)
}
("underdashed" | "underdash", Value::Boolean(true)) => {
style.underline = Some(UnderlineStyle::UnderDash)
}
("underdouble" | "underlineline", Value::Boolean(true)) => {
style.underline = Some(UnderlineStyle::UnderDouble)
}

_ => debug!("Ignored style attribute: {}", name),
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/editor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::{
pub use cursor::{Cursor, CursorMode, CursorShape};
pub use draw_command_batcher::DrawCommandBatcher;
pub use grid::CharacterGrid;
pub use style::{Colors, Style};
pub use style::{Colors, Style, UnderlineStyle};
pub use window::*;

const MODE_CMDLINE: u64 = 4;
Expand Down
15 changes: 11 additions & 4 deletions src/editor/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ pub struct Colors {
pub special: Option<Color4f>,
}

#[derive(PartialEq, Debug, Clone, Copy)]
pub enum UnderlineStyle {
Underline,
UnderDouble,
UnderDash,
UnderDot,
UnderCurl,
}

#[derive(new, Debug, Clone, PartialEq)]
pub struct Style {
pub colors: Colors,
Expand All @@ -19,11 +28,9 @@ pub struct Style {
#[new(default)]
pub strikethrough: bool,
#[new(default)]
pub underline: bool,
#[new(default)]
pub undercurl: bool,
#[new(default)]
pub blend: u8,
#[new(default)]
pub underline: Option<UnderlineStyle>,
}

impl Style {
Expand Down
14 changes: 13 additions & 1 deletion src/editor/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,17 @@ impl Window {
);
}

// Due to the limitations of the current rendering strategy, some underlines get
// clipped by the line below. To mitigate that, we redraw the adjacent lines whenever
// an individual line is redrawn. Unfortunately, some clipping still happens.
// TODO: figure out how to solve this
if row < self.grid.height - 1 {
self.redraw_line(row + 1);
}
self.redraw_line(row);
if row > 0 {
self.redraw_line(row - 1);
}
} else {
warn!("Draw command out of bounds");
}
Expand Down Expand Up @@ -297,7 +307,9 @@ impl Window {

pub fn redraw(&self) {
self.send_command(WindowDrawCommand::Clear);
for row in 0..self.grid.height {
// Draw the lines from the bottom up so that underlines don't get overwritten by the line
// below.
for row in (0..self.grid.height).rev() {
self.redraw_line(row);
}
}
Expand Down
128 changes: 95 additions & 33 deletions src/renderer/grid_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ use std::sync::Arc;

use glutin::dpi::PhysicalSize;
use log::trace;
use skia_safe::{colors, dash_path_effect, BlendMode, Canvas, Color, Paint, Rect, HSV};
use skia_safe::{
colors, dash_path_effect, BlendMode, Canvas, Color, Paint, Path, Point, Rect, HSV,
};

use crate::{
dimensions::Dimensions,
editor::{Colors, Style},
editor::{Colors, Style, UnderlineStyle},
renderer::{CachingShaper, RendererSettings},
settings::*,
window::WindowSettings,
Expand Down Expand Up @@ -135,47 +137,29 @@ impl GridRenderer {

let style = style.as_ref().unwrap_or(&self.default_style);

canvas.save();

// We don't want to clip text in the x position, only the y so we add a buffer of 1
// character on either side of the region so that we clip vertically but not horizontally
let (grid_x, grid_y) = grid_position;
let clip_position = (grid_x.saturating_sub(1), grid_y);
let region = self.compute_text_region(clip_position, cell_width + 2);

canvas.clip_rect(region, None, Some(false));

if style.underline || style.undercurl {
let mut underline_paint = self.paint.clone();

if let Some(underline_style) = style.underline {
let line_position = self.shaper.underline_position();
let stroke_width = self.shaper.current_size() / 10.0;

underline_paint.set_color(style.special(&self.default_style.colors).to_color());
underline_paint.set_stroke_width(stroke_width);

if style.undercurl {
underline_paint.set_path_effect(dash_path_effect::new(
&[stroke_width * 2.0, stroke_width * 2.0],
0.0,
));
} else {
underline_paint.set_path_effect(None);
}

canvas.draw_line(
(
x as f32,
(y - line_position + self.font_dimensions.height) as f32,
),
(
(x + width) as f32,
(y - line_position + self.font_dimensions.height) as f32,
),
&underline_paint,
let p1 = (
x as f32,
(y - line_position + self.font_dimensions.height) as f32,
);
let p2 = (
(x + width) as f32,
(y - line_position + self.font_dimensions.height) as f32,
);

self.draw_underline(canvas, &style, underline_style, p1.into(), p2.into())
}

canvas.save();
canvas.clip_rect(region, None, Some(false));

let y_adjustment = self.shaper.y_adjustment();

if SETTINGS.get::<RendererSettings>().debug_renderer {
Expand Down Expand Up @@ -209,4 +193,82 @@ impl GridRenderer {

canvas.restore();
}

fn draw_underline(
&self,
canvas: &mut Canvas,
style: &Arc<Style>,
underline_style: UnderlineStyle,
p1: Point,
p2: Point,
) {
canvas.save();

let mut underline_paint = self.paint.clone();
let auto_scaling = SETTINGS
.get::<RendererSettings>()
.underline_automatic_scaling;
// Arbitrary value under which we simply round the line thickness to 1. Anything else
// results in ugly aliasing artifacts.
let stroke_width = if self.shaper.current_size() < 15. || auto_scaling == false {
underline_paint.set_anti_alias(false);
1.0
} else {
underline_paint.set_anti_alias(true);
self.shaper.current_size() / 10.
};

underline_paint
.set_color(style.special(&self.default_style.colors).to_color())
.set_stroke_width(stroke_width);

match underline_style {
UnderlineStyle::Underline => {
underline_paint.set_path_effect(None);
canvas.draw_line(p1, p2, &underline_paint);
}
UnderlineStyle::UnderDouble => {
underline_paint.set_path_effect(None);
canvas.draw_line(p1, p2, &underline_paint);
let p1 = (p1.x, p1.y - 2 as f32);
let p2 = (p2.x, p2.y - 2 as f32);
canvas.draw_line(p1, p2, &underline_paint);
}
UnderlineStyle::UnderCurl => {
let p1 = (p1.x, p1.y - 3. + stroke_width);
let p2 = (p2.x, p2.y - 3. + stroke_width);
underline_paint
.set_path_effect(None)
.set_anti_alias(true)
.set_style(skia_safe::paint::Style::Stroke);
let mut path = Path::default();
path.move_to(p1);
let mut i = p1.0;
let mut sin = -2. * stroke_width;
let increment = self.font_dimensions.width as f32 / 2.;
while i < p2.0 {
sin *= -1.;
i += increment;
path.quad_to((i - (increment / 2.), p1.1 + sin), (i, p1.1));
}
canvas.draw_path(&path, &underline_paint);
}
UnderlineStyle::UnderDash => {
underline_paint.set_path_effect(dash_path_effect::new(
&[6.0 * stroke_width, 2.0 * stroke_width],
0.0,
));
canvas.draw_line(p1, p2, &underline_paint);
}
UnderlineStyle::UnderDot => {
underline_paint.set_path_effect(dash_path_effect::new(
&[1.0 * stroke_width, 1.0 * stroke_width],
0.0,
));
canvas.draw_line(p1, p2, &underline_paint);
}
}

canvas.restore();
}
}
2 changes: 2 additions & 0 deletions src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub struct RendererSettings {
floating_blur_amount_y: f32,
debug_renderer: bool,
profiler: bool,
underline_automatic_scaling: bool,
}

impl Default for RendererSettings {
Expand All @@ -52,6 +53,7 @@ impl Default for RendererSettings {
floating_blur_amount_y: 2.0,
debug_renderer: false,
profiler: false,
underline_automatic_scaling: false,
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions website/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,19 @@ let g:neovide_profiler = v:false
Setting this to `v:true` enables the profiler, which shows a frametime graph in the upper left
corner.

#### Underline automatic scaling

```vim
let g:neovide_underline_automatic_scaling = v:false
```

Setting `g:neovide_underline_automatic_scaling` to a boolean value determines whether automatic
scaling of text underlines (including undercurl, underdash, etc.) is enabled. Noticeable for font
sizes above 15.

**Note**: This is currently glitchy, and leads to some underlines being clipped by the line of text
below.

### Input Settings

#### Use Logo Key
Expand Down