Merge #7212
7212: Simplify highlighting r=matklad a=matklad Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
056cabf25d
@ -1,3 +1,6 @@
|
||||
mod highlights;
|
||||
mod injector;
|
||||
|
||||
mod format;
|
||||
mod html;
|
||||
mod injection;
|
||||
@ -69,9 +72,7 @@ pub(crate) fn highlight(
|
||||
};
|
||||
|
||||
let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
|
||||
// We use a stack for the DFS traversal below.
|
||||
// When we leave a node, the we use it to flatten the highlighted ranges.
|
||||
let mut stack = HighlightedRangeStack::new();
|
||||
let mut stack = highlights::Highlights::new(range_to_highlight);
|
||||
|
||||
let mut current_macro_call: Option<ast::MacroCall> = None;
|
||||
let mut current_macro_rules: Option<ast::MacroRules> = None;
|
||||
@ -82,14 +83,8 @@ pub(crate) fn highlight(
|
||||
// Walk all nodes, keeping track of whether we are inside a macro or not.
|
||||
// If in macro, expand it first and highlight the expanded code.
|
||||
for event in root.preorder_with_tokens() {
|
||||
match &event {
|
||||
WalkEvent::Enter(_) => stack.push(),
|
||||
WalkEvent::Leave(_) => stack.pop(),
|
||||
};
|
||||
|
||||
let event_range = match &event {
|
||||
WalkEvent::Enter(it) => it.text_range(),
|
||||
WalkEvent::Leave(it) => it.text_range(),
|
||||
WalkEvent::Enter(it) | WalkEvent::Leave(it) => it.text_range(),
|
||||
};
|
||||
|
||||
// Element outside of the viewport, no need to highlight
|
||||
@ -138,15 +133,8 @@ pub(crate) fn highlight(
|
||||
if ast::Attr::can_cast(node.kind()) {
|
||||
inside_attribute = false
|
||||
}
|
||||
if let Some((doctest, range_mapping, new_comments)) =
|
||||
injection::extract_doc_comments(node)
|
||||
{
|
||||
injection::highlight_doc_comment(
|
||||
doctest,
|
||||
range_mapping,
|
||||
new_comments,
|
||||
&mut stack,
|
||||
);
|
||||
if let Some((new_comments, inj)) = injection::extract_doc_comments(node) {
|
||||
injection::highlight_doc_comment(new_comments, inj, &mut stack);
|
||||
}
|
||||
}
|
||||
WalkEvent::Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
|
||||
@ -217,7 +205,6 @@ pub(crate) fn highlight(
|
||||
format_string_highlighter.highlight_format_string(&mut stack, &string, range);
|
||||
// Highlight escape sequences
|
||||
if let Some(char_ranges) = string.char_ranges() {
|
||||
stack.push();
|
||||
for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) {
|
||||
if string.text()[piece_range.start().into()..].starts_with('\\') {
|
||||
stack.add(HighlightedRange {
|
||||
@ -227,177 +214,12 @@ pub(crate) fn highlight(
|
||||
});
|
||||
}
|
||||
}
|
||||
stack.pop_and_inject(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stack.flattened()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HighlightedRangeStack {
|
||||
stack: Vec<Vec<HighlightedRange>>,
|
||||
}
|
||||
|
||||
/// We use a stack to implement the flattening logic for the highlighted
|
||||
/// syntax ranges.
|
||||
impl HighlightedRangeStack {
|
||||
fn new() -> Self {
|
||||
Self { stack: vec![Vec::new()] }
|
||||
}
|
||||
|
||||
fn push(&mut self) {
|
||||
self.stack.push(Vec::new());
|
||||
}
|
||||
|
||||
/// Flattens the highlighted ranges.
|
||||
///
|
||||
/// For example `#[cfg(feature = "foo")]` contains the nested ranges:
|
||||
/// 1) parent-range: Attribute [0, 23)
|
||||
/// 2) child-range: String [16, 21)
|
||||
///
|
||||
/// The following code implements the flattening, for our example this results to:
|
||||
/// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
|
||||
fn pop(&mut self) {
|
||||
let children = self.stack.pop().unwrap();
|
||||
let prev = self.stack.last_mut().unwrap();
|
||||
let needs_flattening = !children.is_empty()
|
||||
&& !prev.is_empty()
|
||||
&& prev.last().unwrap().range.contains_range(children.first().unwrap().range);
|
||||
if !needs_flattening {
|
||||
prev.extend(children);
|
||||
} else {
|
||||
let mut parent = prev.pop().unwrap();
|
||||
for ele in children {
|
||||
assert!(parent.range.contains_range(ele.range));
|
||||
|
||||
let cloned = Self::intersect(&mut parent, &ele);
|
||||
if !parent.range.is_empty() {
|
||||
prev.push(parent);
|
||||
}
|
||||
prev.push(ele);
|
||||
parent = cloned;
|
||||
}
|
||||
if !parent.range.is_empty() {
|
||||
prev.push(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Intersects the `HighlightedRange` `parent` with `child`.
|
||||
/// `parent` is mutated in place, becoming the range before `child`.
|
||||
/// Returns the range (of the same type as `parent`) *after* `child`.
|
||||
fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> HighlightedRange {
|
||||
assert!(parent.range.contains_range(child.range));
|
||||
|
||||
let mut cloned = parent.clone();
|
||||
parent.range = TextRange::new(parent.range.start(), child.range.start());
|
||||
cloned.range = TextRange::new(child.range.end(), cloned.range.end());
|
||||
|
||||
cloned
|
||||
}
|
||||
|
||||
/// Remove the `HighlightRange` of `parent` that's currently covered by `child`.
|
||||
fn intersect_partial(parent: &mut HighlightedRange, child: &HighlightedRange) {
|
||||
assert!(
|
||||
parent.range.start() <= child.range.start()
|
||||
&& parent.range.end() >= child.range.start()
|
||||
&& child.range.end() > parent.range.end()
|
||||
);
|
||||
|
||||
parent.range = TextRange::new(parent.range.start(), child.range.start());
|
||||
}
|
||||
|
||||
/// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`)
|
||||
/// can only modify the last range currently on the stack.
|
||||
/// Can be used to do injections that span multiple ranges, like the
|
||||
/// doctest injection below.
|
||||
/// If `overwrite_parent` is non-optional, the highlighting of the parent range
|
||||
/// is overwritten with the argument.
|
||||
///
|
||||
/// Note that `pop` can be simulated by `pop_and_inject(false)` but the
|
||||
/// latter is computationally more expensive.
|
||||
fn pop_and_inject(&mut self, overwrite_parent: Option<Highlight>) {
|
||||
let mut children = self.stack.pop().unwrap();
|
||||
let prev = self.stack.last_mut().unwrap();
|
||||
children.sort_by_key(|range| range.range.start());
|
||||
prev.sort_by_key(|range| range.range.start());
|
||||
|
||||
for child in children {
|
||||
if let Some(idx) =
|
||||
prev.iter().position(|parent| parent.range.contains_range(child.range))
|
||||
{
|
||||
if let Some(tag) = overwrite_parent {
|
||||
prev[idx].highlight = tag;
|
||||
}
|
||||
|
||||
let cloned = Self::intersect(&mut prev[idx], &child);
|
||||
let insert_idx = if prev[idx].range.is_empty() {
|
||||
prev.remove(idx);
|
||||
idx
|
||||
} else {
|
||||
idx + 1
|
||||
};
|
||||
prev.insert(insert_idx, child);
|
||||
if !cloned.range.is_empty() {
|
||||
prev.insert(insert_idx + 1, cloned);
|
||||
}
|
||||
} else {
|
||||
let maybe_idx =
|
||||
prev.iter().position(|parent| parent.range.contains(child.range.start()));
|
||||
match (overwrite_parent, maybe_idx) {
|
||||
(Some(_), Some(idx)) => {
|
||||
Self::intersect_partial(&mut prev[idx], &child);
|
||||
let insert_idx = if prev[idx].range.is_empty() {
|
||||
prev.remove(idx);
|
||||
idx
|
||||
} else {
|
||||
idx + 1
|
||||
};
|
||||
prev.insert(insert_idx, child);
|
||||
}
|
||||
(_, None) => {
|
||||
let idx = prev
|
||||
.binary_search_by_key(&child.range.start(), |range| range.range.start())
|
||||
.unwrap_or_else(|x| x);
|
||||
prev.insert(idx, child);
|
||||
}
|
||||
_ => {
|
||||
unreachable!("child range should be completely contained in parent range");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add(&mut self, range: HighlightedRange) {
|
||||
self.stack
|
||||
.last_mut()
|
||||
.expect("during DFS traversal, the stack must not be empty")
|
||||
.push(range)
|
||||
}
|
||||
|
||||
fn flattened(mut self) -> Vec<HighlightedRange> {
|
||||
assert_eq!(
|
||||
self.stack.len(),
|
||||
1,
|
||||
"after DFS traversal, the stack should only contain a single element"
|
||||
);
|
||||
let mut res = self.stack.pop().unwrap();
|
||||
res.sort_by_key(|range| range.range.start());
|
||||
// Check that ranges are sorted and disjoint
|
||||
for (left, right) in res.iter().zip(res.iter().skip(1)) {
|
||||
assert!(
|
||||
left.range.end() <= right.range.start(),
|
||||
"left: {:#?}, right: {:#?}",
|
||||
left,
|
||||
right
|
||||
);
|
||||
}
|
||||
res
|
||||
}
|
||||
stack.to_vec()
|
||||
}
|
||||
|
||||
fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
|
||||
|
@ -4,9 +4,9 @@
|
||||
AstNode, AstToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
syntax_highlighting::HighlightedRangeStack, HighlightTag, HighlightedRange, SymbolKind,
|
||||
};
|
||||
use crate::{HighlightTag, HighlightedRange, SymbolKind};
|
||||
|
||||
use super::highlights::Highlights;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct FormatStringHighlighter {
|
||||
@ -39,22 +39,20 @@ pub(super) fn check_for_format_string(&mut self, parent: &SyntaxNode) {
|
||||
}
|
||||
pub(super) fn highlight_format_string(
|
||||
&self,
|
||||
range_stack: &mut HighlightedRangeStack,
|
||||
stack: &mut Highlights,
|
||||
string: &impl HasFormatSpecifier,
|
||||
range: TextRange,
|
||||
) {
|
||||
if self.format_string.as_ref() == Some(&SyntaxElement::from(string.syntax().clone())) {
|
||||
range_stack.push();
|
||||
string.lex_format_specifier(|piece_range, kind| {
|
||||
if let Some(highlight) = highlight_format_specifier(kind) {
|
||||
range_stack.add(HighlightedRange {
|
||||
stack.add(HighlightedRange {
|
||||
range: piece_range + range.start(),
|
||||
highlight: highlight.into(),
|
||||
binding_hash: None,
|
||||
});
|
||||
}
|
||||
});
|
||||
range_stack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
109
crates/ide/src/syntax_highlighting/highlights.rs
Normal file
109
crates/ide/src/syntax_highlighting/highlights.rs
Normal file
@ -0,0 +1,109 @@
|
||||
//! Collects a tree of highlighted ranges and flattens it.
|
||||
use std::{cmp::Ordering, iter};
|
||||
|
||||
use stdx::equal_range_by;
|
||||
use syntax::TextRange;
|
||||
|
||||
use crate::{HighlightTag, HighlightedRange};
|
||||
|
||||
pub(super) struct Highlights {
|
||||
root: Node,
|
||||
}
|
||||
|
||||
struct Node {
|
||||
highlighted_range: HighlightedRange,
|
||||
nested: Vec<Node>,
|
||||
}
|
||||
|
||||
impl Highlights {
|
||||
pub(super) fn new(range: TextRange) -> Highlights {
|
||||
Highlights {
|
||||
root: Node::new(HighlightedRange {
|
||||
range,
|
||||
highlight: HighlightTag::Dummy.into(),
|
||||
binding_hash: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add(&mut self, highlighted_range: HighlightedRange) {
|
||||
self.root.add(highlighted_range);
|
||||
}
|
||||
|
||||
pub(super) fn to_vec(self) -> Vec<HighlightedRange> {
|
||||
let mut res = Vec::new();
|
||||
self.root.flatten(&mut res);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
fn new(highlighted_range: HighlightedRange) -> Node {
|
||||
Node { highlighted_range, nested: Vec::new() }
|
||||
}
|
||||
|
||||
fn add(&mut self, highlighted_range: HighlightedRange) {
|
||||
assert!(self.highlighted_range.range.contains_range(highlighted_range.range));
|
||||
|
||||
// Fast path
|
||||
if let Some(last) = self.nested.last_mut() {
|
||||
if last.highlighted_range.range.contains_range(highlighted_range.range) {
|
||||
return last.add(highlighted_range);
|
||||
}
|
||||
if last.highlighted_range.range.end() <= highlighted_range.range.start() {
|
||||
return self.nested.push(Node::new(highlighted_range));
|
||||
}
|
||||
}
|
||||
|
||||
let (start, len) = equal_range_by(&self.nested, |n| {
|
||||
ordering(n.highlighted_range.range, highlighted_range.range)
|
||||
});
|
||||
|
||||
if len == 1
|
||||
&& self.nested[start].highlighted_range.range.contains_range(highlighted_range.range)
|
||||
{
|
||||
return self.nested[start].add(highlighted_range);
|
||||
}
|
||||
|
||||
let nested = self
|
||||
.nested
|
||||
.splice(start..start + len, iter::once(Node::new(highlighted_range)))
|
||||
.collect::<Vec<_>>();
|
||||
self.nested[start].nested = nested;
|
||||
}
|
||||
|
||||
fn flatten(&self, acc: &mut Vec<HighlightedRange>) {
|
||||
let mut start = self.highlighted_range.range.start();
|
||||
let mut nested = self.nested.iter();
|
||||
loop {
|
||||
let next = nested.next();
|
||||
let end = next.map_or(self.highlighted_range.range.end(), |it| {
|
||||
it.highlighted_range.range.start()
|
||||
});
|
||||
if start < end {
|
||||
acc.push(HighlightedRange {
|
||||
range: TextRange::new(start, end),
|
||||
highlight: self.highlighted_range.highlight,
|
||||
binding_hash: self.highlighted_range.binding_hash,
|
||||
});
|
||||
}
|
||||
start = match next {
|
||||
Some(child) => {
|
||||
child.flatten(acc);
|
||||
child.highlighted_range.range.end()
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn ordering(r1: TextRange, r2: TextRange) -> Ordering {
|
||||
if r1.end() <= r2.start() {
|
||||
Ordering::Less
|
||||
} else if r2.end() <= r1.start() {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Equal
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
use ide_db::base_db::SourceDatabase;
|
||||
use oorandom::Rand32;
|
||||
use stdx::format_to;
|
||||
use syntax::{AstNode, TextRange, TextSize};
|
||||
use syntax::AstNode;
|
||||
|
||||
use crate::{syntax_highlighting::highlight, FileId, RootDatabase};
|
||||
|
||||
@ -22,17 +22,15 @@ fn rainbowify(seed: u64) -> String {
|
||||
|
||||
let ranges = highlight(db, file_id, None, false);
|
||||
let text = parse.tree().syntax().to_string();
|
||||
let mut prev_pos = TextSize::from(0);
|
||||
let mut buf = String::new();
|
||||
buf.push_str(&STYLE);
|
||||
buf.push_str("<pre><code>");
|
||||
for range in &ranges {
|
||||
if range.range.start() > prev_pos {
|
||||
let curr = &text[TextRange::new(prev_pos, range.range.start())];
|
||||
let text = html_escape(curr);
|
||||
buf.push_str(&text);
|
||||
let curr = &text[range.range];
|
||||
if range.highlight.is_empty() {
|
||||
format_to!(buf, "{}", html_escape(curr));
|
||||
continue;
|
||||
}
|
||||
let curr = &text[TextRange::new(range.range.start(), range.range.end())];
|
||||
|
||||
let class = range.highlight.to_string().replace('.', " ");
|
||||
let color = match (rainbow, range.binding_hash) {
|
||||
@ -42,13 +40,7 @@ fn rainbowify(seed: u64) -> String {
|
||||
_ => "".into(),
|
||||
};
|
||||
format_to!(buf, "<span class=\"{}\"{}>{}</span>", class, color, html_escape(curr));
|
||||
|
||||
prev_pos = range.range.end();
|
||||
}
|
||||
// Add the remaining (non-highlighted) text
|
||||
let curr = &text[TextRange::new(prev_pos, TextSize::of(&text))];
|
||||
let text = html_escape(curr);
|
||||
buf.push_str(&text);
|
||||
buf.push_str("</code></pre>");
|
||||
buf
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
//! Syntax highlighting injections such as highlighting of documentation tests.
|
||||
|
||||
use std::{collections::BTreeMap, convert::TryFrom};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use hir::Semantics;
|
||||
use ide_db::call_info::ActiveParameter;
|
||||
use itertools::Itertools;
|
||||
use syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize};
|
||||
|
||||
use crate::{Analysis, Highlight, HighlightModifier, HighlightTag, HighlightedRange, RootDatabase};
|
||||
use crate::{Analysis, HighlightModifier, HighlightTag, HighlightedRange, RootDatabase};
|
||||
|
||||
use super::HighlightedRangeStack;
|
||||
use super::{highlights::Highlights, injector::Injector};
|
||||
|
||||
pub(super) fn highlight_injection(
|
||||
acc: &mut HighlightedRangeStack,
|
||||
acc: &mut Highlights,
|
||||
sema: &Semantics<RootDatabase>,
|
||||
literal: ast::String,
|
||||
expanded: SyntaxToken,
|
||||
@ -98,9 +98,6 @@ fn map_offset_up(&self, mut offset: TextSize, start: bool) -> TextSize {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mapping from extracted documentation code to original code
|
||||
type RangesMap = BTreeMap<TextSize, TextSize>;
|
||||
|
||||
const RUSTDOC_FENCE: &'static str = "```";
|
||||
const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[
|
||||
"",
|
||||
@ -119,20 +116,20 @@ fn map_offset_up(&self, mut offset: TextSize, start: bool) -> TextSize {
|
||||
/// Lastly, a vector of new comment highlight ranges (spanning only the
|
||||
/// comment prefix) is returned which is used in the syntax highlighting
|
||||
/// injection to replace the previous (line-spanning) comment ranges.
|
||||
pub(super) fn extract_doc_comments(
|
||||
node: &SyntaxNode,
|
||||
) -> Option<(String, RangesMap, Vec<HighlightedRange>)> {
|
||||
pub(super) fn extract_doc_comments(node: &SyntaxNode) -> Option<(Vec<HighlightedRange>, Injector)> {
|
||||
let mut inj = Injector::default();
|
||||
// wrap the doctest into function body to get correct syntax highlighting
|
||||
let prefix = "fn doctest() {\n";
|
||||
let suffix = "}\n";
|
||||
// Mapping from extracted documentation code to original code
|
||||
let mut range_mapping: RangesMap = BTreeMap::new();
|
||||
let mut line_start = TextSize::try_from(prefix.len()).unwrap();
|
||||
|
||||
let mut line_start = TextSize::of(prefix);
|
||||
let mut is_codeblock = false;
|
||||
let mut is_doctest = false;
|
||||
// Replace the original, line-spanning comment ranges by new, only comment-prefix
|
||||
// spanning comment ranges.
|
||||
let mut new_comments = Vec::new();
|
||||
|
||||
inj.add_unmapped(prefix);
|
||||
let doctest = node
|
||||
.children_with_tokens()
|
||||
.filter_map(|el| el.into_token().and_then(ast::Comment::cast))
|
||||
@ -169,7 +166,6 @@ pub(super) fn extract_doc_comments(
|
||||
pos
|
||||
};
|
||||
|
||||
range_mapping.insert(line_start, range.start() + TextSize::try_from(pos).unwrap());
|
||||
new_comments.push(HighlightedRange {
|
||||
range: TextRange::new(
|
||||
range.start(),
|
||||
@ -179,62 +175,43 @@ pub(super) fn extract_doc_comments(
|
||||
binding_hash: None,
|
||||
});
|
||||
line_start += range.len() - TextSize::try_from(pos).unwrap();
|
||||
line_start += TextSize::try_from('\n'.len_utf8()).unwrap();
|
||||
line_start += TextSize::of("\n");
|
||||
|
||||
inj.add(
|
||||
&line[pos..],
|
||||
TextRange::new(range.start() + TextSize::try_from(pos).unwrap(), range.end()),
|
||||
);
|
||||
inj.add_unmapped("\n");
|
||||
line[pos..].to_owned()
|
||||
})
|
||||
.join("\n");
|
||||
inj.add_unmapped(suffix);
|
||||
|
||||
if doctest.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let doctest = format!("{}{}{}", prefix, doctest, suffix);
|
||||
Some((doctest, range_mapping, new_comments))
|
||||
Some((new_comments, inj))
|
||||
}
|
||||
|
||||
/// Injection of syntax highlighting of doctests.
|
||||
pub(super) fn highlight_doc_comment(
|
||||
text: String,
|
||||
range_mapping: RangesMap,
|
||||
new_comments: Vec<HighlightedRange>,
|
||||
stack: &mut HighlightedRangeStack,
|
||||
inj: Injector,
|
||||
stack: &mut Highlights,
|
||||
) {
|
||||
let (analysis, tmp_file_id) = Analysis::from_single_file(text);
|
||||
|
||||
stack.push();
|
||||
for mut h in analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap() {
|
||||
// Determine start offset and end offset in case of multi-line ranges
|
||||
let mut start_offset = None;
|
||||
let mut end_offset = None;
|
||||
for (line_start, orig_line_start) in range_mapping.range(..h.range.end()).rev() {
|
||||
// It's possible for orig_line_start - line_start to be negative. Add h.range.start()
|
||||
// here and remove it from the end range after the loop below so that the values are
|
||||
// always non-negative.
|
||||
let offset = h.range.start() + orig_line_start - line_start;
|
||||
if line_start <= &h.range.start() {
|
||||
start_offset.get_or_insert(offset);
|
||||
break;
|
||||
} else {
|
||||
end_offset.get_or_insert(offset);
|
||||
}
|
||||
}
|
||||
if let Some(start_offset) = start_offset {
|
||||
h.range = TextRange::new(
|
||||
start_offset,
|
||||
h.range.end() + end_offset.unwrap_or(start_offset) - h.range.start(),
|
||||
);
|
||||
|
||||
h.highlight |= HighlightModifier::Injected;
|
||||
stack.add(h);
|
||||
}
|
||||
}
|
||||
|
||||
// Inject the comment prefix highlight ranges
|
||||
stack.push();
|
||||
let (analysis, tmp_file_id) = Analysis::from_single_file(inj.text().to_string());
|
||||
for comment in new_comments {
|
||||
stack.add(comment);
|
||||
}
|
||||
stack.pop_and_inject(None);
|
||||
stack.pop_and_inject(Some(Highlight::from(HighlightTag::Dummy) | HighlightModifier::Injected));
|
||||
|
||||
for h in analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap() {
|
||||
for r in inj.map_range_up(h.range) {
|
||||
stack.add(HighlightedRange {
|
||||
range: r,
|
||||
highlight: h.highlight | HighlightModifier::Injected,
|
||||
binding_hash: h.binding_hash,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
83
crates/ide/src/syntax_highlighting/injector.rs
Normal file
83
crates/ide/src/syntax_highlighting/injector.rs
Normal file
@ -0,0 +1,83 @@
|
||||
//! Extracts a subsequence of a text document, remembering the mapping of ranges
|
||||
//! between original and extracted texts.
|
||||
use std::ops::{self, Sub};
|
||||
|
||||
use stdx::equal_range_by;
|
||||
use syntax::{TextRange, TextSize};
|
||||
|
||||
use super::highlights::ordering;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct Injector {
|
||||
buf: String,
|
||||
ranges: Vec<(TextRange, Option<Delta<TextSize>>)>,
|
||||
}
|
||||
|
||||
impl Injector {
|
||||
pub(super) fn add(&mut self, text: &str, source_range: TextRange) {
|
||||
let len = TextSize::of(text);
|
||||
assert_eq!(len, source_range.len());
|
||||
|
||||
let target_range = TextRange::at(TextSize::of(&self.buf), len);
|
||||
self.ranges
|
||||
.push((target_range, Some(Delta::new(target_range.start(), source_range.start()))));
|
||||
self.buf.push_str(text);
|
||||
}
|
||||
pub(super) fn add_unmapped(&mut self, text: &str) {
|
||||
let len = TextSize::of(text);
|
||||
|
||||
let target_range = TextRange::at(TextSize::of(&self.buf), len);
|
||||
self.ranges.push((target_range, None));
|
||||
self.buf.push_str(text);
|
||||
}
|
||||
|
||||
pub(super) fn text(&self) -> &str {
|
||||
&self.buf
|
||||
}
|
||||
pub(super) fn map_range_up(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ {
|
||||
let (start, len) = equal_range_by(&self.ranges, |&(r, _)| ordering(r, range));
|
||||
(start..start + len).filter_map(move |i| {
|
||||
let (target_range, delta) = self.ranges[i];
|
||||
let intersection = target_range.intersect(range).unwrap();
|
||||
Some(intersection + delta?)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Delta<T> {
|
||||
Add(T),
|
||||
Sub(T),
|
||||
}
|
||||
|
||||
impl<T> Delta<T> {
|
||||
fn new(from: T, to: T) -> Delta<T>
|
||||
where
|
||||
T: Ord + Sub<Output = T>,
|
||||
{
|
||||
if to >= from {
|
||||
Delta::Add(to - from)
|
||||
} else {
|
||||
Delta::Sub(from - to)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add<Delta<TextSize>> for TextSize {
|
||||
type Output = TextSize;
|
||||
|
||||
fn add(self, rhs: Delta<TextSize>) -> TextSize {
|
||||
match rhs {
|
||||
Delta::Add(it) => self + it,
|
||||
Delta::Sub(it) => self - it,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add<Delta<TextSize>> for TextRange {
|
||||
type Output = TextRange;
|
||||
|
||||
fn add(self, rhs: Delta<TextSize>) -> TextRange {
|
||||
TextRange::at(self.start() + rhs, self.len())
|
||||
}
|
||||
}
|
@ -94,13 +94,13 @@ fn as_str(self) -> &'static str {
|
||||
HighlightTag::Comment => "comment",
|
||||
HighlightTag::EscapeSequence => "escape_sequence",
|
||||
HighlightTag::FormatSpecifier => "format_specifier",
|
||||
HighlightTag::Dummy => "dummy",
|
||||
HighlightTag::Keyword => "keyword",
|
||||
HighlightTag::Punctuation => "punctuation",
|
||||
HighlightTag::NumericLiteral => "numeric_literal",
|
||||
HighlightTag::Operator => "operator",
|
||||
HighlightTag::StringLiteral => "string_literal",
|
||||
HighlightTag::UnresolvedReference => "unresolved_reference",
|
||||
HighlightTag::Dummy => "dummy",
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -173,6 +173,9 @@ impl Highlight {
|
||||
pub(crate) fn new(tag: HighlightTag) -> Highlight {
|
||||
Highlight { tag, modifiers: HighlightModifiers::default() }
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.tag == HighlightTag::Dummy && self.modifiers == HighlightModifiers::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::BitOr<HighlightModifier> for HighlightTag {
|
||||
|
@ -37,13 +37,18 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
||||
.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
|
||||
</style>
|
||||
<pre><code><span class="comment documentation">/// ```</span>
|
||||
<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="punctuation injected">_</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="string_literal injected">"early doctests should not go boom"</span><span class="punctuation injected">;</span><span class="punctuation injected">
|
||||
</span><span class="comment documentation">/// ```</span>
|
||||
<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="punctuation injected">_</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="string_literal injected">"early doctests should not go boom"</span><span class="punctuation injected">;</span>
|
||||
<span class="comment documentation">/// ```</span>
|
||||
<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="punctuation">{</span>
|
||||
<span class="field declaration">bar</span><span class="punctuation">:</span> <span class="builtin_type">bool</span><span class="punctuation">,</span>
|
||||
<span class="punctuation">}</span>
|
||||
|
||||
<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
|
||||
<span class="comment documentation">/// ```</span>
|
||||
<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="punctuation injected">_</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="string_literal injected">"Call me</span>
|
||||
<span class="comment">// KILLER WHALE</span>
|
||||
<span class="comment documentation">/// </span><span class="string_literal injected"> Ishmael."</span><span class="punctuation injected">;</span>
|
||||
<span class="comment documentation">/// ```</span>
|
||||
<span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant declaration associated">bar</span><span class="punctuation">:</span> <span class="builtin_type">bool</span> <span class="operator">=</span> <span class="bool_literal">true</span><span class="punctuation">;</span>
|
||||
|
||||
<span class="comment documentation">/// Constructs a new `Foo`.</span>
|
||||
@ -52,8 +57,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
||||
<span class="comment documentation">///</span>
|
||||
<span class="comment documentation">/// ```</span>
|
||||
<span class="comment documentation">/// #</span><span class="dummy injected"> </span><span class="attribute attribute injected">#</span><span class="attribute attribute injected">!</span><span class="attribute attribute injected">[</span><span class="function attribute injected">allow</span><span class="punctuation attribute injected">(</span><span class="attribute attribute injected">unused_mut</span><span class="punctuation attribute injected">)</span><span class="attribute attribute injected">]</span>
|
||||
<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="keyword injected">mut</span><span class="dummy injected"> </span><span class="variable declaration injected mutable">foo</span><span class="punctuation injected">:</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected">
|
||||
</span> <span class="comment documentation">/// ```</span>
|
||||
<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="keyword injected">mut</span><span class="dummy injected"> </span><span class="variable declaration injected mutable">foo</span><span class="punctuation injected">:</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span>
|
||||
<span class="comment documentation">/// ```</span>
|
||||
<span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration static associated">new</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-></span> <span class="struct">Foo</span> <span class="punctuation">{</span>
|
||||
<span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">bar</span><span class="punctuation">:</span> <span class="bool_literal">true</span> <span class="punctuation">}</span>
|
||||
<span class="punctuation">}</span>
|
||||
@ -72,18 +77,18 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
||||
<span class="comment documentation">///</span>
|
||||
<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">bar</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="variable injected">foo</span><span class="operator injected">.</span><span class="field injected">bar</span><span class="dummy injected"> </span><span class="operator injected">||</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="constant injected">bar</span><span class="punctuation injected">;</span>
|
||||
<span class="comment documentation">///</span>
|
||||
<span class="comment documentation">/// </span><span class="comment injected">/* multi-line
|
||||
</span><span class="comment documentation">/// </span><span class="comment injected"> comment */</span>
|
||||
<span class="comment documentation">/// </span><span class="comment injected">/* multi-line</span>
|
||||
<span class="comment documentation">/// </span><span class="comment injected"> comment */</span>
|
||||
<span class="comment documentation">///</span>
|
||||
<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">multi_line_string</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="string_literal injected">"Foo
|
||||
</span><span class="comment documentation">/// </span><span class="string_literal injected"> bar
|
||||
</span><span class="comment documentation">/// </span><span class="string_literal injected"> "</span><span class="punctuation injected">;</span>
|
||||
<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">multi_line_string</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="string_literal injected">"Foo</span>
|
||||
<span class="comment documentation">/// </span><span class="string_literal injected"> bar</span>
|
||||
<span class="comment documentation">/// </span><span class="string_literal injected"> "</span><span class="punctuation injected">;</span>
|
||||
<span class="comment documentation">///</span>
|
||||
<span class="comment documentation">/// ```</span>
|
||||
<span class="comment documentation">///</span>
|
||||
<span class="comment documentation">/// ```rust,no_run</span>
|
||||
<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">foobar</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="operator injected">.</span><span class="function injected">bar</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected">
|
||||
</span> <span class="comment documentation">/// ```</span>
|
||||
<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">foobar</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="operator injected">.</span><span class="function injected">bar</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span>
|
||||
<span class="comment documentation">/// ```</span>
|
||||
<span class="comment documentation">///</span>
|
||||
<span class="comment documentation">/// ```sh</span>
|
||||
<span class="comment documentation">/// echo 1</span>
|
||||
@ -94,8 +99,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
||||
<span class="punctuation">}</span>
|
||||
|
||||
<span class="comment documentation">/// ```</span>
|
||||
<span class="comment documentation">/// </span><span class="macro injected">noop!</span><span class="punctuation injected">(</span><span class="numeric_literal injected">1</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected">
|
||||
</span><span class="comment documentation">/// ```</span>
|
||||
<span class="comment documentation">/// </span><span class="macro injected">noop!</span><span class="punctuation injected">(</span><span class="numeric_literal injected">1</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span>
|
||||
<span class="comment documentation">/// ```</span>
|
||||
<span class="keyword">macro_rules</span><span class="punctuation">!</span> <span class="macro declaration">noop</span> <span class="punctuation">{</span>
|
||||
<span class="punctuation">(</span><span class="punctuation">$</span>expr<span class="punctuation">:</span>expr<span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">></span> <span class="punctuation">{</span>
|
||||
<span class="punctuation">$</span>expr
|
||||
|
@ -446,6 +446,11 @@ struct Foo {
|
||||
}
|
||||
|
||||
impl Foo {
|
||||
/// ```
|
||||
/// let _ = "Call me
|
||||
// KILLER WHALE
|
||||
/// Ishmael.";
|
||||
/// ```
|
||||
pub const bar: bool = true;
|
||||
|
||||
/// Constructs a new `Foo`.
|
||||
|
@ -343,6 +343,9 @@ pub(crate) fn semantic_tokens(
|
||||
let mut builder = semantic_tokens::SemanticTokensBuilder::new(id);
|
||||
|
||||
for highlight_range in highlights {
|
||||
if highlight_range.highlight.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let (type_, mods) = semantic_token_type_and_modifiers(highlight_range.highlight);
|
||||
let token_index = semantic_tokens::type_index(type_);
|
||||
let modifier_bitset = mods.0;
|
||||
|
@ -1,5 +1,5 @@
|
||||
//! Missing batteries for standard libraries.
|
||||
use std::{ops, process, time::Instant};
|
||||
use std::{cmp::Ordering, ops, process, time::Instant};
|
||||
|
||||
mod macros;
|
||||
pub mod panic_context;
|
||||
@ -117,7 +117,12 @@ fn next(&mut self) -> Option<&'a str> {
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/rust-lang/rust/issues/73831
|
||||
/// Returns `idx` such that:
|
||||
///
|
||||
/// ∀ x in slice[..idx]: pred(x)
|
||||
/// && ∀ x in slice[idx..]: !pred(x)
|
||||
///
|
||||
/// https://github.com/rust-lang/rust/issues/73831
|
||||
pub fn partition_point<T, P>(slice: &[T], mut pred: P) -> usize
|
||||
where
|
||||
P: FnMut(&T) -> bool,
|
||||
@ -147,6 +152,15 @@ pub fn partition_point<T, P>(slice: &[T], mut pred: P) -> usize
|
||||
left
|
||||
}
|
||||
|
||||
pub fn equal_range_by<T, F>(slice: &[T], mut key: F) -> (usize, usize)
|
||||
where
|
||||
F: FnMut(&T) -> Ordering,
|
||||
{
|
||||
let start = partition_point(slice, |it| key(it) == Ordering::Less);
|
||||
let len = partition_point(&slice[start..], |it| key(it) == Ordering::Equal);
|
||||
(start, len)
|
||||
}
|
||||
|
||||
pub struct JodChild(pub process::Child);
|
||||
|
||||
impl ops::Deref for JodChild {
|
||||
|
Loading…
Reference in New Issue
Block a user