diff --git a/extensions/vscode/src/constants.ts b/extensions/vscode/src/constants.ts new file mode 100644 index 000000000..24553e5ef --- /dev/null +++ b/extensions/vscode/src/constants.ts @@ -0,0 +1,6 @@ +export const POSIT_FOLDER = ".posit"; +export const PUBLISH_FOLDER = ".posit/publish"; +export const PUBLISH_DEPLOYMENTS_FOLDER = ".posit/publish/deployments"; + +export const CONFIGURATIONS_PATTERN = ".posit/publish/*.toml"; +export const DEPLOYMENTS_PATTERN = ".posit/publish/deployments/*.toml"; diff --git a/extensions/vscode/src/extension.ts b/extensions/vscode/src/extension.ts index 12aebd3e9..6296d9b04 100644 --- a/extensions/vscode/src/extension.ts +++ b/extensions/vscode/src/extension.ts @@ -1,6 +1,6 @@ // Copyright (C) 2024 by Posit Software, PBC. -import { ExtensionContext, commands } from "vscode"; +import { ExtensionContext, commands, workspace } from "vscode"; import * as ports from "src/ports"; import { Service } from "src/services"; @@ -12,6 +12,7 @@ import { HelpAndFeedbackTreeDataProvider } from "src/views/helpAndFeedback"; import { LogsTreeDataProvider } from "src/views/logs"; import { EventStream } from "src/events"; import { HomeViewProvider } from "src/views/homeView"; +import { WatcherManager } from "./watchers"; const STATE_CONTEXT = "posit.publish.state"; @@ -50,6 +51,9 @@ export async function activate(context: ExtensionContext) { service = new Service(context, port); + const watchers = new WatcherManager(workspace.workspaceFolders?.[0]); + context.subscriptions.push(watchers); + // First the construction of the data providers const projectTreeDataProvider = new ProjectTreeDataProvider(context); @@ -74,12 +78,12 @@ export async function activate(context: ExtensionContext) { // Then the registration of the data providers with the VSCode framework projectTreeDataProvider.register(); - deploymentsTreeDataProvider.register(); - configurationsTreeDataProvider.register(); + deploymentsTreeDataProvider.register(watchers); + configurationsTreeDataProvider.register(watchers); credentialsTreeDataProvider.register(); helpAndFeedbackTreeDataProvider.register(); logsTreeDataProvider.register(); - homeViewProvider.register(); + homeViewProvider.register(watchers); await service.start(); diff --git a/extensions/vscode/src/views/configurations.ts b/extensions/vscode/src/views/configurations.ts index 3032d5b19..5083fbb95 100644 --- a/extensions/vscode/src/views/configurations.ts +++ b/extensions/vscode/src/views/configurations.ts @@ -5,7 +5,6 @@ import { EventEmitter, ExtensionContext, InputBoxValidationSeverity, - RelativePattern, ThemeIcon, TreeDataProvider, TreeItem, @@ -28,6 +27,7 @@ import { getSummaryStringFromError } from "src/utils/errors"; import { ensureSuffix, fileExists, isValidFilename } from "src/utils/files"; import { untitledConfigurationName } from "src/utils/names"; import { newConfig } from "src/multiStepInputs/newConfig"; +import { WatcherManager } from "src/watchers"; const viewName = "posit.publisher.configurations"; const refreshCommand = viewName + ".refresh"; @@ -36,7 +36,6 @@ const editCommand = viewName + ".edit"; const cloneCommand = viewName + ".clone"; const renameCommand = viewName + ".rename"; const deleteCommand = viewName + ".delete"; -const fileStore = ".posit/publish/*.toml"; type ConfigurationEventEmitter = EventEmitter< ConfigurationTreeItem | undefined | void @@ -94,7 +93,7 @@ export class ConfigurationsTreeDataProvider } } - public register() { + public register(watchers: WatcherManager) { const treeView = window.createTreeView(viewName, { treeDataProvider: this, }); @@ -108,35 +107,13 @@ export class ConfigurationsTreeDataProvider commands.registerCommand(cloneCommand, this.clone), commands.registerCommand(deleteCommand, this.delete), ); - if (this.root !== undefined) { - const positDirWatcher = workspace.createFileSystemWatcher( - new RelativePattern(this.root, ".posit"), - true, - true, - false, - ); - positDirWatcher.onDidDelete(this.refresh, this); - const publishDirWatcher = workspace.createFileSystemWatcher( - new RelativePattern(this.root, ".posit/publish"), - true, - true, - false, - ); - publishDirWatcher.onDidDelete(this.refresh, this); - const watcher = workspace.createFileSystemWatcher( - new RelativePattern(this.root, fileStore), - ); - watcher.onDidCreate(this.refresh); - watcher.onDidDelete(this.refresh); - watcher.onDidChange(this.refresh); + watchers.positDir?.onDidDelete(this.refresh, this); + watchers.publishDir?.onDidDelete(this.refresh, this); - this._context.subscriptions.push( - positDirWatcher, - publishDirWatcher, - watcher, - ); - } + watchers.configurations?.onDidCreate(this.refresh, this); + watchers.configurations?.onDidDelete(this.refresh, this); + watchers.configurations?.onDidChange(this.refresh, this); } private refresh = () => { diff --git a/extensions/vscode/src/views/deployments.ts b/extensions/vscode/src/views/deployments.ts index d30247b5a..aea565cb7 100644 --- a/extensions/vscode/src/views/deployments.ts +++ b/extensions/vscode/src/views/deployments.ts @@ -4,7 +4,6 @@ import { Event, EventEmitter, ExtensionContext, - RelativePattern, ThemeIcon, TreeDataProvider, TreeItem, @@ -34,6 +33,7 @@ import { formatDateString } from "src/utils/date"; import { getSummaryStringFromError } from "src/utils/errors"; import { ensureSuffix } from "src/utils/files"; import { deploymentNameValidator } from "src/utils/names"; +import { WatcherManager } from "src/watchers"; const viewName = "posit.publisher.deployments"; const refreshCommand = viewName + ".refresh"; @@ -44,8 +44,6 @@ const visitCommand = viewName + ".visit"; const addDeploymentCommand = viewName + ".addDeployment"; const createNewDeploymentFileCommand = viewName + ".createNewDeploymentFile"; -const fileStore = ".posit/publish/deployments/*.toml"; - type DeploymentsEventEmitter = EventEmitter< DeploymentsTreeItem | undefined | void >; @@ -113,7 +111,7 @@ export class DeploymentsTreeDataProvider } } - public register() { + public register(watchers: WatcherManager) { const treeView = window.createTreeView(viewName, { treeDataProvider: this, }); @@ -232,43 +230,13 @@ export class DeploymentsTreeDataProvider ), ); - if (this.root !== undefined) { - const positDirWatcher = workspace.createFileSystemWatcher( - new RelativePattern(this.root, ".posit"), - true, - true, - false, - ); - positDirWatcher.onDidDelete(this.refresh, this); - const publishDirWatcher = workspace.createFileSystemWatcher( - new RelativePattern(this.root, ".posit/publish"), - true, - true, - false, - ); - publishDirWatcher.onDidDelete(this.refresh, this); - const deploymentsDirWatcher = workspace.createFileSystemWatcher( - new RelativePattern(this.root, ".posit/publish/deployments"), - true, - true, - false, - ); - deploymentsDirWatcher.onDidDelete(this.refresh, this); - - const watcher = workspace.createFileSystemWatcher( - new RelativePattern(this.root, fileStore), - ); - watcher.onDidCreate(this.refresh); - watcher.onDidDelete(this.refresh); - watcher.onDidChange(this.refresh); + watchers.positDir?.onDidDelete(this.refresh, this); + watchers.publishDir?.onDidDelete(this.refresh, this); + watchers.deploymentsDir?.onDidDelete(this.refresh, this); - this._context.subscriptions.push( - positDirWatcher, - publishDirWatcher, - deploymentsDirWatcher, - watcher, - ); - } + watchers.deployments?.onDidCreate(this.refresh, this); + watchers.deployments?.onDidDelete(this.refresh, this); + watchers.deployments?.onDidChange(this.refresh, this); } } diff --git a/extensions/vscode/src/views/homeView.ts b/extensions/vscode/src/views/homeView.ts index 3fc967d07..3530e5956 100644 --- a/extensions/vscode/src/views/homeView.ts +++ b/extensions/vscode/src/views/homeView.ts @@ -62,9 +62,7 @@ import { DestinationQuickPick } from "src/types/quickPicks"; import { normalizeURL } from "src/utils/url"; import { selectConfig } from "src/multiStepInputs/selectConfig"; import { RPackage, RVersionConfig } from "src/api/types/packages"; - -const deploymentFiles = ".posit/publish/deployments/*.toml"; -const configFiles = ".posit/publish/*.toml"; +import { WatcherManager } from "src/watchers"; const viewName = "posit.publisher.homeView"; const refreshCommand = viewName + ".refresh"; @@ -1130,7 +1128,7 @@ export class HomeViewProvider implements WebviewViewProvider { this._context.subscriptions.push(watcher); } - public register() { + public register(watchers: WatcherManager) { this._stream.register("publish/start", () => { this._onPublishStart(); }); @@ -1171,62 +1169,25 @@ export class HomeViewProvider implements WebviewViewProvider { ), ); - if (this.root !== undefined) { - const positDirWatcher = workspace.createFileSystemWatcher( - new RelativePattern(this.root, ".posit"), - true, - true, - false, - ); - positDirWatcher.onDidDelete(() => { - this.refreshDeployments(); - this.refreshConfigurations(); - }, this); - const publishDirWatcher = workspace.createFileSystemWatcher( - new RelativePattern(this.root, ".posit/publish"), - true, - true, - false, - ); - publishDirWatcher.onDidDelete(() => { - this.refreshDeployments(); - this.refreshConfigurations(); - }, this); - const deploymentsDirWatcher = workspace.createFileSystemWatcher( - new RelativePattern(this.root, ".posit/publish/deployments"), - true, - true, - false, - ); - deploymentsDirWatcher.onDidDelete(this.refreshDeployments, this); - this._context.subscriptions.push( - positDirWatcher, - publishDirWatcher, - deploymentsDirWatcher, - ); - - const configFileWatcher = workspace.createFileSystemWatcher( - new RelativePattern(this.root, configFiles), - ); - configFileWatcher.onDidCreate(this.refreshConfigurations); - configFileWatcher.onDidDelete(this.refreshConfigurations); - configFileWatcher.onDidChange(this.refreshConfigurations); - this._context.subscriptions.push(configFileWatcher); - - const deploymentFileWatcher = workspace.createFileSystemWatcher( - new RelativePattern(this.root, deploymentFiles), - ); - deploymentFileWatcher.onDidCreate(this.refreshDeployments); - deploymentFileWatcher.onDidDelete(this.refreshDeployments); - deploymentFileWatcher.onDidChange(this.refreshDeployments); - this._context.subscriptions.push(deploymentFileWatcher); - - const allFileWatcher = workspace.createFileSystemWatcher( - new RelativePattern(this.root, "**"), - ); - allFileWatcher.onDidCreate(this.sendRefreshedFilesLists); - allFileWatcher.onDidDelete(this.sendRefreshedFilesLists); - this._context.subscriptions.push(allFileWatcher); - } + watchers.positDir?.onDidDelete(() => { + this.refreshDeployments(); + this.refreshConfigurations(); + }, this); + watchers.publishDir?.onDidDelete(() => { + this.refreshDeployments(); + this.refreshConfigurations(); + }, this); + watchers.deploymentsDir?.onDidDelete(this.refreshDeployments, this); + + watchers.configurations?.onDidCreate(this.refreshConfigurations, this); + watchers.configurations?.onDidDelete(this.refreshConfigurations, this); + watchers.configurations?.onDidChange(this.refreshConfigurations, this); + + watchers.deployments?.onDidCreate(this.refreshDeployments, this); + watchers.deployments?.onDidDelete(this.refreshDeployments, this); + watchers.deployments?.onDidChange(this.refreshDeployments, this); + + watchers.allFiles?.onDidCreate(this.sendRefreshedFilesLists, this); + watchers.allFiles?.onDidDelete(this.sendRefreshedFilesLists, this); } } diff --git a/extensions/vscode/src/watchers.ts b/extensions/vscode/src/watchers.ts new file mode 100644 index 000000000..8c7664407 --- /dev/null +++ b/extensions/vscode/src/watchers.ts @@ -0,0 +1,77 @@ +import { + Disposable, + RelativePattern, + WorkspaceFolder, + FileSystemWatcher, + workspace, +} from "vscode"; + +import { + PUBLISH_DEPLOYMENTS_FOLDER, + POSIT_FOLDER, + PUBLISH_FOLDER, + CONFIGURATIONS_PATTERN, + DEPLOYMENTS_PATTERN, +} from "src/constants"; + +/** + * Manages all the file system watchers for the extension. + * + * The directory watchers only watch for onDidDelete events. + */ +export class WatcherManager implements Disposable { + positDir: FileSystemWatcher | undefined; + publishDir: FileSystemWatcher | undefined; + configurations: FileSystemWatcher | undefined; + deploymentsDir: FileSystemWatcher | undefined; + deployments: FileSystemWatcher | undefined; + allFiles: FileSystemWatcher | undefined; + + constructor(root?: WorkspaceFolder) { + if (root === undefined) { + return; + } + + this.positDir = workspace.createFileSystemWatcher( + new RelativePattern(root, POSIT_FOLDER), + true, + true, + false, + ); + + this.publishDir = workspace.createFileSystemWatcher( + new RelativePattern(root, PUBLISH_FOLDER), + true, + true, + false, + ); + + this.configurations = workspace.createFileSystemWatcher( + new RelativePattern(root, CONFIGURATIONS_PATTERN), + ); + + this.deploymentsDir = workspace.createFileSystemWatcher( + new RelativePattern(root, PUBLISH_DEPLOYMENTS_FOLDER), + true, + true, + false, + ); + + this.deployments = workspace.createFileSystemWatcher( + new RelativePattern(root, DEPLOYMENTS_PATTERN), + ); + + this.allFiles = workspace.createFileSystemWatcher( + new RelativePattern(root, "**"), + ); + } + + dispose() { + this.positDir?.dispose(); + this.publishDir?.dispose(); + this.configurations?.dispose(); + this.deploymentsDir?.dispose(); + this.deployments?.dispose(); + this.allFiles?.dispose(); + } +}