Skip to content

Commit

Permalink
Refactor: QuickPickOptions and InputBoxOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
AsriFox committed Dec 7, 2023
1 parent 5a8d952 commit 0b84a6b
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 96 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ All notable changes to the "gnuradio-integration" extension will be documented i
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.

## [Unreleased]

## [0.2.1] - 2023-12-07
### Added
- Added automatic copyright value (`git config user.name`) when creating a block
- Added dependency on builtin Git extension for calling `git` properly
Expand Down Expand Up @@ -48,7 +50,8 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
### Added
- This is the initial release of gnuradio-integration VS Code extension

[Unreleased]: https://github.com/AsriFox/gnuradio-integration/compare/v0.2.0...HEAD
[Unreleased]: https://github.com/AsriFox/gnuradio-integration/compare/v0.2.1...HEAD
[0.2.1]: https://github.com/AsriFox/gnuradio-integration/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/AsriFox/gnuradio-integration/compare/v0.1.3...v0.2.0
[0.1.3]: https://github.com/AsriFox/gnuradio-integration/compare/v0.1.2...v0.1.3
[0.1.2]: https://github.com/AsriFox/gnuradio-integration/compare/v0.1.1...v0.1.2
Expand Down
91 changes: 48 additions & 43 deletions src/modtool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,27 +56,6 @@ export async function createModule() {
}

export async function createBlock(context: ExtensionContext, existingBlocks: Set<string>) {
async function validateName(value: string) {
let name = value.trim();
if (!name.length) {
return 'Name cannot be empty';
}
if (!/^([\w,\_]+)$/.test(name)) {
return 'Name can only contain ASCII letters, digits and underscores';
}
// if (name.length < 3) {
// return {
// message: 'Descriptive names usually contain at least 3 symbols',
// severity: vscode.InputBoxValidationSeverity.Warning,
// then: null,
// };
// }
if (existingBlocks.has(name)) {
return 'Block with that name is already present';
}
return undefined;
}

interface State {
title: string;
step: number;
Expand All @@ -98,8 +77,6 @@ export async function createBlock(context: ExtensionContext, existingBlocks: Set
totalSteps: state.totalSteps,
value: state.copyright || '',
prompt: 'Please specify the copyright holder',
validate: async () => undefined,
shouldResume: async () => false,
});
return (input: MultiStepInput) => inputName(input, state);
}
Expand All @@ -112,19 +89,41 @@ export async function createBlock(context: ExtensionContext, existingBlocks: Set
totalSteps: state.totalSteps,
value: state.name || '',
prompt: 'Choose a unique name for the block',
validate: validateName,
shouldResume: async () => false,
validateInput(value) {
let name = value.trim();
if (!name.length) {
return {
message: 'Name cannot be empty',
severity: InputBoxValidationSeverity.Error,
};
}
if (!/^([\w,\_]+)$/.test(name)) {
return {
message: 'Name can only contain ASCII letters, digits and underscores',
severity: InputBoxValidationSeverity.Error,
};
}
if (name.length < 3) {
return {
message: 'Descriptive names usually contain at least 3 symbols',
severity: InputBoxValidationSeverity.Warning,
then: null,
};
}
if (existingBlocks.has(name)) {
return {
message: 'Block with that name is already present',
severity: InputBoxValidationSeverity.Error,
};
}
},
});
return (input: MultiStepInput) => pickBlockType(input, state);
}

