diff --git a/vscode/microsoft-kiota/src/commands/generate/generateClientCommand.ts b/vscode/microsoft-kiota/src/commands/generate/generateClientCommand.ts index e5a3380d9a..c22ffbb7fa 100644 --- a/vscode/microsoft-kiota/src/commands/generate/generateClientCommand.ts +++ b/vscode/microsoft-kiota/src/commands/generate/generateClientCommand.ts @@ -6,9 +6,9 @@ import { API_MANIFEST_FILE, extensionId, treeViewFocusCommand, treeViewId } from import { setGenerationConfiguration } from "../../handlers/configurationHandler"; import { clearDeepLinkParams, getDeepLinkParams } from "../../handlers/deepLinkParamsHandler"; import { ConsumerOperation, generationLanguageToString, getLogEntriesForLevel, KiotaLogEntry, LogLevel } from "../../kiotaInterop"; +import { GenerateState, generateSteps } from "../../modules/steps/generateSteps"; import { DependenciesViewProvider } from "../../providers/dependenciesViewProvider"; import { OpenApiTreeProvider } from "../../providers/openApiTreeProvider"; -import { GenerateState, generateSteps } from "../../steps"; import { GenerationType, KiotaGenerationLanguage, KiotaPluginType } from "../../types/enums"; import { ExtensionSettings, getExtensionSettings } from "../../types/extensionSettings"; import { GeneratedOutputState } from "../../types/GeneratedOutputState"; diff --git a/vscode/microsoft-kiota/src/commands/openApiTreeView/filterDescriptionCommand.ts b/vscode/microsoft-kiota/src/commands/openApiTreeView/filterDescriptionCommand.ts index 9e212c1e58..e956f960b8 100644 --- a/vscode/microsoft-kiota/src/commands/openApiTreeView/filterDescriptionCommand.ts +++ b/vscode/microsoft-kiota/src/commands/openApiTreeView/filterDescriptionCommand.ts @@ -1,6 +1,6 @@ import { treeViewId } from "../../constants"; +import { filterSteps } from "../../modules/steps/filterSteps"; import { OpenApiTreeProvider } from "../../providers/openApiTreeProvider"; -import { filterSteps } from "../../steps"; import { Command } from "../Command"; export class FilterDescriptionCommand extends Command { diff --git a/vscode/microsoft-kiota/src/commands/openApidescription/searchOrOpenApiDescriptionCommand.ts b/vscode/microsoft-kiota/src/commands/openApidescription/searchOrOpenApiDescriptionCommand.ts index 7aca393698..7e58aca26b 100644 --- a/vscode/microsoft-kiota/src/commands/openApidescription/searchOrOpenApiDescriptionCommand.ts +++ b/vscode/microsoft-kiota/src/commands/openApidescription/searchOrOpenApiDescriptionCommand.ts @@ -3,8 +3,8 @@ import * as vscode from "vscode"; import { extensionId, treeViewId } from "../../constants"; import { setDeepLinkParams } from "../../handlers/deepLinkParamsHandler"; +import { searchSteps } from "../../modules/steps/searchSteps"; import { OpenApiTreeProvider } from "../../providers/openApiTreeProvider"; -import { searchSteps } from "../../steps"; import { getExtensionSettings } from "../../types/extensionSettings"; import { IntegrationParams, validateDeepLinkQueryParams } from "../../utilities/deep-linking"; import { openTreeViewWithProgress } from "../../utilities/progress"; diff --git a/vscode/microsoft-kiota/src/handlers/configurationHandler.ts b/vscode/microsoft-kiota/src/handlers/configurationHandler.ts index 96afb4c22e..2216f10440 100644 --- a/vscode/microsoft-kiota/src/handlers/configurationHandler.ts +++ b/vscode/microsoft-kiota/src/handlers/configurationHandler.ts @@ -1,4 +1,4 @@ -import { GenerateState } from "../steps"; +import { GenerateState } from "../modules/steps/generateSteps"; let configuration: Partial; diff --git a/vscode/microsoft-kiota/src/modules/steps/filterSteps.ts b/vscode/microsoft-kiota/src/modules/steps/filterSteps.ts new file mode 100644 index 0000000000..f78d9772fc --- /dev/null +++ b/vscode/microsoft-kiota/src/modules/steps/filterSteps.ts @@ -0,0 +1,27 @@ +import { l10n } from "vscode"; +import { BaseStepsState, MultiStepInput } from "."; +import { shouldResume } from "./utils"; + +export async function filterSteps(existingFilter: string, filterCallback: (searchQuery: string) => void) { + const state = {} as Partial; + const title = l10n.t('Filter the API description'); + let step = 1; + let totalSteps = 1; + async function inputFilterQuery(input: MultiStepInput, state: Partial) { + await input.showInputBox({ + title, + step: step++, + totalSteps: totalSteps, + value: existingFilter, + prompt: l10n.t('Enter a filter'), + validate: x => { + filterCallback(x.length === 0 && existingFilter.length > 0 ? existingFilter : x); + existingFilter = ''; + return Promise.resolve(undefined); + }, + shouldResume: shouldResume + }); + } + await MultiStepInput.run(input => inputFilterQuery(input, state), () => step -= 2); + return state; +} diff --git a/vscode/microsoft-kiota/src/modules/steps/generateSteps.ts b/vscode/microsoft-kiota/src/modules/steps/generateSteps.ts new file mode 100644 index 0000000000..e26c457efa --- /dev/null +++ b/vscode/microsoft-kiota/src/modules/steps/generateSteps.ts @@ -0,0 +1,338 @@ +import * as path from 'path'; +import * as vscode from 'vscode'; +import { l10n, QuickPickItem, workspace } from 'vscode'; + +import { BaseStepsState, MultiStepInput } from '.'; +import { allGenerationLanguages, generationLanguageToString, LanguagesInformation, maturityLevelToString } from '../../kiotaInterop'; +import { findAppPackageDirectory, getWorkspaceJsonDirectory } from '../../util'; +import { IntegrationParams, isDeeplinkEnabled } from '../../utilities/deep-linking'; +import { isTemporaryDirectory } from '../../utilities/temporary-folder'; +import { shouldResume, validateIsNotEmpty } from './utils'; + +export interface GenerateState extends BaseStepsState { + generationType: QuickPickItem | string; + pluginTypes: QuickPickItem | string[]; + pluginName: string; + clientClassName: string; + clientNamespaceName: QuickPickItem | string; + language: QuickPickItem | string; + outputPath: QuickPickItem | string; + workingDirectory: string; +} +export async function generateSteps(existingConfiguration: Partial, languagesInformation?: LanguagesInformation, deepLinkParams?: Partial) { + const state = { ...existingConfiguration } as Partial; + if (existingConfiguration.generationType && existingConfiguration.clientClassName && existingConfiguration.clientNamespaceName && existingConfiguration.outputPath && existingConfiguration.language && + typeof existingConfiguration.generationType === 'string' && existingConfiguration.clientNamespaceName === 'string' && typeof existingConfiguration.outputPath === 'string' && typeof existingConfiguration.language === 'string' && + existingConfiguration.generationType.length > 0 && existingConfiguration.clientClassName.length > 0 && existingConfiguration.clientNamespaceName.length > 0 && existingConfiguration.outputPath.length > 0 && existingConfiguration.language.length > 0) { + return state; + } + + const deeplinkEnabled = deepLinkParams && isDeeplinkEnabled(deepLinkParams); + const isDeepLinkPluginNameProvided = deeplinkEnabled && state.pluginName; + const isDeepLinkGenerationTypeProvided = deeplinkEnabled && state.generationType; + const isDeepLinkPluginTypeProvided = deeplinkEnabled && state.pluginTypes; + const isDeepLinkLanguageProvided = deeplinkEnabled && state.language; + const isDeepLinkOutputPathProvided = deeplinkEnabled && state.outputPath; + const isDeepLinkClientClassNameProvided = deeplinkEnabled && state.clientClassName; + + if (typeof state.outputPath === 'string' && !isTemporaryDirectory(state.outputPath)) { + state.outputPath = workspace.asRelativePath(state.outputPath); + } + const workspaceOpen = vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0; + + let workspaceFolder = getWorkspaceJsonDirectory(); + const appPackagePath = findAppPackageDirectory(workspaceFolder); + if (appPackagePath) { + workspaceFolder = appPackagePath; + } + + if (isDeepLinkOutputPathProvided && deepLinkParams.source && deepLinkParams.source.toLowerCase() === 'ttk') { + state.workingDirectory = state.outputPath as string; + } + + let step = 1; + const folderSelectionOption = l10n.t('Browse your output directory'); + let inputOptions = [ + { label: l10n.t('Default folder'), description: workspaceFolder }, + { label: folderSelectionOption } + ]; + + function updateWorkspaceFolder(name: string | undefined) { + if (name && (!workspaceOpen)) { + workspaceFolder = getWorkspaceJsonDirectory(name); + inputOptions = [ + { label: l10n.t('Default folder'), description: workspaceFolder }, + { label: folderSelectionOption } + ]; + } + } + function getNextStepForGenerationType(generationType: string | QuickPickItem) { + switch (generationType) { + case 'client': + return inputClientClassName; + case 'plugin': + return inputPluginName; + case 'other': + return chooseOtherGenerationType; + default: + throw new Error('unknown generation type'); + } + } + async function inputGenerationType(input: MultiStepInput, state: Partial) { + if (!isDeepLinkGenerationTypeProvided) { + const items = [ + l10n.t('Client'), + l10n.t('Copilot plugin'), + l10n.t('Other') + ]; + const option = await input.showQuickPick({ + title: l10n.t('What do you want to generate?'), + step: step++, + totalSteps: 3, + placeholder: l10n.t('Select an option'), + items: items.map(x => ({ label: x })), + validate: validateIsNotEmpty, + shouldResume: shouldResume + }); + if (option.label === l10n.t('Client')) { + state.generationType = "client"; + } + else if (option.label === l10n.t('Copilot plugin')) { + state.generationType = "plugin"; + } + else if (option.label === l10n.t('Other')) { + state.generationType = "other"; + } + } + let nextStep = getNextStepForGenerationType(state.generationType?.toString() || ''); + return (input: MultiStepInput) => nextStep(input, state); + } + async function inputClientClassName(input: MultiStepInput, state: Partial) { + if (!isDeepLinkClientClassNameProvided) { + state.clientClassName = await input.showInputBox({ + title: `${l10n.t('Create a new API client')} - ${l10n.t('class')}`, + step: step++, + totalSteps: 5, + value: state.clientClassName ?? '', + placeholder: 'ApiClient', + prompt: l10n.t('Choose a name for the client class'), + validate: validateIsNotEmpty, + shouldResume: shouldResume + }); + } + updateWorkspaceFolder(state.clientClassName); + return (input: MultiStepInput) => inputClientNamespaceName(input, state); + } + async function inputClientNamespaceName(input: MultiStepInput, state: Partial) { + state.clientNamespaceName = await input.showInputBox({ + title: `${l10n.t('Create a new API client')} - ${l10n.t('namespace')}`, + step: step++, + totalSteps: 5, + value: typeof state.clientNamespaceName === 'string' ? state.clientNamespaceName : '', + placeholder: 'ApiSDK', + prompt: l10n.t('Choose a name for the client class namespace'), + validate: validateIsNotEmpty, + shouldResume: shouldResume + }); + return (input: MultiStepInput) => inputOutputPath(input, state); + } + async function inputOutputPath(input: MultiStepInput, state: Partial) { + while (true) { + const selectedOption = await input.showQuickPick({ + title: `${l10n.t('Create a new API client')} - ${l10n.t('output directory')}`, + step: step++, + totalSteps: 5, + placeholder: l10n.t('Enter an output path relative to the root of the project'), + items: inputOptions, + shouldResume: shouldResume + }); + if (selectedOption) { + if (selectedOption?.label === folderSelectionOption) { + const folderUri = await input.showOpenDialog({ + canSelectMany: false, + openLabel: 'Select', + canSelectFolders: true, + canSelectFiles: false + }); + + if (folderUri && folderUri[0]) { + state.outputPath = folderUri[0].fsPath; + } else { + continue; + } + } else { + state.outputPath = selectedOption.description; + if (workspaceOpen) { + state.workingDirectory = vscode.workspace.workspaceFolders![0].uri.fsPath; + } else { + state.workingDirectory = path.dirname(selectedOption.description!); + } + } + } + state.outputPath = state.outputPath === '' ? 'output' : state.outputPath; + return (input: MultiStepInput) => pickLanguage(input, state); + } + + } + async function pickLanguage(input: MultiStepInput, state: Partial) { + if (!isDeepLinkLanguageProvided) { + const items = allGenerationLanguages.map(x => { + const lngName = generationLanguageToString(x); + const lngInfo = languagesInformation ? languagesInformation[lngName] : undefined; + const lngMaturity = lngInfo ? ` - ${maturityLevelToString(lngInfo.MaturityLevel)}` : ''; + return { + label: `${lngName}${lngMaturity}`, + languageName: lngName, + } as (QuickPickItem & { languageName: string }); + }); + const pick = await input.showQuickPick({ + title: `${l10n.t('Create a new API client')} - ${l10n.t('language')}`, + step: step++, + totalSteps: 5, + placeholder: l10n.t('Pick a language'), + items, + activeItem: typeof state.language === 'string' ? items.find(x => x.languageName === state.language) : undefined, + shouldResume: shouldResume + }); + state.language = pick.label.split('-')[0].trim(); + } + } + async function inputPluginName(input: MultiStepInput, state: Partial) { + if (!isDeepLinkPluginNameProvided) { + state.pluginName = await input.showInputBox({ + title: `${l10n.t('Create a new plugin')} - ${l10n.t('plugin name')}`, + step: step++, + totalSteps: 3, + value: state.pluginName ?? '', + placeholder: 'MyPlugin', + prompt: l10n.t('Choose a name for the plugin'), + validate: validateIsNotEmpty, + shouldResume: shouldResume + }); + } + state.pluginTypes = ['ApiPlugin']; + updateWorkspaceFolder(state.pluginName); + return (input: MultiStepInput) => inputPluginOutputPath(input, state); + } + async function chooseOtherGenerationType(input: MultiStepInput, state: Partial) { + if (!isDeepLinkPluginTypeProvided) { + const items = ['API Manifest', 'Open AI Plugin'].map(x => ({ label: x }) as QuickPickItem); + const pluginTypes = await input.showQuickPick({ + title: l10n.t('Choose a type'), + step: step++, + totalSteps: 4, + placeholder: l10n.t('Select an option'), + items: items, + validate: validateIsNotEmpty, + shouldResume: shouldResume + }); + pluginTypes.label === 'API Manifest' ? state.pluginTypes = ['ApiManifest'] : state.pluginTypes = ['OpenAI']; + + } + + Array.isArray(state.pluginTypes) && state.pluginTypes.includes('ApiManifest') ? + state.generationType = 'apimanifest' : state.generationType = 'plugin'; + return (input: MultiStepInput) => inputOtherGenerationTypeName(input, state); + } + async function inputPluginOutputPath(input: MultiStepInput, state: Partial) { + while (!isDeepLinkOutputPathProvided) { + const selectedOption = await input.showQuickPick({ + title: `${l10n.t('Create a new plugin')} - ${l10n.t('output directory')}`, + step: step++, + totalSteps: 4, + placeholder: l10n.t('Enter an output path relative to the root of the project'), + items: inputOptions, + shouldResume: shouldResume + }); + if (selectedOption) { + if (selectedOption?.label === folderSelectionOption) { + const folderUri = await input.showOpenDialog({ + canSelectMany: false, + openLabel: 'Select', + canSelectFolders: true, + canSelectFiles: false + }); + + if (folderUri && folderUri[0]) { + state.outputPath = folderUri[0].fsPath; + } else { + continue; + } + } else { + state.outputPath = selectedOption.description; + if (workspaceOpen) { + state.workingDirectory = vscode.workspace.workspaceFolders![0].uri.fsPath; + } else { + state.workingDirectory = path.dirname(selectedOption.description!); + } + } + } + state.outputPath = state.outputPath === '' ? 'output' : state.outputPath; + break; + } + } + async function inputOtherGenerationTypeName(input: MultiStepInput, state: Partial) { + if (!isDeepLinkPluginNameProvided) { + const isManifest = state.pluginTypes && Array.isArray(state.pluginTypes) && state.pluginTypes.includes('ApiManifest'); + state.pluginName = await input.showInputBox({ + title: `${isManifest ? l10n.t('Create a new manifest') : l10n.t('Create a new OpenAI plugin')} - ${l10n.t('output name')}`, + step: step++, + totalSteps: 4, + value: state.pluginName ?? '', + placeholder: `${isManifest ? 'MyManifest' : 'MyOpenAIPlugin'}`, + prompt: `${isManifest ? l10n.t('Choose a name for the manifest') : l10n.t('Choose a name for the OpenAI plugin')}`, + validate: validateIsNotEmpty, + shouldResume: shouldResume + }); + } + updateWorkspaceFolder(state.pluginName); + return (input: MultiStepInput) => inputOtherGenerationTypeOutputPath(input, state); + } + async function inputOtherGenerationTypeOutputPath(input: MultiStepInput, state: Partial) { + while (true) { + const isManifest = state.pluginTypes && Array.isArray(state.pluginTypes) && state.pluginTypes.includes('ApiManifest'); + + const selectedOption = await input.showQuickPick({ + title: `${isManifest ? l10n.t('Create a new manifest') : l10n.t('Create a new OpenAI plugin')} - ${l10n.t('output directory')}`, + step: step++, + totalSteps: 4, + placeholder: l10n.t('Enter an output path relative to the root of the project'), + items: inputOptions, + shouldResume: shouldResume + }); + if (selectedOption) { + if (selectedOption?.label === folderSelectionOption) { + const folderUri = await input.showOpenDialog({ + canSelectMany: false, + openLabel: 'Select', + canSelectFolders: true, + canSelectFiles: false + }); + + if (folderUri && folderUri[0]) { + state.outputPath = folderUri[0].fsPath; + } else { + continue; + } + } else { + state.outputPath = selectedOption.description; + if (workspaceOpen) { + state.workingDirectory = vscode.workspace.workspaceFolders![0].uri.fsPath; + } else { + state.workingDirectory = path.dirname(selectedOption.description!); + } + } + } + if (state.outputPath === '') { + state.outputPath = 'output'; + } + break; + } + + } + await MultiStepInput.run(input => inputGenerationType(input, state), () => step -= 2); + if (!state.workingDirectory) { + state.workingDirectory = workspaceOpen ? vscode.workspace.workspaceFolders![0].uri.fsPath : state.outputPath as string; + } + return state; +} \ No newline at end of file diff --git a/vscode/microsoft-kiota/src/modules/steps/index.ts b/vscode/microsoft-kiota/src/modules/steps/index.ts new file mode 100644 index 0000000000..92441f98b5 --- /dev/null +++ b/vscode/microsoft-kiota/src/modules/steps/index.ts @@ -0,0 +1,215 @@ +import { Disposable, OpenDialogOptions, QuickInput, QuickInputButton, QuickInputButtons, QuickPickItem, Uri, window } from 'vscode'; + +export interface BaseStepsState { + title: string; + step: number; + totalSteps: number; +} + +class InputFlowAction { + static back = new InputFlowAction(); + static cancel = new InputFlowAction(); + static resume = new InputFlowAction(); +} + +type InputStep = (input: MultiStepInput) => Thenable; + +interface QuickPickParameters { + title: string; + step: number; + totalSteps: number; + items: T[]; + activeItem?: T; + ignoreFocusOut?: boolean; + placeholder: string; + buttons?: QuickInputButton[]; + shouldResume: () => Thenable; +} + +interface InputBoxParameters { + title: string; + step: number; + totalSteps: number; + value: string; + prompt: string; + validate?: (value: string) => Promise; + buttons?: QuickInputButton[]; + ignoreFocusOut?: boolean; + placeholder?: string; + shouldResume: () => Thenable; +} + +interface OpenDialogParameters { + canSelectMany: boolean; + openLabel: string; + canSelectFolders: boolean; + canSelectFiles: boolean; +} + +export class MultiStepInput { + async showOpenDialog({ canSelectMany, openLabel, canSelectFolders, canSelectFiles }: OpenDialogParameters): Promise { + return await new Promise((resolve) => { + const input: OpenDialogOptions = { + canSelectMany, + openLabel, + canSelectFolders, + canSelectFiles + }; + + void window.showOpenDialog(input).then(folderUris => { + if (folderUris && folderUris.length > 0) { + resolve([folderUris[0]]); + } else { + resolve(undefined); + } + }); + }); + } + + static async run(start: InputStep, onNavBack?: () => void) { + const input = new MultiStepInput(); + return input.stepThrough(start, onNavBack); + } + + private current?: QuickInput; + private steps: InputStep[] = []; + + private async stepThrough(start: InputStep, onNavBack?: () => void) { + let step: InputStep | void = start; + while (step) { + this.steps.push(step); + if (this.current) { + this.current.enabled = false; + this.current.busy = true; + } + try { + step = await step(this); + } catch (err) { + if (err === InputFlowAction.back) { + if (onNavBack) { + onNavBack(); //Currently, step -= 2 passed as onNavBack because of using postfix increment in steps in the input functions + } + this.steps.pop(); + step = this.steps.pop(); + } else if (err === InputFlowAction.resume) { + step = this.steps.pop(); + } else if (err === InputFlowAction.cancel) { + step = undefined; + } else { + throw err; + } + } + } + if (this.current) { + this.current.dispose(); + } + } + + async showQuickPick>({ title, step, totalSteps, items, activeItem, ignoreFocusOut, placeholder, buttons, shouldResume }: P) { + const disposables: Disposable[] = []; + try { + return await new Promise((resolve, reject) => { + const input = window.createQuickPick(); + input.title = title; + input.step = step; + input.totalSteps = totalSteps; + input.ignoreFocusOut = ignoreFocusOut ?? false; + input.placeholder = placeholder; + input.items = items; + input.buttons = [ + ...(this.steps.length > 1 ? [QuickInputButtons.Back] : []), + ...(buttons || []) + ]; + disposables.push( + input.onDidTriggerButton(item => { + if (item === QuickInputButtons.Back) { + reject(InputFlowAction.back); + } else { + resolve(item); + } + }), + input.onDidChangeSelection(items => resolve(items[0])), + input.onDidHide(() => { + (async () => { + reject(shouldResume && await shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel); + })() + .catch(reject); + }) + ); + if (this.current) { + this.current.dispose(); + } + this.current = input; + this.current.show(); + if (activeItem) { + input.activeItems = [activeItem]; + } + }); + } finally { + disposables.forEach(d => d.dispose()); + } + } + + async showInputBox

({ title, step, totalSteps, value, prompt, validate, buttons, ignoreFocusOut, placeholder, shouldResume }: P) { + const disposables: Disposable[] = []; + try { + return await new Promise((resolve, reject) => { + const input = window.createInputBox(); + input.title = title; + input.step = step; + input.totalSteps = totalSteps; + input.value = value || ''; + input.prompt = prompt; + input.ignoreFocusOut = ignoreFocusOut ?? false; + input.placeholder = placeholder; + input.buttons = [ + ...(this.steps.length > 1 ? [QuickInputButtons.Back] : []), + ...(buttons || []) + ]; + let validating = validate ? validate('') : Promise.resolve(undefined); + disposables.push( + input.onDidTriggerButton(item => { + if (item === QuickInputButtons.Back) { + reject(InputFlowAction.back); + } else { + resolve(item); + } + }), + input.onDidAccept(async () => { + const value = input.value; + input.enabled = false; + input.busy = true; + if (!(validate && await validate(value))) { + resolve(value); + } + input.enabled = true; + input.busy = false; + }), + input.onDidChangeValue(async text => { + if (validate) { + const current = validate(text); + validating = current; + const validationMessage = await current; + if (current === validating) { + input.validationMessage = validationMessage; + } + } + }), + input.onDidHide(() => { + (async () => { + reject(shouldResume && await shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel); + })() + .catch(reject); + }) + ); + if (this.current) { + this.current.dispose(); + } + this.current = input; + this.current.show(); + }); + } finally { + disposables.forEach(d => d.dispose()); + } + } +} diff --git a/vscode/microsoft-kiota/src/modules/steps/searchSteps.ts b/vscode/microsoft-kiota/src/modules/steps/searchSteps.ts new file mode 100644 index 0000000000..1e9d8c0c13 --- /dev/null +++ b/vscode/microsoft-kiota/src/modules/steps/searchSteps.ts @@ -0,0 +1,102 @@ +import * as vscode from 'vscode'; +import { l10n, QuickPickItem } from 'vscode'; + +import { BaseStepsState, MultiStepInput } from '.'; +import { KiotaSearchResultItem } from '../../kiotaInterop'; +import { isValidUrl } from '../../util'; +import { isFilePath } from '../../utilities/temporary-folder'; +import { shouldResume, validateIsNotEmpty } from './utils'; + +interface SearchState extends BaseStepsState { + searchQuery: string; + searchResults: Record; + descriptionPath: string; +} + +interface OpenState extends BaseStepsState { + descriptionPath: string; +} + +interface SearchItem { + descriptionUrl?: string; +} + +type QuickSearchPickItem = QuickPickItem & SearchItem; + + +export async function searchSteps(searchCallBack: (searchQuery: string) => Thenable | undefined>) { + const state: Partial = {}; + const title = l10n.t('Add an API description'); + let step = 1; + let totalSteps = 2; + async function inputPathOrSearch(input: MultiStepInput, state: Partial) { + const selectedOption = await input.showQuickPick({ + title, + step: step++, + totalSteps: totalSteps, + placeholder: l10n.t('Search or browse a path to an API description'), + items: [{ label: l10n.t('Search') }, { label: l10n.t('Browse path') }], + validate: validateIsNotEmpty, + shouldResume: shouldResume + }); + if (selectedOption?.label === l10n.t('Search')) { + return (input: MultiStepInput) => inputSearch(input, state); + } + else if (selectedOption?.label === l10n.t('Browse path')) { + const fileUri = await input.showOpenDialog({ + canSelectMany: false, + openLabel: 'Select', + canSelectFolders: false, + canSelectFiles: true + }); + + if (fileUri && fileUri[0]) { + state.descriptionPath = fileUri[0].fsPath; + } + } + } + + async function inputSearch(input: MultiStepInput, state: Partial) { + state.searchQuery = await input.showInputBox({ + title, + step: step++, + totalSteps: totalSteps, + value: state.searchQuery ?? '', + prompt: l10n.t('Search or paste a path to an API description'), + validate: validateIsNotEmpty, + shouldResume: shouldResume + }); + if (state.searchQuery && (isValidUrl(state.searchQuery) || isFilePath(state.searchQuery))) { + state.descriptionPath = state.searchQuery; + return; + } + state.searchResults = await searchCallBack(state.searchQuery); + if (state.searchResults && Object.keys(state.searchResults).length > 0) { + return (input: MultiStepInput) => pickSearchResult(input, state); + } else { + vscode.window.showErrorMessage(l10n.t('No results found. Try pasting a path or url instead.')); + return; + } + } + + async function pickSearchResult(input: MultiStepInput, state: Partial) { + const items: QuickSearchPickItem[] = []; + if (state.searchResults) { + for (const key of Object.keys(state.searchResults)) { + const value = state.searchResults[key]; + items.push({ label: key, description: value.Description, descriptionUrl: value.DescriptionUrl }); + } + } + const pick = await input.showQuickPick({ + title, + step: step++, + totalSteps: totalSteps + 1, + placeholder: l10n.t('Pick a search result'), + items: items, + shouldResume: shouldResume + }); + state.descriptionPath = items.find(x => x.label === pick?.label)?.descriptionUrl || ''; + } + await MultiStepInput.run(input => inputPathOrSearch(input, state), () => step -= 2); + return state; +} \ No newline at end of file diff --git a/vscode/microsoft-kiota/src/modules/steps/utils.ts b/vscode/microsoft-kiota/src/modules/steps/utils.ts new file mode 100644 index 0000000000..c235c23231 --- /dev/null +++ b/vscode/microsoft-kiota/src/modules/steps/utils.ts @@ -0,0 +1,12 @@ +import { l10n } from 'vscode'; + +export function validateIsNotEmpty(value: string) { + return Promise.resolve(value.length > 0 ? undefined : l10n.t('Required')); +} + +export function shouldResume() { + // Could show a notification with the option to resume. + return new Promise((resolve, reject) => { + // noop + }); +} \ No newline at end of file diff --git a/vscode/microsoft-kiota/src/steps.ts b/vscode/microsoft-kiota/src/steps.ts deleted file mode 100644 index f427c447bc..0000000000 --- a/vscode/microsoft-kiota/src/steps.ts +++ /dev/null @@ -1,676 +0,0 @@ -import * as path from 'path'; -import * as vscode from 'vscode'; -import { Disposable, l10n, OpenDialogOptions, QuickInput, QuickInputButton, QuickInputButtons, QuickPickItem, Uri, window, workspace } from 'vscode'; - -import { allGenerationLanguages, generationLanguageToString, KiotaSearchResultItem, LanguagesInformation, maturityLevelToString } from './kiotaInterop'; -import { findAppPackageDirectory, getWorkspaceJsonDirectory, isValidUrl } from './util'; -import { IntegrationParams, isDeeplinkEnabled } from './utilities/deep-linking'; -import { isFilePath, isTemporaryDirectory } from './utilities/temporary-folder'; - -export async function filterSteps(existingFilter: string, filterCallback: (searchQuery: string) => void) { - const state = {} as Partial; - const title = l10n.t('Filter the API description'); - let step = 1; - let totalSteps = 1; - async function inputFilterQuery(input: MultiStepInput, state: Partial) { - await input.showInputBox({ - title, - step: step++, - totalSteps: totalSteps, - value: existingFilter, - prompt: l10n.t('Enter a filter'), - validate: x => { - filterCallback(x.length === 0 && existingFilter.length > 0 ? existingFilter : x); - existingFilter = ''; - return Promise.resolve(undefined); - }, - shouldResume: shouldResume - }); - } - await MultiStepInput.run(input => inputFilterQuery(input, state), () => step -= 2); - return state; -} - -export async function searchSteps(searchCallBack: (searchQuery: string) => Thenable | undefined>) { - const state: Partial = {}; - const title = l10n.t('Add an API description'); - let step = 1; - let totalSteps = 2; - async function inputPathOrSearch(input: MultiStepInput, state: Partial) { - const selectedOption = await input.showQuickPick({ - title, - step: step++, - totalSteps: totalSteps, - placeholder: l10n.t('Search or browse a path to an API description'), - items: [{ label: l10n.t('Search') }, { label: l10n.t('Browse path') }], - validate: validateIsNotEmpty, - shouldResume: shouldResume - }); - if (selectedOption?.label === l10n.t('Search')) { - return (input: MultiStepInput) => inputSearch(input, state); - } - else if (selectedOption?.label === l10n.t('Browse path')) { - const fileUri = await input.showOpenDialog({ - canSelectMany: false, - openLabel: 'Select', - canSelectFolders: false, - canSelectFiles: true - }); - - if (fileUri && fileUri[0]) { - state.descriptionPath = fileUri[0].fsPath; - } - } - } - - async function inputSearch(input: MultiStepInput, state: Partial) { - state.searchQuery = await input.showInputBox({ - title, - step: step++, - totalSteps: totalSteps, - value: state.searchQuery ?? '', - prompt: l10n.t('Search or paste a path to an API description'), - validate: validateIsNotEmpty, - shouldResume: shouldResume - }); - if (state.searchQuery && (isValidUrl(state.searchQuery) || isFilePath(state.searchQuery))) { - state.descriptionPath = state.searchQuery; - return; - } - state.searchResults = await searchCallBack(state.searchQuery); - if (state.searchResults && Object.keys(state.searchResults).length > 0) { - return (input: MultiStepInput) => pickSearchResult(input, state); - } else { - vscode.window.showErrorMessage(l10n.t('No results found. Try pasting a path or url instead.')); - return; - } - } - - async function pickSearchResult(input: MultiStepInput, state: Partial) { - const items: QuickSearchPickItem[] = []; - if (state.searchResults) { - for (const key of Object.keys(state.searchResults)) { - const value = state.searchResults[key]; - items.push({ label: key, description: value.Description, descriptionUrl: value.DescriptionUrl }); - } - } - const pick = await input.showQuickPick({ - title, - step: step++, - totalSteps: totalSteps + 1, - placeholder: l10n.t('Pick a search result'), - items: items, - shouldResume: shouldResume - }); - state.descriptionPath = items.find(x => x.label === pick?.label)?.descriptionUrl || ''; - } - await MultiStepInput.run(input => inputPathOrSearch(input, state), () => step -= 2); - return state; -} - -export async function generateSteps(existingConfiguration: Partial, languagesInformation?: LanguagesInformation, deepLinkParams?: Partial) { - const state = { ...existingConfiguration } as Partial; - if (existingConfiguration.generationType && existingConfiguration.clientClassName && existingConfiguration.clientNamespaceName && existingConfiguration.outputPath && existingConfiguration.language && - typeof existingConfiguration.generationType === 'string' && existingConfiguration.clientNamespaceName === 'string' && typeof existingConfiguration.outputPath === 'string' && typeof existingConfiguration.language === 'string' && - existingConfiguration.generationType.length > 0 && existingConfiguration.clientClassName.length > 0 && existingConfiguration.clientNamespaceName.length > 0 && existingConfiguration.outputPath.length > 0 && existingConfiguration.language.length > 0) { - return state; - } - - const deeplinkEnabled = deepLinkParams && isDeeplinkEnabled(deepLinkParams); - const isDeepLinkPluginNameProvided = deeplinkEnabled && state.pluginName; - const isDeepLinkGenerationTypeProvided = deeplinkEnabled && state.generationType; - const isDeepLinkPluginTypeProvided = deeplinkEnabled && state.pluginTypes; - const isDeepLinkLanguageProvided = deeplinkEnabled && state.language; - const isDeepLinkOutputPathProvided = deeplinkEnabled && state.outputPath; - const isDeepLinkClientClassNameProvided = deeplinkEnabled && state.clientClassName; - - if (typeof state.outputPath === 'string' && !isTemporaryDirectory(state.outputPath)) { - state.outputPath = workspace.asRelativePath(state.outputPath); - } - const workspaceOpen = vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0; - - let workspaceFolder = getWorkspaceJsonDirectory(); - const appPackagePath = findAppPackageDirectory(workspaceFolder); - if (appPackagePath) { - workspaceFolder = appPackagePath; - } - - if (isDeepLinkOutputPathProvided && deepLinkParams.source && deepLinkParams.source.toLowerCase() === 'ttk') { - state.workingDirectory = state.outputPath as string; - } - - let step = 1; - const folderSelectionOption = l10n.t('Browse your output directory'); - let inputOptions = [ - { label: l10n.t('Default folder'), description: workspaceFolder }, - { label: folderSelectionOption } - ]; - - function updateWorkspaceFolder(name: string | undefined) { - if (name && (!workspaceOpen)) { - workspaceFolder = getWorkspaceJsonDirectory(name); - inputOptions = [ - { label: l10n.t('Default folder'), description: workspaceFolder }, - { label: folderSelectionOption } - ]; - } - } - function getNextStepForGenerationType(generationType: string | QuickPickItem) { - switch (generationType) { - case 'client': - return inputClientClassName; - case 'plugin': - return inputPluginName; - case 'other': - return chooseOtherGenerationType; - default: - throw new Error('unknown generation type'); - } - } - async function inputGenerationType(input: MultiStepInput, state: Partial) { - if (!isDeepLinkGenerationTypeProvided) { - const items = [ - l10n.t('Client'), - l10n.t('Copilot plugin'), - l10n.t('Other') - ]; - const option = await input.showQuickPick({ - title: l10n.t('What do you want to generate?'), - step: step++, - totalSteps: 3, - placeholder: l10n.t('Select an option'), - items: items.map(x => ({ label: x })), - validate: validateIsNotEmpty, - shouldResume: shouldResume - }); - if (option.label === l10n.t('Client')) { - state.generationType = "client"; - } - else if (option.label === l10n.t('Copilot plugin')) { - state.generationType = "plugin"; - } - else if (option.label === l10n.t('Other')) { - state.generationType = "other"; - } - } - let nextStep = getNextStepForGenerationType(state.generationType?.toString() || ''); - return (input: MultiStepInput) => nextStep(input, state); - } - async function inputClientClassName(input: MultiStepInput, state: Partial) { - if (!isDeepLinkClientClassNameProvided) { - state.clientClassName = await input.showInputBox({ - title: `${l10n.t('Create a new API client')} - ${l10n.t('class')}`, - step: step++, - totalSteps: 5, - value: state.clientClassName ?? '', - placeholder: 'ApiClient', - prompt: l10n.t('Choose a name for the client class'), - validate: validateIsNotEmpty, - shouldResume: shouldResume - }); - } - updateWorkspaceFolder(state.clientClassName); - return (input: MultiStepInput) => inputClientNamespaceName(input, state); - } - async function inputClientNamespaceName(input: MultiStepInput, state: Partial) { - state.clientNamespaceName = await input.showInputBox({ - title: `${l10n.t('Create a new API client')} - ${l10n.t('namespace')}`, - step: step++, - totalSteps: 5, - value: typeof state.clientNamespaceName === 'string' ? state.clientNamespaceName : '', - placeholder: 'ApiSDK', - prompt: l10n.t('Choose a name for the client class namespace'), - validate: validateIsNotEmpty, - shouldResume: shouldResume - }); - return (input: MultiStepInput) => inputOutputPath(input, state); - } - async function inputOutputPath(input: MultiStepInput, state: Partial) { - while (true) { - const selectedOption = await input.showQuickPick({ - title: `${l10n.t('Create a new API client')} - ${l10n.t('output directory')}`, - step: step++, - totalSteps: 5, - placeholder: l10n.t('Enter an output path relative to the root of the project'), - items: inputOptions, - shouldResume: shouldResume - }); - if (selectedOption) { - if (selectedOption?.label === folderSelectionOption) { - const folderUri = await input.showOpenDialog({ - canSelectMany: false, - openLabel: 'Select', - canSelectFolders: true, - canSelectFiles: false - }); - - if (folderUri && folderUri[0]) { - state.outputPath = folderUri[0].fsPath; - } else { - continue; - } - } else { - state.outputPath = selectedOption.description; - if (workspaceOpen) { - state.workingDirectory = vscode.workspace.workspaceFolders![0].uri.fsPath; - } else { - state.workingDirectory = path.dirname(selectedOption.description!); - } - } - } - state.outputPath = state.outputPath === '' ? 'output' : state.outputPath; - return (input: MultiStepInput) => pickLanguage(input, state); - } - - } - async function pickLanguage(input: MultiStepInput, state: Partial) { - if (!isDeepLinkLanguageProvided) { - const items = allGenerationLanguages.map(x => { - const lngName = generationLanguageToString(x); - const lngInfo = languagesInformation ? languagesInformation[lngName] : undefined; - const lngMaturity = lngInfo ? ` - ${maturityLevelToString(lngInfo.MaturityLevel)}` : ''; - return { - label: `${lngName}${lngMaturity}`, - languageName: lngName, - } as (QuickPickItem & { languageName: string }); - }); - const pick = await input.showQuickPick({ - title: `${l10n.t('Create a new API client')} - ${l10n.t('language')}`, - step: step++, - totalSteps: 5, - placeholder: l10n.t('Pick a language'), - items, - activeItem: typeof state.language === 'string' ? items.find(x => x.languageName === state.language) : undefined, - shouldResume: shouldResume - }); - state.language = pick.label.split('-')[0].trim(); - } - } - async function inputPluginName(input: MultiStepInput, state: Partial) { - if (!isDeepLinkPluginNameProvided) { - state.pluginName = await input.showInputBox({ - title: `${l10n.t('Create a new plugin')} - ${l10n.t('plugin name')}`, - step: step++, - totalSteps: 3, - value: state.pluginName ?? '', - placeholder: 'MyPlugin', - prompt: l10n.t('Choose a name for the plugin'), - validate: validateIsNotEmpty, - shouldResume: shouldResume - }); - } - state.pluginTypes = ['ApiPlugin']; - updateWorkspaceFolder(state.pluginName); - return (input: MultiStepInput) => inputPluginOutputPath(input, state); - } - async function chooseOtherGenerationType(input: MultiStepInput, state: Partial) { - if (!isDeepLinkPluginTypeProvided) { - const items = ['API Manifest', 'Open AI Plugin'].map(x => ({ label: x }) as QuickPickItem); - const pluginTypes = await input.showQuickPick({ - title: l10n.t('Choose a type'), - step: step++, - totalSteps: 4, - placeholder: l10n.t('Select an option'), - items: items, - validate: validateIsNotEmpty, - shouldResume: shouldResume - }); - pluginTypes.label === 'API Manifest' ? state.pluginTypes = ['ApiManifest'] : state.pluginTypes = ['OpenAI']; - - } - - Array.isArray(state.pluginTypes) && state.pluginTypes.includes('ApiManifest') ? - state.generationType = 'apimanifest' : state.generationType = 'plugin'; - return (input: MultiStepInput) => inputOtherGenerationTypeName(input, state); - } - async function inputPluginOutputPath(input: MultiStepInput, state: Partial) { - while (!isDeepLinkOutputPathProvided) { - const selectedOption = await input.showQuickPick({ - title: `${l10n.t('Create a new plugin')} - ${l10n.t('output directory')}`, - step: step++, - totalSteps: 4, - placeholder: l10n.t('Enter an output path relative to the root of the project'), - items: inputOptions, - shouldResume: shouldResume - }); - if (selectedOption) { - if (selectedOption?.label === folderSelectionOption) { - const folderUri = await input.showOpenDialog({ - canSelectMany: false, - openLabel: 'Select', - canSelectFolders: true, - canSelectFiles: false - }); - - if (folderUri && folderUri[0]) { - state.outputPath = folderUri[0].fsPath; - } else { - continue; - } - } else { - state.outputPath = selectedOption.description; - if (workspaceOpen) { - state.workingDirectory = vscode.workspace.workspaceFolders![0].uri.fsPath; - } else { - state.workingDirectory = path.dirname(selectedOption.description!); - } - } - } - state.outputPath = state.outputPath === '' ? 'output' : state.outputPath; - break; - } - } - async function inputOtherGenerationTypeName(input: MultiStepInput, state: Partial) { - if (!isDeepLinkPluginNameProvided) { - const isManifest = state.pluginTypes && Array.isArray(state.pluginTypes) && state.pluginTypes.includes('ApiManifest'); - state.pluginName = await input.showInputBox({ - title: `${isManifest ? l10n.t('Create a new manifest') : l10n.t('Create a new OpenAI plugin')} - ${l10n.t('output name')}`, - step: step++, - totalSteps: 4, - value: state.pluginName ?? '', - placeholder: `${isManifest ? 'MyManifest' : 'MyOpenAIPlugin'}`, - prompt: `${isManifest ? l10n.t('Choose a name for the manifest') : l10n.t('Choose a name for the OpenAI plugin')}`, - validate: validateIsNotEmpty, - shouldResume: shouldResume - }); - } - updateWorkspaceFolder(state.pluginName); - return (input: MultiStepInput) => inputOtherGenerationTypeOutputPath(input, state); - } - async function inputOtherGenerationTypeOutputPath(input: MultiStepInput, state: Partial) { - while (true) { - const isManifest = state.pluginTypes && Array.isArray(state.pluginTypes) && state.pluginTypes.includes('ApiManifest'); - - const selectedOption = await input.showQuickPick({ - title: `${isManifest ? l10n.t('Create a new manifest') : l10n.t('Create a new OpenAI plugin')} - ${l10n.t('output directory')}`, - step: step++, - totalSteps: 4, - placeholder: l10n.t('Enter an output path relative to the root of the project'), - items: inputOptions, - shouldResume: shouldResume - }); - if (selectedOption) { - if (selectedOption?.label === folderSelectionOption) { - const folderUri = await input.showOpenDialog({ - canSelectMany: false, - openLabel: 'Select', - canSelectFolders: true, - canSelectFiles: false - }); - - if (folderUri && folderUri[0]) { - state.outputPath = folderUri[0].fsPath; - } else { - continue; - } - } else { - state.outputPath = selectedOption.description; - if (workspaceOpen) { - state.workingDirectory = vscode.workspace.workspaceFolders![0].uri.fsPath; - } else { - state.workingDirectory = path.dirname(selectedOption.description!); - } - } - } - if (state.outputPath === '') { - state.outputPath = 'output'; - } - break; - } - - } - await MultiStepInput.run(input => inputGenerationType(input, state), () => step -= 2); - if (!state.workingDirectory) { - state.workingDirectory = workspaceOpen ? vscode.workspace.workspaceFolders![0].uri.fsPath : state.outputPath as string; - } - return state; -} - -function shouldResume() { - // Could show a notification with the option to resume. - return new Promise((resolve, reject) => { - // noop - }); -} - -function validateIsNotEmpty(value: string) { - return Promise.resolve(value.length > 0 ? undefined : l10n.t('Required')); -} - -interface BaseStepsState { - title: string; - step: number; - totalSteps: number; -} - -interface SearchState extends BaseStepsState { - searchQuery: string; - searchResults: Record; - descriptionPath: string; -} - -interface OpenState extends BaseStepsState { - descriptionPath: string; -} - -interface SearchItem { - descriptionUrl?: string; -} -type QuickSearchPickItem = QuickPickItem & SearchItem; - -export interface GenerateState extends BaseStepsState { - generationType: QuickPickItem | string; - pluginTypes: QuickPickItem | string[]; - pluginName: string; - clientClassName: string; - clientNamespaceName: QuickPickItem | string; - language: QuickPickItem | string; - outputPath: QuickPickItem | string; - workingDirectory: string; -} - -class InputFlowAction { - static back = new InputFlowAction(); - static cancel = new InputFlowAction(); - static resume = new InputFlowAction(); -} -type InputStep = (input: MultiStepInput) => Thenable; - -interface QuickPickParameters { - title: string; - step: number; - totalSteps: number; - items: T[]; - activeItem?: T; - ignoreFocusOut?: boolean; - placeholder: string; - buttons?: QuickInputButton[]; - shouldResume: () => Thenable; -} - -interface InputBoxParameters { - title: string; - step: number; - totalSteps: number; - value: string; - prompt: string; - validate?: (value: string) => Promise; - buttons?: QuickInputButton[]; - ignoreFocusOut?: boolean; - placeholder?: string; - shouldResume: () => Thenable; -} -interface OpenDialogParameters { - canSelectMany: boolean; - openLabel: string; - canSelectFolders: boolean; - canSelectFiles: boolean; -} - -class MultiStepInput { - async showOpenDialog({ canSelectMany, openLabel, canSelectFolders, canSelectFiles }: OpenDialogParameters): Promise { - return await new Promise((resolve) => { - const input: OpenDialogOptions = { - canSelectMany, - openLabel, - canSelectFolders, - canSelectFiles - }; - - void window.showOpenDialog(input).then(folderUris => { - if (folderUris && folderUris.length > 0) { - resolve([folderUris[0]]); - } else { - resolve(undefined); - } - }); - }); - } - - static async run(start: InputStep, onNavBack?: () => void) { - const input = new MultiStepInput(); - return input.stepThrough(start, onNavBack); - } - - private current?: QuickInput; - private steps: InputStep[] = []; - - private async stepThrough(start: InputStep, onNavBack?: () => void) { - let step: InputStep | void = start; - while (step) { - this.steps.push(step); - if (this.current) { - this.current.enabled = false; - this.current.busy = true; - } - try { - step = await step(this); - } catch (err) { - if (err === InputFlowAction.back) { - if (onNavBack) { - onNavBack(); //Currently, step -= 2 passed as onNavBack because of using postfix increment in steps in the input functions - } - this.steps.pop(); - step = this.steps.pop(); - } else if (err === InputFlowAction.resume) { - step = this.steps.pop(); - } else if (err === InputFlowAction.cancel) { - step = undefined; - } else { - throw err; - } - } - } - if (this.current) { - this.current.dispose(); - } - } - - async showQuickPick>({ title, step, totalSteps, items, activeItem, ignoreFocusOut, placeholder, buttons, shouldResume }: P) { - const disposables: Disposable[] = []; - try { - return await new Promise((resolve, reject) => { - const input = window.createQuickPick(); - input.title = title; - input.step = step; - input.totalSteps = totalSteps; - input.ignoreFocusOut = ignoreFocusOut ?? false; - input.placeholder = placeholder; - input.items = items; - input.buttons = [ - ...(this.steps.length > 1 ? [QuickInputButtons.Back] : []), - ...(buttons || []) - ]; - disposables.push( - input.onDidTriggerButton(item => { - if (item === QuickInputButtons.Back) { - reject(InputFlowAction.back); - } else { - resolve(item); - } - }), - input.onDidChangeSelection(items => resolve(items[0])), - input.onDidHide(() => { - (async () => { - reject(shouldResume && await shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel); - })() - .catch(reject); - }) - ); - if (this.current) { - this.current.dispose(); - } - this.current = input; - this.current.show(); - if (activeItem) { - input.activeItems = [activeItem]; - } - }); - } finally { - disposables.forEach(d => d.dispose()); - } - } - - async showInputBox

({ title, step, totalSteps, value, prompt, validate, buttons, ignoreFocusOut, placeholder, shouldResume }: P) { - const disposables: Disposable[] = []; - try { - return await new Promise((resolve, reject) => { - const input = window.createInputBox(); - input.title = title; - input.step = step; - input.totalSteps = totalSteps; - input.value = value || ''; - input.prompt = prompt; - input.ignoreFocusOut = ignoreFocusOut ?? false; - input.placeholder = placeholder; - input.buttons = [ - ...(this.steps.length > 1 ? [QuickInputButtons.Back] : []), - ...(buttons || []) - ]; - let validating = validate ? validate('') : Promise.resolve(undefined); - disposables.push( - input.onDidTriggerButton(item => { - if (item === QuickInputButtons.Back) { - reject(InputFlowAction.back); - } else { - resolve(item); - } - }), - input.onDidAccept(async () => { - const value = input.value; - input.enabled = false; - input.busy = true; - if (!(validate && await validate(value))) { - resolve(value); - } - input.enabled = true; - input.busy = false; - }), - input.onDidChangeValue(async text => { - if (validate) { - const current = validate(text); - validating = current; - const validationMessage = await current; - if (current === validating) { - input.validationMessage = validationMessage; - } - } - }), - input.onDidHide(() => { - (async () => { - reject(shouldResume && await shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel); - })() - .catch(reject); - }) - ); - if (this.current) { - this.current.dispose(); - } - this.current = input; - this.current.show(); - }); - } finally { - disposables.forEach(d => d.dispose()); - } - } -} diff --git a/vscode/microsoft-kiota/src/utilities/deep-linking.ts b/vscode/microsoft-kiota/src/utilities/deep-linking.ts index b67f03fc34..c7b5dbfc30 100644 --- a/vscode/microsoft-kiota/src/utilities/deep-linking.ts +++ b/vscode/microsoft-kiota/src/utilities/deep-linking.ts @@ -1,4 +1,4 @@ -import { GenerateState } from "../steps"; +import { GenerateState } from "../modules/steps/generateSteps"; import { KiotaGenerationLanguage, KiotaPluginType } from "../types/enums"; import { allGenerationLanguagesToString, getSanitizedString, parseGenerationLanguage, parsePluginType } from "../util"; import { createTemporaryFolder } from "./temporary-folder";