Skip to content

Commit

Permalink
Show LSP diagnostics inline
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalkuthe committed Mar 26, 2023
1 parent aeed88e commit 60f4dc7
Show file tree
Hide file tree
Showing 16 changed files with 695 additions and 42 deletions.
1 change: 1 addition & 0 deletions book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ The following statusline elements can be configured:
| `auto-signature-help` | Enable automatic popup of signature help (parameter hints) | `true` |
| `display-inlay-hints` | Display inlay hints[^2] | `false` |
| `display-signature-help-docs` | Display docs under signature help popup | `true` |
| `display-inline-diagnostics` | Display diagnostics under their starting line | `true` |

[^1]: By default, a progress spinner is shown in the statusline beside the file path.
[^2]: You may also have to activate them in the LSP config for them to appear, not just in Helix.
Expand Down
1 change: 1 addition & 0 deletions book/src/themes.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ These scopes are used for theming the editor interface:
| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
| `ui.virtual.diagnostics` | Default style for inline diagnostics lines (notably control the background) |
| `ui.virtual.whitespace` | Visible whitespace characters |
| `ui.virtual.indent-guide` | Vertical indent width guides |
| `ui.virtual.inlay-hint` | Default style for inlay hints of all kinds |
Expand Down
50 changes: 48 additions & 2 deletions helix-core/src/diagnostic.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,49 @@
//! LSP diagnostic utility types.
use serde::{Deserialize, Serialize};
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

/// Describes the severity level of a [`Diagnostic`].
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)]
pub enum Severity {
Hint,
Info,
Warning,
Error,
}

impl Serialize for Severity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(match *self {
Severity::Hint => "hint",
Severity::Info => "info",
Severity::Warning => "warning",
Severity::Error => "error",
})
}
}

