4876: Syntactic highlighting of NAME_REF for injections r=matklad a=ltentrup

This commit adds a function that tries to determine the syntax highlighting class of NAME_REFs based on the usage.
It is used for highlighting injections (such as highlighting of doctests) as the semantic logic will most of the time result in unresolved references.
It also adds a color to unresolved references in HTML encoding.

Follow up of #4683.
Fixes #4809.

Co-authored-by: Leander Tentrup <leander.tentrup@gmail.com>
This commit is contained in:
bors[bot] 2020-06-16 11:40:51 +00:00 committed by GitHub
commit 4342b71715
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 102 additions and 16 deletions

View file

@ -440,12 +440,14 @@ impl Analysis {
/// Computes syntax highlighting for the given file
pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> {
self.with_db(|db| syntax_highlighting::highlight(db, file_id, None))
self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false))
}
/// Computes syntax highlighting for the given file range.
pub fn highlight_range(&self, frange: FileRange) -> Cancelable<Vec<HighlightedRange>> {
self.with_db(|db| syntax_highlighting::highlight(db, frange.file_id, Some(frange.range)))
self.with_db(|db| {
syntax_highlighting::highlight(db, frange.file_id, Some(frange.range), false)
})
}
/// Computes syntax highlighting for the given file.

View file

@ -7,6 +7,6 @@ use crate::{FileId, RootDatabase};
pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) {
for file in files {
let _ = crate::syntax_highlighting::highlight(db, file, None);
let _ = crate::syntax_highlighting::highlight(db, file, None, false);
}
}

View file

