Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for 'back' to all create env UI. #20693

Merged
merged 3 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions pythonFiles/create_venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,14 @@ def main(argv: Optional[Sequence[str]] = None) -> None:
if pip_installed:
upgrade_pip(venv_path)

if args.requirements:
print(f"VENV_INSTALLING_REQUIREMENTS: {args.requirements}")
install_requirements(venv_path, args.requirements)

if args.toml:
print(f"VENV_INSTALLING_PYPROJECT: {args.toml}")
install_toml(venv_path, args.extras)

if args.requirements:
print(f"VENV_INSTALLING_REQUIREMENTS: {args.requirements}")
install_requirements(venv_path, args.requirements)


if __name__ == "__main__":
main(sys.argv[1:])
2 changes: 1 addition & 1 deletion src/client/common/utils/async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface Deferred<T> {
readonly rejected: boolean;
readonly completed: boolean;
resolve(value?: T | PromiseLike<T>): void;
reject(reason?: string | Error | Record<string, unknown>): void;
reject(reason?: string | Error | Record<string, unknown> | unknown): void;
}

class DeferredImpl<T> implements Deferred<T> {
Expand Down
66 changes: 38 additions & 28 deletions src/client/common/utils/multiStepInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { inject, injectable } from 'inversify';
import { Disposable, QuickInput, QuickInputButton, QuickInputButtons, QuickPick, QuickPickItem, Event } from 'vscode';
import { IApplicationShell } from '../application/types';
import { createDeferred } from './async';

// Borrowed from https://github.com/Microsoft/vscode-extension-samples/blob/master/quickinput-sample/src/multiStepInput.ts
// Why re-invent the wheel :)
Expand All @@ -29,7 +30,7 @@ export type InputStep<T extends any> = (input: MultiStepInput<T>, state: T) => P

type buttonCallbackType<T extends QuickPickItem> = (quickPick: QuickPick<T>) => void;

type QuickInputButtonSetup = {
export type QuickInputButtonSetup = {
/**
* Button for an action in a QuickPick.
*/
Expand Down Expand Up @@ -164,35 +165,41 @@ export class MultiStepInput<S> implements IMultiStepInput<S> {
// so do it after initialization. This ensures quickpick starts with the active
// item in focus when this is true, instead of having scroll position at top.
input.keepScrollPosition = keepScrollPosition;
try {
return await new Promise<MultiStepInputQuickPicResponseType<T, P>>((resolve, reject) => {
disposables.push(
input.onDidTriggerButton(async (item) => {
if (item === QuickInputButtons.Back) {
reject(InputFlowAction.back);
}
if (customButtonSetups) {
for (const customButtonSetup of customButtonSetups) {
if (JSON.stringify(item) === JSON.stringify(customButtonSetup?.button)) {
await customButtonSetup?.callback(input);
}
}

const deferred = createDeferred<T>();

disposables.push(
input.onDidTriggerButton(async (item) => {
if (item === QuickInputButtons.Back) {
deferred.reject(InputFlowAction.back);
input.hide();
}
if (customButtonSetups) {
for (const customButtonSetup of customButtonSetups) {
if (JSON.stringify(item) === JSON.stringify(customButtonSetup?.button)) {
await customButtonSetup?.callback(input);
}
}),
input.onDidChangeSelection((selectedItems) => resolve(selectedItems[0])),
input.onDidHide(() => {
resolve(undefined);
}),
);
if (acceptFilterBoxTextAsSelection) {
disposables.push(
input.onDidAccept(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
resolve(input.value as any);
}),
);
}
}
});
}),
input.onDidChangeSelection((selectedItems) => deferred.resolve(selectedItems[0])),
input.onDidHide(() => {
if (!deferred.completed) {
deferred.resolve(undefined);
}
}),
);
if (acceptFilterBoxTextAsSelection) {
disposables.push(
input.onDidAccept(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
deferred.resolve(input.value as any);
}),
);
}

try {
return await deferred.promise;
} finally {
disposables.forEach((d) => d.dispose());
}
Expand Down Expand Up @@ -277,6 +284,9 @@ export class MultiStepInput<S> implements IMultiStepInput<S> {
if (err === InputFlowAction.back) {
this.steps.pop();
step = this.steps.pop();
if (step === undefined) {
throw err;
}
} else if (err === InputFlowAction.resume) {
step = this.steps.pop();
} else if (err === InputFlowAction.cancel) {
Expand Down
130 changes: 129 additions & 1 deletion src/client/common/vscodeApis/windowApis.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable max-classes-per-file */

import {
CancellationToken,
MessageItem,
MessageOptions,
Progress,
ProgressOptions,
QuickPick,
QuickInputButtons,
QuickPickItem,
QuickPickOptions,
TextEditor,
window,
Disposable,
} from 'vscode';
import { createDeferred, Deferred } from '../utils/async';

/* eslint-disable @typescript-eslint/no-explicit-any */
export function showQuickPick<T extends QuickPickItem>(
items: readonly T[] | Thenable<readonly T[]>,
options?: QuickPickOptions,
Expand All @@ -22,6 +27,10 @@ export function showQuickPick<T extends QuickPickItem>(
return window.showQuickPick(items, options, token);
}

export function createQuickPick<T extends QuickPickItem>(): QuickPick<T> {
return window.createQuickPick<T>();
}

export function showErrorMessage<T extends string>(message: string, ...items: T[]): Thenable<T | undefined>;
export function showErrorMessage<T extends string>(
message: string,
Expand Down Expand Up @@ -67,3 +76,122 @@ export function getActiveTextEditor(): TextEditor | undefined {
const { activeTextEditor } = window;
return activeTextEditor;
}

export enum MultiStepAction {
Back = 'Back',
Cancel = 'Cancel',
Continue = 'Continue',
}

export async function showQuickPickWithBack<T extends QuickPickItem>(
items: readonly T[],
options?: QuickPickOptions,
token?: CancellationToken,
): Promise<T | T[] | undefined> {
const quickPick: QuickPick<T> = window.createQuickPick<T>();
const disposables: Disposable[] = [quickPick];

quickPick.items = items;
quickPick.buttons = [QuickInputButtons.Back];
quickPick.canSelectMany = options?.canPickMany ?? false;
quickPick.ignoreFocusOut = options?.ignoreFocusOut ?? false;
quickPick.matchOnDescription = options?.matchOnDescription ?? false;
quickPick.matchOnDetail = options?.matchOnDetail ?? false;
quickPick.placeholder = options?.placeHolder;
quickPick.title = options?.title;

const deferred = createDeferred<T | T[] | undefined>();

disposables.push(
quickPick,
quickPick.onDidTriggerButton((item) => {
if (item === QuickInputButtons.Back) {
deferred.reject(MultiStepAction.Back);
quickPick.hide();
}
}),
quickPick.onDidAccept(() => {
if (!deferred.completed) {
deferred.resolve(quickPick.selectedItems.map((item) => item));
quickPick.hide();
}
}),
quickPick.onDidHide(() => {
if (!deferred.completed) {
deferred.resolve(undefined);
}
}),
);
if (token) {
disposables.push(
token.onCancellationRequested(() => {
quickPick.hide();
}),
);
}
quickPick.show();

try {
return await deferred.promise;
} finally {
disposables.forEach((d) => d.dispose());
}
}

export class MultiStepNode {
constructor(
public previous: MultiStepNode | undefined,
public readonly current: (context?: MultiStepAction) => Promise<MultiStepAction>,
public next: MultiStepNode | undefined,
) {}

public static async run(step: MultiStepNode, context?: MultiStepAction): Promise<MultiStepAction> {
let nextStep: MultiStepNode | undefined = step;
let flowAction = await nextStep.current(context);
while (nextStep !== undefined) {
if (flowAction === MultiStepAction.Cancel) {
return flowAction;
}
if (flowAction === MultiStepAction.Back) {
nextStep = nextStep?.previous;
}
if (flowAction === MultiStepAction.Continue) {
nextStep = nextStep?.next;
}

if (nextStep) {
flowAction = await nextStep?.current(flowAction);
}
}

return flowAction;
}
}

export function createStepBackEndNode<T>(deferred?: Deferred<T>): MultiStepNode {
return new MultiStepNode(
undefined,
async () => {
if (deferred) {
// This is to ensure we don't leave behind any pending promises.
deferred.reject(MultiStepAction.Back);
}
return Promise.resolve(MultiStepAction.Back);
},
undefined,
);
}

export function createStepForwardEndNode<T>(deferred?: Deferred<T>, result?: T): MultiStepNode {
return new MultiStepNode(
undefined,
async () => {
if (deferred) {
// This is to ensure we don't leave behind any pending promises.
deferred.resolve(result);
}
return Promise.resolve(MultiStepAction.Back);
},
undefined,
);
}
Loading