diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 409b993d05b..15b63cf0386 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -16,7 +16,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; +import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { LanguageId } from 'vs/editor/common/encodedTokenAttributes'; import * as model from 'vs/editor/common/model'; import { TokenizationRegistry as TokenizationRegistryImpl } from 'vs/editor/common/tokenizationRegistry'; @@ -2033,3 +2033,33 @@ export interface DocumentOnDropEditProvider { provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): ProviderResult; } + +export interface RelatedContextItem { + readonly uri: URI; + readonly range: IRange; +} + +export interface MappedEditsContext { + selections: ISelection[]; + related: RelatedContextItem[]; +} + +export interface MappedEditsProvider { + + /** + * Provider maps code blocks from the chat into a workspace edit. + * + * @param document The document to provide mapped edits for. + * @param codeBlocks Code blocks that come from an LLM's reply. + * "Insert at cursor" in the panel chat only sends one edit that the user clicks on, but inline chat can send multiple blocks and let the lang server decide what to do with them. + * @param context The context for providing mapped edits. + * @param token A cancellation token. + * @returns A provider result of text edits. + */ + provideMappedEdits( + document: model.ITextModel, + codeBlocks: string[], + context: MappedEditsContext, + token: CancellationToken + ): Promise; +} diff --git a/src/vs/editor/common/services/languageFeatures.ts b/src/vs/editor/common/services/languageFeatures.ts index 2c80887df1a..df53beaecdb 100644 --- a/src/vs/editor/common/services/languageFeatures.ts +++ b/src/vs/editor/common/services/languageFeatures.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { LanguageFeatureRegistry, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry'; -import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentPasteEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; +import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentPasteEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, MappedEditsProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export const ILanguageFeaturesService = createDecorator('ILanguageFeaturesService'); @@ -71,6 +71,8 @@ export interface ILanguageFeaturesService { readonly documentOnDropEditProvider: LanguageFeatureRegistry; + readonly mappedEditsProvider: LanguageFeatureRegistry; + // -- setNotebookTypeResolver(resolver: NotebookInfoResolver | undefined): void; diff --git a/src/vs/editor/common/services/languageFeaturesService.ts b/src/vs/editor/common/services/languageFeaturesService.ts index 35d321b4ef2..3ef13891fb7 100644 --- a/src/vs/editor/common/services/languageFeaturesService.ts +++ b/src/vs/editor/common/services/languageFeaturesService.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { LanguageFeatureRegistry, NotebookInfo, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry'; -import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DocumentPasteEditProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; +import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DocumentPasteEditProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, MappedEditsProvider } from 'vs/editor/common/languages'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -42,6 +42,7 @@ export class LanguageFeaturesService implements ILanguageFeaturesService { readonly documentSemanticTokensProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly documentOnDropEditProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly documentPasteEditProvider = new LanguageFeatureRegistry(this._score.bind(this)); + readonly mappedEditsProvider: LanguageFeatureRegistry = new LanguageFeatureRegistry(this._score.bind(this)); private _notebookTypeResolver?: NotebookInfoResolver; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 8fbfcd493ae..4d749af6eaf 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7891,6 +7891,30 @@ declare namespace monaco.languages { provideDocumentRangeSemanticTokens(model: editor.ITextModel, range: Range, token: CancellationToken): ProviderResult; } + export interface RelatedContextItem { + readonly uri: Uri; + readonly range: IRange; + } + + export interface MappedEditsContext { + selections: ISelection[]; + related: RelatedContextItem[]; + } + + export interface MappedEditsProvider { + /** + * Provider maps code blocks from the chat into a workspace edit. + * + * @param document The document to provide mapped edits for. + * @param codeBlocks Code blocks that come from an LLM's reply. + * "Insert at cursor" in the panel chat only sends one edit that the user clicks on, but inline chat can send multiple blocks and let the lang server decide what to do with them. + * @param context The context for providing mapped edits. + * @param token A cancellation token. + * @returns A provider result of text edits. + */ + provideMappedEdits(document: editor.ITextModel, codeBlocks: string[], context: MappedEditsContext, token: CancellationToken): Promise; + } + export interface ILanguageExtensionPoint { id: string; extensions?: string[]; diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 6d836e715e1..774cc0cc158 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -86,7 +86,6 @@ import './mainThreadTimeline'; import './mainThreadTesting'; import './mainThreadSecretState'; import './mainThreadShare'; -import './mainThreadMappedEdits'; import './mainThreadProfilContentHandlers'; import './mainThreadSemanticSimilarity'; import './mainThreadIssueReporter'; diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 84247a03e27..fb79323e683 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -931,6 +931,13 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread } return provider.resolveDocumentOnDropFileData(requestId, dataId); } + + // --- mapped edits + + $registerMappedEditsProvider(handle: number, selector: IDocumentFilterDto[]): void { + const provider = new MainThreadMappedEditsProvider(handle, this._proxy, this._uriIdentService); + this._registrations.set(handle, this._languageFeaturesService.mappedEditsProvider.register(selector, provider)); + } } class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider { @@ -1124,3 +1131,17 @@ export class MainThreadDocumentRangeSemanticTokensProvider implements languages. throw new Error(`Unexpected`); } } + +export class MainThreadMappedEditsProvider implements languages.MappedEditsProvider { + + constructor( + private readonly _handle: number, + private readonly _proxy: ExtHostLanguageFeaturesShape, + private readonly _uriService: IUriIdentityService, + ) { } + + async provideMappedEdits(document: ITextModel, codeBlocks: string[], context: languages.MappedEditsContext, token: CancellationToken): Promise { + const res = await this._proxy.$provideMappedEdits(this._handle, document.uri, codeBlocks, context, token); + return res ? reviveWorkspaceEditDto(res, this._uriService) : null; + } +} diff --git a/src/vs/workbench/api/browser/mainThreadMappedEdits.ts b/src/vs/workbench/api/browser/mainThreadMappedEdits.ts deleted file mode 100644 index a268da7bbe1..00000000000 --- a/src/vs/workbench/api/browser/mainThreadMappedEdits.ts +++ /dev/null @@ -1,57 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { reviveWorkspaceEditDto } from 'vs/workbench/api/browser/mainThreadBulkEdits'; -import { ExtHostContext, ExtHostMappedEditsShape, IDocumentFilterDto, MainContext, MainThreadMappedEditsShape } from 'vs/workbench/api/common/extHost.protocol'; -import { IMappedEditsProvider, IMappedEditsService } from 'vs/workbench/services/mappedEdits/common/mappedEdits'; -import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; - -@extHostNamedCustomer(MainContext.MainThreadMappedEdits) -export class MainThreadMappedEdits implements MainThreadMappedEditsShape { - - private readonly proxy: ExtHostMappedEditsShape; - - private providers = new Map(); - - private providerDisposables = new Map(); - - constructor( - extHostContext: IExtHostContext, - @IMappedEditsService private readonly mappedEditsService: IMappedEditsService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - ) { - this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostMappedEdits); - } - - $registerMappedEditsProvider(handle: number, selector: IDocumentFilterDto[]): void { - const provider: IMappedEditsProvider = { - selector, - provideMappedEdits: async (document, codeBlocks, context, token) => { - const result = await this.proxy.$provideMappedEdits(handle, document.uri, codeBlocks, context, token); - return result ? reviveWorkspaceEditDto(result, this.uriIdentityService) : null; - } - }; - this.providers.set(handle, provider); - const disposable = this.mappedEditsService.registerMappedEditsProvider(provider); - this.providerDisposables.set(handle, disposable); - } - - $unregisterMappedEditsProvider(handle: number): void { - if (this.providers.has(handle)) { - this.providers.delete(handle); - } - if (this.providerDisposables.has(handle)) { - this.providerDisposables.delete(handle); - } - } - - dispose(): void { - this.providers.clear(); - dispose(this.providerDisposables.values()); - this.providerDisposables.clear(); - } -} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 3b77e80ec96..492598f96ff 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -106,7 +106,6 @@ import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSo import { ExtHostShare } from 'vs/workbench/api/common/extHostShare'; import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; import { ExtHostChatSlashCommands } from 'vs/workbench/api/common/extHostChatSlashCommand'; -import { ExtHostMappedEdits } from 'vs/workbench/api/common/extHostMappedEdits'; import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables'; export interface IExtensionRegistries { @@ -211,7 +210,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostChatSlashCommands = rpcProtocol.set(ExtHostContext.ExtHostChatSlashCommands, new ExtHostChatSlashCommands(rpcProtocol, extHostChatProvider, extHostLogService)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol, extHostLogService)); - const extHostMappedEdits = rpcProtocol.set(ExtHostContext.ExtHostMappedEdits, new ExtHostMappedEdits(rpcProtocol, extHostDocuments, uriTransformer)); const extHostSemanticSimilarity = rpcProtocol.set(ExtHostContext.ExtHostSemanticSimilarity, new ExtHostSemanticSimilarity(rpcProtocol)); const extHostIssueReporter = rpcProtocol.set(ExtHostContext.ExtHostIssueReporter, new ExtHostIssueReporter(rpcProtocol)); const extHostStatusBar = rpcProtocol.set(ExtHostContext.ExtHostStatusBar, new ExtHostStatusBar(rpcProtocol, extHostCommands.converter)); @@ -1348,7 +1346,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, registerMappedEditsProvider(selector: vscode.DocumentSelector, provider: vscode.MappedEditsProvider) { checkProposedApiEnabled(extension, 'mappedEditsProvider'); - return extHostMappedEdits.registerMappedEditsProvider(selector, provider); + return extHostLanguageFeatures.registerMappedEditsProvider(extension, selector, provider); } }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 686e4ad8af6..9e0e46a7578 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -377,7 +377,7 @@ export interface IRelatedContextItemDto { } export interface IMappedEditsContextDto { - selections: ISelection[]; //FIXME@ulugbekna: is this serializable? should I use ISelection? + selections: ISelection[]; related: IRelatedContextItemDto[]; } @@ -436,6 +436,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $resolvePasteFileData(handle: number, requestId: number, dataId: string): Promise; $resolveDocumentOnDropFileData(handle: number, requestId: number, dataId: string): Promise; $setLanguageConfiguration(handle: number, languageId: string, configuration: ILanguageConfigurationDto): void; + $registerMappedEditsProvider(handle: number, selector: IDocumentFilterDto[]): void; } export interface MainThreadLanguagesShape extends IDisposable { @@ -1325,11 +1326,6 @@ export interface MainThreadShareShape extends IDisposable { $unregisterShareProvider(handle: number): void; } -export interface MainThreadMappedEditsShape extends IDisposable { - $registerMappedEditsProvider(handle: number, selector: IDocumentFilterDto[]): void; - $unregisterMappedEditsProvider(handle: number): void; -} - export interface MainThreadTaskShape extends IDisposable { $createTaskId(task: tasks.ITaskDTO): Promise; $registerTaskProvider(handle: number, type: string): Promise; @@ -2014,6 +2010,7 @@ export interface ExtHostLanguageFeaturesShape { $provideTypeHierarchySubtypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; $releaseTypeHierarchy(handle: number, sessionId: string): void; $provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise; + $provideMappedEdits(handle: number, document: UriComponents, codeBlocks: string[], context: IMappedEditsContextDto, token: CancellationToken): Promise; } export interface ExtHostQuickOpenShape { @@ -2113,10 +2110,6 @@ export interface ExtHostShareShape { $provideShare(handle: number, shareableItem: IShareableItemDto, token: CancellationToken): Promise; } -export interface ExtHostMappedEditsShape { - $provideMappedEdits(handle: number, document: UriComponents, codeBlocks: string[], context: IMappedEditsContextDto, token: CancellationToken): Promise; -} - export interface ExtHostTaskShape { $provideTasks(handle: number, validTypes: { [key: string]: boolean }): Promise; $resolveTask(handle: number, taskDTO: tasks.ITaskDTO): Promise; @@ -2642,7 +2635,6 @@ export const MainContext = { MainThreadSCM: createProxyIdentifier('MainThreadSCM'), MainThreadSearch: createProxyIdentifier('MainThreadSearch'), MainThreadShare: createProxyIdentifier('MainThreadShare'), - MainThreadMappedEdits: createProxyIdentifier('MainThreadMappedEdits'), MainThreadTask: createProxyIdentifier('MainThreadTask'), MainThreadWindow: createProxyIdentifier('MainThreadWindow'), MainThreadLabelService: createProxyIdentifier('MainThreadLabelService'), @@ -2720,7 +2712,6 @@ export const ExtHostContext = { ExtHostChatSlashCommands: createProxyIdentifier('ExtHostChatSlashCommands'), ExtHostChatVariables: createProxyIdentifier('ExtHostChatVariables'), ExtHostChatProvider: createProxyIdentifier('ExtHostChatProvider'), - ExtHostMappedEdits: createProxyIdentifier('ExtHostMappedEdits'), ExtHostSemanticSimilarity: createProxyIdentifier('ExtHostSemanticSimilarity'), ExtHostTheming: createProxyIdentifier('ExtHostTheming'), ExtHostTunnelService: createProxyIdentifier('ExtHostTunnelService'), diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index cd8b2c1de2c..ec2fbcebeef 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1817,6 +1817,34 @@ class DocumentOnDropEditAdapter { } } +class MappedEditsAdapter { + + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.MappedEditsProvider, + ) { } + + async provideMappedEdits( + resource: UriComponents, + codeBlocks: string[], + context: extHostProtocol.IMappedEditsContextDto, + token: CancellationToken + ): Promise { + + const uri = URI.revive(resource); + const doc = this._documents.getDocument(uri); + + const ctx = { + selections: context.selections.map(s => typeConvert.Selection.to(s)), + related: context.related.map(r => ({ uri: URI.revive(r.uri), range: typeConvert.Range.to(r.range) })), + }; + + const mappedEdits = await this._provider.provideMappedEdits(doc, codeBlocks, ctx, token); + + return mappedEdits ? typeConvert.WorkspaceEdit.from(mappedEdits) : null; + } +} + type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter | DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentPasteEditProvider | DocumentFormattingAdapter | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter @@ -1826,7 +1854,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter | InlineValuesAdapter | LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionAdapter - | DocumentOnDropEditAdapter; + | DocumentOnDropEditAdapter | MappedEditsAdapter; class AdapterData { constructor( @@ -2383,7 +2411,14 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF } $provideFoldingRanges(handle: number, resource: UriComponents, context: vscode.FoldingContext, token: CancellationToken): Promise { - return this._withAdapter(handle, FoldingProviderAdapter, adapter => adapter.provideFoldingRanges(URI.revive(resource), context, token), undefined, token); + return this._withAdapter( + handle, + FoldingProviderAdapter, + (adapter) => + adapter.provideFoldingRanges(URI.revive(resource), context, token), + undefined, + token + ); } // --- smart select @@ -2462,6 +2497,19 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF Promise.resolve(adapter.provideDocumentOnDropEdits(requestId, URI.revive(resource), position, dataTransferDto, token)), undefined, undefined); } + // --- mapped edits + + registerMappedEditsProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.MappedEditsProvider): vscode.Disposable { + const handle = this._addNewAdapter(new MappedEditsAdapter(this._documents, provider), extension); + this._proxy.$registerMappedEditsProvider(handle, this._transformDocumentSelector(selector, extension)); + return this._createDisposable(handle); + } + + $provideMappedEdits(handle: number, document: UriComponents, codeBlocks: string[], context: extHostProtocol.IMappedEditsContextDto, token: CancellationToken): Promise { + return this._withAdapter(handle, MappedEditsAdapter, adapter => + Promise.resolve(adapter.provideMappedEdits(document, codeBlocks, context, token)), null, token); + } + // --- copy/paste actions registerDocumentPasteEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider, metadata: vscode.DocumentPasteProviderMetadata): vscode.Disposable { diff --git a/src/vs/workbench/api/common/extHostMappedEdits.ts b/src/vs/workbench/api/common/extHostMappedEdits.ts deleted file mode 100644 index c98adda03b2..00000000000 --- a/src/vs/workbench/api/common/extHostMappedEdits.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CancellationToken } from 'vs/base/common/cancellation'; -import { URI, UriComponents } from 'vs/base/common/uri'; -import { IURITransformer } from 'vs/base/common/uriIpc'; -import { ExtHostMappedEditsShape, IMainContext, IMappedEditsContextDto, IWorkspaceEditDto, MainContext, MainThreadMappedEditsShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; -import { Range, Selection, DocumentSelector, WorkspaceEdit } from 'vs/workbench/api/common/extHostTypeConverters'; -import type * as vscode from 'vscode'; - -export class ExtHostMappedEdits implements ExtHostMappedEditsShape { - - private static handlePool: number = 0; - - private proxy: MainThreadMappedEditsShape; - private providers = new Map(); - - constructor( - mainContext: IMainContext, - private readonly _documents: ExtHostDocuments, - private readonly uriTransformer: IURITransformer | undefined - ) { - this.proxy = mainContext.getProxy(MainContext.MainThreadMappedEdits); - } - - async $provideMappedEdits(handle: number, docUri: UriComponents, codeBlocks: string[], context: IMappedEditsContextDto, token: CancellationToken): Promise { - const provider = this.providers.get(handle); - if (!provider) { - return null; - } - const uri = URI.revive(docUri); - const doc = this._documents.getDocument(uri); - const ctx = { - selections: context.selections.map(s => Selection.to(s)), - related: context.related.map(r => ({ uri: URI.revive(r.uri), range: Range.to(r.range) })), - }; - const mappedEdits = await provider.provideMappedEdits(doc, codeBlocks, ctx, token); - if (!mappedEdits) { - return null; - } - - return WorkspaceEdit.from(mappedEdits); - } - - registerMappedEditsProvider(selector: vscode.DocumentSelector, provider: vscode.MappedEditsProvider): vscode.Disposable { - const handle = ExtHostMappedEdits.handlePool++; - this.providers.set(handle, provider); - this.proxy.$registerMappedEditsProvider(handle, DocumentSelector.from(selector, this.uriTransformer)); - return { - dispose: () => { - ExtHostMappedEdits.handlePool--; - this.proxy.$unregisterMappedEditsProvider(handle); - this.providers.delete(handle); - } - }; - } - -} diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 0f7efee2be4..e745869c9cf 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -32,7 +32,6 @@ import { IMarkerData, IRelatedInformation, MarkerSeverity, MarkerTag } from 'vs/ import { ProgressLocation as MainProgressLocation } from 'vs/platform/progress/common/progress'; import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { getPrivateApiFor } from 'vs/workbench/api/common/extHostTestingPrivateApi'; -import type * as mappedEdits from 'vs/workbench/services/mappedEdits/common/mappedEdits'; import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/editor'; import { IViewBadge } from 'vs/workbench/common/views'; import { IChatFollowup, IChatReplyFollowup, IChatResponseCommandFollowup } from 'vs/workbench/contrib/chat/common/chatService'; @@ -1577,10 +1576,10 @@ export namespace MappedEditsContext { v.related.every(e => e && typeof e === 'object' && URI.isUri(e.uri) && e.range instanceof types.Range)); } - export function from(context: vscode.MappedEditsContext): mappedEdits.MappedEditsContext { + export function from(extContext: vscode.MappedEditsContext): languages.MappedEditsContext { return { - selections: context.selections.map(s => Selection.from(s)), - related: context.related.map(r => ({ + selections: extContext.selections.map(s => Selection.from(s)), + related: extContext.related.map(r => ({ uri: URI.from(r.uri), range: Range.from(r.range) })) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 85fd1ee7ad2..3761e137dc5 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -25,13 +25,14 @@ import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatCopyAction, IChatService, IChatUserActionEvent, InteractiveSessionCopyKind } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; -import { IMappedEditsService, RelatedContextItem } from 'vs/workbench/services/mappedEdits/common/mappedEdits'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ITerminalEditorService, ITerminalGroupService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { WorkspaceEdit, RelatedContextItem } from 'vs/editor/common/languages'; export interface IChatCodeBlockActionContext { code: string; @@ -236,19 +237,33 @@ export function registerChatCodeBlockActions() { private async handleTextEditor(accessor: ServicesAccessor, codeEditor: ICodeEditor, activeModel: ITextModel, chatCodeBlockActionContext: IChatCodeBlockActionContext) { this.notifyUserAction(accessor, chatCodeBlockActionContext); + const bulkEditService = accessor.get(IBulkEditService); const codeEditorService = accessor.get(ICodeEditorService); - const mappedEditsService = accessor.get(IMappedEditsService); + + const mappedEditsProviders = accessor.get(ILanguageFeaturesService).mappedEditsProvider.ordered(activeModel); // try applying workspace edit that was returned by a MappedEditsProvider, else simply insert at selection - const selections = codeEditor.getSelections() ?? []; - const mappedEditsContext = { - selections, - related: [] as RelatedContextItem[], // FIXME@ulugbekna: this needs to be populated but we don't yet have a way to get this info from extensions - }; - const cancellationTokenSource = new CancellationTokenSource(); - const workspaceEdit = await mappedEditsService.provideMappedEdits(activeModel, [chatCodeBlockActionContext.code], mappedEditsContext, cancellationTokenSource.token); + let workspaceEdit: WorkspaceEdit | null = null; + + if (mappedEditsProviders.length > 0) { + const mostRelevantProvider = mappedEditsProviders[0]; + + const selections = codeEditor.getSelections() ?? []; + const mappedEditsContext = { + selections, + related: [] as RelatedContextItem[], // TODO@ulugbekna: we do have not yet decided what to populate this with + }; + const cancellationTokenSource = new CancellationTokenSource(); + + workspaceEdit = await mostRelevantProvider.provideMappedEdits( + activeModel, + [chatCodeBlockActionContext.code], + mappedEditsContext, + cancellationTokenSource.token); + } + if (workspaceEdit) { await bulkEditService.apply(workspaceEdit); } else { diff --git a/src/vs/workbench/contrib/mappedEdits/common/mappedEdits.contribution.ts b/src/vs/workbench/contrib/mappedEdits/common/mappedEdits.contribution.ts index 82171c7fbed..fc753a4b673 100644 --- a/src/vs/workbench/contrib/mappedEdits/common/mappedEdits.contribution.ts +++ b/src/vs/workbench/contrib/mappedEdits/common/mappedEdits.contribution.ts @@ -5,23 +5,47 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IMappedEditsService, MappedEditsContext } from 'vs/workbench/services/mappedEdits/common/mappedEdits'; +import * as languages from 'vs/editor/common/languages'; -CommandsRegistry.registerCommand('_executeMappedEditsProvider', async (accessor: ServicesAccessor, documentUri: URI, codeBlocks: string[], context: MappedEditsContext) => { +CommandsRegistry.registerCommand( + '_executeMappedEditsProvider', + async ( + accessor: ServicesAccessor, + documentUri: URI, + codeBlocks: string[], + context: languages.MappedEditsContext + ): Promise => { - const mappedEditsService = accessor.get(IMappedEditsService); - const modelService = accessor.get(ITextModelService); + const modelService = accessor.get(ITextModelService); + const langFeaturesService = accessor.get(ILanguageFeaturesService); - const document = await modelService.createModelReference(documentUri); + const document = await modelService.createModelReference(documentUri); - const cancellationTokenSource = new CancellationTokenSource(); + let result: languages.WorkspaceEdit | null = null; - const result = await mappedEditsService.provideMappedEdits(document.object.textEditorModel, codeBlocks, context, cancellationTokenSource.token); + try { + const providers = langFeaturesService.mappedEditsProvider.ordered(document.object.textEditorModel); - document.dispose(); + if (providers.length > 0) { + const mostRelevantProvider = providers[0]; - return result; -}); + const cancellationTokenSource = new CancellationTokenSource(); + + result = await mostRelevantProvider.provideMappedEdits( + document.object.textEditorModel, + codeBlocks, + context, + cancellationTokenSource.token + ); + } + } finally { + document.dispose(); + } + + return result; + } +); diff --git a/src/vs/workbench/services/mappedEdits/browser/mappedEditsService.ts b/src/vs/workbench/services/mappedEdits/browser/mappedEditsService.ts deleted file mode 100644 index fe1cec5815a..00000000000 --- a/src/vs/workbench/services/mappedEdits/browser/mappedEditsService.ts +++ /dev/null @@ -1,51 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CancellationToken } from 'vs/base/common/cancellation'; -import { ITextModel } from 'vs/editor/common/model'; -import { IMappedEditsProvider, IMappedEditsService, MappedEditsContext } from 'vs/workbench/services/mappedEdits/common/mappedEdits'; -import { score } from 'vs/editor/common/languageSelector'; -import { WorkspaceEdit } from 'vs/editor/common/languages'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; - -export class MappedEditsService implements IMappedEditsService { - readonly _serviceBrand: undefined; - - private readonly _providers = new Set(); - - constructor() { } - - registerMappedEditsProvider(provider: IMappedEditsProvider) { - this._providers.add(provider); - return { - dispose: () => { - this._providers.delete(provider); - } - }; - } - - async provideMappedEdits(document: ITextModel, codeBlocks: string[], context: MappedEditsContext, token: CancellationToken): Promise { - - const language = document.getLanguageId(); - - const providers = [...this._providers.values()] - .map((p): [IMappedEditsProvider, number] => { - const pts = score(p.selector, document.uri, language, true, undefined, undefined); - return [p, pts]; - }) - .filter(([p, pts]) => pts > 0) - .sort((a, b) => b[1] - a[1]); - - if (providers.length === 0) { - return null; - } - - const provider = providers[0][0]; - - return provider.provideMappedEdits(document, codeBlocks, context, token); - } -} - -registerSingleton(IMappedEditsService, MappedEditsService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/mappedEdits/common/mappedEdits.ts b/src/vs/workbench/services/mappedEdits/common/mappedEdits.ts deleted file mode 100644 index 9a60304881c..00000000000 --- a/src/vs/workbench/services/mappedEdits/common/mappedEdits.ts +++ /dev/null @@ -1,65 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from 'vs/base/common/uri'; -import { ITextModel } from 'vs/editor/common/model'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { LanguageSelector } from 'vs/editor/common/languageSelector'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { WorkspaceEdit } from 'vs/editor/common/languages'; -import { ISelection } from 'vs/editor/common/core/selection'; -import { IRange } from 'vs/editor/common/core/range'; - -export interface RelatedContextItem { - readonly uri: URI; - readonly range: IRange; -} - -export interface MappedEditsContext { - selections: ISelection[]; - - /** - * If there's no context, the array should be empty. It's also empty until we figure out how to compute this or retrieve from an extension (eg, copilot chat) - * - * TODO@ulugbekna: should this array be sorted from highest priority to lowest? - */ - related: RelatedContextItem[]; -} - -export interface IMappedEditsProvider { - - selector: LanguageSelector; - - /** - * Provide mapped edits for a given document. - * - * @param document The document to provide mapped edits for. - * @param codeBlocks Code blocks that come from an LLM's reply. - * "Insert at cursor" in the panel chat only sends one edit that the user clicks on, but inline chat can send multiple blocks and let the lang server decide what to do with them. - * @param context The context for providing mapped edits. - * @param token A cancellation token. - * @returns A provider result of text edits. - */ - provideMappedEdits( - document: ITextModel, - codeBlocks: string[], - context: MappedEditsContext, - token: CancellationToken - ): Promise; -} - - -export const IMappedEditsService = createDecorator('mappedEditsService'); - -export interface IMappedEditsService { - _serviceBrand: undefined; - registerMappedEditsProvider(provider: IMappedEditsProvider): IDisposable; - provideMappedEdits( - document: ITextModel, - codeBlocks: string[], - context: MappedEditsContext, - token: CancellationToken): Promise; -} diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index d30cd73ec49..98d5a3cc130 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -111,7 +111,6 @@ import 'vs/workbench/services/textMate/browser/textMateTokenizationFeature.contr import 'vs/workbench/services/userActivity/common/userActivityService'; import 'vs/workbench/services/userActivity/browser/userActivityBrowser'; import 'vs/workbench/services/issue/browser/issueTroubleshoot'; -import 'vs/workbench/services/mappedEdits/browser/mappedEditsService'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';