3335: Refactor highlighting a bit r=matklad a=matklad

bors r+

Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2020-02-27 08:32:44 +00:00 committed by GitHub
commit 2180591593
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 481 additions and 308 deletions

View File

@ -74,7 +74,9 @@ pub use crate::{
runnables::{Runnable, RunnableKind, TestId},
source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
ssr::SsrError,
syntax_highlighting::{HighlightTag, HighlightedRange},
syntax_highlighting::{
Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange,
},
};
pub use hir::Documentation;

View File

@ -10,29 +10,29 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.type\.builtin { color: #8CD0D3; }
.type\.param { color: #20999D; }
.type.builtin { color: #8CD0D3; }
.type.param { color: #20999D; }
.attribute { color: #94BFF3; }
.literal { color: #BFEBBF; }
.literal\.numeric { color: #6A8759; }
.literal.numeric { color: #6A8759; }
.macro { color: #94BFF3; }
.module { color: #AFD8AF; }
.variable { color: #DCDCCC; }
.variable\.mut { color: #DCDCCC; text-decoration: underline; }
.variable.mut { color: #DCDCCC; text-decoration: underline; }
.keyword { color: #F0DFAF; }
.keyword\.unsafe { color: #DFAF8F; }
.keyword\.control { color: #F0DFAF; font-weight: bold; }
.keyword.unsafe { color: #DFAF8F; }
.keyword.control { color: #F0DFAF; font-weight: bold; }
</style>
<pre><code><span class="attribute">#</span><span class="attribute">[</span><span class="attribute">derive</span><span class="attribute">(</span><span class="attribute">Clone</span><span class="attribute">,</span><span class="attribute"> </span><span class="attribute">Debug</span><span class="attribute">)</span><span class="attribute">]</span>
<span class="keyword">struct</span> <span class="type">Foo</span> {
<span class="keyword">pub</span> <span class="field">x</span>: <span class="type.builtin">i32</span>,
<span class="keyword">pub</span> <span class="field">y</span>: <span class="type.builtin">i32</span>,
<span class="keyword">pub</span> <span class="field">x</span>: <span class="type builtin">i32</span>,
<span class="keyword">pub</span> <span class="field">y</span>: <span class="type builtin">i32</span>,
}
<span class="keyword">fn</span> <span class="function">foo</span>&lt;<span class="type.param">T</span>&gt;() -&gt; <span class="type.param">T</span> {
<span class="keyword">fn</span> <span class="function">foo</span>&lt;<span class="type param">T</span>&gt;() -&gt; <span class="type param">T</span> {
<span class="macro">unimplemented</span><span class="macro">!</span>();
<span class="function">foo</span>::&lt;<span class="type.builtin">i32</span>&gt;();
<span class="function">foo</span>::&lt;<span class="type builtin">i32</span>&gt;();
}
<span class="macro">macro_rules</span><span class="macro">!</span> def_fn {
@ -40,33 +40,33 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
}
<span class="macro">def_fn</span><span class="macro">!</span>{
<span class="keyword">fn</span> <span class="function">bar</span>() -&gt; <span class="type.builtin">u32</span> {
<span class="literal.numeric">100</span>
<span class="keyword">fn</span> <span class="function">bar</span>() -&gt; <span class="type builtin">u32</span> {
<span class="literal numeric">100</span>
}
}
<span class="comment">// comment</span>
<span class="keyword">fn</span> <span class="function">main</span>() {
<span class="macro">println</span><span class="macro">!</span>(<span class="string">"Hello, {}!"</span>, <span class="literal.numeric">92</span>);
<span class="macro">println</span><span class="macro">!</span>(<span class="string">"Hello, {}!"</span>, <span class="literal numeric">92</span>);
<span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable.mut">vec</span> = Vec::new();
<span class="keyword.control">if</span> <span class="keyword">true</span> {
<span class="keyword">let</span> <span class="variable">x</span> = <span class="literal.numeric">92</span>;
<span class="variable.mut">vec</span>.push(<span class="type">Foo</span> { <span class="field">x</span>, <span class="field">y</span>: <span class="literal.numeric">1</span> });
<span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable mutable">vec</span> = Vec::new();
<span class="keyword control">if</span> <span class="keyword">true</span> {
<span class="keyword">let</span> <span class="variable">x</span> = <span class="literal numeric">92</span>;
<span class="variable mutable">vec</span>.push(<span class="type">Foo</span> { <span class="field">x</span>, <span class="field">y</span>: <span class="literal numeric">1</span> });
}
<span class="keyword.unsafe">unsafe</span> { <span class="variable.mut">vec</span>.set_len(<span class="literal.numeric">0</span>); }
<span class="keyword unsafe">unsafe</span> { <span class="variable mutable">vec</span>.set_len(<span class="literal numeric">0</span>); }
<span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable.mut">x</span> = <span class="literal.numeric">42</span>;
<span class="keyword">let</span> <span class="variable.mut">y</span> = &<span class="keyword">mut</span> <span class="variable.mut">x</span>;
<span class="keyword">let</span> <span class="variable">z</span> = &<span class="variable.mut">y</span>;
<span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable mutable">x</span> = <span class="literal numeric">42</span>;
<span class="keyword">let</span> <span class="variable mutable">y</span> = &<span class="keyword">mut</span> <span class="variable mutable">x</span>;
<span class="keyword">let</span> <span class="variable">z</span> = &<span class="variable mutable">y</span>;
<span class="variable.mut">y</span>;
<span class="variable mutable">y</span>;
}
<span class="keyword">enum</span> <span class="type">E</span>&lt;<span class="type.param">X</span>&gt; {
<span class="constant">V</span>(<span class="type.param">X</span>)
<span class="keyword">enum</span> <span class="type">E</span>&lt;<span class="type param">X</span>&gt; {
<span class="constant">V</span>(<span class="type param">X</span>)
}
<span class="keyword">impl</span>&lt;<span class="type.param">X</span>&gt; <span class="type">E</span>&lt;<span class="type.param">X</span>&gt; {
<span class="keyword">fn</span> <span class="function">new</span>&lt;<span class="type.param">T</span>&gt;() -&gt; <span class="type">E</span>&lt;<span class="type.param">T</span>&gt; {}
<span class="keyword">impl</span>&lt;<span class="type param">X</span>&gt; <span class="type">E</span>&lt;<span class="type param">X</span>&gt; {
<span class="keyword">fn</span> <span class="function">new</span>&lt;<span class="type param">T</span>&gt;() -&gt; <span class="type">E</span>&lt;<span class="type param">T</span>&gt; {}
}</code></pre>

View File

@ -10,19 +10,19 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.type\.builtin { color: #8CD0D3; }
.type\.param { color: #20999D; }
.type.builtin { color: #8CD0D3; }
.type.param { color: #20999D; }
.attribute { color: #94BFF3; }
.literal { color: #BFEBBF; }
.literal\.numeric { color: #6A8759; }
.literal.numeric { color: #6A8759; }
.macro { color: #94BFF3; }
.module { color: #AFD8AF; }
.variable { color: #DCDCCC; }
.variable\.mut { color: #DCDCCC; text-decoration: underline; }
.variable.mut { color: #DCDCCC; text-decoration: underline; }
.keyword { color: #F0DFAF; }
.keyword\.unsafe { color: #DFAF8F; }
.keyword\.control { color: #F0DFAF; font-weight: bold; }
.keyword.unsafe { color: #DFAF8F; }
.keyword.control { color: #F0DFAF; font-weight: bold; }
</style>
<pre><code><span class="keyword">fn</span> <span class="function">main</span>() {
<span class="keyword">let</span> <span class="variable" data-binding-hash="8121853618659664005" style="color: hsl(261,57%,61%);">hello</span> = <span class="string">"hello"</span>;
@ -34,5 +34,5 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
}
<span class="keyword">fn</span> <span class="function">bar</span>() {
<span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable.mut" data-binding-hash="8121853618659664005" style="color: hsl(261,57%,61%);">hello</span> = <span class="string">"hello"</span>;
<span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable mutable" data-binding-hash="8121853618659664005" style="color: hsl(261,57%,61%);">hello</span> = <span class="string">"hello"</span>;
}</code></pre>

View File

@ -1,9 +1,9 @@
//! FIXME: write short doc here
mod highlight_tag;
mod highlight;
mod html;
use hir::{Name, Semantics};
use ra_db::SourceDatabase;
use ra_ide_db::{
defs::{classify_name, NameDefinition},
RootDatabase,
@ -17,12 +17,14 @@ use rustc_hash::FxHashMap;
use crate::{references::classify_name_ref, FileId};
pub use highlight_tag::HighlightTag;
pub use highlight::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
pub(crate) use html::highlight_as_html;
#[derive(Debug)]
pub struct HighlightedRange {
pub range: TextRange,
pub tag: HighlightTag,
pub highlight: Highlight,
pub binding_hash: Option<u64>,
}
@ -79,33 +81,33 @@ pub(crate) fn highlight(
if let Some(range) = highlight_macro(node) {
res.push(HighlightedRange {
range,
tag: HighlightTag::MACRO,
highlight: HighlightTag::Macro.into(),
binding_hash: None,
});
}
}
_ if in_macro_call.is_some() => {
if let Some(token) = node.as_token() {
if let Some((tag, binding_hash)) = highlight_token_tree(
if let Some((highlight, binding_hash)) = highlight_token_tree(
&sema,
&mut bindings_shadow_count,
token.clone(),
) {
res.push(HighlightedRange {
range: node.text_range(),
tag,
highlight,
binding_hash,
});
}
}
}
_ => {
if let Some((tag, binding_hash)) =
if let Some((highlight, binding_hash)) =
highlight_node(&sema, &mut bindings_shadow_count, node.clone())
{
res.push(HighlightedRange {
range: node.text_range(),
tag,
highlight,
binding_hash,
});
}
@ -150,7 +152,7 @@ fn highlight_token_tree(
sema: &Semantics<RootDatabase>,
bindings_shadow_count: &mut FxHashMap<Name, u32>,
token: SyntaxToken,
) -> Option<(HighlightTag, Option<u64>)> {
) -> Option<(Highlight, Option<u64>)> {
if token.parent().kind() != TOKEN_TREE {
return None;
}
@ -171,19 +173,21 @@ fn highlight_node(
sema: &Semantics<RootDatabase>,
bindings_shadow_count: &mut FxHashMap<Name, u32>,
node: SyntaxElement,
) -> Option<(HighlightTag, Option<u64>)> {
) -> Option<(Highlight, Option<u64>)> {
let db = sema.db;
let mut binding_hash = None;
let tag = match node.kind() {
let highlight: Highlight = match node.kind() {
FN_DEF => {
bindings_shadow_count.clear();
return None;
}
COMMENT => HighlightTag::LITERAL_COMMENT,
STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::LITERAL_STRING,
ATTR => HighlightTag::LITERAL_ATTRIBUTE,
COMMENT => HighlightTag::Comment.into(),
STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::LiteralString.into(),
ATTR => HighlightTag::Attribute.into(),
// Special-case field init shorthand
NAME_REF if node.parent().and_then(ast::RecordField::cast).is_some() => HighlightTag::FIELD,
NAME_REF if node.parent().and_then(ast::RecordField::cast).is_some() => {
HighlightTag::Field.into()
}
NAME_REF if node.ancestors().any(|it| it.kind() == ATTR) => return None,
NAME_REF => {
let name_ref = node.as_node().cloned().and_then(ast::NameRef::cast).unwrap();
@ -217,26 +221,30 @@ fn highlight_node(
match name_kind {
Some(name_kind) => highlight_name(db, name_kind),
None => name.syntax().parent().map_or(HighlightTag::FUNCTION, |x| match x.kind() {
STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => HighlightTag::TYPE,
TYPE_PARAM => HighlightTag::TYPE_PARAM,
RECORD_FIELD_DEF => HighlightTag::FIELD,
_ => HighlightTag::FUNCTION,
None => name.syntax().parent().map_or(HighlightTag::Function.into(), |x| {
match x.kind() {
STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => {
HighlightTag::Type.into()
}
TYPE_PARAM => HighlightTag::TypeParam.into(),
RECORD_FIELD_DEF => HighlightTag::Field.into(),
_ => HighlightTag::Function.into(),
}
}),
}
}
INT_NUMBER | FLOAT_NUMBER => HighlightTag::LITERAL_NUMERIC,
BYTE => HighlightTag::LITERAL_BYTE,
CHAR => HighlightTag::LITERAL_CHAR,
LIFETIME => HighlightTag::TYPE_LIFETIME,
T![unsafe] => HighlightTag::KEYWORD_UNSAFE,
k if is_control_keyword(k) => HighlightTag::KEYWORD_CONTROL,
k if k.is_keyword() => HighlightTag::KEYWORD,
INT_NUMBER | FLOAT_NUMBER => HighlightTag::LiteralNumeric.into(),
BYTE => HighlightTag::LiteralByte.into(),
CHAR => HighlightTag::LiteralChar.into(),
LIFETIME => HighlightTag::TypeLifetime.into(),
T![unsafe] => HighlightTag::Keyword | HighlightModifier::Unsafe,
k if is_control_keyword(k) => HighlightTag::Keyword | HighlightModifier::Control,
k if k.is_keyword() => HighlightTag::Keyword.into(),
_ => return None,
};
return Some((tag, binding_hash));
return Some((highlight, binding_hash));
fn calc_binding_hash(name: &Name, shadow_count: u32) -> u64 {
fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
@ -251,123 +259,34 @@ fn highlight_node(
}
}
pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String {
let parse = db.parse(file_id);
fn rainbowify(seed: u64) -> String {
use rand::prelude::*;
let mut rng = SmallRng::seed_from_u64(seed);
format!(
"hsl({h},{s}%,{l}%)",
h = rng.gen_range::<u16, _, _>(0, 361),
s = rng.gen_range::<u16, _, _>(42, 99),
l = rng.gen_range::<u16, _, _>(40, 91),
)
}
let mut ranges = highlight(db, file_id, None);
ranges.sort_by_key(|it| it.range.start());
// quick non-optimal heuristic to intersect token ranges and highlighted ranges
let mut frontier = 0;
let mut could_intersect: Vec<&HighlightedRange> = Vec::new();
let mut buf = String::new();
buf.push_str(&STYLE);
buf.push_str("<pre><code>");
let tokens = parse.tree().syntax().descendants_with_tokens().filter_map(|it| it.into_token());
for token in tokens {
could_intersect.retain(|it| token.text_range().start() <= it.range.end());
while let Some(r) = ranges.get(frontier) {
if r.range.start() <= token.text_range().end() {
could_intersect.push(r);
frontier += 1;
} else {
break;
}
}
let text = html_escape(&token.text());
let ranges = could_intersect
.iter()
.filter(|it| token.text_range().is_subrange(&it.range))
.collect::<Vec<_>>();
if ranges.is_empty() {
buf.push_str(&text);
} else {
let classes = ranges.iter().map(|x| x.tag.to_string()).collect::<Vec<_>>().join(" ");
let binding_hash = ranges.first().and_then(|x| x.binding_hash);
let color = match (rainbow, binding_hash) {
(true, Some(hash)) => format!(
" data-binding-hash=\"{}\" style=\"color: {};\"",
hash,
rainbowify(hash)
),
_ => "".into(),
};
buf.push_str(&format!("<span class=\"{}\"{}>{}</span>", classes, color, text));
}
}
buf.push_str("</code></pre>");
buf
}
fn highlight_name(db: &RootDatabase, def: NameDefinition) -> HighlightTag {
fn highlight_name(db: &RootDatabase, def: NameDefinition) -> Highlight {
match def {
NameDefinition::Macro(_) => HighlightTag::MACRO,
NameDefinition::StructField(_) => HighlightTag::FIELD,
NameDefinition::ModuleDef(hir::ModuleDef::Module(_)) => HighlightTag::MODULE,
NameDefinition::ModuleDef(hir::ModuleDef::Function(_)) => HighlightTag::FUNCTION,
NameDefinition::ModuleDef(hir::ModuleDef::Adt(_)) => HighlightTag::TYPE,
NameDefinition::ModuleDef(hir::ModuleDef::EnumVariant(_)) => HighlightTag::CONSTANT,
NameDefinition::ModuleDef(hir::ModuleDef::Const(_)) => HighlightTag::CONSTANT,
NameDefinition::ModuleDef(hir::ModuleDef::Static(_)) => HighlightTag::CONSTANT,
NameDefinition::ModuleDef(hir::ModuleDef::Trait(_)) => HighlightTag::TYPE,
NameDefinition::ModuleDef(hir::ModuleDef::TypeAlias(_)) => HighlightTag::TYPE,
NameDefinition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => HighlightTag::TYPE_BUILTIN,
NameDefinition::SelfType(_) => HighlightTag::TYPE_SELF,
NameDefinition::TypeParam(_) => HighlightTag::TYPE_PARAM,
NameDefinition::Macro(_) => HighlightTag::Macro,
NameDefinition::StructField(_) => HighlightTag::Field,
NameDefinition::ModuleDef(hir::ModuleDef::Module(_)) => HighlightTag::Module,
NameDefinition::ModuleDef(hir::ModuleDef::Function(_)) => HighlightTag::Function,
NameDefinition::ModuleDef(hir::ModuleDef::Adt(_)) => HighlightTag::Type,
NameDefinition::ModuleDef(hir::ModuleDef::EnumVariant(_)) => HighlightTag::Constant,
NameDefinition::ModuleDef(hir::ModuleDef::Const(_)) => HighlightTag::Constant,
NameDefinition::ModuleDef(hir::ModuleDef::Static(_)) => HighlightTag::Constant,
NameDefinition::ModuleDef(hir::ModuleDef::Trait(_)) => HighlightTag::Type,
NameDefinition::ModuleDef(hir::ModuleDef::TypeAlias(_)) => HighlightTag::Type,
NameDefinition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => {
return HighlightTag::Type | HighlightModifier::Builtin
}
NameDefinition::SelfType(_) => HighlightTag::TypeSelf,
NameDefinition::TypeParam(_) => HighlightTag::TypeParam,
NameDefinition::Local(local) => {
let mut h = Highlight::new(HighlightTag::Variable);
if local.is_mut(db) || local.ty(db).is_mutable_reference() {
HighlightTag::VARIABLE_MUT
} else {
HighlightTag::VARIABLE
h |= HighlightModifier::Mutable;
}
return h;
}
}
.into()
}
//FIXME: like, real html escaping
fn html_escape(text: &str) -> String {
text.replace("<", "&lt;").replace(">", "&gt;")
}
const STYLE: &str = "
<style>
body { margin: 0; }
pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
.comment { color: #7F9F7F; }
.string { color: #CC9393; }
.field { color: #94BFF3; }
.function { color: #93E0E3; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.type\\.builtin { color: #8CD0D3; }
.type\\.param { color: #20999D; }
.attribute { color: #94BFF3; }
.literal { color: #BFEBBF; }
.literal\\.numeric { color: #6A8759; }
.macro { color: #94BFF3; }
.module { color: #AFD8AF; }
.variable { color: #DCDCCC; }
.variable\\.mut { color: #DCDCCC; text-decoration: underline; }
.keyword { color: #F0DFAF; }
.keyword\\.unsafe { color: #DFAF8F; }
.keyword\\.control { color: #F0DFAF; font-weight: bold; }
</style>
";
#[cfg(test)]
mod tests {
use std::fs;
@ -498,6 +417,6 @@ fn bar() {
})
.unwrap();
assert_eq!(&highlights[0].tag.to_string(), "field");
assert_eq!(&highlights[0].highlight.to_string(), "field");
}
}

View File

@ -0,0 +1,163 @@
//! Defines token tags we use for syntax highlighting.
//! A tag is not unlike a CSS class.
use std::{fmt, ops};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Highlight {
pub tag: HighlightTag,
pub modifiers: HighlightModifiers,
}
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct HighlightModifiers(u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum HighlightTag {
Field,
Function,
Module,
Constant,
Macro,
Variable,
Type,
TypeSelf,
TypeParam,
TypeLifetime,
LiteralByte,
LiteralNumeric,
LiteralChar,
Comment,
LiteralString,
Attribute,
Keyword,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(u8)]
pub enum HighlightModifier {
Mutable = 0,
Unsafe,
/// Used with keywords like `if` and `break`.
Control,
Builtin,
}
impl HighlightTag {
fn as_str(self) -> &'static str {
match self {
HighlightTag::Field => "field",
HighlightTag::Function => "function",
HighlightTag::Module => "module",
HighlightTag::Constant => "constant",
HighlightTag::Macro => "macro",
HighlightTag::Variable => "variable",
HighlightTag::Type => "type",
HighlightTag::TypeSelf => "type.self",
HighlightTag::TypeParam => "type.param",
HighlightTag::TypeLifetime => "type.lifetime",
HighlightTag::LiteralByte => "literal.byte",
HighlightTag::LiteralNumeric => "literal.numeric",
HighlightTag::LiteralChar => "literal.char",
HighlightTag::Comment => "comment",
HighlightTag::LiteralString => "string",
HighlightTag::Attribute => "attribute",
HighlightTag::Keyword => "keyword",
}
}
}
impl fmt::Display for HighlightTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
impl HighlightModifier {
const ALL: &'static [HighlightModifier] = &[
HighlightModifier::Mutable,
HighlightModifier::Unsafe,
HighlightModifier::Control,
HighlightModifier::Builtin,
];
fn as_str(self) -> &'static str {
match self {
HighlightModifier::Mutable => "mutable",
HighlightModifier::Unsafe => "unsafe",
HighlightModifier::Control => "control",
HighlightModifier::Builtin => "builtin",
}
}
fn mask(self) -> u32 {
1 << (self as u32)
}
}
impl fmt::Display for HighlightModifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
impl fmt::Display for Highlight {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.tag)?;
for modifier in self.modifiers.iter() {
write!(f, ".{}", modifier)?
}
Ok(())
}
}
impl From<HighlightTag> for Highlight {
fn from(tag: HighlightTag) -> Highlight {
Highlight::new(tag)
}
}
impl Highlight {
pub(crate) fn new(tag: HighlightTag) -> Highlight {
Highlight { tag, modifiers: HighlightModifiers::default() }
}
}
impl ops::BitOr<HighlightModifier> for HighlightTag {
type Output = Highlight;
fn bitor(self, rhs: HighlightModifier) -> Highlight {
Highlight::new(self) | rhs
}
}
impl ops::BitOrAssign<HighlightModifier> for HighlightModifiers {
fn bitor_assign(&mut self, rhs: HighlightModifier) {
self.0 |= rhs.mask();
}
}
impl ops::BitOrAssign<HighlightModifier> for Highlight {
fn bitor_assign(&mut self, rhs: HighlightModifier) {
self.modifiers |= rhs;
}
}
impl ops::BitOr<HighlightModifier> for Highlight {
type Output = Highlight;
fn bitor(mut self, rhs: HighlightModifier) -> Highlight {
self |= rhs;
self
}
}
impl HighlightModifiers {
pub fn iter(self) -> impl Iterator<Item = HighlightModifier> {
HighlightModifier::ALL.iter().copied().filter(move |it| self.0 & it.mask() == it.mask())
}
}

View File

@ -1,43 +0,0 @@
//! Defines token tags we use for syntax highlighting.
//! A tag is not unlike a CSS class.
use std::fmt;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct HighlightTag(&'static str);
impl fmt::Display for HighlightTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.0, f)
}
}
#[rustfmt::skip]
impl HighlightTag {
pub const FIELD: HighlightTag = HighlightTag("field");
pub const FUNCTION: HighlightTag = HighlightTag("function");
pub const MODULE: HighlightTag = HighlightTag("module");
pub const CONSTANT: HighlightTag = HighlightTag("constant");
pub const MACRO: HighlightTag = HighlightTag("macro");
pub const VARIABLE: HighlightTag = HighlightTag("variable");
pub const VARIABLE_MUT: HighlightTag = HighlightTag("variable.mut");
pub const TYPE: HighlightTag = HighlightTag("type");
pub const TYPE_BUILTIN: HighlightTag = HighlightTag("type.builtin");
pub const TYPE_SELF: HighlightTag = HighlightTag("type.self");
pub const TYPE_PARAM: HighlightTag = HighlightTag("type.param");
pub const TYPE_LIFETIME: HighlightTag = HighlightTag("type.lifetime");
pub const LITERAL_BYTE: HighlightTag = HighlightTag("literal.byte");
pub const LITERAL_NUMERIC: HighlightTag = HighlightTag("literal.numeric");
pub const LITERAL_CHAR: HighlightTag = HighlightTag("literal.char");
pub const LITERAL_COMMENT: HighlightTag = HighlightTag("comment");
pub const LITERAL_STRING: HighlightTag = HighlightTag("string");
pub const LITERAL_ATTRIBUTE: HighlightTag = HighlightTag("attribute");
pub const KEYWORD: HighlightTag = HighlightTag("keyword");
pub const KEYWORD_UNSAFE: HighlightTag = HighlightTag("keyword.unsafe");
pub const KEYWORD_CONTROL: HighlightTag = HighlightTag("keyword.control");
}

View File

@ -0,0 +1,104 @@
//! Renders a bit of code as HTML.
use ra_db::SourceDatabase;
use ra_syntax::AstNode;
use crate::{FileId, HighlightedRange, RootDatabase};
use super::highlight;
pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String {
let parse = db.parse(file_id);
fn rainbowify(seed: u64) -> String {
use rand::prelude::*;
let mut rng = SmallRng::seed_from_u64(seed);
format!(
"hsl({h},{s}%,{l}%)",
h = rng.gen_range::<u16, _, _>(0, 361),
s = rng.gen_range::<u16, _, _>(42, 99),
l = rng.gen_range::<u16, _, _>(40, 91),
)
}
let mut ranges = highlight(db, file_id, None);
ranges.sort_by_key(|it| it.range.start());
// quick non-optimal heuristic to intersect token ranges and highlighted ranges
let mut frontier = 0;
let mut could_intersect: Vec<&HighlightedRange> = Vec::new();
let mut buf = String::new();
buf.push_str(&STYLE);
buf.push_str("<pre><code>");
let tokens = parse.tree().syntax().descendants_with_tokens().filter_map(|it| it.into_token());
for token in tokens {
could_intersect.retain(|it| token.text_range().start() <= it.range.end());
while let Some(r) = ranges.get(frontier) {
if r.range.start() <= token.text_range().end() {
could_intersect.push(r);
frontier += 1;
} else {
break;
}
}
let text = html_escape(&token.text());
let ranges = could_intersect
.iter()
.filter(|it| token.text_range().is_subrange(&it.range))
.collect::<Vec<_>>();
if ranges.is_empty() {
buf.push_str(&text);
} else {
let classes = ranges
.iter()
.map(|it| it.highlight.to_string().replace('.', " "))
.collect::<Vec<_>>()
.join(" ");
let binding_hash = ranges.first().and_then(|x| x.binding_hash);
let color = match (rainbow, binding_hash) {
(true, Some(hash)) => format!(
" data-binding-hash=\"{}\" style=\"color: {};\"",
hash,
rainbowify(hash)
),
_ => "".into(),
};
buf.push_str(&format!("<span class=\"{}\"{}>{}</span>", classes, color, text));
}
}
buf.push_str("</code></pre>");
buf
}
//FIXME: like, real html escaping
fn html_escape(text: &str) -> String {
text.replace("<", "&lt;").replace(">", "&gt;")
}
const STYLE: &str = "
<style>
body { margin: 0; }
pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
.comment { color: #7F9F7F; }
.string { color: #CC9393; }
.field { color: #94BFF3; }
.function { color: #93E0E3; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.type.builtin { color: #8CD0D3; }
.type.param { color: #20999D; }
.attribute { color: #94BFF3; }
.literal { color: #BFEBBF; }
.literal.numeric { color: #6A8759; }
.macro { color: #94BFF3; }
.module { color: #AFD8AF; }
.variable { color: #DCDCCC; }
.variable.mut { color: #DCDCCC; text-decoration: underline; }
.keyword { color: #F0DFAF; }
.keyword.unsafe { color: #DFAF8F; }
.keyword.control { color: #F0DFAF; font-weight: bold; }
</style>
";

View File

@ -63,11 +63,8 @@ pub fn server_capabilities() -> ServerCapabilities {
semantic_tokens_provider: Some(
SemanticTokensOptions {
legend: SemanticTokensLegend {
token_types: semantic_tokens::supported_token_types().iter().cloned().collect(),
token_modifiers: semantic_tokens::supported_token_modifiers()
.iter()
.cloned()
.collect(),
token_types: semantic_tokens::SUPPORTED_TYPES.iter().cloned().collect(),
token_modifiers: semantic_tokens::SUPPORTED_MODIFIERS.iter().cloned().collect(),
},
document_provider: Some(SemanticTokensDocumentProvider::Bool(true)),

View File

@ -10,14 +10,21 @@ use lsp_types::{
};
use ra_ide::{
translate_offset_with_edit, CompletionItem, CompletionItemKind, FileId, FilePosition,
FileRange, FileSystemEdit, Fold, FoldKind, HighlightTag, InsertTextFormat, LineCol, LineIndex,
NavigationTarget, RangeInfo, ReferenceAccess, Severity, SourceChange, SourceFileEdit,
FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HighlightModifier, HighlightTag,
InsertTextFormat, LineCol, LineIndex, NavigationTarget, RangeInfo, ReferenceAccess, Severity,
SourceChange, SourceFileEdit,
};
use ra_syntax::{SyntaxKind, TextRange, TextUnit};
use ra_text_edit::{AtomTextEdit, TextEdit};
use ra_vfs::LineEndings;
use crate::{req, semantic_tokens, world::WorldSnapshot, Result};
use crate::{
req,
semantic_tokens::{self, ModifierSet, BUILTIN, CONSTANT, CONTROL, MUTABLE, UNSAFE},
world::WorldSnapshot,
Result,
};
use semantic_tokens::ATTRIBUTE;
pub trait Conv {
type Output;
@ -303,74 +310,52 @@ impl ConvWith<&FoldConvCtx<'_>> for Fold {
}
}
impl Conv for HighlightTag {
type Output = (SemanticTokenType, Vec<SemanticTokenModifier>);
fn conv(self) -> (SemanticTokenType, Vec<SemanticTokenModifier>) {
let token_type: SemanticTokenType = match self {
HighlightTag::FIELD => SemanticTokenType::MEMBER,
HighlightTag::FUNCTION => SemanticTokenType::FUNCTION,
HighlightTag::MODULE => SemanticTokenType::NAMESPACE,
HighlightTag::CONSTANT => {
return (
SemanticTokenType::VARIABLE,
vec![SemanticTokenModifier::STATIC, SemanticTokenModifier::READONLY],
)
}
HighlightTag::MACRO => SemanticTokenType::MACRO,
HighlightTag::VARIABLE => {
return (SemanticTokenType::VARIABLE, vec![SemanticTokenModifier::READONLY])
}
HighlightTag::VARIABLE_MUT => SemanticTokenType::VARIABLE,
HighlightTag::TYPE => SemanticTokenType::TYPE,
HighlightTag::TYPE_BUILTIN => SemanticTokenType::TYPE,
HighlightTag::TYPE_SELF => {
return (SemanticTokenType::TYPE, vec![SemanticTokenModifier::REFERENCE])
}
HighlightTag::TYPE_PARAM => SemanticTokenType::TYPE_PARAMETER,
HighlightTag::TYPE_LIFETIME => {
return (SemanticTokenType::LABEL, vec![SemanticTokenModifier::REFERENCE])
}
HighlightTag::LITERAL_BYTE => SemanticTokenType::NUMBER,
HighlightTag::LITERAL_NUMERIC => SemanticTokenType::NUMBER,
HighlightTag::LITERAL_CHAR => SemanticTokenType::NUMBER,
HighlightTag::LITERAL_COMMENT => {
return (SemanticTokenType::COMMENT, vec![SemanticTokenModifier::DOCUMENTATION])
}
HighlightTag::LITERAL_STRING => SemanticTokenType::STRING,
HighlightTag::LITERAL_ATTRIBUTE => SemanticTokenType::KEYWORD,
HighlightTag::KEYWORD => SemanticTokenType::KEYWORD,
HighlightTag::KEYWORD_UNSAFE => SemanticTokenType::KEYWORD,
HighlightTag::KEYWORD_CONTROL => SemanticTokenType::KEYWORD,
unknown => panic!("Unknown semantic token: {}", unknown),
};
(token_type, vec![])
}
}
impl Conv for (SemanticTokenType, Vec<SemanticTokenModifier>) {
impl Conv for Highlight {
type Output = (u32, u32);
fn conv(self) -> Self::Output {
let token_index =
semantic_tokens::supported_token_types().iter().position(|it| *it == self.0).unwrap();
let mut token_modifier_bitset = 0;
for modifier in self.1.iter() {
let modifier_index = semantic_tokens::supported_token_modifiers()
.iter()
.position(|it| it == modifier)
.unwrap();
token_modifier_bitset |= 1 << modifier_index;
let mut mods = ModifierSet::default();
let type_ = match self.tag {
HighlightTag::Field => SemanticTokenType::MEMBER,
HighlightTag::Function => SemanticTokenType::FUNCTION,
HighlightTag::Module => SemanticTokenType::NAMESPACE,
HighlightTag::Constant => {
mods |= SemanticTokenModifier::STATIC;
mods |= SemanticTokenModifier::READONLY;
CONSTANT
}
HighlightTag::Macro => SemanticTokenType::MACRO,
HighlightTag::Variable => SemanticTokenType::VARIABLE,
HighlightTag::Type => SemanticTokenType::TYPE,
HighlightTag::TypeSelf => {
mods |= SemanticTokenModifier::REFERENCE;
SemanticTokenType::TYPE
}
HighlightTag::TypeParam => SemanticTokenType::TYPE_PARAMETER,
HighlightTag::TypeLifetime => {
mods |= SemanticTokenModifier::REFERENCE;
SemanticTokenType::LABEL
}
HighlightTag::LiteralByte => SemanticTokenType::NUMBER,
HighlightTag::LiteralNumeric => SemanticTokenType::NUMBER,
HighlightTag::LiteralChar => SemanticTokenType::NUMBER,
HighlightTag::Comment => SemanticTokenType::COMMENT,
HighlightTag::LiteralString => SemanticTokenType::STRING,
HighlightTag::Attribute => ATTRIBUTE,
HighlightTag::Keyword => SemanticTokenType::KEYWORD,
};
for modifier in self.modifiers.iter() {
let modifier = match modifier {
HighlightModifier::Mutable => MUTABLE,
HighlightModifier::Unsafe => UNSAFE,
HighlightModifier::Control => CONTROL,
HighlightModifier::Builtin => BUILTIN,
};
mods |= modifier;
}
(token_index as u32, token_modifier_bitset as u32)
(semantic_tokens::type_index(type_), mods.0)
}
}

View File

@ -16,9 +16,9 @@ use lsp_types::{
CodeAction, CodeActionOrCommand, CodeActionResponse, CodeLens, Command, CompletionItem,
Diagnostic, DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange,
FoldingRangeParams, Hover, HoverContents, Location, MarkupContent, MarkupKind, Position,
PrepareRenameResponse, Range, RenameParams, SemanticTokenModifier, SemanticTokenType,
SemanticTokens, SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit,
PrepareRenameResponse, Range, RenameParams, SemanticTokens, SemanticTokensParams,
SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation,
TextDocumentIdentifier, TextEdit, WorkspaceEdit,
};
use ra_ide::{
AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind,
@ -954,7 +954,7 @@ fn highlight(world: &WorldSnapshot, file_id: FileId) -> Result<Vec<Decoration>>
.into_iter()
.map(|h| Decoration {
range: h.range.conv_with(&line_index),
tag: h.tag.to_string(),
tag: h.highlight.to_string(),
binding_hash: h.binding_hash.map(|x| x.to_string()),
})
.collect();
@ -1082,10 +1082,9 @@ pub fn handle_semantic_tokens(
let mut builder = SemanticTokensBuilder::default();
for h in world.analysis().highlight(file_id)?.into_iter() {
let type_and_modifiers: (SemanticTokenType, Vec<SemanticTokenModifier>) = h.tag.conv();
let (token_type, token_modifiers) = type_and_modifiers.conv();
builder.push(h.range.conv_with(&line_index), token_type, token_modifiers);
for highlight_range in world.analysis().highlight(file_id)?.into_iter() {
let (token_type, token_modifiers) = highlight_range.highlight.conv();
builder.push(highlight_range.range.conv_with(&line_index), token_type, token_modifiers);
}
let tokens = SemanticTokens { data: builder.build(), ..Default::default() };
@ -1104,10 +1103,9 @@ pub fn handle_semantic_tokens_range(
let mut builder = SemanticTokensBuilder::default();
for h in world.analysis().highlight_range(frange)?.into_iter() {
let type_and_modifiers: (SemanticTokenType, Vec<SemanticTokenModifier>) = h.tag.conv();
let (token_type, token_modifiers) = type_and_modifiers.conv();
builder.push(h.range.conv_with(&line_index), token_type, token_modifiers);
for highlight_range in world.analysis().highlight_range(frange)?.into_iter() {
let (token_type, token_modifiers) = highlight_range.highlight.conv();
builder.push(highlight_range.range.conv_with(&line_index), token_type, token_modifiers);
}
let tokens = SemanticTokens { data: builder.build(), ..Default::default() };

View File

@ -1,8 +1,18 @@
//! Semantic Tokens helpers
use std::ops;
use lsp_types::{Range, SemanticToken, SemanticTokenModifier, SemanticTokenType};
const SUPPORTED_TYPES: &[SemanticTokenType] = &[
pub(crate) const ATTRIBUTE: SemanticTokenType = SemanticTokenType::new("attribute");
pub(crate) const CONSTANT: SemanticTokenType = SemanticTokenType::new("constant");
pub(crate) const MUTABLE: SemanticTokenModifier = SemanticTokenModifier::new("mutable");
pub(crate) const UNSAFE: SemanticTokenModifier = SemanticTokenModifier::new("unsafe");
pub(crate) const CONTROL: SemanticTokenModifier = SemanticTokenModifier::new("control");
pub(crate) const BUILTIN: SemanticTokenModifier = SemanticTokenModifier::new("builtin");
pub(crate) const SUPPORTED_TYPES: &[SemanticTokenType] = &[
SemanticTokenType::COMMENT,
SemanticTokenType::KEYWORD,
SemanticTokenType::STRING,
@ -23,9 +33,11 @@ const SUPPORTED_TYPES: &[SemanticTokenType] = &[
SemanticTokenType::VARIABLE,
SemanticTokenType::PARAMETER,
SemanticTokenType::LABEL,
ATTRIBUTE,
CONSTANT,
];
const SUPPORTED_MODIFIERS: &[SemanticTokenModifier] = &[
pub(crate) const SUPPORTED_MODIFIERS: &[SemanticTokenModifier] = &[
SemanticTokenModifier::DOCUMENTATION,
SemanticTokenModifier::DECLARATION,
SemanticTokenModifier::DEFINITION,
@ -36,16 +48,20 @@ const SUPPORTED_MODIFIERS: &[SemanticTokenModifier] = &[
SemanticTokenModifier::ASYNC,
SemanticTokenModifier::VOLATILE,
SemanticTokenModifier::READONLY,
MUTABLE,
UNSAFE,
CONTROL,
BUILTIN,
];
/// Token types that the server supports
pub(crate) fn supported_token_types() -> &'static [SemanticTokenType] {
SUPPORTED_TYPES
}
#[derive(Default)]
pub(crate) struct ModifierSet(pub(crate) u32);
/// Token modifiers that the server supports
pub(crate) fn supported_token_modifiers() -> &'static [SemanticTokenModifier] {
SUPPORTED_MODIFIERS
impl ops::BitOrAssign<SemanticTokenModifier> for ModifierSet {
fn bitor_assign(&mut self, rhs: SemanticTokenModifier) {
let idx = SUPPORTED_MODIFIERS.iter().position(|it| it == &rhs).unwrap();
self.0 |= 1 << idx;
}
}
/// Tokens are encoded relative to each other.
@ -92,3 +108,7 @@ impl SemanticTokensBuilder {
self.data
}
}
pub fn type_index(type_: SemanticTokenType) -> u32 {
SUPPORTED_TYPES.iter().position(|it| *it == type_).unwrap() as u32
}

View File

@ -380,6 +380,28 @@
}
}
],
"semanticTokenTypes": [
{
"id": "attribute"
},
{
"id": "constant"
}
],
"semanticTokenModifiers": [
{
"id": "mutable"
},
{
"id": "unsafe"
},
{
"id": "control"
},
{
"id": "builtin"
}
],
"semanticTokenStyleDefaults": [
{
"selector": "*.mutable",
@ -392,6 +414,12 @@
"highContrast": {
"fontStyle": "underline"
}
},
{
"selector": "constant",
"scope": [
"entity.name.constant"
]
}
]
}