Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/services/inlineCompletionsService.ts
3294 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 { WindowIntervalTimer } from '../../../base/browser/dom.js';
7
import { BugIndicatingError } from '../../../base/common/errors.js';
8
import { Emitter, Event } from '../../../base/common/event.js';
9
import { Disposable } from '../../../base/common/lifecycle.js';
10
import { localize, localize2 } from '../../../nls.js';
11
import { Action2 } from '../../../platform/actions/common/actions.js';
12
import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../platform/contextkey/common/contextkey.js';
13
import { InstantiationType, registerSingleton } from '../../../platform/instantiation/common/extensions.js';
14
import { createDecorator, ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js';
15
import { IQuickInputService, IQuickPickItem } from '../../../platform/quickinput/common/quickInput.js';
16
import { IStorageService, StorageScope, StorageTarget } from '../../../platform/storage/common/storage.js';
17
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry.js';
18
19
export const IInlineCompletionsService = createDecorator<IInlineCompletionsService>('IInlineCompletionsService');
20
21
export interface IInlineCompletionsService {
22
readonly _serviceBrand: undefined;
23
24
onDidChangeIsSnoozing: Event<boolean>;
25
26
/**
27
* Get the remaining time (in ms) for which inline completions should be snoozed,
28
* or 0 if not snoozed.
29
*/
30
readonly snoozeTimeLeft: number;
31
32
/**
33
* Snooze inline completions for the specified duration. If already snoozed, extend the snooze time.
34
*/
35
snooze(durationMs?: number): void;
36
37
/**
38
* Snooze inline completions for the specified duration. If already snoozed, overwrite the existing snooze time.
39
*/
40
setSnoozeDuration(durationMs: number): void;
41
42
/**
43
* Check if inline completions are currently snoozed.
44
*/
45
isSnoozing(): boolean;
46
47
/**
48
* Cancel the current snooze.
49
*/
50
cancelSnooze(): void;
51
52
/**
53
* Report an inline completion.
54
*/
55
reportNewCompletion(requestUuid: string): void;
56
}
57
58
const InlineCompletionsSnoozing = new RawContextKey<boolean>('inlineCompletions.snoozed', false, localize('inlineCompletions.snoozed', "Whether inline completions are currently snoozed"));
59
60
export class InlineCompletionsService extends Disposable implements IInlineCompletionsService {
61
declare readonly _serviceBrand: undefined;
62
63
private _onDidChangeIsSnoozing = this._register(new Emitter<boolean>());
64
readonly onDidChangeIsSnoozing: Event<boolean> = this._onDidChangeIsSnoozing.event;
65
66
private static readonly SNOOZE_DURATION = 300_000; // 5 minutes
67
68
private _snoozeTimeEnd: undefined | number = undefined;
69
get snoozeTimeLeft(): number {
70
if (this._snoozeTimeEnd === undefined) {
71
return 0;
72
}
73
return Math.max(0, this._snoozeTimeEnd - Date.now());
74
}
75
76
private _timer: WindowIntervalTimer;
77
78
constructor(
79
@IContextKeyService private _contextKeyService: IContextKeyService,
80
@ITelemetryService private _telemetryService: ITelemetryService,
81
) {
82
super();
83
84
this._timer = this._register(new WindowIntervalTimer());
85
86
const inlineCompletionsSnoozing = InlineCompletionsSnoozing.bindTo(this._contextKeyService);
87
this._register(this.onDidChangeIsSnoozing(() => inlineCompletionsSnoozing.set(this.isSnoozing())));
88
}
89
90
snooze(durationMs: number = InlineCompletionsService.SNOOZE_DURATION): void {
91
this.setSnoozeDuration(durationMs + this.snoozeTimeLeft);
92
}
93
94
setSnoozeDuration(durationMs: number): void {
95
if (durationMs < 0) {
96
throw new BugIndicatingError(`Invalid snooze duration: ${durationMs}. Duration must be non-negative.`);
97
}
98
if (durationMs === 0) {
99
this.cancelSnooze();
100
return;
101
}
102
103
const wasSnoozing = this.isSnoozing();
104
const timeLeft = this.snoozeTimeLeft;
105
106
this._snoozeTimeEnd = Date.now() + durationMs;
107
108
if (!wasSnoozing) {
109
this._onDidChangeIsSnoozing.fire(true);
110
}
111
112
this._timer.cancelAndSet(
113
() => {
114
if (!this.isSnoozing()) {
115
this._onDidChangeIsSnoozing.fire(false);
116
} else {
117
throw new BugIndicatingError('Snooze timer did not fire as expected');
118
}
119
},
120
this.snoozeTimeLeft + 1,
121
);
122
123
this._reportSnooze(durationMs - timeLeft, durationMs);
124
}
125
126
isSnoozing(): boolean {
127
return this.snoozeTimeLeft > 0;
128
}
129
130
cancelSnooze(): void {
131
if (this.isSnoozing()) {
132
this._reportSnooze(-this.snoozeTimeLeft, 0);
133
this._snoozeTimeEnd = undefined;
134
this._timer.cancel();
135
this._onDidChangeIsSnoozing.fire(false);
136
}
137
}
138
139
private _lastCompletionId: string | undefined;
140
private _recentCompletionIds: string[] = [];
141
reportNewCompletion(requestUuid: string): void {
142
this._lastCompletionId = requestUuid;
143
144
this._recentCompletionIds.unshift(requestUuid);
145
if (this._recentCompletionIds.length > 5) {
146
this._recentCompletionIds.pop();
147
}
148
}
149
150
private _reportSnooze(deltaMs: number, totalMs: number): void {
151
const deltaSeconds = Math.round(deltaMs / 1000);
152
const totalSeconds = Math.round(totalMs / 1000);
153
type WorkspaceStatsClassification = {
154
owner: 'benibenj';
155
comment: 'Snooze duration for inline completions';
156
deltaSeconds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The duration by which the snooze has changed, in seconds.' };
157
totalSeconds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total duration for which inline completions are snoozed, in seconds.' };
158
lastCompletionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the last completion.' };
159
recentCompletionIds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The IDs of the recent completions.' };
160
};
161
type WorkspaceStatsEvent = {
162
deltaSeconds: number;
163
totalSeconds: number;
164
lastCompletionId: string | undefined;
165
recentCompletionIds: string[];
166
};
167
this._telemetryService.publicLog2<WorkspaceStatsEvent, WorkspaceStatsClassification>('inlineCompletions.snooze', {
168
deltaSeconds,
169
totalSeconds,
170
lastCompletionId: this._lastCompletionId,
171
recentCompletionIds: this._recentCompletionIds,
172
});
173
}
174
}
175
176
registerSingleton(IInlineCompletionsService, InlineCompletionsService, InstantiationType.Delayed);
177
178
const snoozeInlineSuggestId = 'editor.action.inlineSuggest.snooze';
179
const cancelSnoozeInlineSuggestId = 'editor.action.inlineSuggest.cancelSnooze';
180
const LAST_SNOOZE_DURATION_KEY = 'inlineCompletions.lastSnoozeDuration';
181
182
export class SnoozeInlineCompletion extends Action2 {
183
public static ID = snoozeInlineSuggestId;
184
constructor() {
185
super({
186
id: SnoozeInlineCompletion.ID,
187
title: localize2('action.inlineSuggest.snooze', "Snooze Inline Suggestions"),
188
precondition: ContextKeyExpr.true(),
189
f1: true,
190
});
191
}
192
193
public async run(accessor: ServicesAccessor, ...args: unknown[]): Promise<void> {
194
const quickInputService = accessor.get(IQuickInputService);
195
const inlineCompletionsService = accessor.get(IInlineCompletionsService);
196
const storageService = accessor.get(IStorageService);
197
198
let durationMinutes: number | undefined;
199
if (args.length > 0 && typeof args[0] === 'number') {
200
durationMinutes = args[0];
201
}
202
203
if (!durationMinutes) {
204
durationMinutes = await this.getDurationFromUser(quickInputService, storageService);
205
}
206
207
if (durationMinutes) {
208
inlineCompletionsService.setSnoozeDuration(durationMinutes);
209
}
210
}
211
212
private async getDurationFromUser(quickInputService: IQuickInputService, storageService: IStorageService): Promise<number | undefined> {
213
const lastSelectedDuration = storageService.getNumber(LAST_SNOOZE_DURATION_KEY, StorageScope.PROFILE, 300_000);
214
215
const items: (IQuickPickItem & { value: number })[] = [
216
{ label: '1 minute', id: '1', value: 60_000 },
217
{ label: '5 minutes', id: '5', value: 300_000 },
218
{ label: '10 minutes', id: '10', value: 600_000 },
219
{ label: '15 minutes', id: '15', value: 900_000 },
220
{ label: '30 minutes', id: '30', value: 1_800_000 },
221
{ label: '60 minutes', id: '60', value: 3_600_000 }
222
];
223
224
const picked = await quickInputService.pick(items, {
225
placeHolder: localize('snooze.placeholder', "Select snooze duration for Code completions and NES"),
226
activeItem: items.find(item => item.value === lastSelectedDuration),
227
});
228
229
if (picked) {
230
storageService.store(LAST_SNOOZE_DURATION_KEY, picked.value, StorageScope.PROFILE, StorageTarget.USER);
231
return picked.value;
232
}
233
234
return undefined;
235
}
236
}
237
238
export class CancelSnoozeInlineCompletion extends Action2 {
239
public static ID = cancelSnoozeInlineSuggestId;
240
constructor() {
241
super({
242
id: CancelSnoozeInlineCompletion.ID,
243
title: localize2('action.inlineSuggest.cancelSnooze', "Cancel Snooze Inline Suggestions"),
244
precondition: InlineCompletionsSnoozing,
245
f1: true,
246
});
247
}
248
249
public async run(accessor: ServicesAccessor): Promise<void> {
250
accessor.get(IInlineCompletionsService).cancelSnooze();
251
}
252
}
253
254