Include diagnostic info in HighlightedChunks iterator

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld 2021-10-26 17:57:50 -07:00
parent f1db618be2
commit c539069cbb
12 changed files with 248 additions and 90 deletions

View file

@ -972,16 +972,16 @@ mod tests {
) -> Vec<(String, Option<&'a str>)> { ) -> Vec<(String, Option<&'a str>)> {
let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx)); let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx));
let mut chunks: Vec<(String, Option<&str>)> = Vec::new(); let mut chunks: Vec<(String, Option<&str>)> = Vec::new();
for (chunk, style_id) in snapshot.highlighted_chunks_for_rows(rows) { for chunk in snapshot.highlighted_chunks_for_rows(rows) {
let style_name = style_id.name(theme); let style_name = chunk.highlight_id.name(theme);
if let Some((last_chunk, last_style_name)) = chunks.last_mut() { if let Some((last_chunk, last_style_name)) = chunks.last_mut() {
if style_name == *last_style_name { if style_name == *last_style_name {
last_chunk.push_str(chunk); last_chunk.push_str(chunk.text);
} else { } else {
chunks.push((chunk.to_string(), style_name)); chunks.push((chunk.text.to_string(), style_name));
} }
} else { } else {
chunks.push((chunk.to_string(), style_name)); chunks.push((chunk.text.to_string(), style_name));
} }
} }
chunks chunks

View file

