mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-08-16 14:40:23 +03:00
[symbol-provider-tree-sitter] Add specs for context
, tag
, icon
This commit is contained in:
parent
58d9a0393e
commit
3565ea822e
@ -3,46 +3,54 @@
|
|||||||
(atx_h1_marker)
|
(atx_h1_marker)
|
||||||
(heading_content) @name) @definition.heading
|
(heading_content) @name) @definition.heading
|
||||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||||
(#set! symbol.prepend "· "))
|
(#set! symbol.prepend "· ")
|
||||||
|
(#set! symbol.icon "chevron-right"))
|
||||||
|
|
||||||
((atx_heading
|
((atx_heading
|
||||||
(atx_h2_marker)
|
(atx_h2_marker)
|
||||||
(heading_content) @name) @definition.heading
|
(heading_content) @name) @definition.heading
|
||||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||||
(#set! symbol.prepend "·· "))
|
(#set! symbol.prepend "·· ")
|
||||||
|
(#set! symbol.icon "chevron-right"))
|
||||||
|
|
||||||
((atx_heading
|
((atx_heading
|
||||||
(atx_h3_marker)
|
(atx_h3_marker)
|
||||||
(heading_content) @name) @definition.heading
|
(heading_content) @name) @definition.heading
|
||||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||||
(#set! symbol.prepend "··· "))
|
(#set! symbol.prepend "··· ")
|
||||||
|
(#set! symbol.icon "chevron-right"))
|
||||||
|
|
||||||
((atx_heading
|
((atx_heading
|
||||||
(atx_h4_marker)
|
(atx_h4_marker)
|
||||||
(heading_content) @name) @definition.heading
|
(heading_content) @name) @definition.heading
|
||||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||||
(#set! symbol.prepend "···· "))
|
(#set! symbol.prepend "···· ")
|
||||||
|
(#set! symbol.icon "chevron-right"))
|
||||||
|
|
||||||
((atx_heading
|
((atx_heading
|
||||||
(atx_h5_marker)
|
(atx_h5_marker)
|
||||||
(heading_content) @name) @definition.heading
|
(heading_content) @name) @definition.heading
|
||||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||||
(#set! symbol.prepend "····· "))
|
(#set! symbol.prepend "····· ")
|
||||||
|
(#set! symbol.icon "chevron-right"))
|
||||||
|
|
||||||
((atx_heading
|
((atx_heading
|
||||||
(atx_h6_marker)
|
(atx_h6_marker)
|
||||||
(heading_content) @name) @definition.heading
|
(heading_content) @name) @definition.heading
|
||||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||||
(#set! symbol.prepend "······ "))
|
(#set! symbol.prepend "······ ")
|
||||||
|
(#set! symbol.icon "chevron-right"))
|
||||||
|
|
||||||
((setext_heading
|
((setext_heading
|
||||||
(heading_content) @name) @definition.heading
|
(heading_content) @name) @definition.heading
|
||||||
(setext_h1_underline)
|
(setext_h1_underline)
|
||||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||||
(#set! symbol.prepend "· "))
|
(#set! symbol.prepend "· ")
|
||||||
|
(#set! symbol.icon "chevron-right"))
|
||||||
|
|
||||||
((setext_heading
|
((setext_heading
|
||||||
(heading_content) @name) @definition.heading
|
(heading_content) @name) @definition.heading
|
||||||
(setext_h2_underline)
|
(setext_h2_underline)
|
||||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||||
(#set! symbol.prepend "·· "))
|
(#set! symbol.prepend "·· ")
|
||||||
|
(#set! symbol.icon "chevron-right"))
|
||||||
|
@ -107,7 +107,7 @@ This allows us to incorporate any transformations that were applied to the other
|
|||||||
|
|
||||||
##### Adding the `context` field
|
##### Adding the `context` field
|
||||||
|
|
||||||
The `context` field of a symbol is a short piece of text meant to give context. For instance, a symbol that represents a class method could have a `context` field that contains the name of the owning class. The `context` field is not filtered on.
|
The `context` field of a symbol is a short piece of text meant to give context. For instance, a symbol that represents a class method could have a `context` field that contains the name of the class it belongs to. The `context` field is not filtered on.
|
||||||
|
|
||||||
###### symbol.contextNode
|
###### symbol.contextNode
|
||||||
|
|
||||||
@ -135,7 +135,11 @@ The point of `context` is to provide information to help you tell symbols apart,
|
|||||||
|
|
||||||
##### Adding a tag
|
##### Adding a tag
|
||||||
|
|
||||||
The `tag` field is a string (ideally a short string) that indicates a symbol’s kind or type. A `tag` for a class method’s symbol might say `method`, whereas the symbol for the class itself might have a `tag` of `class`. These tags will be indicated in the UI with a badge or an icon.
|
The `tag` field is a string that indicates a symbol’s kind or type. It should be a single word wherever possible. A `tag` for a class method’s symbol would typically be `method`, whereas the symbol for the class itself would typically have a `tag` of `class`. These tags will be indicated in the UI with a badge, an icon, or both.
|
||||||
|
|
||||||
|
If you’re not sure what to call something, consult [this list from the Language Server Protocol spec](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#symbolKind). But some symbols may not fit any of those, so ultimately it’s up to the author. (For example, headings in Markdown files are assigned a kind of `heading`.)
|
||||||
|
|
||||||
|
For consistency, tags should be all lowercase. The interface will apply its own casing effect through CSS (`text-transform: capitalize` by default, but customizable in UI themes).
|
||||||
|
|
||||||
The preferred method of adding a tag is to leverage the `@definition.` captures that are typically present in a tags file. For instance, in this excerpt from the JavaScript grammar’s `tags.scm` file…
|
The preferred method of adding a tag is to leverage the `@definition.` captures that are typically present in a tags file. For instance, in this excerpt from the JavaScript grammar’s `tags.scm` file…
|
||||||
|
|
||||||
@ -154,18 +158,7 @@ The preferred method of adding a tag is to leverage the `@definition.` captures
|
|||||||
|
|
||||||
In cases where this is impractical, you can provide the tag explicitly with a predicate.
|
In cases where this is impractical, you can provide the tag explicitly with a predicate.
|
||||||
|
|
||||||
###### symbol.icon
|
Nearly all the tags on [the aforementioned list](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#symbolKind) will also apply an appropriate `icon` to their symbol when assigned. If you choose a tag name not on that list, or want to override the default, you can use the `symbol.icon` predicate described below.
|
||||||
|
|
||||||
```scm
|
|
||||||
(class_body (method_definition
|
|
||||||
name: (property_identifier) @name
|
|
||||||
(#set! symbol.icon "package")
|
|
||||||
))
|
|
||||||
```
|
|
||||||
|
|
||||||
The icon to be shown alongside the symbol in a list. Will only be shown if the user has enabled the “Show Icons in Symbols View” option in the `symbols-view` settings. You can see the full list of available icons by invoking the **Styleguide: Show** command and browsing the “Icons” section. The value can include the preceding `icon-` or can omit it; e.g., `icon-package` and `package` are both valid values.
|
|
||||||
|
|
||||||
If this value is omitted, this provider will still attempt to match certain common tag values to icons. If `tag` is not present on the symbol, or is an uncommon value, there will be a blank space instead of an icon.
|
|
||||||
|
|
||||||
###### symbol.tag
|
###### symbol.tag
|
||||||
|
|
||||||
@ -181,3 +174,16 @@ The `symbol.tag` predicate will set the value of a symbol’s `tag` property to
|
|||||||
The `tag` property is used to supply a word that represents the symbol in some way. For conventional symbols, this will often be something like `class` or `function`.
|
The `tag` property is used to supply a word that represents the symbol in some way. For conventional symbols, this will often be something like `class` or `function`.
|
||||||
|
|
||||||
This provider will attempt to match certain common tag values to icons. This can be overridden by specifying an explicit `symbol.icon` value.
|
This provider will attempt to match certain common tag values to icons. This can be overridden by specifying an explicit `symbol.icon` value.
|
||||||
|
|
||||||
|
###### symbol.icon
|
||||||
|
|
||||||
|
```scm
|
||||||
|
(class_body (method_definition
|
||||||
|
name: (property_identifier) @name
|
||||||
|
(#set! symbol.icon "package")
|
||||||
|
))
|
||||||
|
```
|
||||||
|
|
||||||
|
The icon to be shown alongside the symbol in a list. Will only be shown if the user has enabled the “Show Icons in Symbols View” option in the `symbols-view` settings. You can see the full list of available icons by invoking the **Styleguide: Show** command and browsing the “Icons” section. The value can include the preceding `icon-` or can omit it; e.g., `icon-package` and `package` are both valid values.
|
||||||
|
|
||||||
|
If this value is omitted, this provider will still attempt to match certain common tag values to icons. If `tag` is not present on the symbol, or is an uncommon value, there will be a blank space instead of an icon.
|
||||||
|
@ -27,28 +27,66 @@ const PatternCache = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Assign a default icon type for each tag — or what LSP calls “kind.” This
|
||||||
|
// list is copied directly from the LSP spec's exhaustive list of potential
|
||||||
|
// symbol kinds:
|
||||||
|
//
|
||||||
|
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#symbolKind
|
||||||
function iconForTag(tag) {
|
function iconForTag(tag) {
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
case 'function':
|
case 'file':
|
||||||
return 'icon-gear';
|
return 'icon-file';
|
||||||
case 'method':
|
|
||||||
return 'icon-gear';
|
|
||||||
case 'namespace':
|
|
||||||
return 'icon-tag';
|
|
||||||
case 'variable':
|
|
||||||
return 'icon-code';
|
|
||||||
case 'class':
|
|
||||||
return 'icon-package';
|
|
||||||
case 'constant':
|
|
||||||
return 'icon-primitive-square';
|
|
||||||
case 'property':
|
|
||||||
return 'icon-primitive-dot';
|
|
||||||
case 'interface':
|
|
||||||
return 'icon-key';
|
|
||||||
case 'constructor':
|
|
||||||
return 'icon-tools';
|
|
||||||
case 'module':
|
case 'module':
|
||||||
return 'icon-database';
|
return 'icon-database';
|
||||||
|
case 'namespace':
|
||||||
|
return 'icon-tag';
|
||||||
|
case 'package':
|
||||||
|
return 'icon-package';
|
||||||
|
case 'class':
|
||||||
|
return 'icon-puzzle';
|
||||||
|
case 'method':
|
||||||
|
return 'icon-gear';
|
||||||
|
case 'property':
|
||||||
|
return 'icon-primitive-dot';
|
||||||
|
case 'field':
|
||||||
|
return 'icon-primitive-dot';
|
||||||
|
case 'constructor':
|
||||||
|
return 'icon-tools';
|
||||||
|
case 'enum':
|
||||||
|
return 'icon-list-unordered';
|
||||||
|
case 'interface':
|
||||||
|
return 'icon-key';
|
||||||
|
case 'function':
|
||||||
|
return 'icon-gear';
|
||||||
|
case 'variable':
|
||||||
|
return 'icon-code';
|
||||||
|
case 'constant':
|
||||||
|
return 'icon-primitive-square';
|
||||||
|
case 'string':
|
||||||
|
return 'icon-quote';
|
||||||
|
case 'number':
|
||||||
|
return 'icon-plus';
|
||||||
|
case 'boolean':
|
||||||
|
return 'icon-question';
|
||||||
|
case 'array':
|
||||||
|
return 'icon-list-ordered';
|
||||||
|
case 'object':
|
||||||
|
return 'icon-file-code';
|
||||||
|
case 'key':
|
||||||
|
return 'icon-key';
|
||||||
|
case 'null':
|
||||||
|
return null;
|
||||||
|
case 'enum-member':
|
||||||
|
return 'icon-primitive-dot';
|
||||||
|
case 'struct':
|
||||||
|
return 'icon-book';
|
||||||
|
case 'event':
|
||||||
|
return 'icon-calendar';
|
||||||
|
case 'operator':
|
||||||
|
return 'icon-plus';
|
||||||
|
case 'type-parameter':
|
||||||
|
return null;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -66,9 +104,10 @@ class Container {
|
|||||||
this.capture = capture;
|
this.capture = capture;
|
||||||
this.node = capture.node;
|
this.node = capture.node;
|
||||||
this.organizer = organizer;
|
this.organizer = organizer;
|
||||||
|
this.props = capture.setProperties || {};
|
||||||
|
|
||||||
this.tag = capture.name.substring(capture.name.indexOf('.') + 1);
|
this.tag = capture.name.substring(capture.name.indexOf('.') + 1);
|
||||||
this.icon = iconForTag(this.tag);
|
this.icon = this.resolveIcon();
|
||||||
this.position = capture.node.range.start;
|
this.position = capture.node.range.start;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,6 +147,13 @@ class Container {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolveIcon() {
|
||||||
|
let icon = this.props['symbol.icon'] ?? iconForTag(this.tag);
|
||||||
|
if (icon && !icon.startsWith('icon-'))
|
||||||
|
icon = `icon-${icon}`;
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
toSymbol() {
|
toSymbol() {
|
||||||
if (!this.nameCapture) return null;
|
if (!this.nameCapture) return null;
|
||||||
let nameSymbol = this.nameCapture.toSymbol();
|
let nameSymbol = this.nameCapture.toSymbol();
|
||||||
@ -115,7 +161,7 @@ class Container {
|
|||||||
name: nameSymbol.name,
|
name: nameSymbol.name,
|
||||||
shortName: nameSymbol.shortName,
|
shortName: nameSymbol.shortName,
|
||||||
tag: nameSymbol.tag ?? this.tag,
|
tag: nameSymbol.tag ?? this.tag,
|
||||||
icon: nameSymbol.icon ?? iconForTag(nameSymbol.tag) ?? iconForTag(this.tag),
|
icon: nameSymbol.icon ?? this.icon,
|
||||||
position: this.position
|
position: this.position
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -248,6 +248,166 @@ describe('TreeSitterProvider', () => {
|
|||||||
grammar = editor.getGrammar();
|
grammar = editor.getGrammar();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('symbol.context', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await grammar.setQueryForTest('tagsQuery', scm`
|
||||||
|
(
|
||||||
|
(variable_declaration
|
||||||
|
(variable_declarator
|
||||||
|
name: (identifier) @name
|
||||||
|
value: [(arrow_function) (function)]))
|
||||||
|
(#set! symbol.context "something")
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns a `context` property on each symbol', async () => {
|
||||||
|
let symbols = await getSymbols(editor, 'file');
|
||||||
|
|
||||||
|
expect(symbols[0].context).toBe('something');
|
||||||
|
expect(symbols[0].position.row).toEqual(0);
|
||||||
|
|
||||||
|
expect(symbols[1].context).toBe('something');
|
||||||
|
expect(symbols[1].position.row).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('symbol.contextNode', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await grammar.setQueryForTest('tagsQuery', scm`
|
||||||
|
(
|
||||||
|
(property_identifier) @name
|
||||||
|
(#eq? @name "push")
|
||||||
|
(#set! symbol.contextNode "parent.firstNamedChild")
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns a `context` property on each symbol containing the text of the referenced node', async () => {
|
||||||
|
let symbols = await getSymbols(editor, 'file');
|
||||||
|
|
||||||
|
expect(symbols[0].name).toBe('push');
|
||||||
|
expect(symbols[0].context).toBe('left');
|
||||||
|
expect(symbols[0].position.row).toEqual(6);
|
||||||
|
|
||||||
|
expect(symbols[1].name).toBe('push');
|
||||||
|
expect(symbols[1].context).toBe('right');
|
||||||
|
expect(symbols[1].position.row).toEqual(6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('symbol.icon', () => {
|
||||||
|
it('defines an `icon` property on each symbol', async () => {
|
||||||
|
await grammar.setQueryForTest('tagsQuery', scm`
|
||||||
|
(
|
||||||
|
(variable_declaration
|
||||||
|
(variable_declarator
|
||||||
|
name: (identifier) @name
|
||||||
|
value: [(arrow_function) (function)]))
|
||||||
|
(#set! symbol.icon "book")
|
||||||
|
)
|
||||||
|
|
||||||
|
`);
|
||||||
|
|
||||||
|
let symbols = await getSymbols(editor, 'file');
|
||||||
|
console.log('symbols:', symbols);
|
||||||
|
|
||||||
|
expect(symbols[0].icon).toBe('icon-book');
|
||||||
|
expect(symbols[0].position.row).toEqual(0);
|
||||||
|
|
||||||
|
expect(symbols[1].icon).toBe('icon-book');
|
||||||
|
expect(symbols[1].position.row).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supersedes an `icon` property assigned by a tag', async () => {
|
||||||
|
await grammar.setQueryForTest('tagsQuery', scm`
|
||||||
|
(
|
||||||
|
(variable_declaration
|
||||||
|
(variable_declarator
|
||||||
|
name: (identifier) @name
|
||||||
|
value: [(arrow_function) (function)]))
|
||||||
|
(#set! symbol.tag "class")
|
||||||
|
(#set! symbol.icon "book")
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
let symbols = await getSymbols(editor, 'file');
|
||||||
|
|
||||||
|
expect(symbols[0].icon).toBe('icon-book');
|
||||||
|
expect(symbols[0].position.row).toEqual(0);
|
||||||
|
|
||||||
|
expect(symbols[1].icon).toBe('icon-book');
|
||||||
|
expect(symbols[1].position.row).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supersedes an `icon` property inferred by its container', async () => {
|
||||||
|
await grammar.setQueryForTest('tagsQuery', scm`
|
||||||
|
(
|
||||||
|
(variable_declaration
|
||||||
|
(variable_declarator
|
||||||
|
name: (identifier) @name
|
||||||
|
value: [(arrow_function) (function)]))
|
||||||
|
(#set! symbol.tag "class")
|
||||||
|
(#set! symbol.icon "book")
|
||||||
|
) @definition.namespace
|
||||||
|
`);
|
||||||
|
|
||||||
|
let symbols = await getSymbols(editor, 'file');
|
||||||
|
|
||||||
|
expect(symbols[0].icon).toBe('icon-book');
|
||||||
|
expect(symbols[0].position.row).toEqual(0);
|
||||||
|
|
||||||
|
expect(symbols[1].icon).toBe('icon-book');
|
||||||
|
expect(symbols[1].position.row).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('symbol.tag', () => {
|
||||||
|
it('defines a `tag` property on each symbol', async () => {
|
||||||
|
await grammar.setQueryForTest('tagsQuery', scm`
|
||||||
|
(
|
||||||
|
(variable_declaration
|
||||||
|
(variable_declarator
|
||||||
|
name: (identifier) @name
|
||||||
|
value: [(arrow_function) (function)]))
|
||||||
|
(#set! symbol.tag "class")
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
let symbols = await getSymbols(editor, 'file');
|
||||||
|
|
||||||
|
expect(symbols[0].tag).toBe('class');
|
||||||
|
expect(symbols[0].icon).toBe('icon-puzzle');
|
||||||
|
expect(symbols[0].position.row).toEqual(0);
|
||||||
|
|
||||||
|
expect(symbols[1].tag).toBe('class');
|
||||||
|
expect(symbols[1].icon).toBe('icon-puzzle');
|
||||||
|
expect(symbols[1].position.row).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supersedes the `tag` property inferred by its container', async () => {
|
||||||
|
await grammar.setQueryForTest('tagsQuery', scm`
|
||||||
|
(
|
||||||
|
(variable_declaration
|
||||||
|
(variable_declarator
|
||||||
|
name: (identifier) @name
|
||||||
|
value: [(arrow_function) (function)]))
|
||||||
|
(#set! symbol.tag "class")
|
||||||
|
) @definition.namespace
|
||||||
|
`);
|
||||||
|
|
||||||
|
let symbols = await getSymbols(editor, 'file');
|
||||||
|
|
||||||
|
expect(symbols[0].tag).toBe('class');
|
||||||
|
expect(symbols[0].icon).toBe('icon-puzzle');
|
||||||
|
expect(symbols[0].position.row).toEqual(0);
|
||||||
|
|
||||||
|
expect(symbols[1].tag).toBe('class');
|
||||||
|
expect(symbols[1].icon).toBe('icon-puzzle');
|
||||||
|
expect(symbols[1].position.row).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('symbol.strip', () => {
|
describe('symbol.strip', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await grammar.setQueryForTest('tagsQuery', scm`
|
await grammar.setQueryForTest('tagsQuery', scm`
|
||||||
|
Loading…
Reference in New Issue
Block a user