Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/test/automation/src/quickaccess.ts
5241 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Editors } from './editors';
7
import { Code } from './code';
8
import { QuickInput } from './quickinput';
9
import { basename, isAbsolute } from 'path';
10
11
enum QuickAccessKind {
12
Files = 1,
13
Commands,
14
Symbols
15
}
16
17
export class QuickAccess {
18
19
constructor(private code: Code, private editors: Editors, private quickInput: QuickInput) { }
20
21
async openFileQuickAccessAndWait(searchValue: string, expectedFirstElementNameOrExpectedResultCount: string | number): Promise<void> {
22
23
// make sure the file quick access is not "polluted"
24
// with entries from the editor history when opening
25
await this.runCommand('workbench.action.clearEditorHistoryWithoutConfirm');
26
27
const PollingStrategy = {
28
Stop: true,
29
Continue: false
30
};
31
32
let retries = 0;
33
let success = false;
34
35
while (++retries < 10) {
36
let retry = false;
37
38
try {
39
await this.openQuickAccessWithRetry(QuickAccessKind.Files, searchValue);
40
await this.quickInput.waitForQuickInputElements(elementNames => {
41
this.code.logger.log('QuickAccess: resulting elements: ', elementNames);
42
43
// Quick access seems to be still running -> retry
44
if (elementNames.length === 0) {
45
this.code.logger.log('QuickAccess: file search returned 0 elements, will continue polling...');
46
47
return PollingStrategy.Continue;
48
}
49
50
// Quick access does not seem healthy/ready -> retry
51
const firstElementName = elementNames[0];
52
if (firstElementName === 'No matching results') {
53
this.code.logger.log(`QuickAccess: file search returned "No matching results", will retry...`);
54
55
retry = true;
56
57
return PollingStrategy.Stop;
58
}
59
60
// Expected: number of results
61
if (typeof expectedFirstElementNameOrExpectedResultCount === 'number') {
62
if (elementNames.length === expectedFirstElementNameOrExpectedResultCount) {
63
success = true;
64
65
return PollingStrategy.Stop;
66
}
67
68
this.code.logger.log(`QuickAccess: file search returned ${elementNames.length} results but was expecting ${expectedFirstElementNameOrExpectedResultCount}, will retry...`);
69
70
retry = true;
71
72
return PollingStrategy.Stop;
73
}
74
75
// Expected: string
76
else {
77
if (firstElementName === expectedFirstElementNameOrExpectedResultCount) {
78
success = true;
79
80
return PollingStrategy.Stop;
81
}
82
83
this.code.logger.log(`QuickAccess: file search returned ${firstElementName} as first result but was expecting ${expectedFirstElementNameOrExpectedResultCount}, will retry...`);
84
85
retry = true;
86
87
return PollingStrategy.Stop;
88
}
89
});
90
} catch (error) {
91
this.code.logger.log(`QuickAccess: file search waitForQuickInputElements threw an error ${error}, will retry...`);
92
93
retry = true;
94
}
95
96
if (!retry) {
97
break;
98
}
99
100
await this.quickInput.closeQuickInput();
101
}
102
103
if (!success) {
104
if (typeof expectedFirstElementNameOrExpectedResultCount === 'string') {
105
throw new Error(`Quick open file search was unable to find '${expectedFirstElementNameOrExpectedResultCount}' after 10 attempts, giving up.`);
106
} else {
107
throw new Error(`Quick open file search was unable to find ${expectedFirstElementNameOrExpectedResultCount} result items after 10 attempts, giving up.`);
108
}
109
}
110
}
111
112
async openFile(path: string): Promise<void> {
113
if (!isAbsolute(path)) {
114
// we require absolute paths to get a single
115
// result back that is unique and avoid hitting
116
// the search process to reduce chances of
117
// search needing longer.
118
throw new Error('QuickAccess.openFile requires an absolute path');
119
}
120
121
const fileName = basename(path);
122
123
// quick access shows files with the basename of the path
124
await this.openFileQuickAccessAndWait(path, basename(path));
125
126
// open first element
127
await this.quickInput.selectQuickInputElement(0);
128
129
// wait for editor being focused
130
await this.editors.waitForEditorFocus(fileName);
131
}
132
133
private async openQuickAccessWithRetry(kind: QuickAccessKind, value?: string): Promise<void> {
134
let retries = 0;
135
136
// Other parts of code might steal focus away from quickinput :(
137
while (retries < 5) {
138
139
try {
140
// Await for quick input widget opened
141
const accept = () => this.quickInput.waitForQuickInputOpened(10);
142
// Open via keybinding
143
switch (kind) {
144
case QuickAccessKind.Files:
145
await this.code.dispatchKeybinding(process.platform === 'darwin' ? 'cmd+p' : 'ctrl+p', accept);
146
break;
147
case QuickAccessKind.Symbols:
148
await this.code.dispatchKeybinding(process.platform === 'darwin' ? 'cmd+shift+o' : 'ctrl+shift+o', accept);
149
break;
150
case QuickAccessKind.Commands:
151
await this.code.dispatchKeybinding(process.platform === 'darwin' ? 'cmd+shift+p' : 'ctrl+shift+p', async () => {
152
153
await this.code.wait(100);
154
await this.quickInput.waitForQuickInputOpened(10);
155
});
156
break;
157
}
158
break;
159
} catch (err) {
160
if (++retries > 5) {
161
throw new Error(`QuickAccess.openQuickAccessWithRetry(kind: ${kind}) failed: ${err}`);
162
}
163
164
// Retry
165
await this.code.dispatchKeybinding('escape', async () => { });
166
}
167
}
168
169
// Type value if any
170
if (value) {
171
await this.quickInput.type(value);
172
}
173
}
174
175
async runCommand(commandId: string, options?: { keepOpen?: boolean; exactLabelMatch?: boolean }): Promise<void> {
176
const keepOpen = options?.keepOpen;
177
const exactLabelMatch = options?.exactLabelMatch;
178
179
const openCommandPalletteAndTypeCommand = async (): Promise<boolean> => {
180
// open commands picker
181
await this.openQuickAccessWithRetry(QuickAccessKind.Commands, `>${commandId}`);
182
183
// wait for best choice to be focused
184
await this.quickInput.waitForQuickInputElementFocused();
185
186
// Retry for as long as the command not found
187
const text = await this.quickInput.waitForQuickInputElementText();
188
189
if (text === 'No matching commands' || (exactLabelMatch && text !== commandId)) {
190
return false;
191
}
192
193
return true;
194
};
195
196
let hasCommandFound = await openCommandPalletteAndTypeCommand();
197
198
if (!hasCommandFound) {
199
200
this.code.logger.log(`QuickAccess: No matching commands, will retry...`);
201
await this.quickInput.closeQuickInput();
202
203
let retries = 0;
204
while (++retries < 5) {
205
hasCommandFound = await openCommandPalletteAndTypeCommand();
206
if (hasCommandFound) {
207
break;
208
} else {
209
this.code.logger.log(`QuickAccess: No matching commands, will retry...`);
210
await this.quickInput.closeQuickInput();
211
await this.code.wait(1000);
212
}
213
}
214
215
if (!hasCommandFound) {
216
throw new Error(`QuickAccess.runCommand(commandId: ${commandId}) failed to find command.`);
217
}
218
}
219
220
// wait and click on best choice
221
await this.quickInput.selectQuickInputElement(0, keepOpen);
222
}
223
224
async openQuickOutline(): Promise<void> {
225
let retries = 0;
226
227
while (++retries < 10) {
228
229
// open quick outline via keybinding
230
await this.openQuickAccessWithRetry(QuickAccessKind.Symbols);
231
232
const text = await this.quickInput.waitForQuickInputElementText();
233
234
// Retry for as long as no symbols are found
235
if (text === 'No symbol information for the file') {
236
this.code.logger.log(`QuickAccess: openQuickOutline indicated 'No symbol information for the file', will retry...`);
237
238
// close and retry
239
await this.quickInput.closeQuickInput();
240
241
continue;
242
}
243
}
244
}
245
246
async getVisibleCommandNames(searchValue: string): Promise<string[]> {
247
248
// open commands picker
249
await this.openQuickAccessWithRetry(QuickAccessKind.Commands, `>${searchValue}`);
250
251
// wait for quick input elements to be available
252
let commandNames: string[] = [];
253
await this.quickInput.waitForQuickInputElements(elementNames => {
254
commandNames = elementNames;
255
return true;
256
});
257
258
// close the quick input
259
await this.quickInput.closeQuickInput();
260
261
return commandNames;
262
}
263
}
264
265