iterative/vscode-dvc

View on GitHub
extension/src/extension.ts

Summary

Maintainability
B
6 hrs
Test Coverage
A
96%
import { commands, ExtensionContext, ViewColumn } from 'vscode'
import { DvcConfig } from './cli/dvc/config'
import { DvcExecutor } from './cli/dvc/executor'
import { DvcRunner } from './cli/dvc/runner'
import { DvcReader } from './cli/dvc/reader'
import { Config } from './config'
import { Context } from './context'
import { WorkspaceExperiments } from './experiments/workspace'
import { registerExperimentCommands } from './experiments/commands/register'
import { registerPlotsCommands } from './plots/commands/register'
import { RepositoriesTree } from './repository/model/tree'
import { registerRepositoryCommands } from './repository/commands/register'
import { ResourceLocator } from './resourceLocator'
import { reRegisterVsCodeCommands } from './vscode/commands'
import { InternalCommands } from './commands/internal'
import { ExperimentsColumnsTree } from './experiments/columns/tree'
import { ExperimentsSortByTree } from './experiments/model/sortBy/tree'
import { ExperimentsTree } from './experiments/model/tree'
import { ExperimentsFilterByTree } from './experiments/model/filterBy/tree'
import {
  Context as VsCodeContext,
  getDvcRootFromContext
} from './vscode/context'
import { OutputChannel } from './vscode/outputChannel'
import {
  getTelemetryReporter,
  sendTelemetryEvent,
  sendTelemetryEventAndThrow
} from './telemetry'
import { RegisteredCommands } from './commands/external'
import { StopWatch } from './util/time'
import { registerWalkthroughCommands } from './vscode/walkthrough'
import { showSetupOnFirstUse } from './setup/util'
import { WorkspaceRepositories } from './repository/workspace'
import { recommendRedHatExtensionOnce } from './vscode/recommend'
import { WorkspacePlots } from './plots/workspace'
import { PlotsPathsTree } from './plots/paths/tree'
import { Disposable } from './class/dispose'
import { collectWorkspaceScale } from './telemetry/collect'
import { GitExecutor } from './cli/git/executor'
import { GitReader } from './cli/git/reader'
import { Setup } from './setup'
import { definedAndNonEmpty } from './util/array'
import { stopProcesses } from './process/execution'
import { Flag } from './cli/dvc/constants'
import { LanguageClient } from './languageClient'
import { collectRunningExperimentPids } from './experiments/processExecution/collect'
import { DvcViewer } from './cli/dvc/viewer'
import { registerSetupCommands } from './setup/commands/register'
import { Status } from './status'
import { registerPersistenceCommands } from './persistence/register'
import { showSetupOrExecuteCommand } from './commands/util'
import { WorkspacePipeline } from './pipeline/workspace'
import { registerPipelineCommands } from './pipeline/register'

class Extension extends Disposable {
  protected readonly internalCommands: InternalCommands

  private readonly resourceLocator: ResourceLocator
  private readonly repositories: WorkspaceRepositories
  private readonly experiments: WorkspaceExperiments
  private readonly pipelines: WorkspacePipeline
  private readonly plots: WorkspacePlots
  private readonly setup: Setup
  private readonly repositoriesTree: RepositoriesTree
  private readonly dvcConfig: DvcConfig
  private readonly dvcExecutor: DvcExecutor
  private readonly dvcReader: DvcReader
  private readonly dvcRunner: DvcRunner
  private readonly dvcViewer: DvcViewer
  private readonly gitExecutor: GitExecutor
  private readonly gitReader: GitReader

