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]]
|
||||
name = "tree-sitter-elixir"
|
||||
version = "0.19.0"
|
||||
source = "git+https://github.com/elixir-lang/tree-sitter-elixir?rev=05e3631c6a0701c1fa518b0fee7be95a2ceef5e2#05e3631c6a0701c1fa518b0fee7be95a2ceef5e2"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/elixir-lang/tree-sitter-elixir?rev=4ba9dab6e2602960d95b2b625f3386c27e08084e#4ba9dab6e2602960d95b2b625f3386c27e08084e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
|
|
|
@ -2253,7 +2253,7 @@ impl BufferSnapshot {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -2265,6 +2265,7 @@ impl BufferSnapshot {
|
|||
let position = position.to_offset(self);
|
||||
let mut items = self.outline_items_containing(
|
||||
position.saturating_sub(1)..self.len().min(position + 1),
|
||||
false,
|
||||
theme,
|
||||
)?;
|
||||
let mut prev_depth = None;
|
||||
|
@ -2279,6 +2280,7 @@ impl BufferSnapshot {
|
|||
fn outline_items_containing(
|
||||
&self,
|
||||
range: Range<usize>,
|
||||
include_extra_context: bool,
|
||||
theme: Option<&SyntaxTheme>,
|
||||
) -> Option<Vec<OutlineItem<Anchor>>> {
|
||||
let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| {
|
||||
|
@ -2313,7 +2315,10 @@ impl BufferSnapshot {
|
|||
let node_is_name;
|
||||
if capture.index == config.name_capture_ix {
|
||||
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;
|
||||
} else {
|
||||
continue;
|
||||
|
@ -2340,10 +2345,12 @@ impl BufferSnapshot {
|
|||
buffer_ranges.first().unwrap().0.start..buffer_ranges.last().unwrap().0.end,
|
||||
true,
|
||||
);
|
||||
let mut last_buffer_range_end = 0;
|
||||
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(' ');
|
||||
}
|
||||
last_buffer_range_end = buffer_range.end;
|
||||
if is_name {
|
||||
let mut start = text.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]
|
||||
async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
|
||||
let text = r#"
|
||||
|
|
|
@ -455,6 +455,7 @@ struct OutlineConfig {
|
|||
item_capture_ix: u32,
|
||||
name_capture_ix: u32,
|
||||
context_capture_ix: Option<u32>,
|
||||
extra_context_capture_ix: Option<u32>,
|
||||
}
|
||||
|
||||
struct InjectionConfig {
|
||||
|
@ -771,6 +772,7 @@ impl LanguageRegistry {
|
|||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("failed to load language {name} - {err}");
|
||||
let mut state = this.state.write();
|
||||
state.mark_language_loaded(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> {
|
||||
if let Some(query) = queries.highlights {
|
||||
self = self
|
||||
.with_highlights_query(query.as_ref())
|
||||
.expect("failed to evaluate highlights query");
|
||||
self = self.with_highlights_query(query.as_ref())?;
|
||||
}
|
||||
if let Some(query) = queries.brackets {
|
||||
self = self
|
||||
.with_brackets_query(query.as_ref())
|
||||
.expect("failed to load brackets query");
|
||||
self = self.with_brackets_query(query.as_ref())?;
|
||||
}
|
||||
if let Some(query) = queries.indents {
|
||||
self = self
|
||||
.with_indents_query(query.as_ref())
|
||||
.expect("failed to load indents query");
|
||||
self = self.with_indents_query(query.as_ref())?;
|
||||
}
|
||||
if let Some(query) = queries.outline {
|
||||
self = self
|
||||
.with_outline_query(query.as_ref())
|
||||
.expect("failed to load outline query");
|
||||
self = self.with_outline_query(query.as_ref())?;
|
||||
}
|
||||
if let Some(query) = queries.injections {
|
||||
self = self
|
||||
.with_injection_query(query.as_ref())
|
||||
.expect("failed to load injection query");
|
||||
self = self.with_injection_query(query.as_ref())?;
|
||||
}
|
||||
if let Some(query) = queries.overrides {
|
||||
self = self
|
||||
.with_override_query(query.as_ref())
|
||||
.expect("failed to load override query");
|
||||
self = self.with_override_query(query.as_ref())?;
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
@ -1102,12 +1092,14 @@ impl Language {
|
|||
let mut item_capture_ix = None;
|
||||
let mut name_capture_ix = None;
|
||||
let mut context_capture_ix = None;
|
||||
let mut extra_context_capture_ix = None;
|
||||
get_capture_indices(
|
||||
&query,
|
||||
&mut [
|
||||
("item", &mut item_capture_ix),
|
||||
("name", &mut name_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) {
|
||||
|
@ -1116,6 +1108,7 @@ impl Language {
|
|||
item_capture_ix,
|
||||
name_capture_ix,
|
||||
context_capture_ix,
|
||||
extra_context_capture_ix,
|
||||
});
|
||||
}
|
||||
Ok(self)
|
||||
|
|
|
@ -106,7 +106,7 @@ tree-sitter = "0.20"
|
|||
tree-sitter-c = "0.20.1"
|
||||
tree-sitter-cpp = "0.20.0"
|
||||
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-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" }
|
||||
|
|
|
@ -1,20 +1,5 @@
|
|||
["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
|
||||
operator: "&"
|
||||
operand: (integer) @operator)
|
||||
|
@ -84,6 +69,11 @@
|
|||
quoted_start: _ @string.special
|
||||
quoted_end: _ @string.special) @string.special
|
||||
|
||||
(
|
||||
(identifier) @comment.unused
|
||||
(#match? @comment.unused "^_")
|
||||
)
|
||||
|
||||
(call
|
||||
target: [
|
||||
(identifier) @function
|
||||
|
@ -99,17 +89,12 @@
|
|||
(binary_operator
|
||||
left: (identifier) @function
|
||||
operator: "when")
|
||||
(binary_operator
|
||||
operator: "|>"
|
||||
right: (identifier))
|
||||
])
|
||||
(#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
|
||||
operator: "|>"
|
||||
right: (identifier) @function)
|
||||
|
@ -127,10 +112,18 @@
|
|||
(#match? @constant.builtin "^(__MODULE__|__DIR__|__ENV__|__CALLER__|__STACKTRACE__)$")
|
||||
)
|
||||
|
||||
(
|
||||
(identifier) @comment.unused
|
||||
(#match? @comment.unused "^_")
|
||||
)
|
||||
(unary_operator
|
||||
operator: "@" @comment.doc
|
||||
operand: (call
|
||||
target: (identifier) @__attribute__ @comment.doc
|
||||
(arguments
|
||||
[
|
||||
(string)
|
||||
(charlist)
|
||||
(sigil)
|
||||
(boolean)
|
||||
] @comment.doc))
|
||||
(#match? @__attribute__ "^(moduledoc|typedoc|doc)$"))
|
||||
|
||||
(comment) @comment
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
[
|
||||
(call)
|
||||
] @indent
|
||||
(call) @indent
|
||||
|
||||
(_ "[" "]" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
|
|
|
@ -8,9 +8,19 @@
|
|||
(arguments
|
||||
[
|
||||
(identifier) @name
|
||||
(call target: (identifier) @name)
|
||||
(call
|
||||
target: (identifier) @name
|
||||
(arguments
|
||||
"(" @context.extra
|
||||
_* @context.extra
|
||||
")" @context.extra))
|
||||
(binary_operator
|
||||
left: (call target: (identifier) @name)
|
||||
left: (call
|
||||
target: (identifier) @name
|
||||
(arguments
|
||||
"(" @context.extra
|
||||
_* @context.extra
|
||||
")" @context.extra))
|
||||
operator: "when")
|
||||
])
|
||||
(#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))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
("function a ( )", 0),
|
||||
("async function a2 ( )", 1),
|
||||
("function a()", 0),
|
||||
("async function a2()", 1),
|
||||
("let b", 0),
|
||||
("function getB ( )", 0),
|
||||
("function getB()", 0),
|
||||
("const d", 0),
|
||||
]
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue