Improve syntax highlighting and outline view for Elixir (#2582)
Fixes https://linear.app/zed-industries/issue/Z-2208/outline-view-doesnt-differentiate-between-overloaded-functions Fixes https://linear.app/zed-industries/issue/Z-2205/elixir-syntax-highlighting-not-working-properly-for-doc-attributes-and This PR improves syntax highlighting and outline view in Elixir. It's common to overload elixir functions, with many different versions of the function for different patterns of parameters, so I updated the outline view to show functions' parameters in Elixir. But if we showed functions the same way in the *breadcrumbs*, it would take up too much space. So I added a new capture in languages' `outline` queries called `@context.extra`, which is included in the outline view, but not in breadcrumbs. Release Notes: - Improved syntax highlighting of doc attributes and special macros in Elixir - Updated the outline view in Elixir to display function parameters, to allow differentiating between function overloads.
This commit is contained in:
commit
0d90c6d02e
9 changed files with 106 additions and 59 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -7342,8 +7342,8 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tree-sitter-elixir"
|
name = "tree-sitter-elixir"
|
||||||
version = "0.19.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/elixir-lang/tree-sitter-elixir?rev=05e3631c6a0701c1fa518b0fee7be95a2ceef5e2#05e3631c6a0701c1fa518b0fee7be95a2ceef5e2"
|
source = "git+https://github.com/elixir-lang/tree-sitter-elixir?rev=4ba9dab6e2602960d95b2b625f3386c27e08084e#4ba9dab6e2602960d95b2b625f3386c27e08084e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"tree-sitter",
|
"tree-sitter",
|
||||||
|
|
|
@ -2253,7 +2253,7 @@ impl BufferSnapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> {
|
pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> {
|
||||||
self.outline_items_containing(0..self.len(), theme)
|
self.outline_items_containing(0..self.len(), true, theme)
|
||||||
.map(Outline::new)
|
.map(Outline::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2265,6 +2265,7 @@ impl BufferSnapshot {
|
||||||
let position = position.to_offset(self);
|
let position = position.to_offset(self);
|
||||||
let mut items = self.outline_items_containing(
|
let mut items = self.outline_items_containing(
|
||||||
position.saturating_sub(1)..self.len().min(position + 1),
|
position.saturating_sub(1)..self.len().min(position + 1),
|
||||||
|
false,
|
||||||
theme,
|
theme,
|
||||||
)?;
|
)?;
|
||||||
let mut prev_depth = None;
|
let mut prev_depth = None;
|
||||||
|
@ -2279,6 +2280,7 @@ impl BufferSnapshot {
|
||||||
fn outline_items_containing(
|
fn outline_items_containing(
|
||||||
&self,
|
&self,
|
||||||
range: Range<usize>,
|
range: Range<usize>,
|
||||||
|
include_extra_context: bool,
|
||||||
theme: Option<&SyntaxTheme>,
|
theme: Option<&SyntaxTheme>,
|
||||||
) -> Option<Vec<OutlineItem<Anchor>>> {
|
) -> Option<Vec<OutlineItem<Anchor>>> {
|
||||||
let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| {
|
let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| {
|
||||||
|
@ -2313,7 +2315,10 @@ impl BufferSnapshot {
|
||||||
let node_is_name;
|
let node_is_name;
|
||||||
if capture.index == config.name_capture_ix {
|
if capture.index == config.name_capture_ix {
|
||||||
node_is_name = true;
|
node_is_name = true;
|
||||||
} else if Some(capture.index) == config.context_capture_ix {
|
} else if Some(capture.index) == config.context_capture_ix
|
||||||
|
|| (Some(capture.index) == config.extra_context_capture_ix
|
||||||
|
&& include_extra_context)
|
||||||
|
{
|
||||||
node_is_name = false;
|
node_is_name = false;
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
|
@ -2340,10 +2345,12 @@ impl BufferSnapshot {
|
||||||
buffer_ranges.first().unwrap().0.start..buffer_ranges.last().unwrap().0.end,
|
buffer_ranges.first().unwrap().0.start..buffer_ranges.last().unwrap().0.end,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
let mut last_buffer_range_end = 0;
|
||||||
for (buffer_range, is_name) in buffer_ranges {
|
for (buffer_range, is_name) in buffer_ranges {
|
||||||
if !text.is_empty() {
|
if !text.is_empty() && buffer_range.start > last_buffer_range_end {
|
||||||
text.push(' ');
|
text.push(' ');
|
||||||
}
|
}
|
||||||
|
last_buffer_range_end = buffer_range.end;
|
||||||
if is_name {
|
if is_name {
|
||||||
let mut start = text.len();
|
let mut start = text.len();
|
||||||
let end = start + buffer_range.len();
|
let end = start + buffer_range.len();
|
||||||
|
|
|
@ -592,6 +592,52 @@ async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_outline_with_extra_context(cx: &mut gpui::TestAppContext) {
|
||||||
|
let language = javascript_lang()
|
||||||
|
.with_outline_query(
|
||||||
|
r#"
|
||||||
|
(function_declaration
|
||||||
|
"function" @context
|
||||||
|
name: (_) @name
|
||||||
|
parameters: (formal_parameters
|
||||||
|
"(" @context.extra
|
||||||
|
")" @context.extra)) @item
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let text = r#"
|
||||||
|
function a() {}
|
||||||
|
function b(c) {}
|
||||||
|
"#
|
||||||
|
.unindent();
|
||||||
|
|
||||||
|
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Arc::new(language), cx));
|
||||||
|
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
|
||||||
|
|
||||||
|
// extra context nodes are included in the outline.
|
||||||
|
let outline = snapshot.outline(None).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
outline
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.map(|item| (item.text.as_str(), item.depth))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
&[("function a()", 0), ("function b( )", 0),]
|
||||||
|
);
|
||||||
|
|
||||||
|
// extra context nodes do not appear in breadcrumbs.
|
||||||
|
let symbols = snapshot.symbols_containing(3, None).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
symbols
|
||||||
|
.iter()
|
||||||
|
.map(|item| (item.text.as_str(), item.depth))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
&[("function a", 0)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
|
async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
|
||||||
let text = r#"
|
let text = r#"
|
||||||
|
|
|
@ -455,6 +455,7 @@ struct OutlineConfig {
|
||||||
item_capture_ix: u32,
|
item_capture_ix: u32,
|
||||||
name_capture_ix: u32,
|
name_capture_ix: u32,
|
||||||
context_capture_ix: Option<u32>,
|
context_capture_ix: Option<u32>,
|
||||||
|
extra_context_capture_ix: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InjectionConfig {
|
struct InjectionConfig {
|
||||||
|
@ -771,6 +772,7 @@ impl LanguageRegistry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
log::error!("failed to load language {name} - {err}");
|
||||||
let mut state = this.state.write();
|
let mut state = this.state.write();
|
||||||
state.mark_language_loaded(id);
|
state.mark_language_loaded(id);
|
||||||
if let Some(mut txs) = state.loading_languages.remove(&id) {
|
if let Some(mut txs) = state.loading_languages.remove(&id) {
|
||||||
|
@ -1059,34 +1061,22 @@ impl Language {
|
||||||
|
|
||||||
pub fn with_queries(mut self, queries: LanguageQueries) -> Result<Self> {
|
pub fn with_queries(mut self, queries: LanguageQueries) -> Result<Self> {
|
||||||
if let Some(query) = queries.highlights {
|
if let Some(query) = queries.highlights {
|
||||||
self = self
|
self = self.with_highlights_query(query.as_ref())?;
|
||||||
.with_highlights_query(query.as_ref())
|
|
||||||
.expect("failed to evaluate highlights query");
|
|
||||||
}
|
}
|
||||||
if let Some(query) = queries.brackets {
|
if let Some(query) = queries.brackets {
|
||||||
self = self
|
self = self.with_brackets_query(query.as_ref())?;
|
||||||
.with_brackets_query(query.as_ref())
|
|
||||||
.expect("failed to load brackets query");
|
|
||||||
}
|
}
|
||||||
if let Some(query) = queries.indents {
|
if let Some(query) = queries.indents {
|
||||||
self = self
|
self = self.with_indents_query(query.as_ref())?;
|
||||||
.with_indents_query(query.as_ref())
|
|
||||||
.expect("failed to load indents query");
|
|
||||||
}
|
}
|
||||||
if let Some(query) = queries.outline {
|
if let Some(query) = queries.outline {
|
||||||
self = self
|
self = self.with_outline_query(query.as_ref())?;
|
||||||
.with_outline_query(query.as_ref())
|
|
||||||
.expect("failed to load outline query");
|
|
||||||
}
|
}
|
||||||
if let Some(query) = queries.injections {
|
if let Some(query) = queries.injections {
|
||||||
self = self
|
self = self.with_injection_query(query.as_ref())?;
|
||||||
.with_injection_query(query.as_ref())
|
|
||||||
.expect("failed to load injection query");
|
|
||||||
}
|
}
|
||||||
if let Some(query) = queries.overrides {
|
if let Some(query) = queries.overrides {
|
||||||
self = self
|
self = self.with_override_query(query.as_ref())?;
|
||||||
.with_override_query(query.as_ref())
|
|
||||||
.expect("failed to load override query");
|
|
||||||
}
|
}
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
@ -1102,12 +1092,14 @@ impl Language {
|
||||||
let mut item_capture_ix = None;
|
let mut item_capture_ix = None;
|
||||||
let mut name_capture_ix = None;
|
let mut name_capture_ix = None;
|
||||||
let mut context_capture_ix = None;
|
let mut context_capture_ix = None;
|
||||||
|
let mut extra_context_capture_ix = None;
|
||||||
get_capture_indices(
|
get_capture_indices(
|
||||||
&query,
|
&query,
|
||||||
&mut [
|
&mut [
|
||||||
("item", &mut item_capture_ix),
|
("item", &mut item_capture_ix),
|
||||||
("name", &mut name_capture_ix),
|
("name", &mut name_capture_ix),
|
||||||
("context", &mut context_capture_ix),
|
("context", &mut context_capture_ix),
|
||||||
|
("context.extra", &mut extra_context_capture_ix),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if let Some((item_capture_ix, name_capture_ix)) = item_capture_ix.zip(name_capture_ix) {
|
if let Some((item_capture_ix, name_capture_ix)) = item_capture_ix.zip(name_capture_ix) {
|
||||||
|
@ -1116,6 +1108,7 @@ impl Language {
|
||||||
item_capture_ix,
|
item_capture_ix,
|
||||||
name_capture_ix,
|
name_capture_ix,
|
||||||
context_capture_ix,
|
context_capture_ix,
|
||||||
|
extra_context_capture_ix,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(self)
|
Ok(self)
|
||||||
|
|
|
@ -106,7 +106,7 @@ tree-sitter = "0.20"
|
||||||
tree-sitter-c = "0.20.1"
|
tree-sitter-c = "0.20.1"
|
||||||
tree-sitter-cpp = "0.20.0"
|
tree-sitter-cpp = "0.20.0"
|
||||||
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
|
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
|
||||||
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "05e3631c6a0701c1fa518b0fee7be95a2ceef5e2" }
|
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" }
|
||||||
tree-sitter-embedded-template = "0.20.0"
|
tree-sitter-embedded-template = "0.20.0"
|
||||||
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
|
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
|
||||||
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
|
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
|
||||||
|
|
|
@ -1,20 +1,5 @@
|
||||||
["when" "and" "or" "not" "in" "not in" "fn" "do" "end" "catch" "rescue" "after" "else"] @keyword
|
["when" "and" "or" "not" "in" "not in" "fn" "do" "end" "catch" "rescue" "after" "else"] @keyword
|
||||||
|
|
||||||
(unary_operator
|
|
||||||
operator: "@" @comment.doc
|
|
||||||
operand: (call
|
|
||||||
target: (identifier) @comment.doc.__attribute__
|
|
||||||
(arguments
|
|
||||||
[
|
|
||||||
(string) @comment.doc
|
|
||||||
(charlist) @comment.doc
|
|
||||||
(sigil
|
|
||||||
quoted_start: _ @comment.doc
|
|
||||||
quoted_end: _ @comment.doc) @comment.doc
|
|
||||||
(boolean) @comment.doc
|
|
||||||
]))
|
|
||||||
(#match? @comment.doc.__attribute__ "^(moduledoc|typedoc|doc)$"))
|
|
||||||
|
|
||||||
(unary_operator
|
(unary_operator
|
||||||
operator: "&"
|
operator: "&"
|
||||||
operand: (integer) @operator)
|
operand: (integer) @operator)
|
||||||
|
@ -84,6 +69,11 @@
|
||||||
quoted_start: _ @string.special
|
quoted_start: _ @string.special
|
||||||
quoted_end: _ @string.special) @string.special
|
quoted_end: _ @string.special) @string.special
|
||||||
|
|
||||||
|
(
|
||||||
|
(identifier) @comment.unused
|
||||||
|
(#match? @comment.unused "^_")
|
||||||
|
)
|
||||||
|
|
||||||
(call
|
(call
|
||||||
target: [
|
target: [
|
||||||
(identifier) @function
|
(identifier) @function
|
||||||
|
@ -99,17 +89,12 @@
|
||||||
(binary_operator
|
(binary_operator
|
||||||
left: (identifier) @function
|
left: (identifier) @function
|
||||||
operator: "when")
|
operator: "when")
|
||||||
|
(binary_operator
|
||||||
|
operator: "|>"
|
||||||
|
right: (identifier))
|
||||||
])
|
])
|
||||||
(#match? @keyword "^(def|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defp)$"))
|
(#match? @keyword "^(def|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defp)$"))
|
||||||
|
|
||||||
(call
|
|
||||||
target: (identifier) @keyword
|
|
||||||
(arguments
|
|
||||||
(binary_operator
|
|
||||||
operator: "|>"
|
|
||||||
right: (identifier)))
|
|
||||||
(#match? @keyword "^(def|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defp)$"))
|
|
||||||
|
|
||||||
(binary_operator
|
(binary_operator
|
||||||
operator: "|>"
|
operator: "|>"
|
||||||
right: (identifier) @function)
|
right: (identifier) @function)
|
||||||
|
@ -127,10 +112,18 @@
|
||||||
(#match? @constant.builtin "^(__MODULE__|__DIR__|__ENV__|__CALLER__|__STACKTRACE__)$")
|
(#match? @constant.builtin "^(__MODULE__|__DIR__|__ENV__|__CALLER__|__STACKTRACE__)$")
|
||||||
)
|
)
|
||||||
|
|
||||||
(
|
(unary_operator
|
||||||
(identifier) @comment.unused
|
operator: "@" @comment.doc
|
||||||
(#match? @comment.unused "^_")
|
operand: (call
|
||||||
)
|
target: (identifier) @__attribute__ @comment.doc
|
||||||
|
(arguments
|
||||||
|
[
|
||||||
|
(string)
|
||||||
|
(charlist)
|
||||||
|
(sigil)
|
||||||
|
(boolean)
|
||||||
|
] @comment.doc))
|
||||||
|
(#match? @__attribute__ "^(moduledoc|typedoc|doc)$"))
|
||||||
|
|
||||||
(comment) @comment
|
(comment) @comment
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
[
|
(call) @indent
|
||||||
(call)
|
|
||||||
] @indent
|
|
||||||
|
|
||||||
(_ "[" "]" @end) @indent
|
(_ "[" "]" @end) @indent
|
||||||
(_ "{" "}" @end) @indent
|
(_ "{" "}" @end) @indent
|
||||||
|
|
|
@ -8,9 +8,19 @@
|
||||||
(arguments
|
(arguments
|
||||||
[
|
[
|
||||||
(identifier) @name
|
(identifier) @name
|
||||||
(call target: (identifier) @name)
|
(call
|
||||||
|
target: (identifier) @name
|
||||||
|
(arguments
|
||||||
|
"(" @context.extra
|
||||||
|
_* @context.extra
|
||||||
|
")" @context.extra))
|
||||||
(binary_operator
|
(binary_operator
|
||||||
left: (call target: (identifier) @name)
|
left: (call
|
||||||
|
target: (identifier) @name
|
||||||
|
(arguments
|
||||||
|
"(" @context.extra
|
||||||
|
_* @context.extra
|
||||||
|
")" @context.extra))
|
||||||
operator: "when")
|
operator: "when")
|
||||||
])
|
])
|
||||||
(#match? @context "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item
|
(#match? @context "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item
|
||||||
|
|
|
@ -327,10 +327,10 @@ mod tests {
|
||||||
.map(|item| (item.text.as_str(), item.depth))
|
.map(|item| (item.text.as_str(), item.depth))
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
&[
|
&[
|
||||||
("function a ( )", 0),
|
("function a()", 0),
|
||||||
("async function a2 ( )", 1),
|
("async function a2()", 1),
|
||||||
("let b", 0),
|
("let b", 0),
|
||||||
("function getB ( )", 0),
|
("function getB()", 0),
|
||||||
("const d", 0),
|
("const d", 0),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue