Color diagnostic messages based on their severity

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2021-11-18 17:47:10 +01:00
parent f39942863b
commit 1a8b23e118
6 changed files with 103 additions and 58 deletions

View file

@ -8,7 +8,7 @@ pub use block_map::{BlockDisposition, BlockId, BlockProperties, BufferRows, Chun
use block_map::{BlockMap, BlockPoint}; use block_map::{BlockMap, BlockPoint};
use buffer::Rope; use buffer::Rope;
use fold_map::{FoldMap, ToFoldPoint as _}; use fold_map::{FoldMap, ToFoldPoint as _};
use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle}; use gpui::{fonts::FontId, AppContext, Entity, ModelContext, ModelHandle};
use language::{Anchor, Buffer, Point, ToOffset, ToPoint}; use language::{Anchor, Buffer, Point, ToOffset, ToPoint};
use std::{collections::HashSet, ops::Range}; use std::{collections::HashSet, ops::Range};
use sum_tree::Bias; use sum_tree::Bias;
@ -230,7 +230,7 @@ impl DisplayMapSnapshot {
pub fn text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> { pub fn text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
self.blocks_snapshot self.blocks_snapshot
.chunks(display_row..self.max_point().row() + 1, None) .chunks(display_row..self.max_point().row() + 1, None, None)
.map(|h| h.text) .map(|h| h.text)
} }
@ -238,8 +238,9 @@ impl DisplayMapSnapshot {
&'a self, &'a self,
display_rows: Range<u32>, display_rows: Range<u32>,
theme: Option<&'a SyntaxTheme>, theme: Option<&'a SyntaxTheme>,
cx: &'a AppContext,
) -> block_map::Chunks<'a> { ) -> block_map::Chunks<'a> {
self.blocks_snapshot.chunks(display_rows, theme) self.blocks_snapshot.chunks(display_rows, theme, Some(cx))
} }
pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a { pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {
@ -1025,7 +1026,7 @@ mod tests {
) -> Vec<(String, Option<Color>)> { ) -> Vec<(String, Option<Color>)> {
let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
let mut chunks: Vec<(String, Option<Color>)> = Vec::new(); let mut chunks: Vec<(String, Option<Color>)> = Vec::new();
for chunk in snapshot.chunks(rows, Some(theme)) { for chunk in snapshot.chunks(rows, Some(theme), cx) {
let color = chunk.highlight_style.map(|s| s.color); let color = chunk.highlight_style.map(|s| s.color);
if let Some((last_chunk, last_color)) = chunks.last_mut() { if let Some((last_chunk, last_color)) = chunks.last_mut() {
if color == *last_color { if color == *last_color {

View file

@ -6,13 +6,14 @@ use parking_lot::Mutex;
use std::{ use std::{
cmp::{self, Ordering}, cmp::{self, Ordering},
collections::HashSet, collections::HashSet,
fmt::Debug,
iter, iter,
ops::Range, ops::Range,
slice,
sync::{ sync::{
atomic::{AtomicUsize, Ordering::SeqCst}, atomic::{AtomicUsize, Ordering::SeqCst},
Arc, Arc,
}, },
vec,
}; };
use sum_tree::SumTree; use sum_tree::SumTree;
use theme::SyntaxTheme; use theme::SyntaxTheme;
@ -44,12 +45,11 @@ struct BlockRow(u32);
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
struct WrapRow(u32); struct WrapRow(u32);
#[derive(Debug)]
struct Block { struct Block {
id: BlockId, id: BlockId,
position: Anchor, position: Anchor,
text: Rope, text: Rope,
runs: Vec<(usize, HighlightStyle)>, build_runs: Option<Arc<dyn Fn(&AppContext) -> Vec<(usize, HighlightStyle)>>>,
disposition: BlockDisposition, disposition: BlockDisposition,
} }
@ -61,7 +61,7 @@ where
{ {
pub position: P, pub position: P,
pub text: T, pub text: T,
pub runs: Vec<(usize, HighlightStyle)>, pub build_runs: Option<Arc<dyn Fn(&AppContext) -> Vec<(usize, HighlightStyle)>>>,
pub disposition: BlockDisposition, pub disposition: BlockDisposition,
} }
@ -92,11 +92,12 @@ pub struct Chunks<'a> {
block_chunks: Option<BlockChunks<'a>>, block_chunks: Option<BlockChunks<'a>>,
output_row: u32, output_row: u32,
max_output_row: u32, max_output_row: u32,
cx: Option<&'a AppContext>,
} }
struct BlockChunks<'a> { struct BlockChunks<'a> {
chunks: rope::Chunks<'a>, chunks: rope::Chunks<'a>,
runs: iter::Peekable<slice::Iter<'a, (usize, HighlightStyle)>>, runs: iter::Peekable<vec::IntoIter<(usize, HighlightStyle)>>,
chunk: Option<&'a str>, chunk: Option<&'a str>,
run_start: usize, run_start: usize,
offset: usize, offset: usize,
@ -403,7 +404,7 @@ impl<'a> BlockMapWriter<'a> {
id, id,
position, position,
text: block.text.into(), text: block.text.into(),
runs: block.runs, build_runs: block.build_runs,
disposition: block.disposition, disposition: block.disposition,
}), }),
); );
@ -460,12 +461,17 @@ impl<'a> BlockMapWriter<'a> {
impl BlockSnapshot { impl BlockSnapshot {
#[cfg(test)] #[cfg(test)]
fn text(&mut self) -> String { fn text(&mut self) -> String {
self.chunks(0..self.transforms.summary().output_rows, None) self.chunks(0..self.transforms.summary().output_rows, None, None)
.map(|chunk| chunk.text) .map(|chunk| chunk.text)
.collect() .collect()
} }
pub fn chunks<'a>(&'a self, rows: Range<u32>, theme: Option<&'a SyntaxTheme>) -> Chunks<'a> { pub fn chunks<'a>(
&'a self,
rows: Range<u32>,
theme: Option<&'a SyntaxTheme>,
cx: Option<&'a AppContext>,
) -> Chunks<'a> {
let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows); let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
let input_end = { let input_end = {
@ -499,6 +505,7 @@ impl BlockSnapshot {
transforms: cursor, transforms: cursor,
output_row: rows.start, output_row: rows.start,
max_output_row, max_output_row,
cx,
} }
} }
@ -709,7 +716,11 @@ impl<'a> Iterator for Chunks<'a> {
let start_in_block = self.output_row - block_start; let start_in_block = self.output_row - block_start;
let end_in_block = cmp::min(self.max_output_row, block_end) - block_start; let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
self.transforms.next(&()); self.transforms.next(&());
self.block_chunks = Some(BlockChunks::new(block, start_in_block..end_in_block)); self.block_chunks = Some(BlockChunks::new(
block,
start_in_block..end_in_block,
self.cx,
));
return self.next(); return self.next();
} }
@ -748,11 +759,18 @@ impl<'a> Iterator for Chunks<'a> {
} }
impl<'a> BlockChunks<'a> { impl<'a> BlockChunks<'a> {
fn new(block: &'a Block, rows: Range<u32>) -> Self { fn new(block: &'a Block, rows: Range<u32>, cx: Option<&'a AppContext>) -> Self {
let offset_range = block.text.point_to_offset(Point::new(rows.start, 0)) let offset_range = block.text.point_to_offset(Point::new(rows.start, 0))
..block.text.point_to_offset(Point::new(rows.end, 0)); ..block.text.point_to_offset(Point::new(rows.end, 0));
let mut runs = block.runs.iter().peekable(); let mut runs = block
.build_runs
.as_ref()
.zip(cx)
.map(|(build_runs, cx)| build_runs(cx))
.unwrap_or_default()
.into_iter()
.peekable();
let mut run_start = 0; let mut run_start = 0;
while let Some((run_len, _)) = runs.peek() { while let Some((run_len, _)) = runs.peek() {
let run_end = run_start + run_len; let run_end = run_start + run_len;
@ -874,6 +892,17 @@ impl BlockDisposition {
} }
} }
impl Debug for Block {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Block")
.field("id", &self.id)
.field("position", &self.position)
.field("text", &self.text)
.field("disposition", &self.disposition)
.finish()
}
}
// Count the number of bytes prior to a target point. If the string doesn't contain the target // Count the number of bytes prior to a target point. If the string doesn't contain the target
// point, return its total extent. Otherwise return the target point itself. // point, return its total extent. Otherwise return the target point itself.
fn offset_for_row(s: &str, target: u32) -> (u32, usize) { fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
@ -938,19 +967,19 @@ mod tests {
position: Point::new(1, 0), position: Point::new(1, 0),
text: "BLOCK 1", text: "BLOCK 1",
disposition: BlockDisposition::Above, disposition: BlockDisposition::Above,
runs: vec![], build_runs: None,
}, },
BlockProperties { BlockProperties {
position: Point::new(1, 2), position: Point::new(1, 2),
text: "BLOCK 2", text: "BLOCK 2",
disposition: BlockDisposition::Above, disposition: BlockDisposition::Above,
runs: vec![], build_runs: None,
}, },
BlockProperties { BlockProperties {
position: Point::new(3, 2), position: Point::new(3, 2),
text: "BLOCK 3", text: "BLOCK 3",
disposition: BlockDisposition::Below, disposition: BlockDisposition::Below,
runs: vec![], build_runs: None,
}, },
], ],
cx, cx,
@ -1078,13 +1107,13 @@ mod tests {
position: Point::new(1, 12), position: Point::new(1, 12),
text: "<BLOCK 1", text: "<BLOCK 1",
disposition: BlockDisposition::Above, disposition: BlockDisposition::Above,
runs: vec![], build_runs: None,
}, },
BlockProperties { BlockProperties {
position: Point::new(1, 1), position: Point::new(1, 1),
text: ">BLOCK 2", text: ">BLOCK 2",
disposition: BlockDisposition::Below, disposition: BlockDisposition::Below,
runs: vec![], build_runs: None,
}, },
], ],
cx, cx,
@ -1177,7 +1206,7 @@ mod tests {
BlockProperties { BlockProperties {
position, position,
text, text,
runs: Vec::<(usize, HighlightStyle)>::new(), build_runs: None,
disposition, disposition,
} }
}) })
@ -1252,7 +1281,7 @@ mod tests {
BlockProperties { BlockProperties {
position: row, position: row,
text: block.text, text: block.text,
runs: block.runs, build_runs: block.build_runs.clone(),
disposition: block.disposition, disposition: block.disposition,
}, },
) )
@ -1313,7 +1342,7 @@ mod tests {
for start_row in 0..expected_row_count { for start_row in 0..expected_row_count {
let expected_text = expected_lines[start_row..].join("\n"); let expected_text = expected_lines[start_row..].join("\n");
let actual_text = blocks_snapshot let actual_text = blocks_snapshot
.chunks(start_row as u32..expected_row_count as u32, None) .chunks(start_row as u32..expected_row_count as u32, None, None)
.map(|chunk| chunk.text) .map(|chunk| chunk.text)
.collect::<String>(); .collect::<String>();
assert_eq!( assert_eq!(

View file

@ -17,7 +17,7 @@ use gpui::{
MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
}; };
use json::json; use json::json;
use language::{Chunk, DiagnosticSeverity}; use language::Chunk;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
cmp::{self, Ordering}, cmp::{self, Ordering},
@ -493,7 +493,7 @@ impl EditorElement {
let mut styles = Vec::new(); let mut styles = Vec::new();
let mut row = rows.start; let mut row = rows.start;
let mut line_exceeded_max_len = false; let mut line_exceeded_max_len = false;
let chunks = snapshot.chunks(rows.clone(), Some(&style.syntax)); let chunks = snapshot.chunks(rows.clone(), Some(&style.syntax), cx);
let newline_chunk = Chunk { let newline_chunk = Chunk {
text: "\n", text: "\n",
@ -541,13 +541,7 @@ impl EditorElement {
} }
let underline = if let Some(severity) = chunk.diagnostic { let underline = if let Some(severity) = chunk.diagnostic {
match severity { Some(super::diagnostic_color(severity, style))
DiagnosticSeverity::ERROR => Some(style.error_underline),
DiagnosticSeverity::WARNING => Some(style.warning_underline),
DiagnosticSeverity::INFORMATION => Some(style.information_underline),
DiagnosticSeverity::HINT => Some(style.hint_underline),
_ => highlight_style.underline,
}
} else { } else {
highlight_style.underline highlight_style.underline
}; };

View file

@ -12,6 +12,7 @@ use display_map::*;
pub use element::*; pub use element::*;
use gpui::{ use gpui::{
action, action,
color::Color,
geometry::vector::{vec2f, Vector2F}, geometry::vector::{vec2f, Vector2F},
keymap::Binding, keymap::Binding,
text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
@ -2250,21 +2251,30 @@ impl Editor {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
let diagnostic_group = buffer let diagnostic_group = buffer
.diagnostic_group::<Point>(group_id) .diagnostic_group::<Point>(group_id)
.map(|(range, diagnostic)| (range, diagnostic.message.clone())) .map(|(range, diagnostic)| (range, diagnostic.clone()))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let primary_range = buffer.anchor_after(primary_range.start) let primary_range = buffer.anchor_after(primary_range.start)
..buffer.anchor_before(primary_range.end); ..buffer.anchor_before(primary_range.end);
let block_ids = display_map let block_ids = display_map
.insert_blocks( .insert_blocks(
diagnostic_group diagnostic_group.iter().map(|(range, diagnostic)| {
.iter() let build_settings = self.build_settings.clone();
.map(|(range, message)| BlockProperties { let message_len = diagnostic.message.len();
let severity = diagnostic.severity;
BlockProperties {
position: range.start, position: range.start,
text: message.as_str(), text: diagnostic.message.as_str(),
runs: vec![], build_runs: Some(Arc::new(move |cx| {
disposition: BlockDisposition::Above, let settings = build_settings.borrow()(cx);
}), vec![(
message_len,
diagnostic_color(severity, &settings.style).into(),
)]
})),
disposition: BlockDisposition::Below,
}
}),
cx, cx,
) )
.into_iter() .into_iter()
@ -2813,8 +2823,9 @@ impl Snapshot {
&'a self, &'a self,
display_rows: Range<u32>, display_rows: Range<u32>,
theme: Option<&'a SyntaxTheme>, theme: Option<&'a SyntaxTheme>,
cx: &'a AppContext,
) -> display_map::Chunks<'a> { ) -> display_map::Chunks<'a> {
self.display_snapshot.chunks(display_rows, theme) self.display_snapshot.chunks(display_rows, theme, cx)
} }
pub fn scroll_position(&self) -> Vector2F { pub fn scroll_position(&self) -> Vector2F {
@ -2882,10 +2893,10 @@ impl EditorSettings {
selection: Default::default(), selection: Default::default(),
guest_selections: Default::default(), guest_selections: Default::default(),
syntax: Default::default(), syntax: Default::default(),
error_underline: Default::default(), error_color: Default::default(),
warning_underline: Default::default(), warning_color: Default::default(),
information_underline: Default::default(), information_color: Default::default(),
hint_underline: Default::default(), hint_color: Default::default(),
} }
}, },
} }
@ -3009,6 +3020,16 @@ impl SelectionExt for Selection<Point> {
} }
} }
pub fn diagnostic_color(severity: DiagnosticSeverity, style: &EditorStyle) -> Color {
match severity {
DiagnosticSeverity::ERROR => style.error_color,
DiagnosticSeverity::WARNING => style.warning_color,
DiagnosticSeverity::INFORMATION => style.information_color,
DiagnosticSeverity::HINT => style.hint_color,
_ => style.text.color,
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -227,12 +227,12 @@ pub struct EditorStyle {
pub line_number_active: Color, pub line_number_active: Color,
pub guest_selections: Vec<SelectionStyle>, pub guest_selections: Vec<SelectionStyle>,
pub syntax: Arc<SyntaxTheme>, pub syntax: Arc<SyntaxTheme>,
pub error_underline: Color, pub error_color: Color,
pub warning_underline: Color, pub warning_color: Color,
#[serde(default)] #[serde(default)]
pub information_underline: Color, pub information_color: Color,
#[serde(default)] #[serde(default)]
pub hint_underline: Color, pub hint_color: Color,
} }
#[derive(Clone, Copy, Default, Deserialize)] #[derive(Clone, Copy, Default, Deserialize)]
@ -273,10 +273,10 @@ impl InputEditorStyle {
line_number_active: Default::default(), line_number_active: Default::default(),
guest_selections: Default::default(), guest_selections: Default::default(),
syntax: Default::default(), syntax: Default::default(),
error_underline: Default::default(), error_color: Default::default(),
warning_underline: Default::default(), warning_color: Default::default(),
information_underline: Default::default(), information_color: Default::default(),
hint_underline: Default::default(), hint_color: Default::default(),
} }
} }
} }

View file

@ -173,7 +173,7 @@ corner_radius = 6
[project_panel] [project_panel]
extends = "$panel" extends = "$panel"
padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2
[project_panel.entry] [project_panel.entry]
text = "$text.1" text = "$text.1"
@ -235,7 +235,7 @@ line_number = "$text.2.color"
line_number_active = "$text.0.color" line_number_active = "$text.0.color"
selection = "$selection.host" selection = "$selection.host"
guest_selections = "$selection.guests" guest_selections = "$selection.guests"
error_underline = "$status.bad" error_color = "$status.bad"
warning_underline = "$status.warn" warning_color = "$status.warn"
info_underline = "$status.info" info_color = "$status.info"
hint_underline = "$status.info" hint_color = "$status.info"