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)
|
||||
(heading_content) @name) @definition.heading
|
||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||
(#set! symbol.prepend "· "))
|
||||
(#set! symbol.prepend "· ")
|
||||
(#set! symbol.icon "chevron-right"))
|
||||
|
||||
((atx_heading
|
||||
(atx_h2_marker)
|
||||
(heading_content) @name) @definition.heading
|
||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||
(#set! symbol.prepend "·· "))
|
||||
(#set! symbol.prepend "·· ")
|
||||
(#set! symbol.icon "chevron-right"))
|
||||
|
||||
((atx_heading
|
||||
(atx_h3_marker)
|
||||
(heading_content) @name) @definition.heading
|
||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||
(#set! symbol.prepend "··· "))
|
||||
(#set! symbol.prepend "··· ")
|
||||
(#set! symbol.icon "chevron-right"))
|
||||
|
||||
((atx_heading
|
||||
(atx_h4_marker)
|
||||
(heading_content) @name) @definition.heading
|
||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||
(#set! symbol.prepend "···· "))
|
||||
(#set! symbol.prepend "···· ")
|
||||
(#set! symbol.icon "chevron-right"))
|
||||
|
||||
((atx_heading
|
||||
(atx_h5_marker)
|
||||
(heading_content) @name) @definition.heading
|
||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||
(#set! symbol.prepend "····· "))
|
||||
(#set! symbol.prepend "····· ")
|
||||
(#set! symbol.icon "chevron-right"))
|
||||
|
||||
((atx_heading
|
||||
(atx_h6_marker)
|
||||
(heading_content) @name) @definition.heading
|
||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||
(#set! symbol.prepend "······ "))
|
||||
(#set! symbol.prepend "······ ")
|
||||
(#set! symbol.icon "chevron-right"))
|
||||
|
||||
((setext_heading
|
||||
(heading_content) @name) @definition.heading
|
||||
(setext_h1_underline)
|
||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||
(#set! symbol.prepend "· "))
|
||||
(#set! symbol.prepend "· ")
|
||||
(#set! symbol.icon "chevron-right"))
|
||||
|
||||
((setext_heading
|
||||
(heading_content) @name) @definition.heading
|
||||
(setext_h2_underline)
|
||||
(#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
|
||||
|
||||
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
|
||||
|
||||
@ -135,7 +135,11 @@ The point of `context` is to provide information to help you tell symbols apart,
|
||||
|
||||
##### 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…
|
||||
|
||||
@ -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.
|
||||
|
||||
###### 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.
|
||||
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.
|
||||
|
||||
###### 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`.
|
||||
|
||||
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) {
|
||||
switch (tag) {
|
||||
case 'function':
|
||||
return 'icon-gear';
|
||||
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 'file':
|
||||
return 'icon-file';
|
||||
case 'module':
|
||||
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:
|
||||
return null;
|
||||
}
|
||||
@ -66,9 +104,10 @@ class Container {
|
||||
this.capture = capture;
|
||||
this.node = capture.node;
|
||||
this.organizer = organizer;
|
||||
this.props = capture.setProperties || {};
|
||||
|
||||
this.tag = capture.name.substring(capture.name.indexOf('.') + 1);
|
||||
this.icon = iconForTag(this.tag);
|
||||
this.icon = this.resolveIcon();
|
||||
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() {
|
||||
if (!this.nameCapture) return null;
|
||||
let nameSymbol = this.nameCapture.toSymbol();
|
||||
@ -115,7 +161,7 @@ class Container {
|
||||
name: nameSymbol.name,
|
||||
shortName: nameSymbol.shortName,
|
||||
tag: nameSymbol.tag ?? this.tag,
|
||||
icon: nameSymbol.icon ?? iconForTag(nameSymbol.tag) ?? iconForTag(this.tag),
|
||||
icon: nameSymbol.icon ?? this.icon,
|
||||
position: this.position
|
||||
};
|
||||
|
||||
|
@ -248,6 +248,166 @@ describe('TreeSitterProvider', () => {
|
||||
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', () => {
|
||||
beforeEach(async () => {
|
||||
await grammar.setQueryForTest('tagsQuery', scm`
|
||||
|
Loading…
Reference in New Issue
Block a user