Include diagnostic info in HighlightedChunks iterator
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
f1db618be2
commit
c539069cbb
12 changed files with 248 additions and 90 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<_>>());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue