Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/inlineEdits/vscode-node/inlineEditProviderFeature.ts
13399 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 { commands, languages, window } from 'vscode';
7
import { IAuthenticationService } from '../../../platform/authentication/common/authentication';
8
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
9
import { IEnvService } from '../../../platform/env/common/envService';
10
import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext';
11
import { InlineEditRequestLogContext } from '../../../platform/inlineEdits/common/inlineEditLogContext';
12
import { ObservableGit } from '../../../platform/inlineEdits/common/observableGit';
13
import { NesHistoryContextProvider } from '../../../platform/inlineEdits/common/workspaceEditTracker/nesHistoryContextProvider';
14
import { ILogService } from '../../../platform/log/common/logService';
15
import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';
16
import { isNotebookCell } from '../../../util/common/notebooks';
17
import { Disposable, IDisposable } from '../../../util/vs/base/common/lifecycle';
18
import { autorun, derived, derivedDisposable, observableFromEvent } from '../../../util/vs/base/common/observable';
19
import { join } from '../../../util/vs/base/common/path';
20
import { URI } from '../../../util/vs/base/common/uri';
21
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
22
import { IExtensionContribution } from '../../common/contributions';
23
import { unificationStateObservable } from '../../completions/vscode-node/completionsUnificationContribution';
24
import { TelemetrySender } from '../node/nextEditProviderTelemetry';
25
import { ExpectedEditCaptureController } from './components/expectedEditCaptureController';
26
import { InlineEditDebugComponent, reportFeedbackCommandId } from './components/inlineEditDebugComponent';
27
import { LogContextRecorder } from './components/logContextRecorder';
28
import { DiagnosticsNextEditProvider } from './features/diagnosticsInlineEditProvider';
29
import { InlineCompletionProviderImpl } from './inlineCompletionProvider';
30
import { InlineEditModel } from './inlineEditModel';
31
import { InlineEditLogger } from './parts/inlineEditLogger';
32
import { VSCodeWorkspace } from './parts/vscodeWorkspace';
33
import { makeSettable } from './utils/observablesUtils';
34
35
const useEnhancedNotebookNESContextKey = 'github.copilot.chat.enableEnhancedNotebookNES';
36
37
export class InlineEditProviderFeatureContribution extends Disposable implements IExtensionContribution {
38
39
constructor(
40
@ILogService private readonly _logService: ILogService,
41
@IInstantiationService private readonly _instantiationService: IInstantiationService,
42
@IExperimentationService _experimentationService: IExperimentationService,
43
) {
44
super();
45
46
const logger = this._logService.createSubLogger(['NES', 'Feature']);
47
48
const inlineEditProviderFeature = this._instantiationService.createInstance(InlineEditProviderFeature);
49
this._register(inlineEditProviderFeature.registerProvider());
50
this._register(inlineEditProviderFeature.setContext());
51
52
logger.trace('Return: void');
53
}
54
}
55
56
export class InlineEditProviderFeature {
57
58
private readonly _inlineEditsProviderId = makeSettable(this._configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsProviderId, this._expService));
59
60
private readonly _hideInternalInterface = this._configurationService.getConfigObservable(ConfigKey.TeamInternal.InlineEditsHideInternalInterface);
61
private readonly _enableDiagnosticsProvider = this._configurationService.getExperimentBasedConfigObservable(ConfigKey.InlineEditsEnableDiagnosticsProvider, this._expService);
62
private readonly _yieldToCopilot = this._configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsYieldToCopilot, this._expService);
63
private readonly _excludedProviders = this._configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsExcludedProviders, this._expService).map(v => v ? v.split(',').map(v => v.trim()).filter(v => v !== '') : []);
64
private readonly _copilotToken = observableFromEvent(this, this._authenticationService.onDidAuthenticationChange, () => this._authenticationService.copilotToken);
65
66
public readonly inlineEditsEnabled = derived(this, (reader) => {
67
const copilotToken = this._copilotToken.read(reader);
68
if (copilotToken === undefined) {
69
return false;
70
}
71
if (copilotToken.isCompletionsQuotaExceeded) {
72
return false;
73
}
74
return true;
75
});
76
77
private readonly _internalActionsEnabled = derived(this, (reader) => {
78
return !!this._copilotToken.read(reader)?.isInternal && !this._hideInternalInterface.read(reader);
79
});
80
81
public readonly isInlineEditsLogFileEnabledObservable = this._configurationService.getConfigObservable(ConfigKey.TeamInternal.InlineEditsLogContextRecorderEnabled);
82
83
private readonly _workspace = derivedDisposable(this, _reader => {
84
return this._instantiationService.createInstance(VSCodeWorkspace);
85
});
86
87
constructor(
88
@IVSCodeExtensionContext private readonly _vscodeExtensionContext: IVSCodeExtensionContext,
89
@IConfigurationService private readonly _configurationService: IConfigurationService,
90
@IAuthenticationService private readonly _authenticationService: IAuthenticationService,
91
@IExperimentationService private readonly _expService: IExperimentationService,
92
@IEnvService private readonly _envService: IEnvService,
93
@IInstantiationService private readonly _instantiationService: IInstantiationService,
94
) {
95
}
96
97
public setContext(): IDisposable {
98
// TODO: this should be reactive to config changes
99
const enableEnhancedNotebookNES = this._configurationService.getExperimentBasedConfig(ConfigKey.Advanced.UseAlternativeNESNotebookFormat, this._expService) || this._configurationService.getExperimentBasedConfig(ConfigKey.UseAlternativeNESNotebookFormat, this._expService);
100
commands.executeCommand('setContext', useEnhancedNotebookNESContextKey, enableEnhancedNotebookNES);
101
102
// Set context key for inline edits enabled state (used for keybindings)
103
return autorun((reader) => {
104
const enabled = this.inlineEditsEnabled.read(reader);
105
void commands.executeCommand('setContext', 'github.copilot.inlineEditsEnabled', enabled);
106
});
107
}
108
109
public registerProvider(): IDisposable {
110
const unificationState = unificationStateObservable(this);
111
112
return autorun(reader => {
113
if (!this.inlineEditsEnabled.read(reader)) { return; }
114
115
const logger = reader.store.add(this._instantiationService.createInstance(InlineEditLogger));
116
117
const statelessProviderId = this._inlineEditsProviderId.read(reader);
118
119
const workspace = this._workspace.read(reader);
120
const git = reader.store.add(this._instantiationService.createInstance(ObservableGit));
121
const historyContextProvider = new NesHistoryContextProvider(workspace, git);
122
123
let diagnosticsProvider: DiagnosticsNextEditProvider | undefined = undefined;
124
if (this._enableDiagnosticsProvider.read(reader)) {
125
diagnosticsProvider = reader.store.add(this._instantiationService.createInstance(DiagnosticsNextEditProvider, workspace, git));
126
}
127
128
const model = reader.store.add(this._instantiationService.createInstance(InlineEditModel, statelessProviderId, workspace, historyContextProvider, diagnosticsProvider));
129
130
const recordingDirPath = join(this._vscodeExtensionContext.globalStorageUri.fsPath, 'logContextRecordings');
131
132
const isInlineEditLogFileEnabled = this.isInlineEditsLogFileEnabledObservable.read(reader);
133
134
let logContextRecorder: LogContextRecorder | undefined;
135
if (isInlineEditLogFileEnabled) {
136
logContextRecorder = reader.store.add(this._instantiationService.createInstance(LogContextRecorder, recordingDirPath, logger));
137
} else {
138
void LogContextRecorder.cleanupOldRecordings(recordingDirPath);
139
}
140
141
const inlineEditDebugComponent = reader.store.add(new InlineEditDebugComponent(this._internalActionsEnabled, this.inlineEditsEnabled, model.debugRecorder, this._inlineEditsProviderId));
142
143
const telemetrySender = reader.store.add(this._instantiationService.createInstance(TelemetrySender, workspace));
144
145
// Create the expected edit capture controller
146
const expectedEditCaptureController = reader.store.add(this._instantiationService.createInstance(
147
ExpectedEditCaptureController,
148
model.debugRecorder
149
));
150
151
const provider = this._instantiationService.createInstance(InlineCompletionProviderImpl, model, logger, logContextRecorder, inlineEditDebugComponent, telemetrySender, expectedEditCaptureController);
152
153
const unificationStateValue = unificationState.read(reader);
154
let excludes = this._excludedProviders.read(reader);
155
if (unificationStateValue?.modelUnification) {
156
excludes = excludes.slice(0);
157
if (!excludes.includes('completions')) {
158
excludes.push('completions');
159
}
160
if (!excludes.includes('github.copilot')) {
161
excludes.push('github.copilot');
162
}
163
}
164
165
reader.store.add(languages.registerInlineCompletionItemProvider('*', provider, {
166
displayName: provider.displayName,
167
yieldTo: this._yieldToCopilot.read(reader) ? ['github.copilot'] : undefined,
168
debounceDelayMs: 0, // set 0 debounce to ensure consistent delays/timings
169
groupId: 'nes',
170
excludes,
171
}));
172
173
reader.store.add(commands.registerCommand(learnMoreCommandId, () => {
174
this._envService.openExternal(URI.parse(learnMoreLink));
175
}));
176
177
reader.store.add(commands.registerCommand(clearCacheCommandId, () => {
178
model.nextEditProvider.clearCache();
179
}));
180
181
reader.store.add(commands.registerCommand(reportNotebookNESIssueCommandId, () => {
182
const activeNotebook = window.activeNotebookEditor;
183
const document = window.activeTextEditor?.document;
184
if (!activeNotebook || !document || !isNotebookCell(document.uri)) {
185
return;
186
}
187
const doc = model.workspace.getDocumentByTextDocument(document);
188
const selection = activeNotebook.selection;
189
if (!selection || !doc) {
190
return;
191
}
192
193
const logContext = new InlineEditRequestLogContext(doc.id.uri, document.version, undefined);
194
logContext.recordingBookmark = model.debugRecorder.createBookmark();
195
void commands.executeCommand(reportFeedbackCommandId, { logContext });
196
}));
197
198
// Register expected edit capture commands
199
reader.store.add(commands.registerCommand(captureExpectedStartCommandId, () => {
200
void expectedEditCaptureController.startCapture('manual');
201
}));
202
203
reader.store.add(commands.registerCommand(captureExpectedConfirmCommandId, () => {
204
void expectedEditCaptureController.confirmCapture();
205
}));
206
207
reader.store.add(commands.registerCommand(captureExpectedAbortCommandId, () => {
208
void expectedEditCaptureController.abortCapture();
209
}));
210
211
reader.store.add(commands.registerCommand(captureExpectedSubmitCommandId, () => {
212
void expectedEditCaptureController.submitCaptures();
213
}));
214
});
215
}
216
}
217
218
export const learnMoreCommandId = 'github.copilot.debug.inlineEdit.learnMore';
219
220
export const learnMoreLink = 'https://aka.ms/vscode-nes';
221
222
export const clearCacheCommandId = 'github.copilot.debug.inlineEdit.clearCache';
223
export const reportNotebookNESIssueCommandId = 'github.copilot.debug.inlineEdit.reportNotebookNESIssue';
224
export const captureExpectedStartCommandId = 'github.copilot.nes.captureExpected.start';
225
export const captureExpectedConfirmCommandId = 'github.copilot.nes.captureExpected.confirm';
226
export const captureExpectedAbortCommandId = 'github.copilot.nes.captureExpected.abort';
227
export const captureExpectedSubmitCommandId = 'github.copilot.nes.captureExpected.submit';
228
229