async function pickBlockType(input: MultiStepInput, state: State) {
const pick = await input.showQuickPick({
title: state.title,
step: 3,
totalSteps: state.totalSteps,
placeholder: 'Pick block type',
items: [
const pick = await input.showQuickPick(
[
{ label: 'general', description: 'gr::block', detail: 'General-purpose block type' },
{ label: 'sync', description: 'gr::sync_block', detail: 'Block with synchronous 1:1 input-to-output' },
{ label: 'decimator', description: 'gr::sync_decimator', detail: 'Block with synchronous N:1 input-to-output' },
Expand All @@ -135,22 +134,23 @@ export async function createBlock(context: ExtensionContext, existingBlocks: Set
{ label: 'hier', description: 'gr::hier_block2', detail: 'Hierarchical container block for other blocks; usually can be described by a flowgraph' },
{ label: 'noblock', detail: 'C++ or Python class' },
],
activeItem: state.blockType,
shouldResume: async () => false,
});
{
title: state.title,
step: 3,
totalSteps: state.totalSteps,
placeHolder: 'Pick block type',
activeItem: state.blockType,
}
);
state.blockType = pick[0];
// state.totalSteps = state.blockType.label === 'noblock' ? 4 : 5;
return (input: MultiStepInput) => pickLanguage(input, state);
}

async function pickLanguage(input: MultiStepInput, state: State) {
const baseUri = context.extensionUri;
const pick = await input.showQuickPick({
title: state.title,
step: 4,
totalSteps: state.totalSteps,
placeholder: 'Pick implementation language',
items: [
const pick = await input.showQuickPick(
[
{
label: 'Python',
description: 'python',
Expand All @@ -162,9 +162,14 @@ export async function createBlock(context: ExtensionContext, existingBlocks: Set
iconPath: Uri.joinPath(baseUri, 'media', 'file_type_cpp3.svg')
},
],
activeItem: state.language,
shouldResume: async () => false,
});
{
title: state.title,
step: 4,
totalSteps: state.totalSteps,
placeHolder: 'Pick implementation language',
activeItem: state.language,
}
);
state.language = pick[0];
// if (state.blockType?.label === 'noblock' && state.language.label.includes('Python')) {
state.finished = true;
Expand Down
81 changes: 29 additions & 52 deletions src/multiStepInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { QuickPickItem, window, Disposable, QuickInputButton, QuickInput, QuickInputButtons } from 'vscode';
import { QuickPickItem, window, Disposable, QuickInputButton, QuickInput, QuickInputButtons, InputBoxValidationMessage, InputBoxOptions, QuickPickOptions } from 'vscode';

// -------------------------------------------------------
// Helper code that wraps the API for the multi-step case.
Expand All @@ -18,32 +18,6 @@ class InputFlowAction {

type InputStep = (input: MultiStepInput) => Thenable<InputStep | void>;

interface QuickPickParameters<T extends QuickPickItem> {
title: string;
step: number;
totalSteps: number;
items: T[];
activeItem?: T;
ignoreFocusOut?: boolean;
canSelectMany?: boolean;
placeholder: string;
buttons?: QuickInputButton[];
shouldResume?: () => Thenable<boolean>;
}

interface InputBoxParameters {
title: string;
step: number;
totalSteps: number;
value: string;
prompt: string;
validate: (value: string) => Promise<string | undefined>;
buttons?: QuickInputButton[];
ignoreFocusOut?: boolean;
placeholder?: string;
shouldResume?: () => Thenable<boolean>;
}

export class MultiStepInput {

static async run<T>(start: InputStep) {
Expand Down Expand Up @@ -82,20 +56,20 @@ export class MultiStepInput {
}
}

async showQuickPick<T extends QuickPickItem, P extends QuickPickParameters<T>>({ title, step, totalSteps, items, activeItem, ignoreFocusOut, canSelectMany, placeholder, shouldResume }: P) {
async showQuickPick<T extends QuickPickItem>(items: T[], options: QuickPickOptions & { step: number, totalSteps: number, activeItem?: T, buttons?: QuickInputButton[], shouldResume?: () => Thenable<boolean> }) {
const disposables: Disposable[] = [];
try {
return await new Promise<T[]>((resolve, reject) => {
const input = window.createQuickPick<T>();
input.title = title;
input.step = step;
input.totalSteps = totalSteps;
input.ignoreFocusOut = ignoreFocusOut ?? false;
input.canSelectMany = canSelectMany ?? false;
input.placeholder = placeholder;
input.title = options.title;
input.step = options.step;
input.totalSteps = options.totalSteps;
input.ignoreFocusOut = options.ignoreFocusOut ?? false;
input.canSelectMany = options.canPickMany ?? false;
input.placeholder = options.placeHolder;
input.items = items;
if (activeItem) {
input.activeItems = [activeItem];
if (options.activeItem) {
input.activeItems = [options.activeItem];
}
input.buttons = this.steps.length > 1 ? [QuickInputButtons.Back] : [];
disposables.push(
Expand All @@ -109,7 +83,7 @@ export class MultiStepInput {
input.onDidChangeSelection(items => resolve([...items])),
input.onDidHide(() => {
(async () => {
reject(shouldResume && await shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel);
reject(options.shouldResume && await options.shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel);
})()
.catch(reject);
})
Expand All @@ -125,23 +99,23 @@ export class MultiStepInput {
}
}

async showInputBox<P extends InputBoxParameters>({ title, step, totalSteps, value, prompt, validate, buttons, ignoreFocusOut, placeholder, shouldResume }: P) {
async showInputBox(options: InputBoxOptions & { step: number, totalSteps: number, buttons?: QuickInputButton[], shouldResume?: () => Thenable<boolean> }) {
const disposables: Disposable[] = [];
try {
return await new Promise<string | (P extends { buttons: (infer I)[] } ? I : never)>((resolve, reject) => {
return await new Promise<string>((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.title = options.title;
input.step = options.step;
input.totalSteps = options.totalSteps;
input.value = options.value || '';
input.prompt = options.prompt;
input.ignoreFocusOut = options.ignoreFocusOut ?? false;
input.placeholder = options.placeHolder;
input.buttons = [
...(this.steps.length > 1 ? [QuickInputButtons.Back] : []),
...(buttons || [])
...(options.buttons || [])
];
let validating = validate('');
let validating = options.validateInput ? options.validateInput('') : undefined;
disposables.push(
input.onDidTriggerButton(item => {
if (item === QuickInputButtons.Back) {
Expand All @@ -154,23 +128,26 @@ export class MultiStepInput {
const value = input.value;
input.enabled = false;
input.busy = true;
if (!(await validate(value))) {
if (!options.validateInput || !(await options.validateInput(value))) {
resolve(value);
}
input.enabled = true;
input.busy = false;
}),
input.onDidChangeValue(async text => {
const current = validate(text);
if (!options.validateInput) {
return;
}
const current = options.validateInput(text);
validating = current;
const validationMessage = await current;
if (current === validating) {
input.validationMessage = validationMessage;
input.validationMessage = validationMessage ?? undefined;
}
}),
input.onDidHide(() => {
(async () => {
reject(shouldResume && await shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel);
reject(options.shouldResume && await options.shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel);
})()
.catch(reject);
})
Expand Down

0 comments on commit 0b84a6b

Please sign in to comment.