Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/test/automation/src/quickaccess.ts
3520 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.waitForActiveTab(fileName);
131
await this.editors.selectTab(fileName);
132
}
133
134
private async openQuickAccessWithRetry(kind: QuickAccessKind, value?: string): Promise<void> {
135
let retries = 0;
136
137
// Other parts of code might steal focus away from quickinput :(
138
while (retries < 5) {
139
140
try {
141
// Await for quick input widget opened
142
const accept = () => this.quickInput.waitForQuickInputOpened(10);
143
// Open via keybinding
144
switch (kind) {
145
case QuickAccessKind.Files:
146
await this.code.dispatchKeybinding(process.platform === 'darwin' ? 'cmd+p' : 'ctrl+p', accept);
147
break;
148
case QuickAccessKind.Symbols:
149
await this.code.dispatchKeybinding(process.platform === 'darwin' ? 'cmd+shift+o' : 'ctrl+shift+o', accept);
150
break;
151
case QuickAccessKind.Commands:
152
await this.code.dispatchKeybinding(process.platform === 'darwin' ? 'cmd+shift+p' : 'ctrl+shift+p', async () => {
153
154
await this.code.wait(100);
155
await this.quickInput.waitForQuickInputOpened(10);
156
});
157
break;
158
}
159
break;
160
} catch (err) {
161
if (++retries > 5) {
162
throw new Error(`QuickAccess.openQuickAccessWithRetry(kind: ${kind}) failed: ${err}`);
163
}
164
165
// Retry
166
await this.code.dispatchKeybinding('escape', async () => { });
167
}
168
}
169
170
// Type value if any
171
if (value) {
172
await this.quickInput.type(value);
173
}
174
}
175
176
async runCommand(commandId: string, options?: { keepOpen?: boolean; exactLabelMatch?: boolean }): Promise<void> {
177
const keepOpen = options?.keepOpen;
178
const exactLabelMatch = options?.exactLabelMatch;
179
180
const openCommandPalletteAndTypeCommand = async (): Promise<boolean> => {
181
// open commands picker
182
await this.openQuickAccessWithRetry(QuickAccessKind.Commands, `>${commandId}`);
183
184
// wait for best choice to be focused
185
await this.quickInput.waitForQuickInputElementFocused();
186
187
// Retry for as long as the command not found
188
const text = await this.quickInput.waitForQuickInputElementText();
189
190
if (text === 'No matching commands' || (exactLabelMatch && text !== commandId)) {
191
return false;
192
}
193
194
return true;
195
};
196
197
let hasCommandFound = await openCommandPalletteAndTypeCommand();
198
199
if (!hasCommandFound) {
200
201
this.code.logger.log(`QuickAccess: No matching commands, will retry...`);
202
await this.quickInput.closeQuickInput();
203
204
let retries = 0;
205
while (++retries < 5) {
206
hasCommandFound = await openCommandPalletteAndTypeCommand();
207
if (hasCommandFound) {
208
break;
209
} else {
210
this.code.logger.log(`QuickAccess: No matching commands, will retry...`);
211
await this.quickInput.closeQuickInput();
212
await this.code.wait(1000);
213
}
214
}
215
216
if (!hasCommandFound) {
217
throw new Error(`QuickAccess.runCommand(commandId: ${commandId}) failed to find command.`);
218
}
219
}
220
221
// wait and click on best choice
222
await this.quickInput.selectQuickInputElement(0, keepOpen);
223
}
224
225
async openQuickOutline(): Promise<void> {
226
let retries = 0;
227
228
while (++retries < 10) {
229
230
// open quick outline via keybinding
231
await this.openQuickAccessWithRetry(QuickAccessKind.Symbols);
232
233
const text = await this.quickInput.waitForQuickInputElementText();
234
235
// Retry for as long as no symbols are found
236
if (text === 'No symbol information for the file') {
237
this.code.logger.log(`QuickAccess: openQuickOutline indicated 'No symbol information for the file', will retry...`);
238
239
// close and retry
240
await this.quickInput.closeQuickInput();
241
242
continue;
243
}
244
}
245
}
246
}
247
248