Skip to content

Commit faf8f85

Browse files
committed
Added Quickpick for line numbering
1 parent 82f4bd0 commit faf8f85

4 files changed

Lines changed: 429 additions & 1 deletion

File tree

src/util/commands/addLineNumbers.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
'use strict';
77

88
import { LineNumberer } from '../lineNumberer';
9+
import { LineNumbersInput } from '../quickpicks/lineNumbers';
910
import { GCommand, UtilCommands } from './common';
1011

1112
export class AddLineNumbers extends GCommand {
@@ -14,7 +15,10 @@ export class AddLineNumbers extends GCommand {
1415
}
1516

1617
async execute() {
18+
const lnInputs = new LineNumbersInput();
19+
const state = await lnInputs.collect();
20+
1721
const ln = new LineNumberer();
18-
await ln.addNumbers(10, 10, true);
22+
await ln.addNumbers(state.start, state.increment, true);
1923
}
2024
}

src/util/quickpicks/buttons.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/* ---------------------------------------------------------------------------------------------
2+
* Copyright (c) Applied Eng & Design All rights reserved.
3+
* Licensed under the MIT License. See License.md in the project root for license information.
4+
* -------------------------------------------------------------------------------------------- */
5+
6+
'use strict';
7+
8+
import { QuickInputButton, ThemeIcon, Uri } from 'vscode';
9+
10+
export class GButton implements QuickInputButton {
11+
constructor(public iconPath: Uri | { dark: Uri; light: Uri } | ThemeIcon, public tooltip: string | undefined) {}
12+
}