@ -25,22 +25,29 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.variable { color: #DCDCCC; }
.format_specifier { color: #CC696B; }
.mutable { text-decoration: underline; }
.unresolved_reference { color: #FC5555; }
.keyword { color: #F0DFAF; font-weight: bold; }
.keyword.unsafe { color: #BC8383; font-weight: bold; }
.control { font-style: italic; }
</style>
<pre><code><span class="keyword">impl</span> <span class="unresolved_reference">Foo</span> {
<pre><code><span class="keyword">struct</span> <span class="struct declaration">Foo</span> {
<span class="field declaration">bar</span>: <span class="builtin_type">bool</span>,
}
<span class="keyword">impl</span> <span class="struct">Foo</span> {
<span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant declaration">bar</span>: <span class="builtin_type">bool</span> = <span class="bool_literal">true</span>;
<span class="comment">/// Constructs a new `Foo`.</span>
<span class="comment">///</span>
<span class="comment">/// # Examples</span>
<span class="comment">///</span>
<span class="comment">/// ```</span>
<span class="comment">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span>
<span class="comment">/// </span><span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span>: <span class="unresolved_reference">Foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>();
<span class="comment">/// </span><span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span>: <span class="struct">Foo</span> = <span class="struct">Foo</span>::<span class="function">new</span>();
<span class="comment">/// ```</span>
<span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -&gt; <span class="unresolved_reference">Foo</span> {
<span class="unresolved_reference">Foo</span> { }
<span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -&gt; <span class="struct">Foo</span> {
<span class="struct">Foo</span> { <span class="field">bar</span>: <span class="bool_literal">true</span> }
}
<span class="comment">/// `bar` method on `Foo`.</span>
@ -48,11 +55,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<span class="comment">/// # Examples</span>
<span class="comment">///</span>
<span class="comment">/// ```</span>
<span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>();
<span class="comment">/// </span><span class="keyword">use</span> <span class="module">x</span>::<span class="module">y</span>;
<span class="comment">///</span>
<span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foo</span> = <span class="struct">Foo</span>::<span class="function">new</span>();
<span class="comment">///</span>
<span class="comment">/// </span><span class="comment">// calls bar on foo</span>
<span class="comment">/// </span><span class="macro">assert!</span>(foo.bar());
<span class="comment">///</span>
<span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">bar</span> = <span class="variable">foo</span>.<span class="field">bar</span> || <span class="struct">Foo</span>::<span class="constant">bar</span>;
<span class="comment">///</span>
<span class="comment">/// </span><span class="comment">/* multi-line
</span><span class="comment">/// </span><span class="comment"> comment */</span>
<span class="comment">///</span>
@ -63,7 +74,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<span class="comment">/// ```</span>
<span class="comment">///</span>
<span class="comment">/// ```</span>
<span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foobar</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>().<span class="unresolved_reference">bar</span>();
<span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foobar</span> = <span class="struct">Foo</span>::<span class="function">new</span>().<span class="function">bar</span>();
<span class="comment">/// ```</span>
<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">foo</span>(&<span class="self_keyword">self</span>) -&gt; <span class="builtin_type">bool</span> {
<span class="bool_literal">true</span>

View file

@ -25,6 +25,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.variable { color: #DCDCCC; }
.format_specifier { color: #CC696B; }
.mutable { text-decoration: underline; }
.unresolved_reference { color: #FC5555; }
.keyword { color: #F0DFAF; font-weight: bold; }
.keyword.unsafe { color: #BC8383; font-weight: bold; }

View file

@ -25,6 +25,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.variable { color: #DCDCCC; }
.format_specifier { color: #CC696B; }
.mutable { text-decoration: underline; }
.unresolved_reference { color: #FC5555; }
.keyword { color: #F0DFAF; font-weight: bold; }
.keyword.unsafe { color: #BC8383; font-weight: bold; }

View file

@ -25,6 +25,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.variable { color: #DCDCCC; }
.format_specifier { color: #CC696B; }
.mutable { text-decoration: underline; }
.unresolved_reference { color: #FC5555; }
.keyword { color: #F0DFAF; font-weight: bold; }
.keyword.unsafe { color: #BC8383; font-weight: bold; }

View file

@ -25,6 +25,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.variable { color: #DCDCCC; }
.format_specifier { color: #CC696B; }
.mutable { text-decoration: underline; }
.unresolved_reference { color: #FC5555; }
.keyword { color: #F0DFAF; font-weight: bold; }
.keyword.unsafe { color: #BC8383; font-weight: bold; }

View file

@ -25,6 +25,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.variable { color: #DCDCCC; }
.format_specifier { color: #CC696B; }
.mutable { text-decoration: underline; }
.unresolved_reference { color: #FC5555; }
.keyword { color: #F0DFAF; font-weight: bold; }
.keyword.unsafe { color: #BC8383; font-weight: bold; }

View file

@ -44,6 +44,7 @@ pub(crate) fn highlight(
db: &RootDatabase,
file_id: FileId,
range_to_highlight: Option<TextRange>,
syntactic_name_ref_highlighting: bool,
) -> Vec<HighlightedRange> {
let _p = profile("highlight");
let sema = Semantics::new(db);
@ -104,6 +105,7 @@ pub(crate) fn highlight(
if let Some((highlight, binding_hash)) = highlight_element(
&sema,
&mut bindings_shadow_count,
syntactic_name_ref_highlighting,
name.syntax().clone().into(),
) {
stack.add(HighlightedRange {
@ -200,9 +202,12 @@ pub(crate) fn highlight(
let is_format_string = format_string.as_ref() == Some(&element_to_highlight);
if let Some((highlight, binding_hash)) =
highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight.clone())
{
if let Some((highlight, binding_hash)) = highlight_element(
&sema,
&mut bindings_shadow_count,
syntactic_name_ref_highlighting,
element_to_highlight.clone(),
) {
stack.add(HighlightedRange { range, highlight, binding_hash });
if let Some(string) =
element_to_highlight.as_token().cloned().and_then(ast::String::cast)
@ -410,6 +415,7 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
fn highlight_element(
sema: &Semantics<RootDatabase>,
bindings_shadow_count: &mut FxHashMap<Name, u32>,
syntactic_name_ref_highlighting: bool,
element: SyntaxElement,
) -> Option<(Highlight, Option<u64>)> {
let db = sema.db;
@ -463,6 +469,7 @@ fn highlight_element(
}
NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(),
},
None if syntactic_name_ref_highlighting => highlight_name_ref_by_syntax(name_ref),
None => HighlightTag::UnresolvedReference.into(),
}
}
@ -614,3 +621,53 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
tag.into()
}
fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight {
let default = HighlightTag::UnresolvedReference;
let parent = match name.syntax().parent() {
Some(it) => it,
_ => return default.into(),
};
let tag = match parent.kind() {
METHOD_CALL_EXPR => HighlightTag::Function,
FIELD_EXPR => HighlightTag::Field,
PATH_SEGMENT => {
let path = match parent.parent().and_then(ast::Path::cast) {
Some(it) => it,
_ => return default.into(),
};
let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) {
Some(it) => it,
_ => {
// within path, decide whether it is module or adt by checking for uppercase name
return if name.text().chars().next().unwrap_or_default().is_uppercase() {
HighlightTag::Struct
} else {
HighlightTag::Module
}
.into();
}
};
let parent = match expr.syntax().parent() {
Some(it) => it,
None => return default.into(),
};
match parent.kind() {
CALL_EXPR => HighlightTag::Function,
_ => {
if name.text().chars().next().unwrap_or_default().is_uppercase() {
HighlightTag::Struct
} else {
HighlightTag::Constant
}
}
}
}
_ => default,
};
tag.into()
}

View file

@ -19,7 +19,7 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo
)
}
let ranges = highlight(db, file_id, None);
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();
@ -84,6 +84,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.variable { color: #DCDCCC; }
.format_specifier { color: #CC696B; }
.mutable { text-decoration: underline; }
.unresolved_reference { color: #FC5555; }
.keyword { color: #F0DFAF; font-weight: bold; }
.keyword.unsafe { color: #BC8383; font-weight: bold; }

View file

@ -137,7 +137,7 @@ pub(super) fn highlight_doc_comment(
let (analysis, tmp_file_id) = Analysis::from_single_file(text);
stack.push();
for mut h in analysis.highlight(tmp_file_id).unwrap() {
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;

View file

@ -287,7 +287,13 @@ fn main() {
fn test_highlight_doctest() {
check_highlighting(
r#"
struct Foo {
bar: bool,
}
impl Foo {
pub const bar: bool = true;
/// Constructs a new `Foo`.
///
/// # Examples
@ -297,7 +303,7 @@ impl Foo {
/// let mut foo: Foo = Foo::new();
/// ```
pub const fn new() -> Foo {
Foo { }
Foo { bar: true }
}
/// `bar` method on `Foo`.
@ -305,11 +311,15 @@ impl Foo {
/// # Examples
///
/// ```
/// use x::y;
///
/// let foo = Foo::new();
///
/// // calls bar on foo
/// assert!(foo.bar());
///
/// let bar = foo.bar || Foo::bar;
///
/// /* multi-line
/// comment */
///
@ -330,7 +340,7 @@ impl Foo {
.trim(),
"crates/ra_ide/src/snapshots/highlight_doctest.html",
false,
)
);
}
/// Highlights the code given by the `ra_fixture` argument, renders the