README
nova-extension-utils
This package provides sharable utilities for building Nova extensions.
Features
installWrappedDependencies
This function provides a concurrent-safe, reproducible, non-global-polluting mechanism for installing external nodejs dependencies executed by your extension, without bundling them within the extension artifact and increasing extension size. This is especially useful in dev, as it won't trigger reloads of the extension recursively.
To use it, you must have a valid package.json
and npm-shrinkwrap.json
file in your .novaextension
directory. They'll be copied into your extension's global storage and used to install dependencies specified in the shrinkwrap file. An atomic file lock is used to prevent multiple workspaces writing over each other.
installWrappedDependencies
This is an async function that handles installation. Call it during extension activation. It takes a two parameters
- A composite disposable that on disposal kills the installation process and unlocks. Be sure to dispose when the extension deactivates.
- An optional options object with the optional properties.
console
an object who's properties override those ofConsole
. Passnull
to fully disableconsole
calls.
registerDependencyUnlockCommand
Registers a global command that will force unlock. Not required, but can be useful to cleanup if crashes happen. Make sure to define the command to give users access.
getDependencyDirectory
Returns a path to the directory containing the installed node_modules
directory.
Here's a full example of usage
import { dependencyManagement } from "nova-extension-utils";
const compositeDisposable = new CompositeDisposable();
dependencyManagement.registerDependencyUnlockCommand(
"com.example.extension.unlock"
);
async function asyncActivate() {
await dependencyManagement.installWrappedDependencies(compositeDisposable, {
console: {
log(...args: Array<unknown>) {
console.log("dependency management:", ...args);
},
},
});
const execPath = nova.path.join(
dependencyManagement.getDependencyDirectory(),
"node_modules",
".bin",
"executable"
);
const process = new Process(execPath);
compositeDisposable.add({
dispose() {
process.terminate();
},
});
process.start();
}
export function activate() {
console.log("activating...");
return asyncActivate()
.catch((err) => {
console.error(err);
})
.then(() => {
console.log("activated");
});
}
export function deactivate() {
compositeDisposable.dispose();
}
asyncNova
showChoicePalette
Asyncronous, generic, non-index-based access to the choice palette.
Example of use
import type * as lspTypes from "vscode-languageserver-protocol";
import { asyncNova } from "nova-extension-utils";
async function foo(items: lspTypes.CompletionItem[]) {
const choice: lspTypes.CompletionItem | null = await asyncNova(
items,
(item) => `${item.label}${item.detail ? `- ${item.detail}` : ""}`,
{ placeholder: "suggestions" }
);
if (!choice) {
return;
}
console.log(choice);
}
cleanPath
Function to nicely format paths and file URIs for user-display. Replaces $HOME
with ~/
, removes volume and file://
, and replaces workspace path with ./
.
Example of use
import { cleanPath } from "nova-extension-utils";
console.log(cleanPath(editor.document.uri));
preferences
getOverridableBoolean
This provides a common pattern to have a boolean preference that can be set globally or per-workspace, but allows the workspace to override the global preference. The user has the ability to set a preference globally, but they can override it in each workspace. I recommend defaulting to the least-destructive/mutating action globally, to help make it easier to work in shared codebases.
This expects the preference to be set up properly in your extension manifest. You'll configure a global-level boolean (with any default value), and a workspace-level enum (with three possible values of "null", "false", and "true" and a default of "null") with the same preference key.
{
"config": [
{
"key": "apexskier.example.config.myPreference",
"title": "Example",
"type": "boolean",
"default": false
}
],
"configWorkspace": [
{
"key": "apexskier.example.config.myPreference",
"title": "Example",
"type": "enum",
"values": [
["null", "Inherit from Global Settings"],
["false", "Disable"],
["true", "Enable"]
],
"default": "null"
}
]
}
import { preferences } from "nova-extension-utils";
const defaultPrefValue = false;
const prefValue: boolean =
preferences.getOverridableBoolean("apexskier.example.config.myPreference") ??
defaultPrefValue;