src/util/quickpicks/common.ts

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
/* eslint-disable max-classes-per-file */
2+
/* ---------------------------------------------------------------------------------------------
3+
* Copyright (c) Applied Eng & Design All rights reserved.
4+
* Licensed under the MIT License. See License.md in the project root for license information.
5+
* -------------------------------------------------------------------------------------------- */
6+
7+
'use strict';
8+
9+
import {
10+
Disposable,
11+
InputBoxOptions,
12+
QuickInput,
13+
QuickInputButton,
14+
QuickInputButtons,
15+
QuickPickItem,
16+
QuickPickOptions,
17+
window,
18+
} from 'vscode';
19+
20+
type InputStep = (input: GQuickPick) => Thenable<InputStep | void>;
21+
22+
interface QuickPickParameters<T extends QuickPickItem> {
23+
title: string;
24+
step: number;
25+
totalSteps: number;
26+
items: T[];
27+
activeItem?: T;
28+
placeholder: string;
29+
buttons?: QuickInputButton[];
30+
shouldResume: () => Thenable<boolean>;
31+
}
32+
33+
interface InputBoxParameters {
34+
title: string;
35+
step: number;
36+
totalSteps: number;
37+
value: string;
38+
prompt: string;
39+
validate: (value: string) => Promise<string | undefined>;
40+
buttons?: QuickInputButton[];
41+
shouldResume: () => Thenable<boolean>;
42+
}
43+
44+
class InputFlowAction {
45+
static back = new InputFlowAction();
46+
static cancel = new InputFlowAction();
47+
static resume = new InputFlowAction();
48+
}
49+
50+
export abstract class GQuickPick {
51+
protected _current?: QuickInput;
52+
protected _steps: InputStep[] = [];
53+
54+
constructor(private readonly isMulti: boolean) {}
55+
56+
protected async run(start: InputStep) {
57+
return await this.stepThrough(start);
58+
}
59+
60+
private async stepThrough(start: InputStep) {
61+
let step: InputStep | void = start;
62+
63+
while (step) {
64+
this._steps.push(step);
65+
if (this._current) {
66+
this._current.enabled = false;
67+
this._current.busy = true;
68+
}
69+
70+
try {
71+
step = await step(this);
72+
} catch (err) {
73+
if (err === InputFlowAction.back) {
74+
this._steps.pop();
75+
step = this._steps.pop();
76+
} else if (err === InputFlowAction.resume) {
77+
step = this._steps.pop();
78+
} else if (err === InputFlowAction.cancel) {
79+
step = undefined;
80+
} else {
81+
throw err;
82+
}
83+
}
84+
85+
if (this._current) {
86+
this._current.dispose();
87+
}
88+
}
89+
}
90+
91+
protected validate(_value: number | string): Promise<string | undefined> {
92+
return Promise.resolve(undefined);
93+
}
94+
95+
protected shouldResume() {
96+
return Promise.reject(false);
97+
}
98+
99+
protected async showQuickPick<T extends QuickPickItem>(items: T[], options?: QuickPickOptions) {
100+
const disposables: Disposable[] = [];
101+
102+
try {
103+
return await new Promise<T>((resove, reject) => {
104+
const input = window.createQuickPick<T>();
105+
106+
if (options) {
107+
input.title = options.title ?? '';
108+
input.canSelectMany = options.canPickMany ?? false;
109+
input.placeholder = options.placeHolder ?? '';
110+
input.matchOnDescription = options.matchOnDescription ?? false;
111+
input.matchOnDetail = options.matchOnDetail ?? false;
112+
}
113+
114+
input.items = items;
115+
116+
disposables.push(
117+
input.onDidHide(() => {
118+
() => {
119+
reject(InputFlowAction.cancel);
120+
};
121+
}),
122+
123+
input.onDidChangeSelection(items => resove(items[0])),
124+
);
125+
126+
if (this._current) {
127+
this._current.dispose();
128+
}
129+
this._current = input;
130+
this._current.show();
131+
});
132+
} finally {
133+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
134+
disposables.forEach(d => d.dispose());
135+
}
136+
}
137+
138+
protected async showInputBox(options?: InputBoxOptions) {
139+
const disposables: Disposable[] = [];
140+
141+
try {
142+
return await new Promise((resolve, reject) => {
143+
const input = window.createInputBox();
144+
145+
if (options) {
146+
input.title = options.title ?? '';
147+
input.ignoreFocusOut = options.ignoreFocusOut ?? false;
148+
input.prompt = options.prompt ?? '';
149+
input.value = options.value ?? '';
150+
input.placeholder = options.placeHolder ?? '';
151+
input.password = options.password ?? false;
152+
}
153+
154+
let validating = this.validate('');
155+
156+
disposables.push(
157+
input.onDidAccept(async () => {
158+
const value = input.value;
159+
input.enabled = false;
160+
input.busy = true;
161+
162+
if (!(await this.validate(value))) {
163+
resolve(value);
164+
}
165+
input.enabled = true;
166+
input.busy = false;
167+
}),
168+
input.onDidHide(() => {
169+
reject(InputFlowAction.cancel);
170+
}),
171+
input.onDidChangeValue(async text => {
172+
const current = this.validate(text);
173+
validating = current;
174+
175+
const validationMessage = await current;
176+
if (current === validating) {
177+
input.validationMessage = validationMessage;
178+
}
179+
}),
180+
);
181+
182+
if (this._current) {
183+
this._current.dispose();
184+
}
185+
this._current = input;
186+
this._current.show();
187+
});
188+
} finally {
189+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
190+
disposables.forEach(d => d.dispose());
191+
}
192+
}
193+
194+
protected async showMultiQuickPick<T extends QuickPickItem, P extends QuickPickParameters<T>>({
195+
title,
196+
step,
197+
totalSteps,
198+
items,
199+
activeItem,
200+
placeholder,
201+
buttons,
202+
shouldResume,
203+
}: P) {
204+
const disposables: Disposable[] = [];
205+
206+
try {
207+
return await new Promise<T | (P extends { buttons: (infer I)[] } ? I : never)>((resolve, reject) => {
208+
const input = window.createQuickPick<T>();
209+
210+
input.title = title;
211+
input.step = step;
212+
input.totalSteps = totalSteps;
213+
input.placeholder = placeholder;
214+
input.items = items;
215+
216+
if (activeItem) {
217+
input.activeItems = [activeItem];
218+
}
219+
220+
input.buttons = [...(this._steps.length > 1 ? [QuickInputButtons.Back] : []), ...(buttons || [])];
221+
222+
disposables.push(
223+
input.onDidTriggerButton(item => {
224+
if (item === QuickInputButtons.Back) {
225+
reject(InputFlowAction.back);
226+
} else {
227+
resolve(<any>item);
228+
}
229+
}),
230+
input.onDidChangeSelection(items => resolve(items[0])),
231+
input.onDidHide(() => {
232+
(async () => {
233+
reject(
234+
shouldResume && (await shouldResume())
235+
? InputFlowAction.resume
236+
: InputFlowAction.cancel,
237+
);
238+
})().catch(reject);
239+
}),
240+
);
241+
242+
if ((this._current = input)) {
243+
this._current.dispose();
244+
}
245+
246+
this._current = input;
247+
this._current.show();
248+
});
249+
} finally {
250+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
251+
disposables.forEach(d => d.dispose());
252+
}
253+
}
254+
255+
protected async showMultiInputBox<P extends InputBoxParameters>({
256+
title,
257+
step,
258+
totalSteps,
259+
value,
260+
prompt,
261+
validate,
262+
buttons,
263+
shouldResume,
264+
}: P) {
265+
const disposables: Disposable[] = [];
266+
267+
try {
268+
return await new Promise<string | (P extends { buttons: (infer I)[] } ? I : never)>((resolve, reject) => {
269+
const input = window.createInputBox();
270+
271+
input.title = title;
272+
input.step = step;
273+
input.totalSteps = totalSteps;
274+
input.value = value || '';
275+
input.prompt = prompt;
276+
input.buttons = [...(this._steps.length > 1 ? [QuickInputButtons.Back] : []), ...(buttons || [])];
277+
278+
let validating = validate('');
279+
280+
disposables.push(
281+
input.onDidTriggerButton(item => {
282+
if (item === QuickInputButtons.Back) {
283+
reject(InputFlowAction.back);
284+
} else {
285+
resolve(<any>item);
286+
}
287+
}),
288+
input.onDidAccept(async () => {
289+
const value = input.value;
290+
input.enabled = false;
291+
input.busy = true;
292+
293+
if (!(await validate(value))) {
294+
resolve(value);
295+
}
296+
297+
input.enabled = true;
298+
input.busy = false;
299+
}),
300+
input.onDidChangeValue(async text => {
301+
const current = validate(text);
302+
validating = current;
303+
304+
const validationMessage = await current;
305+
306+
if (current === validating) {
307+
input.validationMessage = validationMessage;
308+
}
309+
}),
310+
input.onDidHide(() => {
311+
(async () => {
312+
reject(
313+
shouldResume && (await shouldResume())
314+
? InputFlowAction.resume
315+
: InputFlowAction.cancel,
316+
);
317+
})().catch(reject);
318+
}),
319+
);
320+
321+
if (this._current) {
322+
this._current.dispose();
323+
}
324+
325+
this._current = input;
326+
this._current.show();
327+
});
328+
} finally {
329+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
330+
disposables.forEach(d => d.dispose());
331+
}
332+
}
333+
}

0 commit comments

Comments
 (0)