feat: search result highlight in pr diff view
This commit is contained in:
@@ -1,21 +1,30 @@
|
||||
use crate::{theme, util};
|
||||
use crate::{component::code_view::symbol_highlight_style, theme, util};
|
||||
|
||||
pub(crate) struct HighlightedContent(Vec<Vec<(std::ops::Range<usize>, gpui::HighlightStyle)>>);
|
||||
pub(crate) type HighlightedRange = (std::ops::Range<usize>, gpui::HighlightStyle);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct HighlightedContent(Vec<HighlightedLine>);
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub(crate) struct HighlightedLine {
|
||||
line_range: std::ops::Range<usize>,
|
||||
highlights: Vec<(std::ops::Range<usize>, gpui::HighlightStyle)>,
|
||||
}
|
||||
|
||||
fn ts_highlight_configuration_for_file_type(
|
||||
file_type: util::file::FileType,
|
||||
) -> Option<tree_sitter_highlight::HighlightConfiguration> {
|
||||
match file_type {
|
||||
| util::file::FileType::Rust => tree_sitter_highlight::HighlightConfiguration::new(
|
||||
tree_sitter_rust::LANGUAGE.into(),
|
||||
"rust",
|
||||
tree_sitter_rust::HIGHLIGHTS_QUERY,
|
||||
tree_sitter_rust::INJECTIONS_QUERY,
|
||||
"",
|
||||
)
|
||||
.ok(),
|
||||
| util::file::FileType::Rust => tree_sitter_highlight::HighlightConfiguration::new(
|
||||
tree_sitter_rust::LANGUAGE.into(),
|
||||
"rust",
|
||||
tree_sitter_rust::HIGHLIGHTS_QUERY,
|
||||
tree_sitter_rust::INJECTIONS_QUERY,
|
||||
"",
|
||||
)
|
||||
.ok(),
|
||||
|
||||
| _ => None,
|
||||
| _ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,43 +47,63 @@ pub(crate) fn highlight_content(
|
||||
|
||||
let line_ranges = util::file::line_ranges(content.as_ref());
|
||||
|
||||
let mut highlights: Vec<Vec<(std::ops::Range<usize>, gpui::HighlightStyle)>> =
|
||||
Vec::with_capacity(line_ranges.len());
|
||||
let mut highlights: Vec<HighlightedLine> = Vec::with_capacity(line_ranges.len());
|
||||
let mut current_line: usize = 0;
|
||||
|
||||
// Tree-sitter produces a flat event stream:
|
||||
//
|
||||
// HighlightStart(style) -> Source { start, end } -> HighlightEnd
|
||||
//
|
||||
// `highlight` tracks the currently active style while each Source span is
|
||||
// converted from absolute byte offsets into per-line, line-relative ranges.
|
||||
// Empty lines before the span are emitted as empty highlight lists, then a
|
||||
// span that crosses line boundaries is clipped into one range per touched
|
||||
// line:
|
||||
//
|
||||
// bytes: [ line 0 ][ line 1 ][ line 2 ]
|
||||
// span: [===========)
|
||||
// out: [--] [------] [--]
|
||||
for highlight_event in events {
|
||||
match highlight_event.ok()? {
|
||||
| tree_sitter_highlight::HighlightEvent::HighlightStart(h) => {
|
||||
highlight = theme_syntax.as_slice()[h.0];
|
||||
}
|
||||
| tree_sitter_highlight::HighlightEvent::Source { start, end } => {
|
||||
while current_line < line_ranges.len() && start >= line_ranges[current_line].end {
|
||||
highlights.push(Vec::new());
|
||||
current_line += 1;
|
||||
| tree_sitter_highlight::HighlightEvent::HighlightStart(h) => {
|
||||
highlight = theme_syntax.as_slice()[h.0];
|
||||
}
|
||||
| tree_sitter_highlight::HighlightEvent::Source { start, end } => {
|
||||
while current_line < line_ranges.len() && start >= line_ranges[current_line].end {
|
||||
if highlights.get(current_line).is_none() {
|
||||
highlights.push(HighlightedLine {
|
||||
line_range: line_ranges[current_line].clone(),
|
||||
highlights: Vec::new(),
|
||||
});
|
||||
}
|
||||
let mut line = current_line;
|
||||
while line < line_ranges.len() && end > line_ranges[line].start {
|
||||
if highlights.get(line).is_none() {
|
||||
highlights.push(Vec::new());
|
||||
}
|
||||
let line_range = &line_ranges[line];
|
||||
let highlight_start = start.max(line_range.start);
|
||||
let highlight_end = end.min(line_range.end);
|
||||
highlights[line].push((
|
||||
(highlight_start - line_range.start)..(highlight_end - line_range.start),
|
||||
gpui::HighlightStyle {
|
||||
color: Some(highlight.color.into()),
|
||||
font_weight: highlight.font_weight,
|
||||
font_style: Some(highlight.font_style),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
line += 1;
|
||||
current_line += 1;
|
||||
}
|
||||
let mut line = current_line;
|
||||
while line < line_ranges.len() && end > line_ranges[line].start {
|
||||
let line_range = &line_ranges[line];
|
||||
if highlights.get(line).is_none() {
|
||||
highlights.push(HighlightedLine {
|
||||
line_range: line_range.clone(),
|
||||
highlights: Vec::new(),
|
||||
});
|
||||
}
|
||||
let highlight_start = start.max(line_range.start);
|
||||
let highlight_end = end.min(line_range.end);
|
||||
highlights[line].highlights.push((
|
||||
(highlight_start - line_range.start)..(highlight_end - line_range.start),
|
||||
gpui::HighlightStyle {
|
||||
color: Some(highlight.color.into()),
|
||||
font_weight: highlight.font_weight,
|
||||
font_style: Some(highlight.font_style),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
line += 1;
|
||||
}
|
||||
| tree_sitter_highlight::HighlightEvent::HighlightEnd => {
|
||||
highlight = default_highlight;
|
||||
}
|
||||
}
|
||||
| tree_sitter_highlight::HighlightEvent::HighlightEnd => {
|
||||
highlight = default_highlight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +114,101 @@ impl HighlightedContent {
|
||||
pub(crate) fn highlights_at_line(
|
||||
&self,
|
||||
line: usize,
|
||||
) -> &Vec<(std::ops::Range<usize>, gpui::HighlightStyle)> {
|
||||
&self.0[line]
|
||||
) -> &[(std::ops::Range<usize>, gpui::HighlightStyle)] {
|
||||
&self.0[line].highlights
|
||||
}
|
||||
|
||||
pub(crate) fn line_range(&self, line: usize) -> &std::ops::Range<usize> {
|
||||
&self.0[line].line_range
|
||||
}
|
||||
|
||||
pub(crate) fn extended_with_highlights(
|
||||
mut self,
|
||||
highlights: impl Iterator<Item = HighlightedRange>,
|
||||
) -> Self {
|
||||
let total_line_count = self.0.len();
|
||||
let mut current_line: usize = 0;
|
||||
|
||||
let mut new_highlights: Vec<HighlightedRange> = Vec::new();
|
||||
for (range, highlight) in highlights {
|
||||
// move to next line until the highlight range start is within bounds of a line (ie range start is before line range end)
|
||||
while current_line < total_line_count
|
||||
&& range.start >= self.line_range(current_line).end
|
||||
{
|
||||
// moving on to new line, flush new highlights and combine into the line highlights
|
||||
if !new_highlights.is_empty() {
|
||||
self.0[current_line].highlights = gpui::combine_highlights(
|
||||
self.0[current_line].highlights.drain(..).into_iter(),
|
||||
new_highlights.drain(..).into_iter(),
|
||||
)
|
||||
.collect();
|
||||
}
|
||||
current_line += 1;
|
||||
}
|
||||
|
||||
let mut line = current_line;
|
||||
while line < total_line_count && range.end > self.line_range(line).start {
|
||||
let highlight_start = range.start.max(self.line_range(line).start);
|
||||
let highlight_end = range.end.min(self.line_range(line).end);
|
||||
new_highlights.push((
|
||||
(highlight_start - self.line_range(line).start)
|
||||
..(highlight_end - self.line_range(line).start),
|
||||
highlight.clone(),
|
||||
));
|
||||
line += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !new_highlights.is_empty() {
|
||||
self.0[current_line].highlights = gpui::combine_highlights(
|
||||
self.0[current_line].highlights.drain(..).into_iter(),
|
||||
new_highlights.drain(..).into_iter(),
|
||||
)
|
||||
.collect();
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn line_index_of_range(&self, range: &std::ops::Range<usize>) -> Option<usize> {
|
||||
self.0
|
||||
.binary_search_by(|line| {
|
||||
let line_start = line.line_range.start;
|
||||
let line_end = line.line_range.end;
|
||||
if (range.start <= line_start && range.end >= line_end)
|
||||
|| (line_start <= range.start && line_end >= range.end)
|
||||
{
|
||||
std::cmp::Ordering::Equal
|
||||
} else if range.start < line_start && range.end < line_end {
|
||||
std::cmp::Ordering::Greater
|
||||
} else {
|
||||
std::cmp::Ordering::Less
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub(crate) fn replace_highlight_range(&mut self, highlighted_range: &HighlightedRange) {
|
||||
let (range, _) = &highlighted_range;
|
||||
|
||||
println!("finding range to replace {:?}", range);
|
||||
|
||||
let line = self
|
||||
.line_index_of_range(range)
|
||||
.map(|line_i| &mut self.0[line_i]);
|
||||
|
||||
let replace_idx = line.as_ref().and_then(|l| {
|
||||
let local_range = (range.start - l.line_range.start)..(range.end - l.line_range.start);
|
||||
(l.highlights.iter().position(|(r, _)| *r == local_range)).map(|i| (i, local_range))
|
||||
});
|
||||
|
||||
println!("line {:?} replace_idx {:?}", line, replace_idx);
|
||||
|
||||
match (line, replace_idx) {
|
||||
| (Some(line), Some((replace_idx, local_range))) => {
|
||||
line.highlights[replace_idx] = (local_range, highlighted_range.1)
|
||||
}
|
||||
| _ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user