  constructor(context: ExtensionContext) {
    super()

    const stopWatch = new StopWatch()

    this.dispose.track(getTelemetryReporter())

    this.resourceLocator = this.dispose.track(
      new ResourceLocator(context.extensionUri)
    )

    const config = this.dispose.track(new Config())

    this.gitExecutor = this.dispose.track(new GitExecutor())
    this.gitReader = this.dispose.track(new GitReader())

    this.dvcConfig = this.dispose.track(new DvcConfig(config))
    this.dvcExecutor = this.dispose.track(new DvcExecutor(config))
    this.dvcReader = this.dispose.track(new DvcReader(config))
    this.dvcRunner = this.dispose.track(new DvcRunner(config))
    this.dvcViewer = this.dispose.track(new DvcViewer(config))

    const clis = [
      this.dvcConfig,
      this.dvcExecutor,
      this.dvcReader,
      this.dvcRunner,
      this.dvcViewer,
      this.gitExecutor,
      this.gitReader
    ]

    const outputChannel = this.dispose.track(
      new OutputChannel(
        clis,
        (context.extension.packageJSON as { version: string }).version
      )
    )

    this.internalCommands = this.dispose.track(
      new InternalCommands(outputChannel, ...clis)
    )

    const status = this.dispose.track(new Status(config, ...clis))

    this.experiments = this.dispose.track(
      new WorkspaceExperiments(this.internalCommands, context.workspaceState)
    )

    this.pipelines = this.dispose.track(
      new WorkspacePipeline(this.internalCommands)
    )

    this.plots = this.dispose.track(
      new WorkspacePlots(this.internalCommands, context.workspaceState)
    )

    this.repositories = this.dispose.track(
      new WorkspaceRepositories(this.internalCommands)
    )

    this.dispose.track(new Context(this.experiments, this.dvcRunner))

    this.dispose.track(
      new ExperimentsColumnsTree(
        this.experiments,
        this.internalCommands,
        this.resourceLocator
      )
    )

    this.dispose.track(
      new ExperimentsSortByTree(this.experiments, this.internalCommands)
    )

    this.dispose.track(
      new ExperimentsFilterByTree(this.experiments, this.internalCommands)
    )

    this.dispose.track(
      new ExperimentsTree(this.experiments, this.resourceLocator)
    )

    this.dispose.track(
      new PlotsPathsTree(
        this.plots,
        this.internalCommands,
        this.resourceLocator
      )
    )

    this.repositoriesTree = this.dispose.track(
      new RepositoriesTree(this.internalCommands, this.repositories)
    )

    this.setup = this.dispose.track(
      new Setup(
        config,
        this.internalCommands,
        this.experiments,
        status,
        this.resourceLocator.dvcIcon,
        stopWatch,
        () => this.initialize(),
        () => this.resetMembers(),
        () =>
          collectWorkspaceScale(
            this.getRoots(),
            this.experiments,
            this.plots,
            this.repositories
          )
      )
    )

    registerExperimentCommands(
      this.experiments,
      this.internalCommands,
      this.setup
    )
    registerPipelineCommands(this.pipelines, this.internalCommands)
    registerPlotsCommands(
      this.plots,
      this.internalCommands,
      this.setup,
      this.pipelines
    )
    registerSetupCommands(this.setup, this.internalCommands)
    this.internalCommands.registerExternalCommand(
      RegisteredCommands.EXPERIMENT_AND_PLOTS_SHOW,
      showSetupOrExecuteCommand(this.setup, async (context: VsCodeContext) => {
        const dvcRoot = getDvcRootFromContext(context)
        await this.experiments.showWebview(dvcRoot, ViewColumn.Active)
        await this.plots.showWebview(dvcRoot, ViewColumn.Beside)
      })
    )

    this.dispose.track(
      commands.registerCommand(
        RegisteredCommands.STOP_EXPERIMENTS,
        async () => {
          const stopWatch = new StopWatch()
          const pids = await collectRunningExperimentPids(this.getRoots())
          const wasRunning =
            this.dvcRunner.isExperimentRunning() ||
            definedAndNonEmpty(pids) ||
            this.experiments.hasRunningExperiment()
          try {
            const processesStopped = await Promise.all([
              stopProcesses(pids),
              ...this.getRoots().map(dvcRoot =>
                this.dvcExecutor.queueStop(dvcRoot, Flag.KILL)
              )
            ])
            const runnerStopped = await this.dvcRunner.stop()

            const stopped = processesStopped.every(Boolean) || runnerStopped
            sendTelemetryEvent(
              RegisteredCommands.STOP_EXPERIMENTS,
              { stopped, wasRunning },
              {
                duration: stopWatch.getElapsedTime()
              }
            )
            return stopped
          } catch (error: unknown) {
            return sendTelemetryEventAndThrow(
              RegisteredCommands.STOP_EXPERIMENTS,
              error as Error,
              stopWatch.getElapsedTime()
            )
          }
        }
      )
    )

    this.internalCommands.registerExternalCommand(
      RegisteredCommands.EXTENSION_SHOW_OUTPUT,
      () => outputChannel.show()
    )

    registerRepositoryCommands(this.repositories, this.internalCommands)

    reRegisterVsCodeCommands(this.internalCommands)
    registerWalkthroughCommands(
      this.internalCommands,
      (context.extension.packageJSON as { id: string }).id,
      (
        context.extension.packageJSON as {
          contributes: {
            walkthroughs: { id: string }[]
          }
        }
      ).contributes.walkthroughs[0].id
    )

    registerPersistenceCommands(
      context.workspaceState,
      context.globalState,
      this.internalCommands
    )

    void showSetupOnFirstUse(context.globalState, context.workspaceState)
    this.dispose.track(recommendRedHatExtensionOnce())

    this.dispose.track(new LanguageClient())
  }

  public async initialize() {
    this.resetMembers()

    const dvcRoots = this.getRoots()
    const subProjects = this.getSubProjects()

    await Promise.all([
      this.repositories.create(dvcRoots, subProjects),
      this.repositoriesTree.initialize(dvcRoots),
      this.pipelines.create(dvcRoots, subProjects)
    ])
    this.experiments.create(
      dvcRoots,
      subProjects,
      this.pipelines,
      this.setup,
      this.resourceLocator
    )
    this.plots.create(
      dvcRoots,
      subProjects,
      this.resourceLocator,
      this.experiments
    )

    return Promise.all([
      this.experiments.isReady(),
      this.pipelines.isReady(),
      this.plots.isReady(),
      this.repositories.isReady()
    ])
  }

  public resetMembers() {
    this.repositories.reset()
    this.repositoriesTree.initialize([])
    this.experiments.reset()
    this.pipelines.reset()
    this.plots.reset()
  }

  private getRoots() {
    return this.setup.getRoots()
  }

  private getSubProjects() {
    return this.setup.getSubProjects()
  }
}

let extension: undefined | Extension

export function activate(context: ExtensionContext): void {
  extension = new Extension(context)
  context.subscriptions.push(extension)
}

export function deactivate(): void {
  if (extension) {
    extension.dispose()
  }
}

export { Disposer, Disposable } from '@hediet/std/disposable'