pulsar/packages/symbol-provider-tree-sitter
2024-01-28 23:43:21 -08:00
..
lib [symbol-provider-tree-sitter] Add specs for context, tag, icon 2024-01-28 23:43:21 -08:00
spec [symbol-provider-tree-sitter] Add specs for context, tag, icon 2024-01-28 23:43:21 -08:00
.eslintrc.js Add symbol provider packages 2024-01-03 10:26:17 -08:00
LICENSE.md Add symbol provider packages 2024-01-03 10:26:17 -08:00
package-lock.json Add symbol provider packages 2024-01-03 10:26:17 -08:00
package.json Add symbol provider packages 2024-01-03 10:26:17 -08:00
README.md [symbol-provider-tree-sitter] Add specs for context, tag, icon 2024-01-28 23:43:21 -08:00

symbol-provider-tree-sitter package

Provides symbols to symbols-view via Tree-sitter queries.

Tree-sitter grammars with tags queries can very easily give us a list of all the symbols in a file without the drawbacks of a ctags-based approach. For instance, they operate on the contents of the buffer, not the contents of the file on disk, so they work just fine in brand-new files and in files that have been modified since the last save.

This provider does not currently support project-wide symbol search, but possibly could do so in the future.

Tags queries

This provider expects for a grammar to have specified a tags query in its grammar definition file. All the built-in Tree-sitter grammars will have such a file. If youre using a third-party Tree-sitter grammar that hasnt defined one, file an issue on Pulsar and well see what we can do.

If youre writing your own grammar, or contributing a tags.scm to a grammar without one, keep reading.

Query syntax

The query syntax starts as a subset of what is described on this page. Heres what this package can understand:

  • A query that consists of a @definition.THING capture with a @name capture inside will properly be understood as a symbol with a tag corresponding to THING and a name corresponding to the @name captures text.
  • A query that consists of a @reference.THING capture with a @name capture inside will be ignored by default. If the proper setting is enabled, each of these references will become a symbol with a tag corresponding to THING and a name corresponding to the @name captures text.
  • All other @name captures that are not within either a @definition or a @reference will be considered as a symbol in isolation. (These symbols can still specify a tag via a #set! predicate.)

To match the current behavior of the symbols-view package, you can usually take a queries/tags.scm file from a Tree-sitter repository — many parsers define them — and paste it straight into your grammars tags.scm file.

Advanced features

The text of the captured node is what will be displayed as the symbols name, but a few predicates are available to alter that field and others. Symbol predicates use #set! and the symbol namespace.

Node position descriptors

Several predicates take a node position descriptor as an argument. Its a string that resembles an object lookup chain in JavaScript:

(#set! symbol.prependTextForNode parent.parent.firstNamedChild)

Starting at the captured node, it describes a path to take within the tree in order to get to another meaningful node.

In all these examples, if the descriptor is invalid and does not return a node, the predicate will be ignored.

Changing the symbols name

There are several ways to add text to the beginning or end of the symbols name:

symbol.prepend
(class_declaration
  name: (identifier) @name
  (#set! symbol.prepend "Class: "))

The symbol.prepend predicate adds a constant string to the beginning of a symbol name. For a class Foo in JavaScript, this predicate would result in a symbol called Class: Foo.

symbol.append
(class_declaration
  name: (identifier) @name
  (#set! symbol.append " (class)"))

The symbol.append predicate adds a constant string to the end of a symbol name. For a class Foo, this predicate would result in a symbol called Foo (class).

symbol.strip
(class_declaration
  name: (identifier) @name
  (#set! symbol.strip "^\\s+|\\s+$"))

The symbol.strip predicate will replace everything matched by the regular expression with an empty string. The pattern given is compiled into a JavaScript RegExp with an implied g (global) flag.

In this example, if the identifier node included whitespace on either side of the symbol, the symbols name would be stripped of that whitespace before being shown in the UI.

symbol.prependTextForNode
(class_body (method_definition
  name: (property_identifier) @name
  (#set! symbol.prependTextForNode "parent.parent.previousNamedSibling")
  (#set! symbol.joiner "#")
))

The symbol.prependTextForNode predicate will look up the text of the node referred to by the provided node position descriptor, then prepend that text to the symbol name. If symbol.joiner is provided, it will be inserted in between the two.

In this example, a bar method on a class named Foo would have a symbol name of Foo#bar.

symbol.prependSymbolForNode
(class_body (method_definition
  name: (property_identifier) @name
  (#set! symbol.prependSymbolForNode "parent.parent.previousNamedSibling")
  (#set! symbol.joiner "#")
))

The symbol.prependSymbolForNode predicate will look up the symbol name of the node referred to by the provided node position descriptor, then prepend that name to the symbol name. If symbol.joiner is provided, it will be inserted in between the two.

Unlike symbol.prependTextForNode, the node referred to with the descriptor must have its own symbol name, and it must have been processed already — that is, it must be a symbol whose name was determined earlier than that of the current node.

This allows us to incorporate any transformations that were applied to the other nodes symbol name. We can use this to build “recursive” symbol names — for instance, JSON keys whose symbols consist of their entire key path from the root.

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 class it belongs to. The context field is not filtered on.

symbol.contextNode
(class_body (method_definition
  name: (property_identifier) @name
  (#set! symbol.contextNode "parent.parent.previousNamedSibling")
))

The symbol.contextNode predicate will set the value of a symbols context property to the text of a node based on the provided node position descriptor.

symbol.context
(class_body (method_definition
  name: (property_identifier) @name
  (#set! symbol.context "class")
))

The symbol.context predicate will set the value of a symbols context property to a fixed string.

The point of context is to provide information to help you tell symbols apart, so you probably dont want to set it to a fixed value. But this predicate is available just in case.

Adding a tag

The tag field is a string that indicates a symbols kind or type. It should be a single word wherever possible. A tag for a class methods 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 youre not sure what to call something, consult this list from the Language Server Protocol spec. But some symbols may not fit any of those, so ultimately its 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 grammars tags.scm file…

(assignment_expression
  left: [
    (identifier) @name
    (member_expression
      property: (property_identifier) @name)
  ]
  right: [(arrow_function) (function)]
) @definition.function

…the resulting symbol will infer a tag value of function.

In cases where this is impractical, you can provide the tag explicitly with a predicate.

Nearly all the tags on the aforementioned list 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
(class_body (method_definition
  name: (property_identifier) @name
  (#set! symbol.tag "class")
))

The symbol.tag predicate will set the value of a symbols 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.

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
(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.