Extensions

Extensions can be composed of several components contributing features and functionality to various parts of the IDE.

This includes the following extension points:

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:

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.

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:

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:

It is recommended to declare the most strict set of entitlements that are required for your extension.