Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/simulation/fixtures/generate/issue-6788/terminalSuggestAddon.ts
13405 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 type { ITerminalAddon, Terminal } from '@xterm/xterm';
7
import * as dom from 'vs/base/browser/dom';
8
import { Codicon } from 'vs/base/common/codicons';
9
import { Emitter, Event } from 'vs/base/common/event';
10
import { combinedDisposable, Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
11
import { ThemeIcon } from 'vs/base/common/themables';
12
import { editorSuggestWidgetSelectedBackground } from 'vs/editor/contrib/suggest/browser/suggestWidget';
13
import { IContextKey } from 'vs/platform/contextkey/common/contextkey';
14
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
15
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
16
import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
17
import { TerminalStorageKeys } from 'vs/workbench/contrib/terminal/common/terminalStorageKeys';
18
import { SimpleCompletionItem } from 'vs/workbench/services/suggest/browser/simpleCompletionItem';
19
import { LineContext, SimpleCompletionModel } from 'vs/workbench/services/suggest/browser/simpleCompletionModel';
20
import { ISimpleSelectedSuggestion, SimpleSuggestWidget } from 'vs/workbench/services/suggest/browser/simpleSuggestWidget';
21
22
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
23
import { TerminalCapability, type ITerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/capabilities';
24
import type { IPromptInputModel, IPromptInputModelState } from 'vs/platform/terminal/common/capabilities/commandDetection/promptInputModel';
25
import { ShellIntegrationOscPs } from 'vs/platform/terminal/common/xterm/shellIntegrationAddon';
26
import { getListStyles } from 'vs/platform/theme/browser/defaultStyles';
27
import type { IXtermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private';
28
import { terminalSuggestConfigSection, type ITerminalSuggestConfiguration } from 'vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration';
29
30
export const enum VSCodeSuggestOscPt {
31
Completions = 'Completions',
32
CompletionsPwshCommands = 'CompletionsPwshCommands',
33
CompletionsBash = 'CompletionsBash',
34
CompletionsBashFirstWord = 'CompletionsBashFirstWord'
35
}
36
37
export type CompressedPwshCompletion = [
38
completionText: string,
39
resultType: number,
40
toolTip: string
41
];
42
43
export type PwshCompletion = {
44
CompletionText: string;
45
ResultType: number;
46
ToolTip: string;
47
};
48
49
50
/**
51
* A map of the pwsh result type enum's value to the corresponding icon to use in completions.
52
*
53
* | Value | Name | Description
54
* |-------|-------------------|------------
55
* | 0 | Text | An unknown result type, kept as text only
56
* | 1 | History | A history result type like the items out of get-history
57
* | 2 | Command | A command result type like the items out of get-command
58
* | 3 | ProviderItem | A provider item
59
* | 4 | ProviderContainer | A provider container
60
* | 5 | Property | A property result type like the property items out of get-member
61
* | 6 | Method | A method result type like the method items out of get-member
62
* | 7 | ParameterName | A parameter name result type like the Parameters property out of get-command items
63
* | 8 | ParameterValue | A parameter value result type
64
* | 9 | Variable | A variable result type like the items out of get-childitem variable:
65
* | 10 | Namespace | A namespace
66
* | 11 | Type | A type name
67
* | 12 | Keyword | A keyword
68
* | 13 | DynamicKeyword | A dynamic keyword
69
*
70
* @see https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.completionresulttype?view=powershellsdk-7.0.0
71
*/
72
const pwshTypeToIconMap: { [type: string]: ThemeIcon | undefined } = {
73
0: Codicon.symbolText,
74
1: Codicon.history,
75
2: Codicon.symbolMethod,
76
3: Codicon.symbolFile,
77
4: Codicon.folder,
78
5: Codicon.symbolProperty,
79
6: Codicon.symbolMethod,
80
7: Codicon.symbolVariable,
81
8: Codicon.symbolValue,
82
9: Codicon.symbolVariable,
83
10: Codicon.symbolNamespace,
84
11: Codicon.symbolInterface,
85
12: Codicon.symbolKeyword,
86
13: Codicon.symbolKeyword
87
};
88
89
export interface ISuggestController {
90
selectPreviousSuggestion(): void;
91
selectPreviousPageSuggestion(): void;
92
selectNextSuggestion(): void;
93
selectNextPageSuggestion(): void;
94
acceptSelectedSuggestion(suggestion?: Pick<ISimpleSelectedSuggestion, 'item' | 'model'>): void;
95
hideSuggestWidget(): void;
96
}
97
98
export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggestController {
99
private _terminal?: Terminal;
100
101
private _promptInputModel?: IPromptInputModel;
102
private readonly _promptInputModelSubscriptions = this._register(new MutableDisposable());
103
104
private _mostRecentPromptInputState?: IPromptInputModelState;
105
private _initialPromptInputState?: IPromptInputModelState;
106
private _currentPromptInputState?: IPromptInputModelState;
107
private _model?: SimpleCompletionModel;
108
109
private _panel?: HTMLElement;
110
private _screen?: HTMLElement;
111
private _suggestWidget?: SimpleSuggestWidget;
112
private _enableWidget: boolean = true;
113
114
// TODO: Remove these in favor of prompt input state
115
private _leadingLineContent?: string;
116
private _cursorIndexDelta: number = 0;
117
118
private _lastUserDataTimestamp: number = 0;
119
private _lastAcceptedCompletionTimestamp: number = 0;
120
private _lastUserData?: string;
121
122
static requestCompletionsSequence = '\x1b[24~e'; // F12,e
123
124
private readonly _onBell = this._register(new Emitter<void>());
125
readonly onBell = this._onBell.event;
126
private readonly _onAcceptedCompletion = this._register(new Emitter<string>());
127
readonly onAcceptedCompletion = this._onAcceptedCompletion.event;
128
129
constructor(
130
private readonly _cachedPwshCommands: Set<SimpleCompletionItem>,
131
private readonly _capabilities: ITerminalCapabilityStore,
132
private readonly _terminalSuggestWidgetVisibleContextKey: IContextKey<boolean>,
133
@IConfigurationService private readonly _configurationService: IConfigurationService,
134
@IInstantiationService private readonly _instantiationService: IInstantiationService,
135
) {
136
super();
137
138
this._register(Event.runAndSubscribe(Event.any(
139
this._capabilities.onDidAddCapabilityType,
140
this._capabilities.onDidRemoveCapabilityType
141
), () => {
142
const commandDetection = this._capabilities.get(TerminalCapability.CommandDetection);
143
if (commandDetection) {
144
if (this._promptInputModel !== commandDetection.promptInputModel) {
145
this._promptInputModel = commandDetection.promptInputModel;
146
this._promptInputModelSubscriptions.value = combinedDisposable(
147
this._promptInputModel.onDidChangeInput(e => this._sync(e)),
148
this._promptInputModel.onDidFinishInput(() => this.hideSuggestWidget()),
149
);
150
}
151
} else {
152
this._promptInputModel = undefined;
153
}
154
}));
155
}
156
157
activate(xterm: Terminal): void {
158
this._terminal = xterm;
159
this._register(xterm.parser.registerOscHandler(ShellIntegrationOscPs.VSCode, data => {
160
return this._handleVSCodeSequence(data);
161
}));
162
this._register(xterm.onData(e => {
163
if (!e.startsWith('\x1b[')) {
164
this._lastUserData = e;
165
this._lastUserDataTimestamp = Date.now();
166
}
167
}));
168
}
169
170
setPanel(panel: HTMLElement): void {
171
this._panel = panel;
172
}
173
174
setScreen(screen: HTMLElement): void {
175
this._screen = screen;
176
}
177
178
private _requestCompletions(): void {
179
// Ensure that a key has been pressed since the last accepted completion in order to prevent
180
// completions being requested again right after accepting a completion
181
if (this._lastUserDataTimestamp > this._lastAcceptedCompletionTimestamp) {
182
this._onAcceptedCompletion.fire(SuggestAddon.requestCompletionsSequence);
183
}
184
}
185
186
private _sync(promptInputState: IPromptInputModelState): void {
187
const config = this._configurationService.getValue<ITerminalSuggestConfiguration>(terminalSuggestConfigSection);
188
189
if (!this._mostRecentPromptInputState || promptInputState.cursorIndex > this._mostRecentPromptInputState.cursorIndex) {
190
// If input has been added
191
let sent = false;
192
193
// Quick suggestions
194
if (!this._terminalSuggestWidgetVisibleContextKey.get()) {
195
if (config.quickSuggestions) {
196
const completionPrefix = promptInputState.value.substring(0, promptInputState.cursorIndex);
197
if (promptInputState.cursorIndex === 1 || completionPrefix.match(/([\s\[])[^\s]$/)) {
198
// Never request completions if the last key sequence was up or down as the user was likely
199
// navigating history
200
if (this._lastUserData !== /*up*/'\x1b[A' && this._lastUserData !== /*down*/'\x1b[B') {
201
this._requestCompletions();
202
sent = true;
203
}
204
}
205
}
206
}
207
208
// Trigger characters - this happens even if the widget is showing
209
if (config.suggestOnTriggerCharacters && !sent) {
210
const lastChar = promptInputState.value.at(promptInputState.cursorIndex - 1);
211
if (lastChar?.match(/[\\\/\-]/)) {
212
this._requestCompletions();
213
sent = true;
214
}
215
}
216
}
217
218
this._mostRecentPromptInputState = promptInputState;
219
if (!this._promptInputModel || !this._terminal || !this._suggestWidget || !this._initialPromptInputState || this._leadingLineContent === undefined) {
220
return;
221
}
222
223
this._currentPromptInputState = promptInputState;
224
225
// Hide the widget if the latest character was a space
226
if (this._currentPromptInputState.cursorIndex > 1 && this._currentPromptInputState.value.at(this._currentPromptInputState.cursorIndex - 1) === ' ') {
227
this.hideSuggestWidget();
228
return;
229
}
230
231
// Hide the widget if the cursor moves to the left of the initial position as the
232
// completions are no longer valid
233
if (this._currentPromptInputState.cursorIndex < this._initialPromptInputState.cursorIndex) {
234
this.hideSuggestWidget();
235
return;
236
}
237
238
if (this._terminalSuggestWidgetVisibleContextKey.get()) {
239
this._cursorIndexDelta = this._currentPromptInputState.cursorIndex - this._initialPromptInputState.cursorIndex;
240
const lineContext = new LineContext(this._leadingLineContent + this._currentPromptInputState.value.substring(this._leadingLineContent.length, this._leadingLineContent.length + this._cursorIndexDelta), this._cursorIndexDelta);
241
this._suggestWidget.setLineContext(lineContext);
242
}
243
244
// Hide and clear model if there are no more items
245
if (!this._suggestWidget.hasCompletions()) {
246
this.hideSuggestWidget();
247
return;
248
}
249
250
const dimensions = this._getTerminalDimensions();
251
if (!dimensions.width || !dimensions.height) {
252
return;
253
}
254
// TODO: What do frozen and auto do?
255
const xtermBox = this._screen!.getBoundingClientRect();
256
257
this._suggestWidget.showSuggestions(0, false, false, {
258
left: xtermBox.left + this._terminal.buffer.active.cursorX * dimensions.width,
259
top: xtermBox.top + this._terminal.buffer.active.cursorY * dimensions.height,
260
height: dimensions.height
261
});
262
}
263
264
private _handleVSCodeSequence(data: string): boolean | Promise<boolean> {
265
if (!this._terminal) {
266
return false;
267
}
268
269
// Pass the sequence along to the capability
270
const [command, ...args] = data.split(';');
271
switch (command) {
272
case VSCodeSuggestOscPt.Completions:
273
this._handleCompletionsSequence(this._terminal, data, command, args);
274
return true;
275
case VSCodeSuggestOscPt.CompletionsBash:
276
this._handleCompletionsBashSequence(this._terminal, data, command, args);
277
return true;
278
case VSCodeSuggestOscPt.CompletionsBashFirstWord:
279
return this._handleCompletionsBashFirstWordSequence(this._terminal, data, command, args);
280
}
281
282
// Unrecognized sequence
283
return false;
284
}
285
286
private _handleCompletionsSequence(terminal: Terminal, data: string, command: string, args: string[]): void {
287
// Nothing to handle if the terminal is not attached
288
if (!terminal.element || !this._enableWidget || !this._promptInputModel) {
289
return;
290
}
291
292
let replacementIndex = 0;
293
let replacementLength = this._promptInputModel.cursorIndex;
294
295
const payload = data.slice(command.length + args[0].length + args[1].length + args[2].length + 4/*semi-colons*/);
296
const rawCompletions: PwshCompletion | PwshCompletion[] | CompressedPwshCompletion[] | CompressedPwshCompletion = args.length === 0 || payload.length === 0 ? undefined : JSON.parse(payload);
297
const completions = parseCompletionsFromShell(rawCompletions);
298
299
this._leadingLineContent = this._promptInputModel.value.substring(0, this._promptInputModel.cursorIndex);
300
301
const firstChar = this._leadingLineContent.length === 0 ? '' : this._leadingLineContent[0];
302
// This is a TabExpansion2 result
303
if (this._leadingLineContent.trim().includes(' ') || firstChar === '[') {
304
replacementIndex = parseInt(args[0]);
305
replacementLength = parseInt(args[1]);
306
this._leadingLineContent = this._promptInputModel.value.substring(0, this._promptInputModel.cursorIndex);
307
}
308
// This is a global command, add cached commands list to completions
309
else {
310
completions.push(...this._cachedPwshCommands);
311
}
312
313
this._currentPromptInputState = {
314
value: this._promptInputModel.value,
315
cursorIndex: this._promptInputModel.cursorIndex,
316
ghostTextIndex: this._promptInputModel.ghostTextIndex
317
};
318
this._cursorIndexDelta = 0;
319
const lineContext = new LineContext(this._leadingLineContent + this._currentPromptInputState.value.substring(this._leadingLineContent.length, this._leadingLineContent.length + this._cursorIndexDelta), this._cursorIndexDelta);
320
const model = new SimpleCompletionModel(completions, lineContext, replacementIndex, replacementLength);
321
this._handleCompletionModel(model);
322
}
323
324
// TODO: These aren't persisted across reloads
325
// TODO: Allow triggering anywhere in the first word based on the cached completions
326
private _cachedBashAliases: Set<SimpleCompletionItem> = new Set();
327
private _cachedBashBuiltins: Set<SimpleCompletionItem> = new Set();
328
private _cachedBashCommands: Set<SimpleCompletionItem> = new Set();
329
private _cachedBashKeywords: Set<SimpleCompletionItem> = new Set();
330
private _cachedFirstWord?: SimpleCompletionItem[];
331
private _handleCompletionsBashFirstWordSequence(terminal: Terminal, data: string, command: string, args: string[]): boolean {
332
const type = args[0];
333
const completionList: string[] = data.slice(command.length + type.length + 2/*semi-colons*/).split(';');
334
let set: Set<SimpleCompletionItem>;
335
switch (type) {
336
case 'alias': set = this._cachedBashAliases; break;
337
case 'builtin': set = this._cachedBashBuiltins; break;
338
case 'command': set = this._cachedBashCommands; break;
339
case 'keyword': set = this._cachedBashKeywords; break;
340
default: return false;
341
}
342
set.clear();
343
const distinctLabels: Set<string> = new Set();
344
for (const label of completionList) {
345
distinctLabels.add(label);
346
}
347
for (const label of distinctLabels) {
348
set.add(new SimpleCompletionItem({
349
label,
350
icon: Codicon.symbolString,
351
detail: type
352
}));
353
}
354
// Invalidate compound list cache
355
this._cachedFirstWord = undefined;
356
return true;
357
}
358
359
private _handleCompletionsBashSequence(terminal: Terminal, data: string, command: string, args: string[]): void {
360
// Nothing to handle if the terminal is not attached
361
if (!terminal.element) {
362
return;
363
}
364
365
let replacementIndex = parseInt(args[0]);
366
const replacementLength = parseInt(args[1]);
367
if (!args[2]) {
368
this._onBell.fire();
369
return;
370
}
371
372
const completionList: string[] = data.slice(command.length + args[0].length + args[1].length + args[2].length + 4/*semi-colons*/).split(';');
373
// TODO: Create a trigger suggest command which encapsulates sendSequence and uses cached if available
374
let completions: SimpleCompletionItem[];
375
// TODO: This 100 is a hack just for the prototype, this should get it based on some terminal input model
376
if (replacementIndex !== 100 && completionList.length > 0) {
377
completions = completionList.map(label => {
378
return new SimpleCompletionItem({
379
label: label,
380
icon: Codicon.symbolProperty
381
});
382
});
383
} else {
384
replacementIndex = 0;
385
if (!this._cachedFirstWord) {
386
this._cachedFirstWord = [
387
...this._cachedBashAliases,
388
...this._cachedBashBuiltins,
389
...this._cachedBashCommands,
390
...this._cachedBashKeywords
391
];
392
this._cachedFirstWord.sort((a, b) => {
393
const aCode = a.completion.label.charCodeAt(0);
394
const bCode = b.completion.label.charCodeAt(0);
395
const isANonAlpha = aCode < 65 || aCode > 90 && aCode < 97 || aCode > 122 ? 1 : 0;
396
const isBNonAlpha = bCode < 65 || bCode > 90 && bCode < 97 || bCode > 122 ? 1 : 0;
397
if (isANonAlpha !== isBNonAlpha) {
398
return isANonAlpha - isBNonAlpha;
399
}
400
return a.completion.label.localeCompare(b.completion.label);
401
});
402
}
403
completions = this._cachedFirstWord;
404
}
405
if (completions.length === 0) {
406
return;
407
}
408
409
this._leadingLineContent = completions[0].completion.label.slice(0, replacementLength);
410
const model = new SimpleCompletionModel(completions, new LineContext(this._leadingLineContent, replacementIndex), replacementIndex, replacementLength);
411
if (completions.length === 1) {
412
const insertText = completions[0].completion.label.substring(replacementLength);
413
if (insertText.length === 0) {
414
this._onBell.fire();
415
return;
416
}
417
}
418
this._handleCompletionModel(model);
419
}
420
421
private _getTerminalDimensions(): { width: number; height: number } {
422
const cssCellDims = (this._terminal as any as { _core: IXtermCore })._core._renderService.dimensions.css.cell;
423
return {
424
width: cssCellDims.width,
425
height: cssCellDims.height,
426
};
427
}
428
429
private _handleCompletionModel(model: SimpleCompletionModel): void {
430
if (model.items.length === 0 || !this._terminal?.element || !this._promptInputModel) {
431
return;
432
}
433
this._model = model;
434
const suggestWidget = this._ensureSuggestWidget(this._terminal);
435
const dimensions = this._getTerminalDimensions();
436
if (!dimensions.width || !dimensions.height) {
437
return;
438
}
439
// TODO: What do frozen and auto do?
440
const xtermBox = this._screen!.getBoundingClientRect();
441
this._initialPromptInputState = {
442
value: this._promptInputModel.value,
443
cursorIndex: this._promptInputModel.cursorIndex,
444
ghostTextIndex: this._promptInputModel.ghostTextIndex
445
};
446
suggestWidget.setCompletionModel(model);
447
suggestWidget.showSuggestions(0, false, false, {
448
left: xtermBox.left + this._terminal.buffer.active.cursorX * dimensions.width,
449
top: xtermBox.top + this._terminal.buffer.active.cursorY * dimensions.height,
450
height: dimensions.height
451
});
452
}
453
454
private _ensureSuggestWidget(terminal: Terminal): SimpleSuggestWidget {
455
this._terminalSuggestWidgetVisibleContextKey.set(true);
456
if (!this._suggestWidget) {
457
this._suggestWidget = this._register(this._instantiationService.createInstance(
458
SimpleSuggestWidget,
459
this._panel!,
460
this._instantiationService.createInstance(PersistedWidgetSize),
461
{}
462
));
463
this._suggestWidget.list.style(getListStyles({
464
listInactiveFocusBackground: editorSuggestWidgetSelectedBackground,
465
listInactiveFocusOutline: activeContrastBorder
466
}));
467
this._register(this._suggestWidget.onDidSelect(async e => this.acceptSelectedSuggestion(e)));
468
this._register(this._suggestWidget.onDidHide(() => this._terminalSuggestWidgetVisibleContextKey.set(false)));
469
this._register(this._suggestWidget.onDidShow(() => this._terminalSuggestWidgetVisibleContextKey.set(true)));
470
}
471
return this._suggestWidget;
472
}
473
474
selectPreviousSuggestion(): void {
475
this._suggestWidget?.selectPrevious();
476
}
477
478
selectPreviousPageSuggestion(): void {
479
this._suggestWidget?.selectPreviousPage();
480
}
481
482
selectNextSuggestion(): void {
483
this._suggestWidget?.selectNext();
484
}
485
486
selectNextPageSuggestion(): void {
487
this._suggestWidget?.selectNextPage();
488
}
489
490
acceptSelectedSuggestion(suggestion?: Pick<ISimpleSelectedSuggestion, 'item' | 'model'>, respectRunOnEnter?: boolean): void {
491
if (!suggestion) {
492
suggestion = this._suggestWidget?.getFocusedItem();
493
}
494
const initialPromptInputState = this._initialPromptInputState ?? this._mostRecentPromptInputState;
495
if (!suggestion || !initialPromptInputState || !this._leadingLineContent || !this._model) {
496
return;
497
}
498
this._lastAcceptedCompletionTimestamp = Date.now();
499
this._suggestWidget?.hide();
500
501
const currentPromptInputState = this._currentPromptInputState ?? initialPromptInputState;
502
503
// The replacement text is any text after the replacement index for the completions, this
504
// includes any text that was there before the completions were requested and any text added
505
// since to refine the completion.
506
const replacementText = currentPromptInputState.value.substring(this._model.replacementIndex, currentPromptInputState.cursorIndex);
507
508
// Right side of replacement text in the same word
509
let rightSideReplacementText = '';
510
if (
511
// The line didn't end with ghost text
512
(currentPromptInputState.ghostTextIndex === -1 || currentPromptInputState.ghostTextIndex > currentPromptInputState.cursorIndex) &&
513
// There is more than one charatcer
514
currentPromptInputState.value.length > currentPromptInputState.cursorIndex + 1 &&
515
// THe next character is not a space
516
currentPromptInputState.value.at(currentPromptInputState.cursorIndex) !== ' '
517
) {
518
const spaceIndex = currentPromptInputState.value.substring(currentPromptInputState.cursorIndex, currentPromptInputState.ghostTextIndex === -1 ? undefined : currentPromptInputState.ghostTextIndex).indexOf(' ');
519
rightSideReplacementText = currentPromptInputState.value.substring(currentPromptInputState.cursorIndex, spaceIndex === -1 ? undefined : currentPromptInputState.cursorIndex + spaceIndex);
520
}
521
522
const completion = suggestion.item.completion;
523
const completionText = completion.label;
524
525
let runOnEnter = false;
526
if (respectRunOnEnter) {
527
const runOnEnterConfig = this._configurationService.getValue<ITerminalSuggestConfiguration>(terminalSuggestConfigSection).runOnEnter;
528
switch (runOnEnterConfig) {
529
case 'always': {
530
runOnEnter = true;
531
break;
532
}
533
case 'exactMatch': {
534
runOnEnter = replacementText.toLowerCase() === completionText.toLowerCase();
535
break;
536
}
537
case 'exactMatchIgnoreExtension': {
538
runOnEnter = replacementText.toLowerCase() === completionText.toLowerCase();
539
if (completion.icon === Codicon.symbolFile || completion.icon === Codicon.symbolMethod) {
540
runOnEnter ||= replacementText.toLowerCase() === completionText.toLowerCase().replace(/\.[^\.]+$/, '');
541
}
542
break;
543
}
544
}
545
}
546
547
// For folders, allow the next completion request to get completions for that folder
548
if (completion.icon === Codicon.folder) {
549
this._lastAcceptedCompletionTimestamp = 0;
550
}
551
552
553
554
// Send the completion
555
this._onAcceptedCompletion.fire([
556
// Backspace (left) to remove all additional input
557
'\x7F'.repeat(replacementText.length),
558
// Delete (right) to remove any additional text in the same word
559
'\x1b[3~'.repeat(rightSideReplacementText.length),
560
// Write the completion
561
completion.label,
562
// Run on enter if needed
563
runOnEnter ? '\r' : ''
564
].join(''));
565
566
this.hideSuggestWidget();
567
}
568
569
hideSuggestWidget(): void {
570
this._initialPromptInputState = undefined;
571
this._currentPromptInputState = undefined;
572
this._suggestWidget?.hide();
573
}
574
}
575
576
class PersistedWidgetSize {
577
578
private readonly _key = TerminalStorageKeys.TerminalSuggestSize;
579
580
constructor(
581
@IStorageService private readonly _storageService: IStorageService
582
) {
583
}
584
585
restore(): dom.Dimension | undefined {
586
const raw = this._storageService.get(this._key, StorageScope.PROFILE) ?? '';
587
try {
588
const obj = JSON.parse(raw);
589
if (dom.Dimension.is(obj)) {
590
return dom.Dimension.lift(obj);
591
}
592
} catch {
593
// ignore
594
}
595
return undefined;
596
}
597
598
store(size: dom.Dimension) {
599
this._storageService.store(this._key, JSON.stringify(size), StorageScope.PROFILE, StorageTarget.MACHINE);
600
}
601
602
reset(): void {
603
this._storageService.remove(this._key, StorageScope.PROFILE);
604
}
605
}
606
607
export function parseCompletionsFromShell(rawCompletions: PwshCompletion | PwshCompletion[] | CompressedPwshCompletion[] | CompressedPwshCompletion) {
608
if (!rawCompletions) {
609
return [];
610
}
611
if (!Array.isArray(rawCompletions)) {
612
return [rawCompletions].map(e => (new SimpleCompletionItem({
613
label: e.CompletionText,
614
icon: pwshTypeToIconMap[e.ResultType],
615
detail: e.ToolTip,
616
isFile: e.ResultType === 3,
617
})));
618
}
619
if (rawCompletions.length === 0) {
620
return [];
621
}
622
if (typeof rawCompletions[0] === 'string') {
623
return [rawCompletions as CompressedPwshCompletion].map(e => (new SimpleCompletionItem({
624
label: e[0],
625
icon: pwshTypeToIconMap[e[1]],
626
detail: e[2],
627
isFile: e[1] === 3,
628
})));
629
}
630
if (Array.isArray(rawCompletions[0])) {
631
return (rawCompletions as CompressedPwshCompletion[]).map(e => (new SimpleCompletionItem({
632
label: e[0],
633
icon: pwshTypeToIconMap[e[1]],
634
detail: e[2],
635
isFile: e[1] === 3,
636
})));
637
}
638
return (rawCompletions as PwshCompletion[]).map(e => (new SimpleCompletionItem({
639
label: e.CompletionText,
640
icon: pwshTypeToIconMap[e.ResultType],
641
detail: e.ToolTip,
642
isFile: e.ResultType === 3,
643
})));
644
}
645
646