diff --git a/extension/src/dependency-installer/dependency-installer.ts b/extension/src/dependency-installer/dependency-installer.ts index 23fee134..0683c55c 100644 --- a/extension/src/dependency-installer/dependency-installer.ts +++ b/extension/src/dependency-installer/dependency-installer.ts @@ -4,7 +4,7 @@ import { Installer, InstallerConstructor, InstallerContext, InstallError } from import { promptUserErrorMessage } from '../ui/prompts' import { StateCache } from '../utils/state-cache' import { LanguageServerAPI } from '../server/language-server' -import { FlowVersionProvider } from '../flow-cli/available-cli-provider' +import { CliProvider } from '../flow-cli/cli-provider' const INSTALLERS: InstallerConstructor[] = [ InstallFlowCLI @@ -15,11 +15,11 @@ export class DependencyInstaller { missingDependencies: StateCache #installerContext: InstallerContext - constructor (languageServerApi: LanguageServerAPI, flowVersionProvider: FlowVersionProvider) { + constructor (languageServerApi: LanguageServerAPI, cliProvider: CliProvider) { this.#installerContext = { refreshDependencies: this.checkDependencies.bind(this), languageServerApi, - flowVersionProvider + cliProvider } this.#registerInstallers() @@ -54,7 +54,8 @@ export class DependencyInstaller { async checkDependencies (): Promise { // Invalidate and wait for state to update // This will trigger the missingDependencies subscriptions - await this.missingDependencies.getValue(true) + this.missingDependencies.invalidate() + await this.missingDependencies.getValue() } async installMissing (): Promise { diff --git a/extension/src/dependency-installer/installer.ts b/extension/src/dependency-installer/installer.ts index 8a34f8b4..15444d26 100644 --- a/extension/src/dependency-installer/installer.ts +++ b/extension/src/dependency-installer/installer.ts @@ -2,7 +2,7 @@ import { window } from 'vscode' import { envVars } from '../utils/shell/env-vars' import { LanguageServerAPI } from '../server/language-server' -import { FlowVersionProvider } from '../flow-cli/available-cli-provider' +import { CliProvider } from '../flow-cli/cli-provider' // InstallError is thrown if install fails export class InstallError extends Error {} @@ -10,7 +10,7 @@ export class InstallError extends Error {} export interface InstallerContext { refreshDependencies: () => Promise languageServerApi: LanguageServerAPI - flowVersionProvider: FlowVersionProvider + cliProvider: CliProvider } export type InstallerConstructor = new (context: InstallerContext) => Installer diff --git a/extension/src/dependency-installer/installers/flow-cli-installer.ts b/extension/src/dependency-installer/installers/flow-cli-installer.ts index 79ae0045..32d4c951 100644 --- a/extension/src/dependency-installer/installers/flow-cli-installer.ts +++ b/extension/src/dependency-installer/installers/flow-cli-installer.ts @@ -99,9 +99,9 @@ export class InstallFlowCLI extends Installer { async checkVersion (vsn?: semver.SemVer): Promise { // Get user's version informaton - this.#context.flowVersionProvider.refresh() - const version = vsn ?? await this.#context.flowVersionProvider.getVersion() - if (version === null) return false + this.#context.cliProvider.refresh() + const version = vsn ?? await this.#context.cliProvider.getAvailableBinaries().then(x => x.find(y => y.name === 'flow')?.version) + if (version == null) return false if (!semver.satisfies(version, COMPATIBLE_FLOW_CLI_VERSIONS, { includePrerelease: true @@ -127,8 +127,8 @@ export class InstallFlowCLI extends Installer { async verifyInstall (): Promise { // Check if flow version is valid to verify install - this.#context.flowVersionProvider.refresh() - const version = await this.#context.flowVersionProvider.getVersion() + this.#context.cliProvider.refresh() + const version = await this.#context.cliProvider.getAvailableBinaries().then(x => x.find(y => y.name === 'flow')?.version) if (version == null) return false // Check flow-cli version number diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 1e229112..0fc0f9ee 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -4,7 +4,7 @@ import { CommandController } from './commands/command-controller' import { ExtensionContext } from 'vscode' import { DependencyInstaller } from './dependency-installer/dependency-installer' import { Settings } from './settings/settings' -import { FlowVersionProvider } from './flow-cli/available-cli-provider' +import { CliProvider } from './flow-cli/cli-provider' import { JSONSchemaProvider } from './json-schema-provider' import { LanguageServerAPI } from './server/language-server' import { FlowConfig } from './server/flow-config' @@ -12,7 +12,7 @@ import { TestProvider } from './test-provider/test-provider' import { StorageProvider } from './storage/storage-provider' import * as path from 'path' import { NotificationProvider } from './ui/notification-provider' -import { FlowCommandProvider } from './settings/flow-command-provider' +import { CliSelectionProvider } from './ui/cli-selection-provider' // The container for all data relevant to the extension. export class Extension { @@ -31,6 +31,7 @@ export class Extension { #dependencyInstaller: DependencyInstaller #commands: CommandController #testProvider: TestProvider + //#schemaProvider: JSONSchemaProvider private constructor (settings: Settings, ctx: ExtensionContext) { this.ctx = ctx @@ -42,27 +43,29 @@ export class Extension { const notificationProvider = new NotificationProvider(storageProvider) notificationProvider.activate() - // Register FlowCommandProvider - const flowCommandProvider = new FlowCommandProvider(settings) + // Register CliProvider + const cliProvider = new CliProvider(settings) - // Register Flow version provider - const flowVersionProvider = new FlowVersionProvider(flowCommandProvider) + // Register CliSelectionProvider + const cliSelectionProvider = new CliSelectionProvider(cliProvider) // Register JSON schema provider - if (ctx != null) JSONSchemaProvider.register(ctx, flowVersionProvider.state$) + if (ctx != null) { + //this.#schemaProvider = new JSONSchemaProvider(ctx.extensionPath, cliProvider.currentBinary$) + } // Initialize Flow Config const flowConfig = new FlowConfig(settings) void flowConfig.activate() // Initialize Language Server - this.languageServer = new LanguageServerAPI(settings, flowCommandProvider, flowConfig) + this.languageServer = new LanguageServerAPI(settings, cliProvider, flowConfig) // Check for any missing dependencies // The language server will start if all dependencies are installed // Otherwise, the language server will not start and will start after // the user installs the missing dependencies - this.#dependencyInstaller = new DependencyInstaller(this.languageServer, flowVersionProvider) + this.#dependencyInstaller = new DependencyInstaller(this.languageServer, cliProvider) this.#dependencyInstaller.missingDependencies.subscribe((missing) => { if (missing.length === 0) { void this.languageServer.activate() @@ -84,5 +87,6 @@ export class Extension { async deactivate (): Promise { await this.languageServer.deactivate() this.#testProvider?.dispose() + //this.#schemaProvider?.dispose() } } diff --git a/extension/src/flow-cli/available-cli-provider.ts b/extension/src/flow-cli/available-cli-provider.ts deleted file mode 100644 index 18c6db74..00000000 --- a/extension/src/flow-cli/available-cli-provider.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { FlowCommandProvider } from '../settings/flow-command-provider' -import { execDefault } from '../utils/shell/exec' -import { StateCache } from '../utils/state-cache' -import * as semver from 'semver' - -const CHECK_FLOW_CLI_CMD = (flowCommand: string): string => `${flowCommand} version` -const KNOWN_BINS = ['flow', 'flow-c1'] - -type AvailableVersionsCache = { - [key: string]: StateCache -} - -export class AvailableCliProvider { - #flowCommandProvider: FlowCommandProvider - #stateCaches: AvailableVersionsCache = {} - #parseCliVersion: (buffer: Buffer | string) => string - - constructor (flowCommandProvider: FlowCommandProvider, parseCliVersion: (buffer: Buffer | string) => string = parseFlowCliVersion) { - this.#stateCaches = KNOWN_BINS.reduce((acc, bin) => { - acc[bin] = new StateCache(async () => await this.#fetchFlowVersion()) - return acc - }, {} as AvailableVersionsCache) - this.#flowCommandProvider = flowCommandProvider - this.#parseCliVersion = parseCliVersion - } - - async #fetchFlowVersion (): Promise { - try { - // Get user's version informaton - const buffer: string = (await execDefault(CHECK_FLOW_CLI_CMD( - this.#flowCommandProvider.flowCommand - ))).stdout - - // Format version string from output - let versionStr: string | null = this.#parseCliVersion(buffer) - - versionStr = semver.clean(versionStr) - if (versionStr === null) return null - - // Ensure user has a compatible version number installed - const version: semver.SemVer | null = semver.parse(versionStr) - if (version === null) return null - - return version - } catch { - return null - } - } - - refresh (): void { - for (const bin in this.#stateCaches) { - this.#stateCaches[bin].invalidate() - } - } - - async getAvailableBinaries (): Promise<{ [key: string]: semver.SemVer }> { - const bins: { [key: string]: semver.SemVer } = {} - for (const bin in this.#stateCaches) { - const version: semver.SemVer | null = await this.#stateCaches[bin].getValue() - if (version !== null) { - bins[bin] = version - } - } - return bins - } - - async getMissingBinaries (): Promise { - const bins: string[] = [] - for (const bin in this.#stateCaches) { - const version: semver.SemVer | null = await this.#stateCaches[bin].getValue() - if (version === null) { - bins.push(bin) - } - } - return bins - } -} - -export function parseFlowCliVersion (buffer: Buffer | string): string { - return (buffer.toString().split('\n')[0]).split(' ')[1] -} diff --git a/extension/src/flow-cli/cli-provider.ts b/extension/src/flow-cli/cli-provider.ts new file mode 100644 index 00000000..8777cd83 --- /dev/null +++ b/extension/src/flow-cli/cli-provider.ts @@ -0,0 +1,153 @@ +import { BehaviorSubject, Observable, Subject, distinctUntilChanged, pairwise, startWith } from 'rxjs' +import { execDefault } from '../utils/shell/exec' +import { StateCache } from '../utils/state-cache' +import * as semver from 'semver' +import { Settings } from '../settings/settings' + +const CHECK_FLOW_CLI_CMD = (flowCommand: string): string => `${flowCommand} version` +const KNOWN_BINS = ['flow', 'flow-c1'] +const DEFAULT_BIN = 'flow' + +export type CliBinary = { + name: string + version: semver.SemVer +} + +type AvailableBinariesCache = { + [key: string]: StateCache +} + +export class CliProvider { + #selectedBinaryName: BehaviorSubject + #currentBinary$: StateCache + #availableBinaries: AvailableBinariesCache = {} + #availableBinaries$: StateCache + #settings: Settings + + constructor (settings: Settings) { + this.#settings = settings + + this.#selectedBinaryName = new BehaviorSubject(settings.getSettings().flowCommand) + this.#settings.watch$(config => config.flowCommand).subscribe((flowCommand) => { + this.#selectedBinaryName.next(flowCommand) + }) + + this.#availableBinaries = KNOWN_BINS.reduce((acc, bin) => { + acc[bin] = new StateCache(async () => await this.#fetchBinaryInformation(bin)) + acc[bin].subscribe(() => { + this.#availableBinaries$.invalidate() + }) + return acc + }, {} as AvailableBinariesCache) + + this.#availableBinaries$ = new StateCache(async () => { + return this.getAvailableBinaries() + }) + + this.#currentBinary$ = new StateCache(async () => { + const name: string = this.#selectedBinaryName.getValue() + return this.#availableBinaries[name].getValue() + }) + + // Subscribe to changes in the selected binary to update the caches + this.#selectedBinaryName.pipe(distinctUntilChanged(), startWith(null), pairwise()).subscribe(([prev, curr]) => { + // Swap out the cache for the selected binary + if (prev != null && !KNOWN_BINS.includes(prev)) { + delete this.#availableBinaries[prev] + } + if (curr != null && !KNOWN_BINS.includes(curr)) { + this.#availableBinaries[curr] = new StateCache(async () => await this.#fetchBinaryInformation(curr)) + this.#availableBinaries[curr].subscribe(() => { + this.#availableBinaries$.invalidate() + }) + } + + // Invalidate the current binary cache + this.#currentBinary$.invalidate() + + // Invalidate the available binaries cache + this.#availableBinaries$.invalidate() + }) + } + + async #fetchBinaryInformation (bin: string): Promise { + try { + // Get user's version informaton + const buffer: string = (await execDefault(CHECK_FLOW_CLI_CMD( + bin + ))).stdout + + // Format version string from output + let versionStr: string | null = parseFlowCliVersion(buffer) + + versionStr = semver.clean(versionStr) + if (versionStr === null) return null + + // Ensure user has a compatible version number installed + const version: semver.SemVer | null = semver.parse(versionStr) + if (version === null) return null + + return { name: bin, version } + } catch { + return null + } + } + + refresh (): void { + for (const bin in this.#availableBinaries) { + this.#availableBinaries[bin].invalidate() + } + this.#currentBinary$.invalidate() + } + + get availableBinaries$ (): Observable { + return new Observable((subscriber) => { + this.#availableBinaries$.subscribe((binaries) => { + subscriber.next(binaries) + }) + }) + } + + + async getAvailableBinaries (): Promise { + const bins: CliBinary[] = [] + for (const name in this.#availableBinaries) { + const binary = await this.#availableBinaries[name].getValue().catch(() => null) + if (binary !== null) { + bins.push(binary) + } + } + return bins + } + + /*async getMissingBinaries (): Promise { + const bins: string[] = [] + for (const bin in this.#availableBinaries) { + const version: semver.SemVer | null = await this.#availableBinaries[bin].getValue().catch(() => null) + if (version === null) { + bins.push(bin) + } + } + return bins + }*/ + + get currentBinary$ (): Observable { + return this.#currentBinary$ + } + + async getCurrentBinary (): Promise { + return this.#currentBinary$.getValue() + } + + setSelectedBinary (name: string): void { + this.#settings.updateSettings({ flowCommand: name }) + } +} + +function isCadenceV1CLI (version: semver.SemVer): boolean { + return /-cadence-v1.0.0/g.test(version.raw) +} + +export function parseFlowCliVersion (buffer: Buffer | string): string { + return (buffer.toString().split('\n')[0]).split(' ')[1] +} diff --git a/extension/src/json-schema-provider.ts b/extension/src/json-schema-provider.ts index a8fdb0f5..bdda1eec 100644 --- a/extension/src/json-schema-provider.ts +++ b/extension/src/json-schema-provider.ts @@ -7,49 +7,33 @@ import fetch from 'node-fetch' import { StateCache } from './utils/state-cache' import { Subscription } from 'rxjs' +const CADENCE_SCHEMA_URI = 'cadence-schema' const GET_FLOW_SCHEMA_URL = (version: SemVer): string => `https://raw.githubusercontent.com/onflow/flow-cli/v${version.format()}/flowkit/schema.json` // This class provides the JSON schema for the flow.json file // It is accessible via the URI scheme "cadence-schema:///flow.json" export class JSONSchemaProvider implements vscode.FileSystemProvider, vscode.Disposable { - static CADENCE_SCHEMA_URI = 'cadence-schema' - static #instance: JSONSchemaProvider | null - #contentProviderDisposable: vscode.Disposable | undefined #flowVersionSubscription: Subscription + #extensionPath: string #flowVersion: StateCache #flowSchema: StateCache #showLocalError: boolean = false - static register (ctx: vscode.ExtensionContext, flowVersion: StateCache): void { - if (JSONSchemaProvider.#instance != null) { - JSONSchemaProvider.#instance.dispose() - } - - // Create a provider for the cadence-schema URI scheme, this will be deactivated when the extension is deactivated - JSONSchemaProvider.#instance = new JSONSchemaProvider( - ctx, - flowVersion, - { dispose: () => contentProviderDisposable.dispose() } - ) - const contentProviderDisposable = vscode.workspace.registerFileSystemProvider( - JSONSchemaProvider.CADENCE_SCHEMA_URI, - JSONSchemaProvider.#instance - ) - ctx.subscriptions.push( - JSONSchemaProvider.#instance - ) - } - - private constructor ( - private readonly ctx: vscode.ExtensionContext, + constructor ( + extensionPath: string, flowVersion: StateCache, - contentProviderDisposable: vscode.Disposable ) { + this.#extensionPath = extensionPath this.#flowVersion = flowVersion - this.#contentProviderDisposable = contentProviderDisposable this.#flowSchema = new StateCache(async () => await this.#resolveFlowSchema()) + // Register the schema provider + this.#contentProviderDisposable = vscode.workspace.registerFileSystemProvider( + CADENCE_SCHEMA_URI, + this + ) + // Invalidate the schema when the flow-cli version changes this.#flowVersionSubscription = this.#flowVersion.subscribe( () => this.#flowSchema.invalidate() @@ -105,7 +89,7 @@ export class JSONSchemaProvider implements vscode.FileSystemProvider, vscode.Dis }).catch(async () => { // Fallback to local schema this.#showLocalError = true - const schemaUrl = resolve(this.ctx.extensionPath, 'flow-schema.json') + const schemaUrl = resolve(this.#extensionPath, 'flow-schema.json') return await promisify(readFile)(schemaUrl).then(x => x.toString()) }) } diff --git a/extension/src/server/language-server.ts b/extension/src/server/language-server.ts index cbfe1edb..7d41c1df 100644 --- a/extension/src/server/language-server.ts +++ b/extension/src/server/language-server.ts @@ -6,7 +6,7 @@ import { ExecuteCommandRequest } from 'vscode-languageclient' import { BehaviorSubject, Subscription, filter, firstValueFrom } from 'rxjs' import { envVars } from '../utils/shell/env-vars' import { FlowConfig } from './flow-config' -import { FlowCommandProvider } from '../settings/flow-command-provider' +import { CliProvider } from '../flow-cli/cli-provider' // Identities for commands handled by the Language server const RELOAD_CONFIGURATION = 'cadence.server.flow.reloadConfiguration' @@ -14,7 +14,7 @@ const RELOAD_CONFIGURATION = 'cadence.server.flow.reloadConfiguration' export class LanguageServerAPI { #settings: Settings #config: FlowConfig - #flowCommandProvider: FlowCommandProvider + #cliProvider: CliProvider client: LanguageClient | null = null clientState$ = new BehaviorSubject(State.Stopped) @@ -22,9 +22,9 @@ export class LanguageServerAPI { #isActive = false - constructor (settings: Settings, flowCommandProvider: FlowCommandProvider, config: FlowConfig) { + constructor (settings: Settings, cliProvider: CliProvider, config: FlowConfig) { this.#settings = settings - this.#flowCommandProvider = flowCommandProvider + this.#cliProvider = cliProvider this.#config = config } @@ -64,7 +64,12 @@ export class LanguageServerAPI { const accessCheckMode: string = this.#settings.getSettings().accessCheckMode const configPath: string | null = this.#config.configPath - if (this.#flowCommandProvider.flowCommand !== 'flow') { + const binaryPath = (await this.#cliProvider.getCurrentBinary())?.name + if (binaryPath == null) { + throw new Error('No flow binary found') + } + + if (binaryPath !== 'flow') { try { exec('killall dlv') // Required when running language server locally on mac } catch (err) { void err } @@ -75,7 +80,7 @@ export class LanguageServerAPI { 'cadence', 'Cadence', { - command: this.#flowCommandProvider.flowCommand, + command: binaryPath, args: ['cadence', 'language-server', '--enable-flow-client=false'], options: { env @@ -151,7 +156,7 @@ export class LanguageServerAPI { void this.restart() } - this.#subscriptions.push(this.#flowCommandProvider.flowCommand$.subscribe(onChange)) + this.#subscriptions.push(this.#cliProvider.currentBinary$.subscribe(onChange)) this.#subscriptions.push(this.#settings.watch$((config) => config.accessCheckMode).subscribe(onChange)) } diff --git a/extension/src/settings/flow-command-provider.ts b/extension/src/settings/flow-command-provider.ts deleted file mode 100644 index 404c176b..00000000 --- a/extension/src/settings/flow-command-provider.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { BehaviorSubject } from "rxjs"; -import { Settings } from "./settings"; - -const CADENCE_V1_COMMAND = "flow-c1"; -const CADENCE_V0_COMMAND = "flow"; - -export class FlowCommandProvider { - #settings: Settings; - #flowCommand$: BehaviorSubject - - constructor(settings: Settings) { - this.#settings = settings; - - // Bind the flow command to the settings - this.#flowCommand$ = new BehaviorSubject(this.#resolveFlowCommand()); - this.#settings.watch$(config => config.flowCommand) - .subscribe(() => { - this.#flowCommand$.next(this.#resolveFlowCommand()); - }); - } - - #resolveFlowCommand(): string { - const {flowCommand, enableCadenceV1} = this.#settings.getSettings(); - if (flowCommand != "") { - return flowCommand; - } - - return enableCadenceV1 ? CADENCE_V1_COMMAND : CADENCE_V0_COMMAND; - } - - get flowCommand() { - return this.#flowCommand$.value; - } - - get flowCommand$() { - return this.#flowCommand$.asObservable(); - } -} \ No newline at end of file diff --git a/extension/src/ui/cli-selection-provider.ts b/extension/src/ui/cli-selection-provider.ts index 2a9e86df..6cd12f2c 100644 --- a/extension/src/ui/cli-selection-provider.ts +++ b/extension/src/ui/cli-selection-provider.ts @@ -1,26 +1,36 @@ -import { BehaviorSubject, Observable, merge, zip } from "rxjs"; +import { BehaviorSubject, zip } from "rxjs"; import * as vscode from 'vscode'; -import { Settings } from "../settings/settings"; -import { FlowVersionProvider } from "../flow-cli/available-cli-provider"; +import { CliBinary, CliProvider } from "../flow-cli/cli-provider"; import { SemVer } from "semver"; -const TOGGLE_CADENCE_VERSION_COMMAND = "cadence.toggleCadenceVersion"; +const TOGGLE_CADENCE_VERSION_COMMAND = "cadence.changeCadenceVersion"; const CADENCE_V1_CLI_REGEX = /-cadence-v1.0.0/g; +const GET_BINARY_LABEL = (binary: CliBinary): string => `Cadence ${binary.version}`; -export class CliSelectionProvider { +export class CliSelectionProvider implements vscode.Disposable { isCadenceV1$: BehaviorSubject = new BehaviorSubject(false); #statusBarItem: vscode.StatusBarItem; - #settings: Settings; - #flowVersionProvider: FlowVersionProvider; + #cliProvider: CliProvider; + #versionSelector: vscode.QuickPick; + #disposables: vscode.Disposable[] = []; - constructor(settings: Settings, flowVersionProvider: FlowVersionProvider) { - this.#settings = settings; - this.#flowVersionProvider = flowVersionProvider; + constructor(cliProvider: CliProvider) { + this.#cliProvider = cliProvider; - // Create a status bar item - this.#statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 1); - this.#statusBarItem.command = TOGGLE_CADENCE_VERSION_COMMAND; - this.#statusBarItem.color = new vscode.ThemeColor("statusBar.foreground"); + // Register the command to toggle the version + vscode.commands.registerCommand(TOGGLE_CADENCE_VERSION_COMMAND, () => this.#versionSelector.show()); + + // Register UI components + this.#versionSelector = this.#createVersionSelector(); + this.#statusBarItem = this.#createStatusBarItem(); + this.#statusBarItem.show(); + } + + #createStatusBarItem(): vscode.StatusBarItem { + const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 1); + statusBarItem.command = TOGGLE_CADENCE_VERSION_COMMAND; + statusBarItem.color = new vscode.ThemeColor("statusBar.foreground"); + statusBarItem.tooltip = "Click to change the Cadence version"; // Update the status bar text when the version changes this.isCadenceV1$.subscribe(() => { @@ -28,25 +38,62 @@ export class CliSelectionProvider { this.#statusBarItem.text = `Cadence ${version}` }); - // Register the command to toggle the version - vscode.commands.registerCommand(TOGGLE_CADENCE_VERSION_COMMAND, () => this.#toggleCadenceVersion()); + this.#disposables.push(statusBarItem); + return statusBarItem; + } - // Show the status bar item - this.#statusBarItem.show(); + #createVersionSelector(): vscode.QuickPick { + const versionSelector = vscode.window.createQuickPick(); + versionSelector.title = "Select a Cadence version"; + + // Update selected binary when the user selects a version + this.#disposables.push(versionSelector.onDidChangeSelection((selection) => { + if (selection.length === 0) return; + const selected = selection[0]; + this.#cliProvider.setSelectedBinary(selected.path); + })); - // Watch for changes to the cadence version - zip(settings.watch$(config => config.enableCadenceV1), ).subscribe(([enableCadenceV1, version]) => { - if (version === null) { - this.isCadenceV1$.next(enableCadenceV1); - return; + // Update the UI when CLI binaries change + zip(this.#cliProvider.currentBinary$, this.#cliProvider.availableBinaries$).subscribe(([binary, binaries]) => { + // Update available versions + versionSelector.items = binaries.map(binary => new AvailableBinaryItem(binary)); + + // Update selected version + if (binary !== null) { + const item = versionSelector.items.find(item => item.path === binary.name); + versionSelector.selectedItems = item ? [item] : []; } + }) - this.isCadenceV1$.next(isCliCadenceV1(version)); - }); + this.#disposables.push(this.#versionSelector); + return versionSelector; + } + + dispose() { + this.#disposables.forEach(disposable => disposable.dispose()); + } +} + +class AvailableBinaryItem implements vscode.QuickPickItem { + detail?: string; + picked?: boolean; + alwaysShow?: boolean; + #binary: CliBinary; + + constructor(binary: CliBinary) { + this.#binary = binary; + } + + get label(): string { + return GET_BINARY_LABEL(this.#binary); + } + + get description(): string { + return this.#binary.name; } - #toggleCadenceVersion() { - this.#settings.updateSettings({ enableCadenceV1: !this.isCadenceV1$.value }); + get path(): string { + return this.#binary.name; } } diff --git a/extension/test/integration/0 - dependencies.test.ts b/extension/test/integration/0 - dependencies.test.ts index 24d4d583..e5ba97a2 100644 --- a/extension/test/integration/0 - dependencies.test.ts +++ b/extension/test/integration/0 - dependencies.test.ts @@ -4,7 +4,7 @@ import { MaxTimeout } from '../globals' import { InstallFlowCLI } from '../../src/dependency-installer/installers/flow-cli-installer' import { stub } from 'sinon' import { before } from 'mocha' -import { FlowVersionProvider } from '../../src/flow-cli/available-cli-provider' +import { FlowVersionProvider } from '../../src/flow-cli/cli-provider' import { getMockSettings } from '../mock/mockSettings' // Note: Dependency installation must run before other integration tests diff --git a/extension/test/unit/parser.test.ts b/extension/test/unit/parser.test.ts index c11b5a6d..6aba70a6 100644 --- a/extension/test/unit/parser.test.ts +++ b/extension/test/unit/parser.test.ts @@ -1,5 +1,5 @@ import * as assert from 'assert' -import { parseFlowCliVersion } from '../../src/flow-cli/available-cli-provider' +import { parseFlowCliVersion } from '../../src/flow-cli/cli-provider' import { execDefault } from '../../src/utils/shell/exec' import { ASSERT_EQUAL } from '../globals' import * as semver from 'semver' diff --git a/package.json b/package.json index 6d132c58..291437cb 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,11 @@ "command": "cadence.checkDepencencies", "category": "Cadence", "title": "Check Dependencies" + }, + { + "command": "cadence.changeCadenceVersion", + "category": "Cadence", + "title": "Change Cadence Version" } ], "configuration": {