Add support for highlight properties that track local variables

This commit is contained in:
Max Brunsfeld 2019-05-09 12:02:18 -07:00
parent d78ac581f3
commit a7d02e7276
4 changed files with 117 additions and 5 deletions

View file

@ -138,7 +138,8 @@ impl Default for Theme {
"tag": 18,
"type": 23,
"type.builtin": {"color": 23, "bold": true},
"variable.builtin": {"bold": true}
"variable.builtin": {"bold": true},
"variable.parameter": {"underline": true}
}
"#,
)

View file

@ -16,6 +16,7 @@ use tree_sitter::{self, PropertyStateJSON, PropertyTransitionJSON};
#[serde(untagged)]
enum PropertyValue {
Number(isize),
Boolean(bool),
String(String),
Object(PropertySet),
Array(Vec<PropertyValue>),
@ -737,8 +738,8 @@ fn parse_sass_value(value: &Value) -> Result<PropertyValue> {
}
Value::Color(_, Some(name)) => Ok(PropertyValue::String(name.clone())),
Value::Numeric(n, _) => Ok(PropertyValue::Number(n.to_integer())),
Value::True => Ok(PropertyValue::String("true".to_string())),
Value::False => Ok(PropertyValue::String("false".to_string())),
Value::True => Ok(PropertyValue::Boolean(true)),
Value::False => Ok(PropertyValue::Boolean(false)),
_ => Err(Error(format!(
"Property values must be strings or function calls. Got {:?}",
value

View file

@ -41,7 +41,11 @@ struct Injection {
#[derive(Debug)]
pub struct Properties {
highlight: Option<Highlight>,
highlight_nonlocal: Option<Highlight>,
injections: Vec<Injection>,
local_scope: Option<bool>,
local_definition: bool,
local_reference: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
@ -73,15 +77,24 @@ pub enum Highlight {
TypeBuiltin,
Variable,
VariableBuiltin,
VariableParameter,
Unknown,
}
#[derive(Debug)]
struct Scope<'a> {
inherits: bool,
local_defs: Vec<(&'a str, Highlight)>,
}
struct Layer<'a> {
_tree: Tree,
cursor: TreePropertyCursor<'a, Properties>,
ranges: Vec<Range>,
at_node_end: bool,
depth: usize,
scope_stack: Vec<Scope<'a>>,
local_highlight: Option<Highlight>,
}
struct Highlighter<'a, T>
@ -144,10 +157,22 @@ enum InjectionContentJSON {
#[derive(Debug, Deserialize)]
struct PropertiesJSON {
highlight: Option<Highlight>,
#[serde(rename = "highlight-nonlocal")]
highlight_nonlocal: Option<Highlight>,
#[serde(rename = "injection-language")]
injection_language: Option<InjectionLanguageJSON>,
#[serde(rename = "injection-content")]
injection_content: Option<InjectionContentJSON>,
#[serde(default, rename = "local-scope")]
local_scope: bool,
#[serde(default, rename = "local-scope-inherit")]
local_scope_inherit: bool,
#[serde(default, rename = "local-definition")]
local_definition: bool,
#[serde(default, rename = "local-reference")]
local_reference: bool,
}
#[derive(Debug)]
@ -282,6 +307,14 @@ impl Properties {
Ok(Self {
highlight: json.highlight,
highlight_nonlocal: json.highlight_nonlocal,
local_scope: if json.local_scope {
Some(json.local_scope_inherit)
} else {
None
},
local_definition: json.local_definition,
local_reference: json.local_reference,
injections,
})
}
@ -629,6 +662,7 @@ where
while !self.layers.is_empty() {
let first_layer = &self.layers[0];
let local_highlight = first_layer.local_highlight;
let properties = &first_layer.cursor.node_properties();
// Add any injections for the current node.
@ -657,7 +691,10 @@ where
// Determine if any scopes start or end at the current position.
let scope_event;
if let Some(highlight) = properties.highlight {
if let Some(highlight) = local_highlight
.or(properties.highlight_nonlocal)
.or(properties.highlight)
{
let next_offset = cmp::min(self.source.len(), self.layers[0].offset());
// Before returning any highlight boundaries, return any remaining slice of
@ -748,6 +785,11 @@ impl<'a> Layer<'a> {
ranges,
depth,
at_node_end: false,
scope_stack: vec![Scope {
inherits: false,
local_defs: Vec::new(),
}],
local_highlight: None,
}
}
@ -770,17 +812,79 @@ impl<'a> Layer<'a> {
}
fn advance(&mut self) -> bool {
// Clear the current local highlighting class, which may be re-populated
// if we enter a node that represents a local definition or local reference.
self.local_highlight = None;
// Step through the tree in a depth-first traversal, stopping at both
// the start and end position of every node.
if self.at_node_end {
self.leave_node();
if self.cursor.goto_next_sibling() {
self.enter_node();
self.at_node_end = false;
} else if !self.cursor.goto_parent() {
return false;
}
} else if !self.cursor.goto_first_child() {
} else if self.cursor.goto_first_child() {
self.enter_node();
} else {
self.at_node_end = true;
}
true
}
fn enter_node(&mut self) {
let node = self.cursor.node();
let props = self.cursor.node_properties();
let node_text = if props.local_definition || props.local_reference {
node.utf8_text(self.cursor.source()).ok()
} else {
None
};
// If this node represents a local definition, then record its highlighting class
// and store the highlighting class in the current local scope.
if props.local_definition {
if let (Some(text), Some(inner_scope), Some(highlight)) =
(node_text, self.scope_stack.last_mut(), props.highlight)
{
self.local_highlight = props.highlight;
if let Err(i) = inner_scope.local_defs.binary_search_by_key(&text, |e| e.0) {
inner_scope.local_defs.insert(i, (text, highlight));
}
}
}
// If this node represents a local reference, then look it up in the current scope
// stack. If a local definition is found, record its highlighting class.
else if props.local_reference {
if let Some(text) = node_text {
for scope in self.scope_stack.iter().rev() {
if let Ok(i) = scope.local_defs.binary_search_by_key(&text, |e| e.0) {
self.local_highlight = Some(scope.local_defs[i].1);
break;
}
if !scope.inherits {
break;
}
}
}
}
// If this node represents a new local scope, then push it onto the scope stack.
if let Some(inherits) = props.local_scope {
self.scope_stack.push(Scope {
inherits,
local_defs: Vec::new(),
});
}
}
fn leave_node(&mut self) {
let props = self.cursor.node_properties();
if props.local_scope.is_some() {
self.scope_stack.pop();
}
}
}
impl<'de> Deserialize<'de> for Highlight {
@ -815,6 +919,7 @@ impl<'de> Deserialize<'de> for Highlight {
"type.builtin" => Ok(Highlight::TypeBuiltin),
"variable" => Ok(Highlight::Variable),
"variable.builtin" => Ok(Highlight::VariableBuiltin),
"variable.parameter" => Ok(Highlight::VariableParameter),
"tag" => Ok(Highlight::Tag),
_ => Ok(Highlight::Unknown),
}
@ -852,6 +957,7 @@ impl Serialize for Highlight {
Highlight::TypeBuiltin => serializer.serialize_str("type.builtin"),
Highlight::Variable => serializer.serialize_str("variable"),
Highlight::VariableBuiltin => serializer.serialize_str("variable.builtin"),
Highlight::VariableParameter => serializer.serialize_str("variable.parameter"),
Highlight::Tag => serializer.serialize_str("tag"),
Highlight::Unknown => serializer.serialize_str(""),
}

View file

@ -755,6 +755,10 @@ impl<'a, P> TreePropertyCursor<'a, P> {
}
}
pub fn source(&self) -> &'a [u8] {
&self.source
}
fn next_state(&self, node_child_index: usize) -> usize {
let state = self.current_state();
let node_field_id = self.cursor.field_id();