Path: blob/main/extensions/copilot/src/extension/inlineEdits/vscode-node/inlineEditProviderFeature.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { commands, languages, window } from 'vscode';6import { IAuthenticationService } from '../../../platform/authentication/common/authentication';7import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';8import { IEnvService } from '../../../platform/env/common/envService';9import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext';10import { InlineEditRequestLogContext } from '../../../platform/inlineEdits/common/inlineEditLogContext';11import { ObservableGit } from '../../../platform/inlineEdits/common/observableGit';12import { NesHistoryContextProvider } from '../../../platform/inlineEdits/common/workspaceEditTracker/nesHistoryContextProvider';13import { ILogService } from '../../../platform/log/common/logService';14import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';15import { isNotebookCell } from '../../../util/common/notebooks';16import { Disposable, IDisposable } from '../../../util/vs/base/common/lifecycle';17import { autorun, derived, derivedDisposable, observableFromEvent } from '../../../util/vs/base/common/observable';18import { join } from '../../../util/vs/base/common/path';19import { URI } from '../../../util/vs/base/common/uri';20import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';21import { IExtensionContribution } from '../../common/contributions';22import { unificationStateObservable } from '../../completions/vscode-node/completionsUnificationContribution';23import { TelemetrySender } from '../node/nextEditProviderTelemetry';24import { ExpectedEditCaptureController } from './components/expectedEditCaptureController';25import { InlineEditDebugComponent, reportFeedbackCommandId } from './components/inlineEditDebugComponent';26import { LogContextRecorder } from './components/logContextRecorder';27import { DiagnosticsNextEditProvider } from './features/diagnosticsInlineEditProvider';28import { InlineCompletionProviderImpl } from './inlineCompletionProvider';29import { InlineEditModel } from './inlineEditModel';30import { InlineEditLogger } from './parts/inlineEditLogger';31import { VSCodeWorkspace } from './parts/vscodeWorkspace';32import { makeSettable } from './utils/observablesUtils';3334const useEnhancedNotebookNESContextKey = 'github.copilot.chat.enableEnhancedNotebookNES';3536export class InlineEditProviderFeatureContribution extends Disposable implements IExtensionContribution {3738constructor(39@ILogService private readonly _logService: ILogService,40@IInstantiationService private readonly _instantiationService: IInstantiationService,41@IExperimentationService _experimentationService: IExperimentationService,42) {43super();4445const logger = this._logService.createSubLogger(['NES', 'Feature']);4647const inlineEditProviderFeature = this._instantiationService.createInstance(InlineEditProviderFeature);48this._register(inlineEditProviderFeature.registerProvider());49this._register(inlineEditProviderFeature.setContext());5051logger.trace('Return: void');52}53}5455export class InlineEditProviderFeature {5657private readonly _inlineEditsProviderId = makeSettable(this._configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsProviderId, this._expService));5859private readonly _hideInternalInterface = this._configurationService.getConfigObservable(ConfigKey.TeamInternal.InlineEditsHideInternalInterface);60private readonly _enableDiagnosticsProvider = this._configurationService.getExperimentBasedConfigObservable(ConfigKey.InlineEditsEnableDiagnosticsProvider, this._expService);61private readonly _yieldToCopilot = this._configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsYieldToCopilot, this._expService);62private readonly _excludedProviders = this._configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsExcludedProviders, this._expService).map(v => v ? v.split(',').map(v => v.trim()).filter(v => v !== '') : []);63private readonly _copilotToken = observableFromEvent(this, this._authenticationService.onDidAuthenticationChange, () => this._authenticationService.copilotToken);6465public readonly inlineEditsEnabled = derived(this, (reader) => {66const copilotToken = this._copilotToken.read(reader);67if (copilotToken === undefined) {68return false;69}70if (copilotToken.isCompletionsQuotaExceeded) {71return false;72}73return true;74});7576private readonly _internalActionsEnabled = derived(this, (reader) => {77return !!this._copilotToken.read(reader)?.isInternal && !this._hideInternalInterface.read(reader);78});7980public readonly isInlineEditsLogFileEnabledObservable = this._configurationService.getConfigObservable(ConfigKey.TeamInternal.InlineEditsLogContextRecorderEnabled);8182private readonly _workspace = derivedDisposable(this, _reader => {83return this._instantiationService.createInstance(VSCodeWorkspace);84});8586constructor(87@IVSCodeExtensionContext private readonly _vscodeExtensionContext: IVSCodeExtensionContext,88@IConfigurationService private readonly _configurationService: IConfigurationService,89@IAuthenticationService private readonly _authenticationService: IAuthenticationService,90@IExperimentationService private readonly _expService: IExperimentationService,91@IEnvService private readonly _envService: IEnvService,92@IInstantiationService private readonly _instantiationService: IInstantiationService,93) {94}9596public setContext(): IDisposable {97// TODO: this should be reactive to config changes98const enableEnhancedNotebookNES = this._configurationService.getExperimentBasedConfig(ConfigKey.Advanced.UseAlternativeNESNotebookFormat, this._expService) || this._configurationService.getExperimentBasedConfig(ConfigKey.UseAlternativeNESNotebookFormat, this._expService);99commands.executeCommand('setContext', useEnhancedNotebookNESContextKey, enableEnhancedNotebookNES);100101// Set context key for inline edits enabled state (used for keybindings)102return autorun((reader) => {103const enabled = this.inlineEditsEnabled.read(reader);104void commands.executeCommand('setContext', 'github.copilot.inlineEditsEnabled', enabled);105});106}107108public registerProvider(): IDisposable {109const unificationState = unificationStateObservable(this);110111return autorun(reader => {112if (!this.inlineEditsEnabled.read(reader)) { return; }113114const logger = reader.store.add(this._instantiationService.createInstance(InlineEditLogger));115116const statelessProviderId = this._inlineEditsProviderId.read(reader);117118const workspace = this._workspace.read(reader);119const git = reader.store.add(this._instantiationService.createInstance(ObservableGit));120const historyContextProvider = new NesHistoryContextProvider(workspace, git);121122let diagnosticsProvider: DiagnosticsNextEditProvider | undefined = undefined;123if (this._enableDiagnosticsProvider.read(reader)) {124diagnosticsProvider = reader.store.add(this._instantiationService.createInstance(DiagnosticsNextEditProvider, workspace, git));125}126127const model = reader.store.add(this._instantiationService.createInstance(InlineEditModel, statelessProviderId, workspace, historyContextProvider, diagnosticsProvider));128129const recordingDirPath = join(this._vscodeExtensionContext.globalStorageUri.fsPath, 'logContextRecordings');130131const isInlineEditLogFileEnabled = this.isInlineEditsLogFileEnabledObservable.read(reader);132133let logContextRecorder: LogContextRecorder | undefined;134if (isInlineEditLogFileEnabled) {135logContextRecorder = reader.store.add(this._instantiationService.createInstance(LogContextRecorder, recordingDirPath, logger));136} else {137void LogContextRecorder.cleanupOldRecordings(recordingDirPath);138}139140const inlineEditDebugComponent = reader.store.add(new InlineEditDebugComponent(this._internalActionsEnabled, this.inlineEditsEnabled, model.debugRecorder, this._inlineEditsProviderId));141142const telemetrySender = reader.store.add(this._instantiationService.createInstance(TelemetrySender, workspace));143144// Create the expected edit capture controller145const expectedEditCaptureController = reader.store.add(this._instantiationService.createInstance(146ExpectedEditCaptureController,147model.debugRecorder148));149150const provider = this._instantiationService.createInstance(InlineCompletionProviderImpl, model, logger, logContextRecorder, inlineEditDebugComponent, telemetrySender, expectedEditCaptureController);151152const unificationStateValue = unificationState.read(reader);153let excludes = this._excludedProviders.read(reader);154if (unificationStateValue?.modelUnification) {155excludes = excludes.slice(0);156if (!excludes.includes('completions')) {157excludes.push('completions');158}159if (!excludes.includes('github.copilot')) {160excludes.push('github.copilot');161}162}163164reader.store.add(languages.registerInlineCompletionItemProvider('*', provider, {165displayName: provider.displayName,166yieldTo: this._yieldToCopilot.read(reader) ? ['github.copilot'] : undefined,167debounceDelayMs: 0, // set 0 debounce to ensure consistent delays/timings168groupId: 'nes',169excludes,170}));171172reader.store.add(commands.registerCommand(learnMoreCommandId, () => {173this._envService.openExternal(URI.parse(learnMoreLink));174}));175176reader.store.add(commands.registerCommand(clearCacheCommandId, () => {177model.nextEditProvider.clearCache();178}));179180reader.store.add(commands.registerCommand(reportNotebookNESIssueCommandId, () => {181const activeNotebook = window.activeNotebookEditor;182const document = window.activeTextEditor?.document;183if (!activeNotebook || !document || !isNotebookCell(document.uri)) {184return;185}186const doc = model.workspace.getDocumentByTextDocument(document);187const selection = activeNotebook.selection;188if (!selection || !doc) {189return;190}191192const logContext = new InlineEditRequestLogContext(doc.id.uri, document.version, undefined);193logContext.recordingBookmark = model.debugRecorder.createBookmark();194void commands.executeCommand(reportFeedbackCommandId, { logContext });195}));196197// Register expected edit capture commands198reader.store.add(commands.registerCommand(captureExpectedStartCommandId, () => {199void expectedEditCaptureController.startCapture('manual');200}));201202reader.store.add(commands.registerCommand(captureExpectedConfirmCommandId, () => {203void expectedEditCaptureController.confirmCapture();204}));205206reader.store.add(commands.registerCommand(captureExpectedAbortCommandId, () => {207void expectedEditCaptureController.abortCapture();208}));209210reader.store.add(commands.registerCommand(captureExpectedSubmitCommandId, () => {211void expectedEditCaptureController.submitCaptures();212}));213});214}215}216217export const learnMoreCommandId = 'github.copilot.debug.inlineEdit.learnMore';218219export const learnMoreLink = 'https://aka.ms/vscode-nes';220221export const clearCacheCommandId = 'github.copilot.debug.inlineEdit.clearCache';222export const reportNotebookNESIssueCommandId = 'github.copilot.debug.inlineEdit.reportNotebookNESIssue';223export const captureExpectedStartCommandId = 'github.copilot.nes.captureExpected.start';224export const captureExpectedConfirmCommandId = 'github.copilot.nes.captureExpected.confirm';225export const captureExpectedAbortCommandId = 'github.copilot.nes.captureExpected.abort';226export const captureExpectedSubmitCommandId = 'github.copilot.nes.captureExpected.submit';227228229