mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-11 11:15:24 +03:00
Merge pull request #861 from savetheclocktower/symbols-view-fixes
`symbols-view` rolling fixes
This commit is contained in:
commit
114a7247de
@ -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"))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
(namespace_definition
|
(namespace_definition
|
||||||
name: (namespace_name) @name) @module
|
name: (namespace_name) @name) @definition.namespace
|
||||||
|
|
||||||
(interface_declaration
|
(interface_declaration
|
||||||
name: (name) @name) @definition.interface
|
name: (name) @name) @definition.interface
|
||||||
@ -11,7 +11,7 @@
|
|||||||
(class_declaration
|
(class_declaration
|
||||||
name: (name) @name) @definition.class
|
name: (name) @name) @definition.class
|
||||||
|
|
||||||
(class_interface_clause [(name) (qualified_name)] @name) @impl
|
(class_interface_clause [(name) (qualified_name)] @name) @reference.interface
|
||||||
|
|
||||||
(property_declaration
|
(property_declaration
|
||||||
(property_element (variable_name (name) @name))) @definition.field
|
(property_element (variable_name (name) @name))) @definition.field
|
||||||
@ -40,4 +40,4 @@
|
|||||||
(member_call_expression
|
(member_call_expression
|
||||||
name: (name) @name) @reference.call
|
name: (name) @name) @reference.call
|
||||||
|
|
||||||
(enum_declaration (name) @name) @definition.enum
|
((enum_declaration (name) @name) @definition.enum)
|
||||||
|
@ -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
|
||||||
|
|
||||||
@ -179,3 +172,18 @@ If this value is omitted, this provider will still attempt to match certain comm
|
|||||||
The `symbol.tag` predicate will set the value of a symbol’s `tag` property to a fixed string.
|
The `symbol.tag` predicate will set the value of a symbol’s `tag` property to a fixed string.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
###### 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
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -246,11 +292,11 @@ class Name {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toSymbol() {
|
toSymbol() {
|
||||||
let { name, shortName, position, context, tag } = this;
|
let { name, shortName, position, context, tag, icon } = this;
|
||||||
let symbol = { name, shortName, position };
|
let symbol = { name, shortName, position, icon };
|
||||||
if (tag) {
|
if (tag) {
|
||||||
symbol.tag = tag;
|
symbol.tag = tag;
|
||||||
symbol.icon = iconForTag(tag);
|
symbol.icon ??= iconForTag(tag);
|
||||||
}
|
}
|
||||||
if (context) symbol.context = context;
|
if (context) symbol.context = context;
|
||||||
|
|
||||||
|
@ -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`
|
||||||
|
@ -7,7 +7,7 @@ const el = require('./element-builder');
|
|||||||
const { badge, isIterable, timeout } = require('./util');
|
const { badge, isIterable, timeout } = require('./util');
|
||||||
|
|
||||||
class FileView extends SymbolsView {
|
class FileView extends SymbolsView {
|
||||||
constructor (stack, broker) {
|
constructor(stack, broker) {
|
||||||
super(stack, broker);
|
super(stack, broker);
|
||||||
this.cachedResults = new Map();
|
this.cachedResults = new Map();
|
||||||
// Cached results can be partially invalidated. If a provider wants to
|
// Cached results can be partially invalidated. If a provider wants to
|
||||||
@ -87,12 +87,12 @@ class FileView extends SymbolsView {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy () {
|
destroy() {
|
||||||
this.editorsSubscription.dispose();
|
this.editorsSubscription.dispose();
|
||||||
return super.destroy();
|
return super.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
elementForItem ({ position, name, tag, icon, context, providerName }) {
|
elementForItem({ position, name, tag, icon, context, providerName }) {
|
||||||
// Style matched characters in search results.
|
// Style matched characters in search results.
|
||||||
const matches = match(name, this.selectListView.getFilterQuery());
|
const matches = match(name, this.selectListView.getFilterQuery());
|
||||||
|
|
||||||
@ -137,12 +137,12 @@ class FileView extends SymbolsView {
|
|||||||
return el('li.two-lines', primary, secondary);
|
return el('li.two-lines', primary, secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
didChangeSelection (item) {
|
didChangeSelection(item) {
|
||||||
let quickJump = Config.get('quickJumpToFileSymbol');
|
let quickJump = Config.get('quickJumpToFileSymbol');
|
||||||
if (quickJump && item) this.openTag(item);
|
if (quickJump && item) this.openTag(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
async didCancelSelection () {
|
async didCancelSelection() {
|
||||||
this.abortController?.abort();
|
this.abortController?.abort();
|
||||||
await this.cancel();
|
await this.cancel();
|
||||||
let editor = this.getEditor();
|
let editor = this.getEditor();
|
||||||
@ -152,12 +152,12 @@ class FileView extends SymbolsView {
|
|||||||
this.initialState = null;
|
this.initialState = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
didConfirmEmptySelection () {
|
didConfirmEmptySelection() {
|
||||||
this.abortController?.abort();
|
this.abortController?.abort();
|
||||||
super.didConfirmEmptySelection();
|
super.didConfirmEmptySelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
async toggle () {
|
async toggle(filterTerm = '') {
|
||||||
if (this.panel.isVisible()) await this.cancel();
|
if (this.panel.isVisible()) await this.cancel();
|
||||||
let editor = this.getEditor();
|
let editor = this.getEditor();
|
||||||
// Remember exactly where the editor is so that we can restore that state
|
// Remember exactly where the editor is so that we can restore that state
|
||||||
@ -170,9 +170,10 @@ class FileView extends SymbolsView {
|
|||||||
let populated = this.populate(editor);
|
let populated = this.populate(editor);
|
||||||
if (!populated) return;
|
if (!populated) return;
|
||||||
this.attach();
|
this.attach();
|
||||||
|
this.selectListView.update({ query: filterTerm });
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeEditorState (editor) {
|
serializeEditorState(editor) {
|
||||||
let editorElement = atom.views.getView(editor);
|
let editorElement = atom.views.getView(editor);
|
||||||
let scrollTop = editorElement.getScrollTop();
|
let scrollTop = editorElement.getScrollTop();
|
||||||
|
|
||||||
@ -182,32 +183,32 @@ class FileView extends SymbolsView {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
deserializeEditorState (editor, { bufferRanges, scrollTop }) {
|
deserializeEditorState(editor, { bufferRanges, scrollTop }) {
|
||||||
let editorElement = atom.views.getView(editor);
|
let editorElement = atom.views.getView(editor);
|
||||||
|
|
||||||
editor.setSelectedBufferRanges(bufferRanges);
|
editor.setSelectedBufferRanges(bufferRanges);
|
||||||
editorElement.setScrollTop(scrollTop);
|
editorElement.setScrollTop(scrollTop);
|
||||||
}
|
}
|
||||||
|
|
||||||
getEditor () {
|
getEditor() {
|
||||||
return atom.workspace.getActiveTextEditor();
|
return atom.workspace.getActiveTextEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
getPath () {
|
getPath() {
|
||||||
return this.getEditor()?.getPath();
|
return this.getEditor()?.getPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
getScopeName () {
|
getScopeName() {
|
||||||
return this.getEditor()?.getGrammar()?.scopeName;
|
return this.getEditor()?.getGrammar()?.scopeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
isValidSymbol (symbol) {
|
isValidSymbol(symbol) {
|
||||||
if (!symbol.position || !(symbol.position instanceof Point)) return false;
|
if (!symbol.position || !(symbol.position instanceof Point)) return false;
|
||||||
if (typeof symbol.name !== 'string') return false;
|
if (typeof symbol.name !== 'string') return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async populate (editor) {
|
async populate(editor) {
|
||||||
let result = this.cachedResults.get(editor);
|
let result = this.cachedResults.get(editor);
|
||||||
let providersToQuery = this.providersWithInvalidatedCaches.get(editor);
|
let providersToQuery = this.providersWithInvalidatedCaches.get(editor);
|
||||||
if (result && !providersToQuery?.size) {
|
if (result && !providersToQuery?.size) {
|
||||||
@ -238,7 +239,7 @@ class FileView extends SymbolsView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateSymbols (editor, existingSymbols = null, onlyProviders = null) {
|
async generateSymbols(editor, existingSymbols = null, onlyProviders = null) {
|
||||||
this.abortController?.abort();
|
this.abortController?.abort();
|
||||||
this.abortController = new AbortController();
|
this.abortController = new AbortController();
|
||||||
|
|
||||||
|
41
packages/symbols-view/lib/main.d.ts
vendored
41
packages/symbols-view/lib/main.d.ts
vendored
@ -228,7 +228,9 @@ export interface SymbolProvider {
|
|||||||
//
|
//
|
||||||
// This method receives the same metadata bundle that will be present in the
|
// This method receives the same metadata bundle that will be present in the
|
||||||
// subsequent call to `getSymbols`. The provider can inspect this metadata
|
// subsequent call to `getSymbols`. The provider can inspect this metadata
|
||||||
// and decide whether it can fulfill the given symbols request.
|
// and decide whether it can fulfill the given symbols request. It _should
|
||||||
|
// not_ start the task of gathering symbols; the point of this method is to
|
||||||
|
// determine which provider is best for the task without starting the work.
|
||||||
//
|
//
|
||||||
// Examples:
|
// Examples:
|
||||||
//
|
//
|
||||||
@ -239,10 +241,16 @@ export interface SymbolProvider {
|
|||||||
// the current unsaved contents of buffers, could return a slightly lower
|
// the current unsaved contents of buffers, could return a slightly lower
|
||||||
// score if asked to complete symbols for a file that has been modified.
|
// score if asked to complete symbols for a file that has been modified.
|
||||||
// This would indicate that it’d be a slightly worse than usual candidate.
|
// This would indicate that it’d be a slightly worse than usual candidate.
|
||||||
|
// * If my provider can do project-wide symbol search but _can't_ do a
|
||||||
|
// go-to-definition lookup, it can still serve as a fallback provider when
|
||||||
|
// `type` is `project-find`. But it should return a lower score to reflect
|
||||||
|
// that it's not the ideal choice.
|
||||||
//
|
//
|
||||||
// Since language server providers will have to ask their servers about
|
// Since language server providers will have to ask their servers about
|
||||||
// capabilities, this method can go async, though it’s strongly suggested to
|
// capabilities, this method can go async, though it’s strongly suggested to
|
||||||
// keep it synchronous if possible.
|
// keep it synchronous if possible. (The `timeoutMs` argument isn't yet
|
||||||
|
// enforced on `canProvideSymbols`, but we reserve the right to start
|
||||||
|
// enforcing it at any point without a bump in the service's version number.)
|
||||||
//
|
//
|
||||||
// To avoid a number war, any numeric value greater than `1` returned from
|
// To avoid a number war, any numeric value greater than `1` returned from
|
||||||
// `canProvideSymbols` will be clamped to `1`. The user can break ties by
|
// `canProvideSymbols` will be clamped to `1`. The user can break ties by
|
||||||
@ -262,13 +270,40 @@ export interface SymbolProvider {
|
|||||||
//
|
//
|
||||||
// This method can go async if needed. Whenever it performs an async task, it
|
// This method can go async if needed. Whenever it performs an async task, it
|
||||||
// should check `meta.signal` afterward to see if it should cancel.
|
// should check `meta.signal` afterward to see if it should cancel.
|
||||||
|
//
|
||||||
|
// The `type` property of `meta` affects which symbols this method should
|
||||||
|
// return:
|
||||||
|
//
|
||||||
|
// * If `type` is `file`, this method should return a full list of symbols
|
||||||
|
// for the current file.
|
||||||
|
//
|
||||||
|
// * If `type` is `project`, this method should return an _appropriate_ list
|
||||||
|
// of symbols for the project. The ideal approach would be to return only
|
||||||
|
// those results that match `meta.query`; you may choose not to return any
|
||||||
|
// symbols at all until `meta.query` is of a minimum length. But you may
|
||||||
|
// also return a full list of project symbols and rely on `symbols-view` to
|
||||||
|
// do all of the filtering as the user types. (In the latter case,
|
||||||
|
// `getSymbols` will still be called after each new keystroke; a future
|
||||||
|
// version of this service may offer a way to control that behavior.)
|
||||||
|
//
|
||||||
|
// If you return an empty list when `meta.query` is too short, you should
|
||||||
|
// use `listController` to set a message in the UI so that users understand
|
||||||
|
// why.
|
||||||
|
//
|
||||||
|
// * If `type` is `project-find`, the user is trying to find where
|
||||||
|
// `meta.query` is defined (a go-to-definition request). If this provider
|
||||||
|
// knows how to do that, it should find the answer and return it as the
|
||||||
|
// _only_ symbol in the returned list. If it doesn't, it is allowed to
|
||||||
|
// treat this similarly to a project-wide symbol search and return more
|
||||||
|
// than one result.
|
||||||
|
//
|
||||||
getSymbols(meta: FileSymbolMeta, listController?: ListController): MaybePromise<FileSymbol[] | null>
|
getSymbols(meta: FileSymbolMeta, listController?: ListController): MaybePromise<FileSymbol[] | null>
|
||||||
getSymbols(meta: ProjectSymbolMeta, listController?: ListController): MaybePromise<ProjectSymbol[] | null>
|
getSymbols(meta: ProjectSymbolMeta, listController?: ListController): MaybePromise<ProjectSymbol[] | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SymbolProviderMainModule = {
|
export type SymbolProviderMainModule = {
|
||||||
activate(): void,
|
activate(): void,
|
||||||
deacivate(): void,
|
deactivate(): void,
|
||||||
|
|
||||||
// No business logic should go in here. If a package wants to provide symbols
|
// No business logic should go in here. If a package wants to provide symbols
|
||||||
// only under certain circumstances, it should decide those circumstances on
|
// only under certain circumstances, it should decide those circumstances on
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const { Disposable } = require('atom');
|
const { Disposable, TextEditor } = require('atom');
|
||||||
const Config = require('./config');
|
const Config = require('./config');
|
||||||
const ProviderBroker = require('./provider-broker');
|
const ProviderBroker = require('./provider-broker');
|
||||||
const Path = require('path');
|
const Path = require('path');
|
||||||
@ -32,9 +32,13 @@ module.exports = {
|
|||||||
this.workspaceSubscription = atom.commands.add(
|
this.workspaceSubscription = atom.commands.add(
|
||||||
'atom-workspace',
|
'atom-workspace',
|
||||||
{
|
{
|
||||||
'symbols-view:toggle-project-symbols': () => {
|
'symbols-view:toggle-project-symbols': (event) => {
|
||||||
if (!this.ensureProvidersExist()) return;
|
if (!this.ensureProvidersExist()) {
|
||||||
this.createProjectView().toggle();
|
event.abortKeyBinding();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let text = this.getSelectedTextIfEnabled(event);
|
||||||
|
this.createProjectView().toggle(text);
|
||||||
},
|
},
|
||||||
'symbols-view:show-active-providers': () => {
|
'symbols-view:show-active-providers': () => {
|
||||||
this.showActiveProviders();
|
this.showActiveProviders();
|
||||||
@ -45,12 +49,13 @@ module.exports = {
|
|||||||
this.editorSubscription = atom.commands.add(
|
this.editorSubscription = atom.commands.add(
|
||||||
'atom-text-editor:not([mini])',
|
'atom-text-editor:not([mini])',
|
||||||
{
|
{
|
||||||
'symbols-view:toggle-file-symbols': (e) => {
|
'symbols-view:toggle-file-symbols': (event) => {
|
||||||
if (!this.ensureProvidersExist()) {
|
if (!this.ensureProvidersExist()) {
|
||||||
e.abortKeyBinding();
|
event.abortKeyBinding();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.createFileView().toggle();
|
let text = this.getSelectedTextIfEnabled(event);
|
||||||
|
this.createFileView().toggle(text);
|
||||||
},
|
},
|
||||||
'symbols-view:go-to-declaration': () => {
|
'symbols-view:go-to-declaration': () => {
|
||||||
if (!this.ensureProvidersExist()) return;
|
if (!this.ensureProvidersExist()) return;
|
||||||
@ -66,6 +71,24 @@ module.exports = {
|
|||||||
migrateOldConfigIfNeeded();
|
migrateOldConfigIfNeeded();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getSelectedTextIfEnabled(event) {
|
||||||
|
let editorView = event.target.closest('atom-text-editor');
|
||||||
|
if (!editorView) return '';
|
||||||
|
let editor = editorView.getModel();
|
||||||
|
let selection = editor.getLastSelection();
|
||||||
|
|
||||||
|
// Don't use the selection if it spans more than one buffer line.
|
||||||
|
let range = selection.getBufferRange();
|
||||||
|
if (range.start.row !== range.end.row) return '';
|
||||||
|
|
||||||
|
// Don't use the selection unless the associated config option is enabled.
|
||||||
|
let prefill = atom.config.get(
|
||||||
|
'symbols-view.prefillSelectedText',
|
||||||
|
{ scope: [editor.getGrammar()?.scopeName] }
|
||||||
|
);
|
||||||
|
return prefill ? editor.getSelectedText() : '';
|
||||||
|
},
|
||||||
|
|
||||||
deactivate() {
|
deactivate() {
|
||||||
this.fileView?.destroy();
|
this.fileView?.destroy();
|
||||||
this.fileView = null;
|
this.fileView = null;
|
||||||
@ -101,9 +124,9 @@ module.exports = {
|
|||||||
|
|
||||||
return new Disposable(() => {
|
return new Disposable(() => {
|
||||||
if (Array.isArray(provider)) {
|
if (Array.isArray(provider)) {
|
||||||
this.broker.remove(...provider);
|
this.broker?.remove(...provider);
|
||||||
} else {
|
} else {
|
||||||
this.broker.remove(provider);
|
this.broker?.remove(provider);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,7 @@ const SymbolsView = require('./symbols-view');
|
|||||||
const { isIterable, timeout } = require('./util');
|
const { isIterable, timeout } = require('./util');
|
||||||
|
|
||||||
module.exports = class ProjectView extends SymbolsView {
|
module.exports = class ProjectView extends SymbolsView {
|
||||||
constructor (stack, broker) {
|
constructor(stack, broker) {
|
||||||
// TODO: Do these defaults make sense? Should we allow a provider to
|
// TODO: Do these defaults make sense? Should we allow a provider to
|
||||||
// override them?
|
// override them?
|
||||||
super(stack, broker, {
|
super(stack, broker, {
|
||||||
@ -16,30 +16,31 @@ module.exports = class ProjectView extends SymbolsView {
|
|||||||
this.shouldReload = true;
|
this.shouldReload = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy () {
|
destroy() {
|
||||||
return super.destroy();
|
return super.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle () {
|
toggle(filterTerm = '') {
|
||||||
if (this.panel.isVisible()) {
|
if (this.panel.isVisible()) {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
} else {
|
} else {
|
||||||
this.populate();
|
this.populate();
|
||||||
this.attach();
|
this.attach();
|
||||||
|
this.selectListView.update({ query: filterTerm });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
didCancelSelection () {
|
didCancelSelection() {
|
||||||
this.abortController?.abort();
|
this.abortController?.abort();
|
||||||
super.didCancelSelection();
|
super.didCancelSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
didConfirmEmptySelection () {
|
didConfirmEmptySelection() {
|
||||||
this.abortController?.abort();
|
this.abortController?.abort();
|
||||||
super.didConfirmEmptySelection();
|
super.didConfirmEmptySelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
isValidSymbol (symbol) {
|
isValidSymbol(symbol) {
|
||||||
if (!super.isValidSymbol(symbol)) return false;
|
if (!super.isValidSymbol(symbol)) return false;
|
||||||
if (
|
if (
|
||||||
!(typeof symbol.file === 'string' && typeof symbol.directory === 'string') &&
|
!(typeof symbol.file === 'string' && typeof symbol.directory === 'string') &&
|
||||||
@ -50,22 +51,22 @@ module.exports = class ProjectView extends SymbolsView {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldUseCache () {
|
shouldUseCache() {
|
||||||
let query = this.selectListView?.getQuery();
|
let query = this.selectListView?.getQuery();
|
||||||
if (query && query.length > 0) return false;
|
if (query && query.length > 0) return false;
|
||||||
if (this.shouldReload) return false;
|
if (this.shouldReload) return false;
|
||||||
return !!this.cachedSymbols;
|
return !!this.cachedSymbols;
|
||||||
}
|
}
|
||||||
|
|
||||||
didChangeQuery () {
|
didChangeQuery() {
|
||||||
this.populate({ retain: true });
|
this.populate({ retain: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
clear () {
|
clear() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async populate ({ retain = false } = {}) {
|
async populate({ retain = false } = {}) {
|
||||||
if (this.shouldUseCache()) {
|
if (this.shouldUseCache()) {
|
||||||
await this.updateView({ items: this.cachedSymbols });
|
await this.updateView({ items: this.cachedSymbols });
|
||||||
return true;
|
return true;
|
||||||
@ -131,7 +132,7 @@ module.exports = class ProjectView extends SymbolsView {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateSymbols (editor, query = '', callback) {
|
async generateSymbols(editor, query = '', callback) {
|
||||||
this.abortController?.abort();
|
this.abortController?.abort();
|
||||||
this.abortController = new AbortController();
|
this.abortController = new AbortController();
|
||||||
|
|
||||||
|
@ -23,6 +23,70 @@ function validateListControllerProps(props) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 '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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class for setting various UI properties on a symbol list palette. This is a
|
* A class for setting various UI properties on a symbol list palette. This is a
|
||||||
* privilege given to the “main” (or _exclusive_) provider for a given task.
|
* privilege given to the “main” (or _exclusive_) provider for a given task.
|
||||||
@ -318,24 +382,33 @@ class SymbolsView {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
normalizeSymbol(symbol, provider) {
|
||||||
|
// We enforce these so that (a) we can show a human-readable name of the
|
||||||
|
// provider for each symbol (if the user opts into it), and (b) we can
|
||||||
|
// selectively clear cached results for certain providers without
|
||||||
|
// affecting others.
|
||||||
|
symbol.providerName ??= provider.name;
|
||||||
|
symbol.providerId ??= provider.packageName;
|
||||||
|
if (symbol.path) {
|
||||||
|
let parts = Path.parse(symbol.path);
|
||||||
|
symbol.directory = `${parts.dir}${Path.sep}`;
|
||||||
|
symbol.file = parts.base;
|
||||||
|
}
|
||||||
|
symbol.name = symbol.name.replace(/[\n\r\t]/, ' ');
|
||||||
|
|
||||||
|
if (symbol.tag && !symbol.icon) {
|
||||||
|
symbol.icon = iconForTag(symbol.tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addSymbols(allSymbols, newSymbols, provider) {
|
addSymbols(allSymbols, newSymbols, provider) {
|
||||||
for (let symbol of newSymbols) {
|
for (let symbol of newSymbols) {
|
||||||
if (!this.isValidSymbol(symbol)) {
|
if (!this.isValidSymbol(symbol)) {
|
||||||
console.warn('Invalid symbol:', symbol);
|
console.warn('Invalid symbol:', symbol);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// We enforce these so that (a) we can show a human-readable name of the
|
|
||||||
// provider for each symbol (if the user opts into it), and (b) we can
|
this.normalizeSymbol(symbol, provider);
|
||||||
// selectively clear cached results for certain providers without
|
|
||||||
// affecting others.
|
|
||||||
symbol.providerName ??= provider.name;
|
|
||||||
symbol.providerId ??= provider.packageName;
|
|
||||||
if (symbol.path) {
|
|
||||||
let parts = Path.parse(symbol.path);
|
|
||||||
symbol.directory = `${parts.dir}${Path.sep}`;
|
|
||||||
symbol.file = parts.base;
|
|
||||||
}
|
|
||||||
symbol.name = symbol.name.replace(/[\n\r\t]/, ' ');
|
|
||||||
allSymbols.push(symbol);
|
allSymbols.push(symbol);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,39 +13,52 @@
|
|||||||
"quickJumpToFileSymbol": {
|
"quickJumpToFileSymbol": {
|
||||||
"default": true,
|
"default": true,
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
"order": 1,
|
||||||
"description": "Automatically visit selected file-symbols as you navigate the symbols list."
|
"description": "Automatically visit selected file-symbols as you navigate the symbols list."
|
||||||
},
|
},
|
||||||
"showProviderNamesInSymbolsView": {
|
|
||||||
"default": false,
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "When enabled, the name of the provider will be shown alongside each result."
|
|
||||||
},
|
|
||||||
"showIconsInSymbolsView": {
|
"showIconsInSymbolsView": {
|
||||||
"default": true,
|
"default": true,
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
"order": 2,
|
||||||
"description": "When enabled, an icon will be shown alongside a symbol if the symbol provider specifies one."
|
"description": "When enabled, an icon will be shown alongside a symbol if the symbol provider specifies one."
|
||||||
},
|
},
|
||||||
|
"useBadgeColors": {
|
||||||
|
"default": false,
|
||||||
|
"type": "boolean",
|
||||||
|
"order": 3,
|
||||||
|
"description": "Whether to use an assortment of colors for symbol badges. If enabled, each badge will be one of sixteen colors based on its text. Badge colors are generated automatically as hue variants of your theme’s ordinary badge color."
|
||||||
|
},
|
||||||
|
"prefillSelectedText": {
|
||||||
|
"default": true,
|
||||||
|
"type": "boolean",
|
||||||
|
"order": 4,
|
||||||
|
"description": "When enabled, any text you may have selected in the active editor will be used as the search query in the symbols list."
|
||||||
|
},
|
||||||
|
"showProviderNamesInSymbolsView": {
|
||||||
|
"default": false,
|
||||||
|
"type": "boolean",
|
||||||
|
"order": 5,
|
||||||
|
"description": "When enabled, the name of the provider will be shown alongside each result."
|
||||||
|
},
|
||||||
"preferCertainProviders": {
|
"preferCertainProviders": {
|
||||||
"default": [],
|
"default": [],
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"order": 6,
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"description": "A comma-separated list of preferred providers. Used to help break ties when more than one provider can contribute symbols. Anything on this list will be preferred over anything not on this list, and earlier items will be preferred over later items. (A provider can be identified by its official name or its package name; run the **Symbols View: Show Active Providers** command to see both values.)"
|
"description": "A comma-separated list of preferred providers. Used to help break ties when more than one provider can contribute symbols. Anything on this list will be preferred over anything not on this list, and earlier items will be preferred over later items. (A provider can be identified by its official name or its package name; run the **Symbols View: Show Active Providers** command to see both values.)"
|
||||||
},
|
},
|
||||||
"useBadgeColors": {
|
|
||||||
"default": false,
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Whether to use an assortment of colors for symbol badges. If enabled, each badge will be one of sixteen colors based on its text. Badge colors are generated automatically as hue variants of your theme’s ordinary badge color."
|
|
||||||
},
|
|
||||||
"providerTimeout": {
|
"providerTimeout": {
|
||||||
"default": 2000,
|
"default": 2000,
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
"order": 7,
|
||||||
"description": "How long providers have to respond to symbol requests before this package gives up and shows the list. If a certain provider is particularly slow, you may have to increase this value. (Does not apply to project-wide symbol search **if** the list is already visible.)"
|
"description": "How long providers have to respond to symbol requests before this package gives up and shows the list. If a certain provider is particularly slow, you may have to increase this value. (Does not apply to project-wide symbol search **if** the list is already visible.)"
|
||||||
},
|
},
|
||||||
"enableDebugLogging": {
|
"enableDebugLogging": {
|
||||||
"default": false,
|
"default": false,
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
"order": 10,
|
||||||
"description": "Whether to log certain diagnostic information to the console. (For example: which provider is chosen for a given task.)"
|
"description": "Whether to log certain diagnostic information to the console. (For example: which provider is chosen for a given task.)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -79,7 +79,7 @@ function registerProvider(...args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('SymbolsView', () => {
|
describe('SymbolsView', () => {
|
||||||
let symbolsView, activationPromise, editor, directory, mainModule;
|
let symbolsView, activationPromise, editor, directory, mainModule, languageMode;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jasmine.unspy(Date, 'now');
|
jasmine.unspy(Date, 'now');
|
||||||
@ -101,10 +101,9 @@ describe('SymbolsView', () => {
|
|||||||
atom.config.set('symbols-view.showIconsInSymbolsView', false);
|
atom.config.set('symbols-view.showIconsInSymbolsView', false);
|
||||||
|
|
||||||
activationPromise = atom.packages.activatePackage('symbols-view');
|
activationPromise = atom.packages.activatePackage('symbols-view');
|
||||||
activationPromise.then(() => {
|
await activationPromise.then(() => {
|
||||||
mainModule = atom.packages.getActivePackage('symbols-view').mainModule;
|
mainModule = atom.packages.getActivePackage('symbols-view').mainModule;
|
||||||
});
|
});
|
||||||
await activationPromise;
|
|
||||||
await atom.packages.activatePackage('language-javascript');
|
await atom.packages.activatePackage('language-javascript');
|
||||||
jasmine.attachToDOM(getWorkspaceView());
|
jasmine.attachToDOM(getWorkspaceView());
|
||||||
});
|
});
|
||||||
@ -117,8 +116,8 @@ describe('SymbolsView', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
atom.config.set('symbols-view.providerTimeout', 500);
|
atom.config.set('symbols-view.providerTimeout', 500);
|
||||||
await atom.workspace.open(directory.resolve('sample.js'));
|
await atom.workspace.open(directory.resolve('sample.js'));
|
||||||
let editor = atom.workspace.getActiveTextEditor();
|
editor = atom.workspace.getActiveTextEditor();
|
||||||
let languageMode = editor.getBuffer().getLanguageMode();
|
languageMode = editor.getBuffer().getLanguageMode();
|
||||||
if (languageMode.ready) await languageMode.ready;
|
if (languageMode.ready) await languageMode.ready;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -144,6 +143,40 @@ describe('SymbolsView', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('prefills the query field if `prefillSelectedText` is `true`', async () => {
|
||||||
|
atom.config.set('symbols-view.prefillSelectedText', true);
|
||||||
|
registerProvider(DummyProvider);
|
||||||
|
await activationPromise;
|
||||||
|
spyOn(editor, 'getSelectedText').andReturn('Symbol on Row 13');
|
||||||
|
await dispatchAndWaitForChoices('symbols-view:toggle-file-symbols');
|
||||||
|
symbolsView = getSymbolsView();
|
||||||
|
|
||||||
|
expect(symbolsView.selectListView.refs.loadingMessage).toBeUndefined();
|
||||||
|
expect(document.body.contains(symbolsView.element)).toBe(true);
|
||||||
|
expect(symbolsView.element.querySelectorAll('li').length).toBe(1);
|
||||||
|
|
||||||
|
expect(symbolsView.element.querySelector('li:first-child .primary-line')).toHaveText('Symbol on Row 13');
|
||||||
|
expect(symbolsView.element.querySelector('li:first-child .secondary-line')).toHaveText('Line 13');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not prefill the query field if `prefillSelectedText` is `false`', async () => {
|
||||||
|
atom.config.set('symbols-view.prefillSelectedText', false);
|
||||||
|
registerProvider(DummyProvider);
|
||||||
|
await activationPromise;
|
||||||
|
spyOn(editor, 'getSelectedText').andReturn('Symbol on Row 13');
|
||||||
|
await dispatchAndWaitForChoices('symbols-view:toggle-file-symbols');
|
||||||
|
symbolsView = getSymbolsView();
|
||||||
|
|
||||||
|
expect(symbolsView.selectListView.refs.loadingMessage).toBeUndefined();
|
||||||
|
expect(document.body.contains(symbolsView.element)).toBe(true);
|
||||||
|
expect(symbolsView.element.querySelectorAll('li').length).toBe(5);
|
||||||
|
|
||||||
|
expect(symbolsView.element.querySelector('li:first-child .primary-line')).toHaveText('Symbol on Row 1');
|
||||||
|
expect(symbolsView.element.querySelector('li:first-child .secondary-line')).toHaveText('Line 1');
|
||||||
|
expect(symbolsView.element.querySelector('li:last-child .primary-line')).toHaveText('Symbol on Row 13');
|
||||||
|
expect(symbolsView.element.querySelector('li:last-child .secondary-line')).toHaveText('Line 13');
|
||||||
|
});
|
||||||
|
|
||||||
it('does not wait for providers that take too long', async () => {
|
it('does not wait for providers that take too long', async () => {
|
||||||
registerProvider(DummyProvider, VerySlowProvider);
|
registerProvider(DummyProvider, VerySlowProvider);
|
||||||
await activationPromise;
|
await activationPromise;
|
||||||
@ -614,6 +647,7 @@ describe('SymbolsView', () => {
|
|||||||
describe('when toggling project symbols', () => {
|
describe('when toggling project symbols', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await atom.workspace.open(directory.resolve('sample.js'));
|
await atom.workspace.open(directory.resolve('sample.js'));
|
||||||
|
editor = atom.workspace.getActiveTextEditor();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays all symbols', async () => {
|
it('displays all symbols', async () => {
|
||||||
@ -636,6 +670,48 @@ describe('SymbolsView', () => {
|
|||||||
expect(symbolsView.element.querySelector('li:last-child .secondary-line')).toHaveText(`${relative}:13`);
|
expect(symbolsView.element.querySelector('li:last-child .secondary-line')).toHaveText(`${relative}:13`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('prefills the query field if `prefillSelectedText` is `true`', async () => {
|
||||||
|
atom.config.set('symbols-view.prefillSelectedText', true);
|
||||||
|
registerProvider(DummyProvider);
|
||||||
|
await activationPromise;
|
||||||
|
spyOn(editor, 'getSelectedText').andReturn('Symbol on Row 13');
|
||||||
|
await dispatchAndWaitForChoices('symbols-view:toggle-project-symbols');
|
||||||
|
symbolsView = getSymbolsView();
|
||||||
|
|
||||||
|
expect(symbolsView.selectListView.refs.loadingMessage).toBeUndefined();
|
||||||
|
expect(document.body.contains(symbolsView.element)).toBe(true);
|
||||||
|
expect(symbolsView.element.querySelectorAll('li').length).toBe(1);
|
||||||
|
|
||||||
|
let root = atom.project.getPaths()[1];
|
||||||
|
let resolved = directory.resolve('other-file.js');
|
||||||
|
let relative = `${path.basename(root)}${resolved.replace(root, '')}`;
|
||||||
|
|
||||||
|
expect(symbolsView.element.querySelector('li:first-child .primary-line')).toHaveText('Symbol on Row 13');
|
||||||
|
expect(symbolsView.element.querySelector('li:first-child .secondary-line')).toHaveText(`${relative}:13`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not prefill the query field if `prefillSelectedText` is `false`', async () => {
|
||||||
|
atom.config.set('symbols-view.prefillSelectedText', false);
|
||||||
|
registerProvider(DummyProvider);
|
||||||
|
await activationPromise;
|
||||||
|
spyOn(editor, 'getSelectedText').andReturn('Symbol on Row 13');
|
||||||
|
await dispatchAndWaitForChoices('symbols-view:toggle-project-symbols');
|
||||||
|
symbolsView = atom.workspace.getModalPanels()[0].item;
|
||||||
|
|
||||||
|
expect(symbolsView.selectListView.refs.loadingMessage).toBeUndefined();
|
||||||
|
expect(document.body.contains(symbolsView.element)).toBe(true);
|
||||||
|
expect(symbolsView.element.querySelectorAll('li').length).toBe(5);
|
||||||
|
|
||||||
|
let root = atom.project.getPaths()[1];
|
||||||
|
let resolved = directory.resolve('other-file.js');
|
||||||
|
let relative = `${path.basename(root)}${resolved.replace(root, '')}`;
|
||||||
|
|
||||||
|
expect(symbolsView.element.querySelector('li:first-child .primary-line')).toHaveText('Symbol on Row 1');
|
||||||
|
expect(symbolsView.element.querySelector('li:first-child .secondary-line')).toHaveText(`${relative}:1`);
|
||||||
|
expect(symbolsView.element.querySelector('li:last-child .primary-line')).toHaveText('Symbol on Row 13');
|
||||||
|
expect(symbolsView.element.querySelector('li:last-child .secondary-line')).toHaveText(`${relative}:13`);
|
||||||
|
});
|
||||||
|
|
||||||
it('asks for new symbols when the user starts typing', async () => {
|
it('asks for new symbols when the user starts typing', async () => {
|
||||||
registerProvider(ProgressiveProjectProvider);
|
registerProvider(ProgressiveProjectProvider);
|
||||||
spyOn(ProgressiveProjectProvider, 'getSymbols').andCallThrough();
|
spyOn(ProgressiveProjectProvider, 'getSymbols').andCallThrough();
|
||||||
@ -756,6 +832,9 @@ describe('SymbolsView', () => {
|
|||||||
describe('when quickJumpToSymbol is true', () => {
|
describe('when quickJumpToSymbol is true', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await atom.workspace.open(directory.resolve('sample.js'));
|
await atom.workspace.open(directory.resolve('sample.js'));
|
||||||
|
editor = atom.workspace.getActiveTextEditor();
|
||||||
|
languageMode = editor.getBuffer().getLanguageMode();
|
||||||
|
if (languageMode.ready) await languageMode.ready;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('jumps to the selected function', async () => {
|
it('jumps to the selected function', async () => {
|
||||||
@ -775,6 +854,7 @@ describe('SymbolsView', () => {
|
|||||||
// dev tools console? That seems to break it on a reliable basis. Not
|
// dev tools console? That seems to break it on a reliable basis. Not
|
||||||
// sure why yet.
|
// sure why yet.
|
||||||
it('restores previous editor state on cancel', async () => {
|
it('restores previous editor state on cancel', async () => {
|
||||||
|
atom.config.set('symbols-view.prefillSelectedText', false);
|
||||||
registerProvider(DummyProvider);
|
registerProvider(DummyProvider);
|
||||||
await activationPromise;
|
await activationPromise;
|
||||||
const bufferRanges = [{start: {row: 0, column: 0}, end: {row: 0, column: 3}}];
|
const bufferRanges = [{start: {row: 0, column: 0}, end: {row: 0, column: 3}}];
|
||||||
|
@ -43,7 +43,11 @@ atom-panel.modal .symbols-view {
|
|||||||
.primary-line {
|
.primary-line {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: baseline;
|
align-items: center;
|
||||||
|
|
||||||
|
&.icon::before {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
Loading…
Reference in New Issue
Block a user