impl<'de> Deserialize<'de> for Severity {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let res = match String::deserialize(deserializer)?.as_str() {
"hint" => Severity::Hint,
"info" => Severity::Info,
"warning" => Severity::Warning,
"error" => Severity::Error,
_ => {
return Err(D::Error::custom(
"expected \"hint\", \"info\", \"warning\" or \"error\"",
))
}
};
Ok(res)
}
}
impl Default for Severity {
fn default() -> Self {
Self::Hint
Expand All @@ -23,6 +57,12 @@ pub struct Range {
pub end: usize,
}

impl Range {
pub fn contains(self, pos: usize) -> bool {
(self.start..self.end).contains(&pos)
}
}

#[derive(Debug, Eq, Hash, PartialEq, Clone, Deserialize, Serialize)]
pub enum NumberOrString {
Number(i32),
Expand All @@ -47,3 +87,9 @@ pub struct Diagnostic {
pub source: Option<String>,
pub data: Option<serde_json::Value>,
}

impl Diagnostic {
pub fn severity(&self) -> Severity {
self.severity.unwrap_or(Severity::Warning)
}
}
5 changes: 5 additions & 0 deletions helix-core/src/graphemes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ pub enum Grapheme<'a> {
}

impl<'a> Grapheme<'a> {
pub fn new_decoration(g: &'static str) -> Grapheme<'a> {
assert_ne!(g, "\t");
Grapheme::new(g.into(), 0, 0)
}

pub fn new(g: GraphemeStr<'a>, visual_x: usize, tab_width: u16) -> Grapheme<'a> {
match g {
g if g == "\t" => Grapheme::Tab {
Expand Down
4 changes: 2 additions & 2 deletions helix-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ pub use {regex, tree_sitter};

pub use graphemes::RopeGraphemes;
pub use position::{
char_idx_at_visual_offset, coords_at_pos, pos_at_coords, visual_offset_from_anchor,
visual_offset_from_block, Position, VisualOffsetError,
char_idx_at_visual_offset, coords_at_pos, pos_at_coords, softwrapped_dimensions,
visual_offset_from_anchor, visual_offset_from_block, Position, VisualOffsetError,
};
#[allow(deprecated)]
pub use position::{pos_at_visual_coords, visual_coords_at_pos};
Expand Down
11 changes: 11 additions & 0 deletions helix-core/src/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,17 @@ pub fn visual_offset_from_block(
(last_pos, block_start)
}

/// Returns the height of the given text when softwrapping
pub fn softwrapped_dimensions(text: RopeSlice, text_fmt: &TextFormat) -> (usize, u16) {
let last_pos =
visual_offset_from_block(text, 0, usize::MAX, text_fmt, &TextAnnotations::default()).0;
if last_pos.row == 0 {
(1, last_pos.col as u16)
} else {
(last_pos.row + 1, text_fmt.viewport_width)
}
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum VisualOffsetError {
PosBeforeAnchorRow,
Expand Down
36 changes: 36 additions & 0 deletions helix-term/src/ui/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,42 @@ impl<'a> TextRenderer<'a> {
col_offset,
}
}
/// Draws a single `grapheme` at the current render position with a specified `style`.
pub fn draw_decoration_grapheme(
&mut self,
grapheme: Grapheme,
mut style: Style,
row: u16,
col: u16,
) -> bool {
if row >= self.viewport.height || col >= self.viewport.width {
return false;
}
let is_whitespace = grapheme.is_whitespace();

// TODO is it correct to apply the whitspace style to all unicode white spaces?
if is_whitespace {
style = style.patch(self.whitespace_style);
}

let grapheme = match grapheme {
Grapheme::Tab { width } => {
let grapheme_tab_width = char_to_byte_idx(&self.virtual_tab, width);
&self.virtual_tab[..grapheme_tab_width]
}
Grapheme::Other { ref g } if g == "\u{00A0}" => " ",
Grapheme::Other { ref g } => g,
Grapheme::Newline => " ",
};

self.surface.set_string(
self.viewport.x + col,
self.viewport.y + row as u16,
grapheme,
style,
);
true
}

/// Draws a single `grapheme` at the current render position with a specified `style`.
pub fn draw_grapheme(
Expand Down
25 changes: 17 additions & 8 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
keymap::{KeymapResult, Keymaps},
ui::{
document::{render_document, LinePos, TextRenderer},
text_decorations::{self, Decoration, DecorationManager},
text_decorations::{self, Decoration, DecorationManager, InlineDiagnostics},
Completion, ProgressSpinners,
},
};
Expand Down Expand Up @@ -187,17 +187,24 @@ impl EditorView {
is_focused,
&mut decorations,
);

let primary_cursor = doc
.selection(view.id)
.primary()
.cursor(doc.text().slice(..));
if is_focused {
decorations.add_decoration(text_decorations::Cursor {
cache: &editor.cursor_cache,
primary_cursor: doc
.selection(view.id)
.primary()
.cursor(doc.text().slice(..)),
primary_cursor,
});
}

if config.lsp.inline_diagnostics.enable(inner.width) {
decorations.add_decoration(InlineDiagnostics::new(
doc.diagnostics(),
theme,
primary_cursor,
config.lsp.inline_diagnostics.clone(),
));
}
render_document(
surface,
inner,
Expand All @@ -222,7 +229,9 @@ impl EditorView {
}
}

Self::render_diagnostics(doc, view, inner, surface, theme);
if config.lsp.display_diagnostic_message {
Self::render_diagnostics(doc, view, inner, surface, theme);
}

let statusline_area = view
.area
Expand Down
13 changes: 10 additions & 3 deletions helix-term/src/ui/text_decorations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ use helix_view::editor::CursorCache;

use crate::ui::document::{LinePos, TextRenderer};

pub use diagnostics::InlineDiagnostics;

mod diagnostics;

/// Decorations are the primary mechanisim for extending the text rendering.
///
/// Any on-screen element which is anchored to the rendered text in some form should
Expand Down Expand Up @@ -53,8 +57,8 @@ pub trait Decoration {
&mut self,
_renderer: &mut TextRenderer,
_pos: LinePos,
_virt_off: usize,
) -> usize {
_virt_off: u16,
) -> u16 {
0
}

Expand Down Expand Up @@ -125,8 +129,11 @@ impl<'a> DecorationManager<'a> {
}

pub fn render_virtual_lines(&mut self, renderer: &mut TextRenderer, pos: LinePos) {
let mut virt_off = 0;
let mut virt_off = 1; // start at 1 so the line is never overwritten
for (decoration, _) in &mut self.decorations {
if pos.visual_line + virt_off >= renderer.viewport.height {
break;
}
virt_off += decoration.render_virt_lines(renderer, pos, virt_off);
}
}
Expand Down
Loading

0 comments on commit 60f4dc7

Please sign in to comment.