Extensions
Extensions can be composed of several components contributing features and functionality to various parts of the IDE.
This includes the following extension points:
- Commands (User-Invokable)
- Preferences (Global and Workspace-specific)
- Completions
- Syntax Grammars
- Language Servers
- Themes
- Sidebars
- Task Templates
- Issue Providers
- Clips
- Key Bindings
Creating A New Extension
To create a new extension, ensure that the extension development features are enabled in the General preferences pane (“Show extension development items in Extensions menu”).
Then, from the Extensions menu, choose Create New Extension…. This will present a sheet asking you what type of extension you would like to create. Once you choose a type, it will ask you to fill in some details about your extension, like its Name, the Vendor Name of the entity releasing it (you, your team, your company, etc.), and options related to what capabilities your extension should have.
Each extension template targets a specific area of extendability, but a single extension may contain any combination of these features if desired. There’s nothing preventing you, for example, from creating an extension that combines a Syntax, Completions, Clips, and Commands for a language or framework into a single extension.
JavaScript Runtime
For certain extension features, such as Commands, extensions can extend the IDE using JavaScript. In these situations, each workspace will run its own instance of an extension within a separate JavaScript sandbox. However, an extension does not need to include JavaScript components if it is not using these features.
The JavaScript runtime powering extensions is based on Apple’s JavaScriptCore. JavaScript components of an extension are executed within a separate process from the IDE, adding additional insulating from extensions causing issues with the main application.
main.js
and JavaScript Entry Points
JavaScript extensions will include a primary script entry point, which is most often called main.js
.
This script may optionally export two special functions: activate
and deactivate
.
activate
is invoked when the extension is loaded into a workspace, such as if a new workspace is opened or a new extension is installed while a workspace is already open.
deactivate
is invoked when the extension is unloaded from a workspace, such as if the workspace window is closed or the extension is uninstalled.
exports.activate = function() {
// Do work when the extension is activated
}
exports.deactivate = function() {
// Clean up state before the extension is deactivated
}
JavaScript Modules
The extension runtime scopes JavaScript code into “modules”, and as such has the ability to import additional scripts as “modules” using a require(path)
function, similar to other JavaScript environments. This function takes a path to a script relative to the current module, and returns the exported interface of the imported module.
All modules, whether it be from main.js
or another, are evaluated within their own JavaScript function context to protect against accidental namespace collisions. All modules has access to all global objects defined by the extension runtime (such as nova
), as well as some special variables:
require
: which can be used to import additional modulesexports
: an object which can be used to “export” objects and interfaces to the caller ofrequire
module
: an object which represents the current module being imported__filename
: the file path of the current module__dirname
: the parent directory path of the current module
Most imported modules will want to export objects or constructors to the caller of require
, and can do so using the exports
object (which, by default, is an alias to module.exports
).
// foo.js:
var foo = 10;
var bar = "a string";
exports.foo = foo;
exports.bar = bar;
// main.js:
const foobar = require('foo.js');
console.log(foobar.foo) // => "10"
console.log(foobar.bar) // => "a string"
By default, exports
is a simple Object
. If the module wishes to export a special object instead of tacking on object properties, it may do so by setting the module.exports
property.
One additional note: Global variables defined within an imported module will be defined in the global namespace of the entire extension. This is a consequence of JavaScript’s global variable handling.
Activation Events
Most extensions will likely apply only to a specific language, toolchain, or process. As such, it doesn’t always make sense for an extension’s JavaScript code to be started within a workspace for all projects a user might open. This is especially true for extensions that might require significant processing time for operations on startup.
To that end, extensions can declare support for “activation events”, which determine when an extension’s JavaScript component will be started by the runtime.
The activationEvents
key of the extension’s extension.json
file should contain a list of the events whose conditions cause the extension to start automatically. The following example shows activation events that might be used by an extension that operates on TypeScript projects:
"activationEvents": [
"onLanguage:javascript",
"onLanguage:typescript",
"onLanguage:jsx",
"onLanguage:tsx",
"onWorkspaceContains:tsconfig.json",
]
Each activation event is specified as a string. The string contains the event name, followed by a colon and an argument that depends on the event type. The same event type may be specified more than once to cover multiple disparate options.
The set of activation events currently supported are:
Value | Description |
---|---|
onCommand | When an extension command with the specified name is invoked |
onLanguage | When a document of the specified language is opened |
onWorkspaceContains | When a project is opened that contains a file matching the specified glob pattern |
* | A special string that may be used to indicate the extension should always start |
In certain cases, the extension should always be started for all projects. In these cases, you may specify the special string *
in the list of activation events. However, keep in mind that this should only be used in cases where other activation events are not sufficient.
Required Runtime Versions
As updates to the application and extension runtime are released, extension authors may require dropping support for older versions of the app so that users can take full advantage of newer extension API features.
To facilitate this, there are two keys of the extension JSON manifest. Extension runtime versions are always the same as the application version, such as 1.1
.
min_runtime
: The minimum version of the runtime supported for the extension. The extension will not be able to be installed in application versions older to this.max_runtime
: The maximum version of the runtime supported by the extension. The extension will not be able to be installed in application versions newer to this.
Categories
Extensions submitted to the Extension Library can be put into several categories so users can easily find them. Defining at least one category is required to be published on the extension library.
Extension categories are specified using an array of identifiers for the categories
key of your extension manifest:
"categories": ["languages", "issues"]
The set of available category identifiers and their Extension Library sections are:
- “clips”: Clips; extensions which contribute clips specified in Nova’s clip JSON format
- “commands”: Commands; extensions which contribute one or more Commands
- “completions”: Completions; extensions which contribute a Completions Provider, completions from a LanguageClient, or completions from other means
- “formatters”: Formatters; extensions which format or “prettify” text and code
- “issues”: Issue Providers / Validators; extensions which contribute an Issue Provider
- “key-bindings”: Key Bindings; extensions which contribute key bindings in Nova’s key binding JSON format
- “languages”: Languages; extensions which contribute languages syntaxes and additional editor intelligence, such as through a LanguageClient
- “sidebars”: Sidebars; extensions which contribute a sidebar using the TreeView API
- “tasks”: Tasks; extensions which contribute Task Templates
- “themes”: Themes; extensions which contribute Themes
Entitlements
Extensions that perform operations that touch files and services outside of the IDE’s sandbox are required to declare such intentions through the use of Entitlements. This includes any direct access to the filesystem, network requests, and subprocesses. By declaring this intent, you inform the user of what your extension might be capable of doing with regards to their privacy.
Within an extension’s extension.json
file, a key may be included to declare extension intents:
"entitlements": {
"clipboard": true,
"process": true,
"requests": true,
"filesystem": "readonly"
}
The following entitlements are available:
"clipboard"
: Provides access to the Clipboard API.- The value of this entitlement is a boolean (
true
orfalse
)
- The value of this entitlement is a boolean (
"filesystem"
: Provides access to the FileSystem and related APIs to read and/or write to files on disk (Note: this entitlement is not required to access the open documents within the IDE)- The value of this entitlement is either the strings
"readonly"
or"readwrite"
, granting read-only or read-writable access, respectively.
- The value of this entitlement is either the strings
"requests"
: Provides access to the Fetch API to perform asynchronous HTTP network requests- The value of this entitlement is a boolean (
true
orfalse
)
- The value of this entitlement is a boolean (
"process"
: Provides access to the Process API to launch and communciate with external tools- The value of this entitlement is a boolean (
true
orfalse
)
- The value of this entitlement is a boolean (
It is recommended to declare the most strict set of entitlements that are required for your extension.
Links
Extensions may provide URL links to several important locations as part of their manifest. The following keys may be provided:
bugs
: A string or object describing the URL to the extension’s issue tracker- If this value is a string this represents the URL
- If this value is an object, it may have the following keys:
url
: A string representing the URLemail
: An email address that will be the target of amailto:
link
- It is generally preferred to use a URL value of some type over an email address /
mailto:
link
homepage
: A string representing the URL to the extension’s website homepagefunding
: A string representing the URL to the extension’s funding page, if any- Extensions may provide this value to show a “Sponsor” button on the extension details page
- This link may point to services such as Patreon or GitHub Sponsor to allow users to donate / sponsor development
repository
: A string representing the URL to the extension’s code repository, if any