diff --git a/.nova/Configuration.json b/.nova/Configuration.json index d2b796d..a414d18 100644 --- a/.nova/Configuration.json +++ b/.nova/Configuration.json @@ -1,14 +1,9 @@ { - "co.gwil.deno.config.trustedImportHosts" : [ - "https:\/\/deno.land" - ], - "deno.cacheOnSave" : true, - "deno.enablePaths" : [ - "src", - "build.ts", - "deno.json" - ], - "deno.lint" : true, - "workspace.art_style" : 0, - "workspace.name" : "nova-deno" + "co.gwil.deno.config.trustedImportHosts": [ + "https:\/\/deno.land" + ], + "deno.cacheOnSave": true, + "deno.lint": true, + "workspace.art_style": 0, + "workspace.name": "nova-deno" } diff --git a/deno.json b/deno.json index b48f4ce..cfc501b 100644 --- a/deno.json +++ b/deno.json @@ -1,10 +1,11 @@ { - "tasks": { - "build": "deno run --allow-all --unstable build.ts" - }, - "imports": { - "lsp": "npm:vscode-languageserver-types", - "strip-json": "https://esm.sh/strip-json-comments", - "nova-utils": "../nova_utils/mod.ts" - } + "tasks": { + "build": "deno run --allow-all --unstable build.ts" + }, + "imports": { + "lsp": "npm:vscode-languageserver-types", + "strip-json": "https://esm.sh/strip-json-comments", + "nova-utils": "../nova_utils/mod.ts" + }, + "exclude": ["deno.novaextension"] } diff --git a/deno.novaextension/CHANGELOG.md b/deno.novaextension/CHANGELOG.md index 952ef56..1dd3a89 100644 --- a/deno.novaextension/CHANGELOG.md +++ b/deno.novaextension/CHANGELOG.md @@ -1,3 +1,10 @@ +## NEXT + +- Add a new "Reload Import Registries" command. +- Manually restart the server when the config file changes. +- The "Cache dependencies" command was broken by the underlying command having been removed from the LSP. It has now been updated to the new command. +- Removed the workspace "Enabled paths" and "Disabled paths" setting. If you were to add some enabled paths and remove all of them, Nova would leave an empty array, which would make the Deno LSP decide not to be enabled for anything. If you need to conditionally disable which files are analysed by the LSP, use the deno.json `exclude` option to do so. + ## v1.4.3 - Changed fallback value for `deno.enablePaths`, as newer versions of Deno LSP @@ -7,7 +14,7 @@ - The per-workspace setting for enabling unstable Deno APIs is now _really_ respected. -- Preferences for lining, cache on save, and complete function calls have all +- Preferences for linting, cache on save, and complete function calls have all been moved to workspace settings. Changing these settings will no longer restart the LSP client! - Fixed an out of bounds issue for LSP <> Nova range conversions. diff --git a/deno.novaextension/extension.json b/deno.novaextension/extension.json index 776310d..41aec0a 100644 --- a/deno.novaextension/extension.json +++ b/deno.novaextension/extension.json @@ -82,23 +82,6 @@ "type": "boolean", "default": true }, - { - "key": "deno.enablePaths", - "title": "Enabled paths", - "description": "Restrict extension functionality to the specified paths.", - "type": "pathArray", - "allowFolders": true, - "relative": true - }, - { - "key": "deno.disablePaths", - "title": "Disabled paths", - "description": "Exclude extension functionality from the specified paths.", - "type": "pathArray", - "allowFolders": true, - "relative": true, - "default": [] - }, { "key": "deno.importMap", "title": "Import map", @@ -203,6 +186,13 @@ "syntaxes": ["typescript", "tsx", "javascript", "jsx"] } }, + { + "title": "Reload Import Registries", + "command": "co.gwil.deno.commands.reloadImportRegistries", + "filters": { + "syntaxes": ["typescript", "tsx", "javascript", "jsx"] + } + }, { "title": "Restart Deno LSP server", "command": "co.gwil.deno.commands.restartServer" diff --git a/src/client_disposable.ts b/src/client_disposable.ts index 89fd01e..8744baf 100644 --- a/src/client_disposable.ts +++ b/src/client_disposable.ts @@ -1,5 +1,6 @@ import registerFormatDocument from "./commands/format_document.ts"; import registerCache from "./commands/cache.ts"; +import registerReloadImportRegistries from "./commands/reload_import_registries.ts"; import registerRenameSymbol from "./commands/rename_symbol.ts"; import registerPaletteFindSymbol from "./commands/palette_find_symbol.ts"; import registerSymbolSidebarFindSymbol from "./commands/sidebar_find_symbol.ts"; @@ -9,298 +10,291 @@ import { getOverridableBoolean, wrapCommand } from "nova-utils"; const FORMAT_ON_SAVE_CONFIG_KEY = "co.gwil.deno.config.formatOnSave"; const TRUSTED_HOSTS_CONFIG_KEY = "co.gwil.deno.config.trustedImportHosts"; const UNTRUSTED_HOSTS_CONFIG_KEY = "co.gwil.deno.config.untrustedImportHosts"; -const ENABLED_PATHS_CONFIG_KEY = "deno.enablePaths"; // Deno expects a map of hosts for its autosuggestion feature, where each key is a URL and its value a bool representing whether it is trusted or not. Nova does not have a Configurable like this, so we'll have to assemble one out of two arrays. function getHostsMap() { - const trustedHosts = nova.config.get(TRUSTED_HOSTS_CONFIG_KEY) as string[] || - []; + const trustedHosts = nova.config.get(TRUSTED_HOSTS_CONFIG_KEY) as string[] || + []; - const untrustedHosts = - nova.config.get(UNTRUSTED_HOSTS_CONFIG_KEY) as string[] || - []; + const untrustedHosts = + nova.config.get(UNTRUSTED_HOSTS_CONFIG_KEY) as string[] || + []; - const hostsMap: Record = {}; + const hostsMap: Record = {}; - for (const trusted of trustedHosts) { - hostsMap[trusted] = true; - } + for (const trusted of trustedHosts) { + hostsMap[trusted] = true; + } - for (const untrusted of untrustedHosts) { - hostsMap[untrusted] = false; - } + for (const untrusted of untrustedHosts) { + hostsMap[untrusted] = false; + } - return hostsMap; + return hostsMap; } export class CanNotEnsureError extends Error {} async function ensureDenoIsInstalled(): Promise { - function startProcess(location: string, args: string[], cwd?: string) { - const options = { - args, - cwd, - }; - const process = new Process(location, options); - const onExit = new Promise((resolve, reject) => { - process.onDidExit((status) => { - const action = status == 0 ? resolve : reject; - action(status); - }); - }); - - process.start(); - return onExit; - } - - try { - await startProcess("/usr/bin/env", ["deno", "--version"]); - } catch (e) { - if (e == 127) { - const failureNotificationRequest = new NotificationRequest( - "co.gwil.deno.notifications.findSymbolUnavailable", - ); - failureNotificationRequest.title = "Deno can't be found."; - failureNotificationRequest.body = - "To use the Deno extension, install Deno."; - failureNotificationRequest.actions = [ - "Retry", - "Ignore", - ]; - - const { actionIdx } = await nova.notifications.add( - failureNotificationRequest, - ); - - if (actionIdx == 1) { - const informationalNotificationRequest = new NotificationRequest( - "co.gwil.deno.notifications.howToEnableIntelliSense", - ); - informationalNotificationRequest.title = "If you change your mind…"; - informationalNotificationRequest.body = - "Restart the extension to enable its features."; - nova.notifications.add(informationalNotificationRequest); - - throw new CanNotEnsureError("Can't ensure Deno is installed!"); - } else { - return ensureDenoIsInstalled(); - } - } else { - throw e; - } - } + function startProcess(location: string, args: string[], cwd?: string) { + const options = { + args, + cwd, + }; + const process = new Process(location, options); + const onExit = new Promise((resolve, reject) => { + process.onDidExit((status) => { + const action = status == 0 ? resolve : reject; + action(status); + }); + }); + + process.start(); + return onExit; + } + + try { + await startProcess("/usr/bin/env", ["deno", "--version"]); + } catch (e) { + if (e == 127) { + const failureNotificationRequest = new NotificationRequest( + "co.gwil.deno.notifications.findSymbolUnavailable", + ); + failureNotificationRequest.title = "Deno can't be found."; + failureNotificationRequest.body = + "To use the Deno extension, install Deno."; + failureNotificationRequest.actions = [ + "Retry", + "Ignore", + ]; + + const { actionIdx } = await nova.notifications.add( + failureNotificationRequest, + ); + + if (actionIdx == 1) { + const informationalNotificationRequest = new NotificationRequest( + "co.gwil.deno.notifications.howToEnableIntelliSense", + ); + informationalNotificationRequest.title = "If you change your mind…"; + informationalNotificationRequest.body = + "Restart the extension to enable its features."; + nova.notifications.add(informationalNotificationRequest); + + throw new CanNotEnsureError("Can't ensure Deno is installed!"); + } else { + return ensureDenoIsInstalled(); + } + } else { + throw e; + } + } } // Indicates whether the server is being 'restarted' (replaced) let isRestarting = false; export async function makeClientDisposable( - parentDisposable: CompositeDisposable, + parentDisposable: CompositeDisposable, ) { - const clientDisposable = new CompositeDisposable(); - - await ensureDenoIsInstalled(); - - const importMap = nova.workspace.config.get("deno.importMap"); - - const enabledPaths = nova.workspace.config.get( - ENABLED_PATHS_CONFIG_KEY, - "array", - ); - - const client = new LanguageClient( - "co.gwil.deno", - "Deno Language Server", - { - type: "stdio", - path: "/usr/bin/env", - args: ["deno", "lsp", "--quiet"], - }, - { - syntaxes, - initializationOptions: { - enable: true, - enablePaths: enabledPaths && enabledPaths.length > 0 - ? enabledPaths - : null, - cacheOnSave: nova.workspace.config.get("deno.cacheOnSave", "boolean"), - lint: nova.workspace.config.get("deno.lint", "boolean"), - unstable: nova.workspace.config.get( - "deno.unstable", - "boolean", - ), - ...(importMap ? { importMap } : {}), - suggest: { - names: true, - paths: true, - autoImports: true, - completeFunctionCalls: nova.workspace.config.get( - "deno.suggest.completeFunctionCalls", - "boolean", - ) || false, - imports: { - autoDiscover: true, - hosts: getHostsMap(), - }, - }, - documentPreloadLimit: nova.workspace.config.get( - "co.gwil.deno.config.documentPreloadLimit", - ) || undefined, - maxTsServerMemory: nova.workspace.config.get( - "co.gwil.deno.config.maxTsServerMemory", - ) || undefined, - }, - }, - ); - - try { - clientDisposable.add(registerFormatDocument(client)); - clientDisposable.add(registerCache(client)); - clientDisposable.add(registerRenameSymbol(client)); - - // palette Find Symbol command - clientDisposable.add(registerPaletteFindSymbol()); - // sidebar Find Symbol command - clientDisposable.add(registerSymbolSidebarFindSymbol(client)); - - nova.workspace.onDidAddTextEditor((editor) => { - const editorDisposable = new CompositeDisposable(); - - clientDisposable.add(editorDisposable); - clientDisposable.add( - editor.onDidDestroy(() => editorDisposable.dispose()), - ); - - // Formatting - - editorDisposable.add( - editor.document.onDidChangeSyntax(refreshOnSaveListener), - ); - - editorDisposable.add( - nova.workspace.config.onDidChange( - FORMAT_ON_SAVE_CONFIG_KEY, - refreshOnSaveListener, - ), - ); - - editorDisposable.add( - nova.config.onDidChange( - FORMAT_ON_SAVE_CONFIG_KEY, - refreshOnSaveListener, - ), - ); - - let willSaveListener = setupOnSaveListener(); - // @ts-ignore Getting Nova's Disposable and ES Disposables confused. - clientDisposable.add({ - dispose() { - willSaveListener?.dispose(); - }, - }); - - function refreshOnSaveListener() { - willSaveListener?.dispose(); - willSaveListener = setupOnSaveListener(); - } - - function setupOnSaveListener() { - if ( - !(syntaxes as Array).includes(editor.document.syntax) - ) { - return; - } - - return editor.onWillSave(async () => { - if (getOverridableBoolean(FORMAT_ON_SAVE_CONFIG_KEY) === false) { - return; - } - - await nova.commands.invoke("co.gwil.deno.commands.formatDocument"); - }); - } - }); - - // Listen for Deno detecting a registry's intellisense capabilities... - client.onNotification( - "deno/registryState", - async ( - { origin, suggestions }: { origin: string; suggestions: string }, - ) => { - const trustedHosts = nova.workspace.config.get( - TRUSTED_HOSTS_CONFIG_KEY, - ) as string[] || []; - const untrustedHosts = nova.workspace.config.get( - UNTRUSTED_HOSTS_CONFIG_KEY, - ) as string[] || []; - - const isUntrusted = Array.isArray(untrustedHosts) && - untrustedHosts.includes(origin); - const isTrusted = Array.isArray(trustedHosts) && - trustedHosts.includes(origin); - - if (!isUntrusted && !isTrusted) { - if (suggestions) { - const notificationReq = new NotificationRequest( - "co.gwil.deno.notifications.hostIntellisenseAvailable", - ); - - notificationReq.title = "Intellisense available"; - notificationReq.body = - `Would you like to enable import IntelliSense for ${origin}? Only do this if you trust ${origin}.`; - - notificationReq.actions = ["No", "Yes"]; - - // if action idx is 0, add it to untrusted - const { actionIdx } = await nova.notifications.add(notificationReq); - - if (actionIdx === 0) { - nova.config.set(UNTRUSTED_HOSTS_CONFIG_KEY, [ - ...untrustedHosts, - origin, - ]); - } else if (actionIdx === 1) { - nova.config.set(TRUSTED_HOSTS_CONFIG_KEY, [ - ...trustedHosts, - origin, - ]); - } - } - } - }, - ); - - clientDisposable.add(nova.commands.register( - "co.gwil.deno.commands.restartServer", - wrapCommand(() => { - if (!isRestarting) { - const listener = client.onDidStop(async () => { - listener.dispose(); - parentDisposable.add(await makeClientDisposable(parentDisposable)); - isRestarting = false; - }); - - isRestarting = true; - - parentDisposable.remove(clientDisposable); - clientDisposable.dispose(); - } - }), - )); - - // @ts-ignore Getting Nova's Disposable and ES Disposables confused. - clientDisposable.add({ - dispose() { - client.stop(); - }, - }); - - client.start(); - } catch (err) { - if (nova.inDevMode()) { - console.error(err); - } - } - - return clientDisposable; + const clientDisposable = new CompositeDisposable(); + + await ensureDenoIsInstalled(); + + const importMap = nova.workspace.config.get("deno.importMap"); + + const client = new LanguageClient( + "co.gwil.deno", + "Deno Language Server", + { + type: "stdio", + path: "/usr/bin/env", + args: ["deno", "lsp", "--quiet"], + }, + { + syntaxes, + initializationOptions: { + enable: true, + + cacheOnSave: nova.workspace.config.get("deno.cacheOnSave", "boolean"), + lint: nova.workspace.config.get("deno.lint", "boolean"), + unstable: nova.workspace.config.get( + "deno.unstable", + "boolean", + ), + ...(importMap ? { importMap } : {}), + suggest: { + names: true, + paths: true, + autoImports: true, + completeFunctionCalls: nova.workspace.config.get( + "deno.suggest.completeFunctionCalls", + "boolean", + ) || false, + imports: { + autoDiscover: true, + hosts: getHostsMap(), + }, + }, + documentPreloadLimit: nova.workspace.config.get( + "co.gwil.deno.config.documentPreloadLimit", + ) || undefined, + maxTsServerMemory: nova.workspace.config.get( + "co.gwil.deno.config.maxTsServerMemory", + ) || undefined, + }, + }, + ); + + try { + clientDisposable.add(registerFormatDocument(client)); + clientDisposable.add(registerCache(client)); + clientDisposable.add(registerReloadImportRegistries(client)); + clientDisposable.add(registerRenameSymbol(client)); + + // palette Find Symbol command + clientDisposable.add(registerPaletteFindSymbol()); + // sidebar Find Symbol command + clientDisposable.add(registerSymbolSidebarFindSymbol(client)); + + nova.workspace.onDidAddTextEditor((editor) => { + const editorDisposable = new CompositeDisposable(); + + clientDisposable.add(editorDisposable); + clientDisposable.add( + editor.onDidDestroy(() => editorDisposable.dispose()), + ); + + // Formatting + + editorDisposable.add( + editor.document.onDidChangeSyntax(refreshOnSaveListener), + ); + + editorDisposable.add( + nova.workspace.config.onDidChange( + FORMAT_ON_SAVE_CONFIG_KEY, + refreshOnSaveListener, + ), + ); + + editorDisposable.add( + nova.config.onDidChange( + FORMAT_ON_SAVE_CONFIG_KEY, + refreshOnSaveListener, + ), + ); + + let willSaveListener = setupOnSaveListener(); + // @ts-ignore Getting Nova's Disposable and ES Disposables confused. + clientDisposable.add({ + dispose() { + willSaveListener?.dispose(); + }, + }); + + function refreshOnSaveListener() { + willSaveListener?.dispose(); + willSaveListener = setupOnSaveListener(); + } + + function setupOnSaveListener() { + if ( + !(syntaxes as Array).includes(editor.document.syntax) + ) { + return; + } + + return editor.onWillSave(async () => { + if (getOverridableBoolean(FORMAT_ON_SAVE_CONFIG_KEY) === false) { + return; + } + + await nova.commands.invoke("co.gwil.deno.commands.formatDocument"); + }); + } + }); + + // Listen for Deno detecting a registry's intellisense capabilities... + client.onNotification( + "deno/registryState", + async ( + { origin, suggestions }: { origin: string; suggestions: string }, + ) => { + const trustedHosts = nova.workspace.config.get( + TRUSTED_HOSTS_CONFIG_KEY, + ) as string[] || []; + const untrustedHosts = nova.workspace.config.get( + UNTRUSTED_HOSTS_CONFIG_KEY, + ) as string[] || []; + + const isUntrusted = Array.isArray(untrustedHosts) && + untrustedHosts.includes(origin); + const isTrusted = Array.isArray(trustedHosts) && + trustedHosts.includes(origin); + + if (!isUntrusted && !isTrusted) { + if (suggestions) { + const notificationReq = new NotificationRequest( + "co.gwil.deno.notifications.hostIntellisenseAvailable", + ); + + notificationReq.title = "Intellisense available"; + notificationReq.body = + `Would you like to enable import IntelliSense for ${origin}? Only do this if you trust ${origin}.`; + + notificationReq.actions = ["No", "Yes"]; + + // if action idx is 0, add it to untrusted + const { actionIdx } = await nova.notifications.add(notificationReq); + + if (actionIdx === 0) { + nova.config.set(UNTRUSTED_HOSTS_CONFIG_KEY, [ + ...untrustedHosts, + origin, + ]); + } else if (actionIdx === 1) { + nova.config.set(TRUSTED_HOSTS_CONFIG_KEY, [ + ...trustedHosts, + origin, + ]); + } + } + } + }, + ); + + clientDisposable.add(nova.commands.register( + "co.gwil.deno.commands.restartServer", + wrapCommand(() => { + if (!isRestarting) { + const listener = client.onDidStop(async () => { + listener.dispose(); + parentDisposable.add(await makeClientDisposable(parentDisposable)); + isRestarting = false; + }); + + isRestarting = true; + + parentDisposable.remove(clientDisposable); + clientDisposable.dispose(); + } + }), + )); + + // @ts-ignore Getting Nova's Disposable and ES Disposables confused. + clientDisposable.add({ + dispose() { + client.stop(); + }, + }); + + client.start(); + } catch (err) { + if (nova.inDevMode()) { + console.error(err); + } + } + + return clientDisposable; } diff --git a/src/commands/cache.ts b/src/commands/cache.ts index 7716447..37a7759 100644 --- a/src/commands/cache.ts +++ b/src/commands/cache.ts @@ -1,25 +1,25 @@ import { isWorkspace, wrapCommand } from "nova-utils"; -export default function cache(client: LanguageClient) { - return nova.commands.register( - "co.gwil.deno.commands.cache", - wrapCommand(cache), - ); +export default function registerCache(client: LanguageClient) { + return nova.commands.register( + "co.gwil.deno.commands.cache", + wrapCommand(cache), + ); - async function cache( - workspaceOrEditor: Workspace | TextEditor, - ) { - const editor = isWorkspace(workspaceOrEditor) - ? workspaceOrEditor.activeTextEditor - : workspaceOrEditor; + async function cache( + workspaceOrEditor: Workspace | TextEditor, + ) { + const editor = isWorkspace(workspaceOrEditor) + ? workspaceOrEditor.activeTextEditor + : workspaceOrEditor; - const referrer = { - uri: editor!.document.uri, - }; + const referrer = { + uri: editor!.document.uri, + }; - await client.sendRequest("deno/cache", { - referrer, - uris: [], - }); - } + await client.sendRequest("workspace/executeCommand", { + command: "deno.cache", + arguments: [[], referrer.uri], + }); + } } diff --git a/src/commands/reload_import_registries.ts b/src/commands/reload_import_registries.ts new file mode 100644 index 0000000..f7d960e --- /dev/null +++ b/src/commands/reload_import_registries.ts @@ -0,0 +1,14 @@ +import { wrapCommand } from "nova-utils"; + +export default function registerReloadImportRegistries(client: LanguageClient) { + return nova.commands.register( + "co.gwil.deno.commands.reloadImportRegistries", + wrapCommand(reloadImportRegistries), + ); + + async function reloadImportRegistries() { + await client.sendRequest("workspace/executeCommand", { + command: "deno.reloadImportRegistries", + }); + } +} diff --git a/src/nova_deno.ts b/src/nova_deno.ts index 5e016b4..121ef32 100644 --- a/src/nova_deno.ts +++ b/src/nova_deno.ts @@ -1,78 +1,79 @@ import { configFilenames, registerDenoTasks } from "./tasks.ts"; import { - CanNotEnsureError, - makeClientDisposable, + CanNotEnsureError, + makeClientDisposable, } from "./client_disposable.ts"; const compositeDisposable = new CompositeDisposable(); const workspaceConfigRestartKeys = [ - "co.gwil.deno.config.trustedImportHosts", - "co.gwil.deno.config.untrustedImportHosts", - "co.gwil.deno.config.documentPreloadLimit", - "co.gwil.deno.config.maxTsServerMemory", + "co.gwil.deno.config.trustedImportHosts", + "co.gwil.deno.config.untrustedImportHosts", + "co.gwil.deno.config.documentPreloadLimit", + "co.gwil.deno.config.maxTsServerMemory", ]; export async function activate() { - let clientDisposable; - try { - clientDisposable = await makeClientDisposable(compositeDisposable); - } catch (e) { - if (e instanceof CanNotEnsureError) { - // This happens if the user clicks on 'Ignore' after they are requested to install Deno. - return; - } - throw e; - } - compositeDisposable.add(clientDisposable); - - compositeDisposable.add(registerDenoTasks()); - - compositeDisposable.add(registerEditorWatcher()); - - const configFileWatchingDisposables = watchConfigFiles( - nova.workspace.path, - configFilenames, - ); - - const workspaceRestartDisposables = restartServerOnWorkspaceConfigChanges( - workspaceConfigRestartKeys, - ); - - [ - ...configFileWatchingDisposables, - ...workspaceRestartDisposables, - ].forEach( - (disposable) => { - compositeDisposable.add(disposable); - }, - ); - - nova.subscriptions.add(compositeDisposable); + let clientDisposable; + try { + clientDisposable = await makeClientDisposable(compositeDisposable); + } catch (e) { + if (e instanceof CanNotEnsureError) { + // This happens if the user clicks on 'Ignore' after they are requested to install Deno. + return; + } + throw e; + } + compositeDisposable.add(clientDisposable); + + compositeDisposable.add(registerDenoTasks()); + + compositeDisposable.add(registerEditorWatcher()); + + const configFileWatchingDisposables = watchConfigFiles( + nova.workspace.path, + configFilenames, + ); + + const workspaceRestartDisposables = restartServerOnWorkspaceConfigChanges( + workspaceConfigRestartKeys, + ); + + [ + ...configFileWatchingDisposables, + ...workspaceRestartDisposables, + ].forEach( + (disposable) => { + compositeDisposable.add(disposable); + }, + ); + + nova.subscriptions.add(compositeDisposable); } export function deactivate() { - compositeDisposable.dispose(); + compositeDisposable.dispose(); } function watchConfigFiles(workspacePath: string | null, filenames: string[]) { - if (!workspacePath) return []; + if (!workspacePath) return []; - return filenames.map((filename) => { - const denoConfigPath = nova.path.join(workspacePath, filename); + return filenames.map((filename) => { + const denoConfigPath = nova.path.join(workspacePath, filename); - return nova.fs.watch(denoConfigPath, () => { - nova.workspace.reloadTasks("co.gwil.deno.tasks.auto"); - }); - }); + return nova.fs.watch(denoConfigPath, () => { + nova.commands.invoke("co.gwil.deno.commands.restartServer"); + nova.workspace.reloadTasks("co.gwil.deno.tasks.auto"); + }); + }); } function restartServerOnWorkspaceConfigChanges(keys: string[]) { - return keys.map((key) => { - return nova.workspace.config.onDidChange(key, () => { - nova.commands.invoke("co.gwil.deno.commands.restartServer"); - }); - }); + return keys.map((key) => { + return nova.workspace.config.onDidChange(key, () => { + nova.commands.invoke("co.gwil.deno.commands.restartServer"); + }); + }); } /** @@ -80,52 +81,52 @@ function restartServerOnWorkspaceConfigChanges(keys: string[]) { * command, are available. */ function registerEditorWatcher() { - // This callback is called immediately and once. Then, it is called every time the user opens a text editor. - const disposable = new CompositeDisposable(); - - const compatibleSyntaxes = ["typescript", "tsx", "javascript", "jsx"]; - function checkFeatureAvailability( - documents: readonly TextDocument[], - ) { - const syntaxes = documents.map((document) => document.syntax ?? "plain"); - const isCompatible = compatibleSyntaxes.some((syntax) => - syntaxes.includes(syntax) - ); - - // @ts-expect-error: The Nova types are outdated. This feature was added in version 5. - (nova.workspace.context as Configuration).set( - "shouldDisplayFeatures", - isCompatible, - ); - } - - disposable.add( - nova.workspace.onDidAddTextEditor((editor) => { - checkFeatureAvailability(nova.workspace.textDocuments); - - disposable.add(editor.document.onDidChangeSyntax(() => { - checkFeatureAvailability(nova.workspace.textDocuments); - })); - - disposable.add(editor.onDidDestroy((deletedEditor) => { - // TODO: remember what this copy is for - const editors = [...nova.workspace.textEditors]; - - // remove one editor whose URI is the same as this editor's - const index = (editors as TextEditor[]).findIndex( - (editor) => editor.document.uri === deletedEditor.document.uri, - ); - const remainingEditors = editors.filter( - (_editor, editorIndex) => editorIndex !== index, - ); - - const remainingDocuments = remainingEditors.map( - (editor) => editor.document, - ); - checkFeatureAvailability(remainingDocuments); - })); - }), - ); - - return disposable; + // This callback is called immediately and once. Then, it is called every time the user opens a text editor. + const disposable = new CompositeDisposable(); + + const compatibleSyntaxes = ["typescript", "tsx", "javascript", "jsx"]; + function checkFeatureAvailability( + documents: readonly TextDocument[], + ) { + const syntaxes = documents.map((document) => document.syntax ?? "plain"); + const isCompatible = compatibleSyntaxes.some((syntax) => + syntaxes.includes(syntax) + ); + + // @ts-expect-error: The Nova types are outdated. This feature was added in version 5. + (nova.workspace.context as Configuration).set( + "shouldDisplayFeatures", + isCompatible, + ); + } + + disposable.add( + nova.workspace.onDidAddTextEditor((editor) => { + checkFeatureAvailability(nova.workspace.textDocuments); + + disposable.add(editor.document.onDidChangeSyntax(() => { + checkFeatureAvailability(nova.workspace.textDocuments); + })); + + disposable.add(editor.onDidDestroy((deletedEditor) => { + // TODO: remember what this copy is for + const editors = [...nova.workspace.textEditors]; + + // remove one editor whose URI is the same as this editor's + const index = (editors as TextEditor[]).findIndex( + (editor) => editor.document.uri === deletedEditor.document.uri, + ); + const remainingEditors = editors.filter( + (_editor, editorIndex) => editorIndex !== index, + ); + + const remainingDocuments = remainingEditors.map( + (editor) => editor.document, + ); + checkFeatureAvailability(remainingDocuments); + })); + }), + ); + + return disposable; }