@ -1,5 +1,7 @@
use gpui::{AppContext, ModelHandle}; use gpui::{AppContext, ModelHandle};
use language::{Anchor, AnchorRangeExt, Buffer, HighlightId, Point, TextSummary, ToOffset}; use language::{
Anchor, AnchorRangeExt, Buffer, HighlightId, HighlightedChunk, Point, TextSummary, ToOffset,
};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::{
cmp::{self, Ordering}, cmp::{self, Ordering},
@ -995,12 +997,12 @@ impl<'a> Iterator for Chunks<'a> {
pub struct HighlightedChunks<'a> { pub struct HighlightedChunks<'a> {
transform_cursor: Cursor<'a, Transform, (FoldOffset, usize)>, transform_cursor: Cursor<'a, Transform, (FoldOffset, usize)>,
buffer_chunks: language::HighlightedChunks<'a>, buffer_chunks: language::HighlightedChunks<'a>,
buffer_chunk: Option<(usize, &'a str, HighlightId)>, buffer_chunk: Option<(usize, HighlightedChunk<'a>)>,
buffer_offset: usize, buffer_offset: usize,
} }
impl<'a> Iterator for HighlightedChunks<'a> { impl<'a> Iterator for HighlightedChunks<'a> {
type Item = (&'a str, HighlightId); type Item = HighlightedChunk<'a>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let transform = if let Some(item) = self.transform_cursor.item() { let transform = if let Some(item) = self.transform_cursor.item() {
@ -1022,34 +1024,35 @@ impl<'a> Iterator for HighlightedChunks<'a> {
self.transform_cursor.next(&()); self.transform_cursor.next(&());
} }
return Some((output_text, HighlightId::default())); return Some(HighlightedChunk {
text: output_text,
highlight_id: HighlightId::default(),
diagnostic: None,
});
} }
// Retrieve a chunk from the current location in the buffer. // Retrieve a chunk from the current location in the buffer.
if self.buffer_chunk.is_none() { if self.buffer_chunk.is_none() {
let chunk_offset = self.buffer_chunks.offset(); let chunk_offset = self.buffer_chunks.offset();
self.buffer_chunk = self self.buffer_chunk = self.buffer_chunks.next().map(|chunk| (chunk_offset, chunk));
.buffer_chunks
.next()
.map(|(chunk, capture_ix)| (chunk_offset, chunk, capture_ix));
} }
// Otherwise, take a chunk from the buffer's text. // Otherwise, take a chunk from the buffer's text.
if let Some((chunk_offset, mut chunk, capture_ix)) = self.buffer_chunk { if let Some((chunk_offset, mut chunk)) = self.buffer_chunk {
let offset_in_chunk = self.buffer_offset - chunk_offset; let offset_in_chunk = self.buffer_offset - chunk_offset;
chunk = &chunk[offset_in_chunk..]; chunk.text = &chunk.text[offset_in_chunk..];
// Truncate the chunk so that it ends at the next fold. // Truncate the chunk so that it ends at the next fold.
let region_end = self.transform_cursor.end(&()).1 - self.buffer_offset; let region_end = self.transform_cursor.end(&()).1 - self.buffer_offset;
if chunk.len() >= region_end { if chunk.text.len() >= region_end {
chunk = &chunk[0..region_end]; chunk.text = &chunk.text[0..region_end];
self.transform_cursor.next(&()); self.transform_cursor.next(&());
} else { } else {
self.buffer_chunk.take(); self.buffer_chunk.take();
} }
self.buffer_offset += chunk.len(); self.buffer_offset += chunk.text.len();
return Some((chunk, capture_ix)); return Some(chunk);
} }
None None

View file

@ -1,5 +1,5 @@
use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot}; use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot};
use language::{rope, HighlightId}; use language::{rope, HighlightedChunk};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{mem, ops::Range}; use std::{mem, ops::Range};
use sum_tree::Bias; use sum_tree::Bias;
@ -173,9 +173,11 @@ impl Snapshot {
.highlighted_chunks(input_start..input_end), .highlighted_chunks(input_start..input_end),
column: expanded_char_column, column: expanded_char_column,
tab_size: self.tab_size, tab_size: self.tab_size,
chunk: &SPACES[0..to_next_stop], chunk: HighlightedChunk {
text: &SPACES[0..to_next_stop],
..Default::default()
},
skip_leading_tab: to_next_stop > 0, skip_leading_tab: to_next_stop > 0,
style_id: Default::default(),
} }
} }
@ -415,23 +417,21 @@ impl<'a> Iterator for Chunks<'a> {
pub struct HighlightedChunks<'a> { pub struct HighlightedChunks<'a> {
fold_chunks: fold_map::HighlightedChunks<'a>, fold_chunks: fold_map::HighlightedChunks<'a>,
chunk: &'a str, chunk: HighlightedChunk<'a>,
style_id: HighlightId,
column: usize, column: usize,
tab_size: usize, tab_size: usize,
skip_leading_tab: bool, skip_leading_tab: bool,
} }
impl<'a> Iterator for HighlightedChunks<'a> { impl<'a> Iterator for HighlightedChunks<'a> {
type Item = (&'a str, HighlightId); type Item = HighlightedChunk<'a>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if self.chunk.is_empty() { if self.chunk.text.is_empty() {
if let Some((chunk, style_id)) = self.fold_chunks.next() { if let Some(chunk) = self.fold_chunks.next() {
self.chunk = chunk; self.chunk = chunk;
self.style_id = style_id;
if self.skip_leading_tab { if self.skip_leading_tab {
self.chunk = &self.chunk[1..]; self.chunk.text = &self.chunk.text[1..];
self.skip_leading_tab = false; self.skip_leading_tab = false;
} }
} else { } else {
@ -439,18 +439,24 @@ impl<'a> Iterator for HighlightedChunks<'a> {
} }
} }
for (ix, c) in self.chunk.char_indices() { for (ix, c) in self.chunk.text.char_indices() {
match c { match c {
'\t' => { '\t' => {
if ix > 0 { if ix > 0 {
let (prefix, suffix) = self.chunk.split_at(ix); let (prefix, suffix) = self.chunk.text.split_at(ix);
self.chunk = suffix; self.chunk.text = suffix;
return Some((prefix, self.style_id)); return Some(HighlightedChunk {
text: prefix,
..self.chunk
});
} else { } else {
self.chunk = &self.chunk[1..]; self.chunk.text = &self.chunk.text[1..];
let len = self.tab_size - self.column % self.tab_size; let len = self.tab_size - self.column % self.tab_size;
self.column += len; self.column += len;
return Some((&SPACES[0..len], self.style_id)); return Some(HighlightedChunk {
text: &SPACES[0..len],
..self.chunk
});
} }
} }
'\n' => self.column = 0, '\n' => self.column = 0,
@ -458,7 +464,7 @@ impl<'a> Iterator for HighlightedChunks<'a> {
} }
} }
Some((mem::take(&mut self.chunk), mem::take(&mut self.style_id))) Some(mem::take(&mut self.chunk))
} }
} }

View file

@ -3,7 +3,7 @@ use super::{
tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary}, tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary},
}; };
use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task}; use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task};
use language::{HighlightId, Point}; use language::{HighlightedChunk, Point};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use smol::future::yield_now; use smol::future::yield_now;
use std::{collections::VecDeque, ops::Range, time::Duration}; use std::{collections::VecDeque, ops::Range, time::Duration};
@ -52,8 +52,7 @@ pub struct Chunks<'a> {
pub struct HighlightedChunks<'a> { pub struct HighlightedChunks<'a> {
input_chunks: tab_map::HighlightedChunks<'a>, input_chunks: tab_map::HighlightedChunks<'a>,
input_chunk: &'a str, input_chunk: HighlightedChunk<'a>,
style_id: HighlightId,
output_position: WrapPoint, output_position: WrapPoint,
max_output_row: u32, max_output_row: u32,
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>, transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
@ -490,8 +489,7 @@ impl Snapshot {
.min(self.tab_snapshot.max_point()); .min(self.tab_snapshot.max_point());
HighlightedChunks { HighlightedChunks {
input_chunks: self.tab_snapshot.highlighted_chunks(input_start..input_end), input_chunks: self.tab_snapshot.highlighted_chunks(input_start..input_end),
input_chunk: "", input_chunk: Default::default(),
style_id: HighlightId::default(),
output_position: output_start, output_position: output_start,
max_output_row: rows.end, max_output_row: rows.end,
transforms, transforms,
@ -674,7 +672,7 @@ impl<'a> Iterator for Chunks<'a> {
} }
impl<'a> Iterator for HighlightedChunks<'a> { impl<'a> Iterator for HighlightedChunks<'a> {
type Item = (&'a str, HighlightId); type Item = HighlightedChunk<'a>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if self.output_position.row() >= self.max_output_row { if self.output_position.row() >= self.max_output_row {
@ -699,18 +697,19 @@ impl<'a> Iterator for HighlightedChunks<'a> {
self.output_position.0 += summary; self.output_position.0 += summary;
self.transforms.next(&()); self.transforms.next(&());
return Some((&display_text[start_ix..end_ix], self.style_id)); return Some(HighlightedChunk {
text: &display_text[start_ix..end_ix],
..self.input_chunk
});
} }
if self.input_chunk.is_empty() { if self.input_chunk.text.is_empty() {
let (chunk, style_id) = self.input_chunks.next().unwrap(); self.input_chunk = self.input_chunks.next().unwrap();
self.input_chunk = chunk;
self.style_id = style_id;
} }
let mut input_len = 0; let mut input_len = 0;
let transform_end = self.transforms.end(&()).0; let transform_end = self.transforms.end(&()).0;
for c in self.input_chunk.chars() { for c in self.input_chunk.text.chars() {
let char_len = c.len_utf8(); let char_len = c.len_utf8();
input_len += char_len; input_len += char_len;
if c == '\n' { if c == '\n' {
@ -726,9 +725,12 @@ impl<'a> Iterator for HighlightedChunks<'a> {
} }
} }
let (prefix, suffix) = self.input_chunk.split_at(input_len); let (prefix, suffix) = self.input_chunk.text.split_at(input_len);
self.input_chunk = suffix; self.input_chunk.text = suffix;
Some((prefix, self.style_id)) Some(HighlightedChunk {
text: prefix,
..self.input_chunk
})
} }
} }
@ -1090,7 +1092,7 @@ mod tests {
let actual_text = self let actual_text = self
.highlighted_chunks_for_rows(start_row..end_row) .highlighted_chunks_for_rows(start_row..end_row)
.map(|c| c.0) .map(|c| c.text)
.collect::<String>(); .collect::<String>();
assert_eq!( assert_eq!(
expected_text, expected_text,

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::HighlightId; use language::{DiagnosticSeverity, HighlightedChunk};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
cmp::{self, Ordering}, cmp::{self, Ordering},
@ -495,8 +495,12 @@ impl EditorElement {
let mut line_exceeded_max_len = false; let mut line_exceeded_max_len = false;
let chunks = snapshot.highlighted_chunks_for_rows(rows.clone()); let chunks = snapshot.highlighted_chunks_for_rows(rows.clone());
'outer: for (chunk, style_ix) in chunks.chain(Some(("\n", HighlightId::default()))) { let newline_chunk = HighlightedChunk {
for (ix, mut line_chunk) in chunk.split('\n').enumerate() { text: "\n",
..Default::default()
};
'outer: for chunk in chunks.chain([newline_chunk]) {
for (ix, mut line_chunk) in chunk.text.split('\n').enumerate() {
if ix > 0 { if ix > 0 {
layouts.push(cx.text_layout_cache.layout_str( layouts.push(cx.text_layout_cache.layout_str(
&line, &line,
@ -513,7 +517,8 @@ impl EditorElement {
} }
if !line_chunk.is_empty() && !line_exceeded_max_len { if !line_chunk.is_empty() && !line_exceeded_max_len {
let highlight_style = style_ix let highlight_style = chunk
.highlight_id
.style(&style.syntax) .style(&style.syntax)
.unwrap_or(style.text.clone().into()); .unwrap_or(style.text.clone().into());
// Avoid a lookup if the font properties match the previous ones. // Avoid a lookup if the font properties match the previous ones.
@ -537,13 +542,25 @@ impl EditorElement {
line_exceeded_max_len = true; line_exceeded_max_len = true;
} }
let underline = if let Some(severity) = chunk.diagnostic {
match severity {
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 {
highlight_style.underline
};
line.push_str(line_chunk); line.push_str(line_chunk);
styles.push(( styles.push((
line_chunk.len(), line_chunk.len(),
RunStyle { RunStyle {
font_id, font_id,
color: highlight_style.color, color: highlight_style.color,
underline: highlight_style.underline, underline,
}, },
)); ));
prev_font_id = font_id; prev_font_id = font_id;

View file

@ -2774,6 +2774,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(),
warning_underline: Default::default(),
information_underline: Default::default(),
hint_underline: Default::default(),
} }
}, },
} }

View file

@ -13,7 +13,7 @@ use clock::ReplicaId;
use futures::FutureExt as _; use futures::FutureExt as _;
use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task}; use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use lsp::{DiagnosticSeverity, LanguageServer}; use lsp::LanguageServer;
use parking_lot::Mutex; use parking_lot::Mutex;
use postage::{prelude::Stream, sink::Sink, watch}; use postage::{prelude::Stream, sink::Sink, watch};
use rpc::proto; use rpc::proto;
@ -26,16 +26,19 @@ use std::{
collections::{BTreeMap, HashMap, HashSet}, collections::{BTreeMap, HashMap, HashSet},
ffi::OsString, ffi::OsString,
future::Future, future::Future,
iter::Iterator, iter::{Iterator, Peekable},
ops::{Deref, DerefMut, Range}, ops::{Deref, DerefMut, Range},
path::{Path, PathBuf}, path::{Path, PathBuf},
str, str,
sync::Arc, sync::Arc,
time::{Duration, Instant, SystemTime, UNIX_EPOCH}, time::{Duration, Instant, SystemTime, UNIX_EPOCH},
vec,
}; };
use tree_sitter::{InputEdit, Parser, QueryCursor, Tree}; use tree_sitter::{InputEdit, Parser, QueryCursor, Tree};
use util::{post_inc, TryFutureExt as _}; use util::{post_inc, TryFutureExt as _};
pub use lsp::DiagnosticSeverity;
thread_local! { thread_local! {
static PARSER: RefCell<Parser> = RefCell::new(Parser::new()); static PARSER: RefCell<Parser> = RefCell::new(Parser::new());
} }
@ -68,6 +71,7 @@ pub struct Buffer {
pub struct Snapshot { pub struct Snapshot {
text: buffer::Snapshot, text: buffer::Snapshot,
tree: Option<Tree>, tree: Option<Tree>,
diagnostics: AnchorRangeMultimap<(DiagnosticSeverity, String)>,
is_parsing: bool, is_parsing: bool,
language: Option<Arc<Language>>, language: Option<Arc<Language>>,
query_cursor: QueryCursorHandle, query_cursor: QueryCursorHandle,
@ -182,15 +186,34 @@ struct Highlights<'a> {
pub struct HighlightedChunks<'a> { pub struct HighlightedChunks<'a> {
range: Range<usize>, range: Range<usize>,
chunks: Chunks<'a>, chunks: Chunks<'a>,
diagnostic_endpoints: Peekable<vec::IntoIter<DiagnosticEndpoint>>,
error_depth: usize,
warning_depth: usize,
information_depth: usize,
hint_depth: usize,
highlights: Option<Highlights<'a>>, highlights: Option<Highlights<'a>>,
} }
#[derive(Clone, Copy, Debug, Default)]
pub struct HighlightedChunk<'a> {
pub text: &'a str,
pub highlight_id: HighlightId,
pub diagnostic: Option<DiagnosticSeverity>,
}
struct Diff { struct Diff {
base_version: clock::Global, base_version: clock::Global,
new_text: Arc<str>, new_text: Arc<str>,
changes: Vec<(ChangeTag, usize)>, changes: Vec<(ChangeTag, usize)>,
} }
#[derive(Clone, Copy)]
struct DiagnosticEndpoint {
offset: usize,
is_start: bool,
severity: DiagnosticSeverity,
}
impl Buffer { impl Buffer {
pub fn new<T: Into<Arc<str>>>( pub fn new<T: Into<Arc<str>>>(
replica_id: ReplicaId, replica_id: ReplicaId,
@ -275,6 +298,7 @@ impl Buffer {
Snapshot { Snapshot {
text: self.text.snapshot(), text: self.text.snapshot(),
tree: self.syntax_tree(), tree: self.syntax_tree(),
diagnostics: self.diagnostics.clone(),
is_parsing: self.parsing_in_background, is_parsing: self.parsing_in_background,
language: self.language.clone(), language: self.language.clone(),
query_cursor: QueryCursorHandle::new(), query_cursor: QueryCursorHandle::new(),
@ -673,7 +697,7 @@ impl Buffer {
let content = self.content(); let content = self.content();
let range = range.start.to_offset(&content)..range.end.to_offset(&content); let range = range.start.to_offset(&content)..range.end.to_offset(&content);
self.diagnostics self.diagnostics
.intersecting_point_ranges(range, content, true) .intersecting_ranges(range, content, true)
.map(move |(_, range, (severity, message))| Diagnostic { .map(move |(_, range, (severity, message))| Diagnostic {
range, range,
severity: *severity, severity: *severity,
@ -1021,7 +1045,9 @@ impl Buffer {
let abs_path = self let abs_path = self
.file .file
.as_ref() .as_ref()
.map_or(PathBuf::new(), |file| file.abs_path(cx).unwrap()); .map_or(Path::new("/").to_path_buf(), |file| {
file.abs_path(cx).unwrap()
});
let version = post_inc(&mut language_server.next_version); let version = post_inc(&mut language_server.next_version);
let snapshot = LanguageServerSnapshot { let snapshot = LanguageServerSnapshot {
@ -1462,30 +1488,54 @@ impl Snapshot {
range: Range<T>, range: Range<T>,
) -> HighlightedChunks { ) -> HighlightedChunks {
let range = range.start.to_offset(&*self)..range.end.to_offset(&*self); let range = range.start.to_offset(&*self)..range.end.to_offset(&*self);
let chunks = self.text.as_rope().chunks_in_range(range.clone());
if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) {
let captures = self.query_cursor.set_byte_range(range.clone()).captures(
&language.highlights_query,
tree.root_node(),
TextProvider(self.text.as_rope()),
);
HighlightedChunks { let mut diagnostic_endpoints = Vec::<DiagnosticEndpoint>::new();
range, for (_, range, (severity, _)) in
chunks, self.diagnostics
highlights: Some(Highlights { .intersecting_ranges(range.clone(), self.content(), true)
{
diagnostic_endpoints.push(DiagnosticEndpoint {
offset: range.start,
is_start: true,
severity: *severity,
});
diagnostic_endpoints.push(DiagnosticEndpoint {
offset: range.end,
is_start: false,
severity: *severity,
});
}
diagnostic_endpoints.sort_unstable_by_key(|endpoint| endpoint.offset);
let diagnostic_endpoints = diagnostic_endpoints.into_iter().peekable();
let chunks = self.text.as_rope().chunks_in_range(range.clone());
let highlights =
if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) {
let captures = self.query_cursor.set_byte_range(range.clone()).captures(
&language.highlights_query,
tree.root_node(),
TextProvider(self.text.as_rope()),
);
Some(Highlights {
captures, captures,
next_capture: None, next_capture: None,
stack: Default::default(), stack: Default::default(),
highlight_map: language.highlight_map(), highlight_map: language.highlight_map(),
}), })
} } else {
} else { None
HighlightedChunks { };
range,
chunks, HighlightedChunks {
highlights: None, range,
} chunks,
diagnostic_endpoints,
error_depth: 0,
warning_depth: 0,
information_depth: 0,
hint_depth: 0,
highlights,
} }
} }
} }
@ -1495,6 +1545,7 @@ impl Clone for Snapshot {
Self { Self {
text: self.text.clone(), text: self.text.clone(),
tree: self.tree.clone(), tree: self.tree.clone(),
diagnostics: self.diagnostics.clone(),
is_parsing: self.is_parsing, is_parsing: self.is_parsing,
language: self.language.clone(), language: self.language.clone(),
query_cursor: QueryCursorHandle::new(), query_cursor: QueryCursorHandle::new(),
@ -1556,13 +1607,43 @@ impl<'a> HighlightedChunks<'a> {
pub fn offset(&self) -> usize { pub fn offset(&self) -> usize {
self.range.start self.range.start
} }
fn update_diagnostic_depths(&mut self, endpoint: DiagnosticEndpoint) {
let depth = match endpoint.severity {
DiagnosticSeverity::ERROR => &mut self.error_depth,
DiagnosticSeverity::WARNING => &mut self.warning_depth,
DiagnosticSeverity::INFORMATION => &mut self.information_depth,
DiagnosticSeverity::HINT => &mut self.hint_depth,
_ => return,
};
if endpoint.is_start {
*depth += 1;
} else {
*depth -= 1;
}
}
fn current_diagnostic_severity(&mut self) -> Option<DiagnosticSeverity> {
if self.error_depth > 0 {
Some(DiagnosticSeverity::ERROR)
} else if self.warning_depth > 0 {
Some(DiagnosticSeverity::WARNING)
} else if self.information_depth > 0 {
Some(DiagnosticSeverity::INFORMATION)
} else if self.hint_depth > 0 {
Some(DiagnosticSeverity::HINT)
} else {
None
}
}
} }
impl<'a> Iterator for HighlightedChunks<'a> { impl<'a> Iterator for HighlightedChunks<'a> {
type Item = (&'a str, HighlightId); type Item = HighlightedChunk<'a>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let mut next_capture_start = usize::MAX; let mut next_capture_start = usize::MAX;
let mut next_diagnostic_endpoint = usize::MAX;
if let Some(highlights) = self.highlights.as_mut() { if let Some(highlights) = self.highlights.as_mut() {
while let Some((parent_capture_end, _)) = highlights.stack.last() { while let Some((parent_capture_end, _)) = highlights.stack.last() {
@ -1583,22 +1664,36 @@ impl<'a> Iterator for HighlightedChunks<'a> {
next_capture_start = capture.node.start_byte(); next_capture_start = capture.node.start_byte();
break; break;
} else { } else {
let style_id = highlights.highlight_map.get(capture.index); let highlight_id = highlights.highlight_map.get(capture.index);
highlights.stack.push((capture.node.end_byte(), style_id)); highlights
.stack
.push((capture.node.end_byte(), highlight_id));
highlights.next_capture = highlights.captures.next(); highlights.next_capture = highlights.captures.next();
} }
} }
} }
while let Some(endpoint) = self.diagnostic_endpoints.peek().copied() {
if endpoint.offset <= self.range.start {
self.update_diagnostic_depths(endpoint);
self.diagnostic_endpoints.next();
} else {
next_diagnostic_endpoint = endpoint.offset;
break;
}
}
if let Some(chunk) = self.chunks.peek() { if let Some(chunk) = self.chunks.peek() {
let chunk_start = self.range.start; let chunk_start = self.range.start;
let mut chunk_end = (self.chunks.offset() + chunk.len()).min(next_capture_start); let mut chunk_end = (self.chunks.offset() + chunk.len())
let mut style_id = HighlightId::default(); .min(next_capture_start)
if let Some((parent_capture_end, parent_style_id)) = .min(next_diagnostic_endpoint);
let mut highlight_id = HighlightId::default();
if let Some((parent_capture_end, parent_highlight_id)) =
self.highlights.as_ref().and_then(|h| h.stack.last()) self.highlights.as_ref().and_then(|h| h.stack.last())
{ {
chunk_end = chunk_end.min(*parent_capture_end); chunk_end = chunk_end.min(*parent_capture_end);
style_id = *parent_style_id; highlight_id = *parent_highlight_id;
} }
let slice = let slice =
@ -1608,7 +1703,11 @@ impl<'a> Iterator for HighlightedChunks<'a> {
self.chunks.next().unwrap(); self.chunks.next().unwrap();
} }
Some((slice, style_id)) Some(HighlightedChunk {
text: slice,
highlight_id,
diagnostic: self.current_diagnostic_severity(),
})
} else { } else {
None None
} }

View file

@ -475,7 +475,12 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
message: "undefined variable 'CCC'".to_string() message: "undefined variable 'CCC'".to_string()
} }
] ]
) );
dbg!(buffer
.snapshot()
.highlighted_text_for_range(0..buffer.len())
.collect::<Vec<_>>());
}); });
} }

View file

@ -214,6 +214,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 warning_underline: Color,
#[serde(default)]
pub information_underline: Color,
#[serde(default)]
pub hint_underline: Color,
} }
#[derive(Clone, Copy, Default, Deserialize)] #[derive(Clone, Copy, Default, Deserialize)]
@ -254,6 +260,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(),
warning_underline: Default::default(),
information_underline: Default::default(),
hint_underline: Default::default(),
} }
} }
} }

View file

@ -39,6 +39,10 @@ bad = "#b7372e"
active_line = "#00000033" active_line = "#00000033"
hover = "#00000033" hover = "#00000033"
[editor]
error_underline = "#ff0000"
warning_underline = "#00ffff"
[editor.syntax] [editor.syntax]
keyword = { color = "#0086c0", weight = "bold" } keyword = { color = "#0086c0", weight = "bold" }
function = "#dcdcaa" function = "#dcdcaa"

View file

@ -39,6 +39,10 @@ bad = "#b7372e"
active_line = "#00000022" active_line = "#00000022"
hover = "#00000033" hover = "#00000033"
[editor]
error_underline = "#ff0000"
warning_underline = "#00ffff"
[editor.syntax] [editor.syntax]
keyword = { color = "#0086c0", weight = "bold" } keyword = { color = "#0086c0", weight = "bold" }
function = "#dcdcaa" function = "#dcdcaa"

View file

@ -26,7 +26,7 @@ guests = [
{ selection = "#EE823133", cursor = "#EE8231" }, { selection = "#EE823133", cursor = "#EE8231" },
{ selection = "#5A2B9233", cursor = "#5A2B92" }, { selection = "#5A2B9233", cursor = "#5A2B92" },
{ selection = "#FDF35133", cursor = "#FDF351" }, { selection = "#FDF35133", cursor = "#FDF351" },
{ selection = "#4EACAD33", cursor = "#4EACAD" } { selection = "#4EACAD33", cursor = "#4EACAD" },
] ]
[status] [status]
@ -39,6 +39,10 @@ bad = "#b7372e"
active_line = "#00000008" active_line = "#00000008"
hover = "#0000000D" hover = "#0000000D"
[editor]
error_underline = "#ff0000"
warning_underline = "#00ffff"
[editor.syntax] [editor.syntax]
keyword = { color = "#0000fa", weight = "bold" } keyword = { color = "#0000fa", weight = "bold" }
function = "#795e26" function = "#795e26"