mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
Merge pull request #190649 from microsoft/ulugbekna/insert-at-cursor-api
MappedEditsProvider API
This commit is contained in:
commit
e4bfe48d92
|
@ -58,6 +58,10 @@
|
|||
"name": "vs/workbench/contrib/commands",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/contrib/mappedEdits",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/contrib/comments",
|
||||
"project": "vscode-workbench"
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"fileSearchProvider",
|
||||
"findTextInFiles",
|
||||
"fsChunks",
|
||||
"mappedEditsProvider",
|
||||
"notebookCellExecutionState",
|
||||
"notebookDeprecated",
|
||||
"notebookLiveShare",
|
||||
|
@ -45,7 +46,7 @@
|
|||
"timeline",
|
||||
"tokenInformation",
|
||||
"treeItemCheckbox",
|
||||
"treeViewActiveItem",
|
||||
"treeViewActiveItem",
|
||||
"treeViewReveal",
|
||||
"workspaceTrust",
|
||||
"telemetry",
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as assert from 'assert';
|
||||
|
||||
suite('mapped edits provider', () => {
|
||||
|
||||
test('mapped edits does not provide edits for unregistered langs', async function () {
|
||||
|
||||
const uri = vscode.Uri.file(path.join(vscode.workspace.rootPath || '', './myFile.ts'));
|
||||
|
||||
const tsDocFilter = [{ language: 'json' }];
|
||||
|
||||
const r1 = vscode.chat.registerMappedEditsProvider(tsDocFilter, {
|
||||
provideMappedEdits: (_doc: vscode.TextDocument, codeBlocks: string[], context: vscode.MappedEditsContext, _token: vscode.CancellationToken) => {
|
||||
|
||||
assert(context.selections.length === 1);
|
||||
assert(context.related.length === 1);
|
||||
assert('uri' in context.related[0] && 'range' in context.related[0]);
|
||||
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
const text = codeBlocks.join('\n//----\n');
|
||||
edit.replace(uri, context.selections[0], text);
|
||||
return edit;
|
||||
}
|
||||
});
|
||||
await vscode.workspace.openTextDocument(uri);
|
||||
const result = await vscode.commands.executeCommand<vscode.ProviderResult<vscode.WorkspaceEdit | null>>(
|
||||
'vscode.executeMappedEditsProvider',
|
||||
uri,
|
||||
[
|
||||
'// hello',
|
||||
`function foo() {\n\treturn 1;\n}`,
|
||||
],
|
||||
{
|
||||
selections: [new vscode.Selection(0, 0, 1, 0)],
|
||||
related: [
|
||||
{
|
||||
uri,
|
||||
range: new vscode.Range(new vscode.Position(0, 0), new vscode.Position(1, 0))
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
r1.dispose();
|
||||
|
||||
assert(result === null, 'returned null');
|
||||
});
|
||||
|
||||
test('mapped edits provides a single edit replacing the selection', async function () {
|
||||
|
||||
const uri = vscode.Uri.file(path.join(vscode.workspace.rootPath || '', './myFile.ts'));
|
||||
|
||||
const tsDocFilter = [{ language: 'typescript' }];
|
||||
|
||||
const r1 = vscode.chat.registerMappedEditsProvider(tsDocFilter, {
|
||||
provideMappedEdits: (_doc: vscode.TextDocument, codeBlocks: string[], context: vscode.MappedEditsContext, _token: vscode.CancellationToken) => {
|
||||
|
||||
assert(context.selections.length === 1);
|
||||
assert(context.related.length === 1);
|
||||
assert('uri' in context.related[0] && 'range' in context.related[0]);
|
||||
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
const text = codeBlocks.join('\n//----\n');
|
||||
edit.replace(uri, context.selections[0], text);
|
||||
return edit;
|
||||
}
|
||||
});
|
||||
|
||||
await vscode.workspace.openTextDocument(uri);
|
||||
const result = await vscode.commands.executeCommand<vscode.ProviderResult<vscode.WorkspaceEdit | null>>(
|
||||
'vscode.executeMappedEditsProvider',
|
||||
uri,
|
||||
[
|
||||
'// hello',
|
||||
`function foo() {\n\treturn 1;\n}`,
|
||||
],
|
||||
{
|
||||
selections: [new vscode.Selection(0, 0, 1, 0)],
|
||||
related: [
|
||||
{
|
||||
uri,
|
||||
range: new vscode.Range(new vscode.Position(0, 0), new vscode.Position(1, 0))
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
r1.dispose();
|
||||
|
||||
assert(result, 'non null response');
|
||||
const edits = result.get(uri);
|
||||
assert(edits.length === 1);
|
||||
assert(edits[0].range.start.line === 0);
|
||||
assert(edits[0].range.start.character === 0);
|
||||
assert(edits[0].range.end.line === 1);
|
||||
assert(edits[0].range.end.character === 0);
|
||||
});
|
||||
});
|
3
extensions/vscode-api-tests/testWorkspace/myFile.ts
Normal file
3
extensions/vscode-api-tests/testWorkspace/myFile.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
// 1
|
||||
// 2
|
||||
// 3
|
|
@ -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<DocumentOnDropEdit>;
|
||||
}
|
||||
|
||||
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<WorkspaceEdit | null>;
|
||||
}
|
||||
|
|
|
@ -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>('ILanguageFeaturesService');
|
||||
|
@ -71,6 +71,8 @@ export interface ILanguageFeaturesService {
|
|||
|
||||
readonly documentOnDropEditProvider: LanguageFeatureRegistry<DocumentOnDropEditProvider>;
|
||||
|
||||
readonly mappedEditsProvider: LanguageFeatureRegistry<MappedEditsProvider>;
|
||||
|
||||
// --
|
||||
|
||||
setNotebookTypeResolver(resolver: NotebookInfoResolver | undefined): void;
|
||||
|
|
|
@ -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<DocumentSemanticTokensProvider>(this._score.bind(this));
|
||||
readonly documentOnDropEditProvider = new LanguageFeatureRegistry<DocumentOnDropEditProvider>(this._score.bind(this));
|
||||
readonly documentPasteEditProvider = new LanguageFeatureRegistry<DocumentPasteEditProvider>(this._score.bind(this));
|
||||
readonly mappedEditsProvider: LanguageFeatureRegistry<MappedEditsProvider> = new LanguageFeatureRegistry<MappedEditsProvider>(this._score.bind(this));
|
||||
|
||||
private _notebookTypeResolver?: NotebookInfoResolver;
|
||||
|
||||
|
|
24
src/vs/monaco.d.ts
vendored
24
src/vs/monaco.d.ts
vendored
|
@ -7893,6 +7893,30 @@ declare namespace monaco.languages {
|
|||
provideDocumentRangeSemanticTokens(model: editor.ITextModel, range: Range, token: CancellationToken): ProviderResult<SemanticTokens>;
|
||||
}
|
||||
|
||||
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<WorkspaceEdit | null>;
|
||||
}
|
||||
|
||||
export interface ILanguageExtensionPoint {
|
||||
id: string;
|
||||
extensions?: string[];
|
||||
|
|
|
@ -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<languages.WorkspaceEdit | null> {
|
||||
const res = await this._proxy.$provideMappedEdits(this._handle, document.uri, codeBlocks, context, token);
|
||||
return res ? reviveWorkspaceEditDto(res, this._uriService) : null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1356,9 +1356,13 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
checkProposedApiEnabled(extension, 'chatRequestAccess');
|
||||
return extHostChatProvider.requestChatResponseProvider(extension.identifier, id);
|
||||
},
|
||||
registerVariable(name, description, resolver) {
|
||||
registerVariable(name: string, description: string, resolver: vscode.ChatVariableResolver) {
|
||||
checkProposedApiEnabled(extension, 'chatVariables');
|
||||
return extHostChatVariables.registerVariableResolver(extension, name, description, resolver);
|
||||
},
|
||||
registerMappedEditsProvider(selector: vscode.DocumentSelector, provider: vscode.MappedEditsProvider) {
|
||||
checkProposedApiEnabled(extension, 'mappedEditsProvider');
|
||||
return extHostLanguageFeatures.registerMappedEditsProvider(extension, selector, provider);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -372,6 +372,16 @@ export interface IShareableItemDto {
|
|||
selection?: IRange;
|
||||
}
|
||||
|
||||
export interface IRelatedContextItemDto {
|
||||
readonly uri: UriComponents;
|
||||
readonly range: IRange;
|
||||
}
|
||||
|
||||
export interface IMappedEditsContextDto {
|
||||
selections: ISelection[];
|
||||
related: IRelatedContextItemDto[];
|
||||
}
|
||||
|
||||
export interface ISignatureHelpProviderMetadataDto {
|
||||
readonly triggerCharacters: readonly string[];
|
||||
readonly retriggerCharacters: readonly string[];
|
||||
|
@ -427,6 +437,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
|
|||
$resolvePasteFileData(handle: number, requestId: number, dataId: string): Promise<VSBuffer>;
|
||||
$resolveDocumentOnDropFileData(handle: number, requestId: number, dataId: string): Promise<VSBuffer>;
|
||||
$setLanguageConfiguration(handle: number, languageId: string, configuration: ILanguageConfigurationDto): void;
|
||||
$registerMappedEditsProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
}
|
||||
|
||||
export interface MainThreadLanguagesShape extends IDisposable {
|
||||
|
@ -2019,6 +2030,7 @@ export interface ExtHostLanguageFeaturesShape {
|
|||
$provideTypeHierarchySubtypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise<ITypeHierarchyItemDto[] | undefined>;
|
||||
$releaseTypeHierarchy(handle: number, sessionId: string): void;
|
||||
$provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise<IDocumentOnDropEditDto | undefined>;
|
||||
$provideMappedEdits(handle: number, document: UriComponents, codeBlocks: string[], context: IMappedEditsContextDto, token: CancellationToken): Promise<IWorkspaceEditDto | null>;
|
||||
}
|
||||
|
||||
export interface ExtHostQuickOpenShape {
|
||||
|
|
|
@ -464,7 +464,26 @@ const newCommands: ApiCommand[] = [
|
|||
new ApiCommandArgument('value', 'The context key value', () => true, v => v),
|
||||
],
|
||||
ApiCommandResult.Void
|
||||
)
|
||||
),
|
||||
// --- mapped edits
|
||||
new ApiCommand(
|
||||
'vscode.executeMappedEditsProvider', '_executeMappedEditsProvider', 'Execute Mapped Edits Provider',
|
||||
[
|
||||
ApiCommandArgument.Uri,
|
||||
ApiCommandArgument.StringArray,
|
||||
new ApiCommandArgument(
|
||||
'MappedEditsContext',
|
||||
'Mapped Edits Context',
|
||||
(v: unknown) => typeConverters.MappedEditsContext.is(v),
|
||||
(v: vscode.MappedEditsContext) => typeConverters.MappedEditsContext.from(v)
|
||||
)
|
||||
],
|
||||
new ApiCommandResult<IWorkspaceEditDto | null, vscode.WorkspaceEdit | null>(
|
||||
'A promise that resolves to a workspace edit or null',
|
||||
(value) => {
|
||||
return value ? typeConverters.WorkspaceEdit.to(value) : null;
|
||||
})
|
||||
),
|
||||
];
|
||||
|
||||
//#endregion
|
||||
|
|
|
@ -444,6 +444,16 @@ export class ApiCommandArgument<V, O = V> {
|
|||
static readonly Selection = new ApiCommandArgument<extHostTypes.Selection, ISelection>('selection', 'A selection in a text document', v => extHostTypes.Selection.isSelection(v), extHostTypeConverter.Selection.from);
|
||||
static readonly Number = new ApiCommandArgument<number>('number', '', v => typeof v === 'number', v => v);
|
||||
static readonly String = new ApiCommandArgument<string>('string', '', v => typeof v === 'string', v => v);
|
||||
static readonly StringArray = ApiCommandArgument.Arr(ApiCommandArgument.String);
|
||||
|
||||
static Arr<T, K = T>(element: ApiCommandArgument<T, K>) {
|
||||
return new ApiCommandArgument(
|
||||
`${element.name}_array`,
|
||||
`Array of ${element.name}, ${element.description}`,
|
||||
(v: unknown) => Array.isArray(v) && v.every(e => element.validate(e)),
|
||||
(v: T[]) => v.map(e => element.convert(e))
|
||||
);
|
||||
}
|
||||
|
||||
static readonly CallHierarchyItem = new ApiCommandArgument('item', 'A call hierarchy item', v => v instanceof extHostTypes.CallHierarchyItem, extHostTypeConverter.CallHierarchyItem.from);
|
||||
static readonly TypeHierarchyItem = new ApiCommandArgument('item', 'A type hierarchy item', v => v instanceof extHostTypes.TypeHierarchyItem, extHostTypeConverter.TypeHierarchyItem.from);
|
||||
|
|
|
@ -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<extHostProtocol.IWorkspaceEditDto | null> {
|
||||
|
||||
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<languages.FoldingRange[] | undefined> {
|
||||
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<extHostProtocol.IWorkspaceEditDto | null> {
|
||||
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 {
|
||||
|
|
|
@ -1563,6 +1563,30 @@ export namespace LanguageSelector {
|
|||
}
|
||||
}
|
||||
|
||||
export namespace MappedEditsContext {
|
||||
|
||||
export function is(v: unknown): v is vscode.MappedEditsContext {
|
||||
return (!!v &&
|
||||
typeof v === 'object' &&
|
||||
'selections' in v &&
|
||||
Array.isArray(v.selections) &&
|
||||
v.selections.every(s => s instanceof types.Selection) &&
|
||||
'related' in v &&
|
||||
Array.isArray(v.related) &&
|
||||
v.related.every(e => e && typeof e === 'object' && URI.isUri(e.uri) && e.range instanceof types.Range));
|
||||
}
|
||||
|
||||
export function from(extContext: vscode.MappedEditsContext): languages.MappedEditsContext {
|
||||
return {
|
||||
selections: extContext.selections.map(s => Selection.from(s)),
|
||||
related: extContext.related.map(r => ({
|
||||
uri: URI.from(r.uri),
|
||||
range: Range.from(r.range)
|
||||
}))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export namespace NotebookRange {
|
||||
|
||||
export function from(range: vscode.NotebookRange): ICellRange {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
|
@ -30,6 +31,8 @@ import { CellKind, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/comm
|
|||
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;
|
||||
|
@ -232,17 +235,44 @@ export function registerChatCodeBlockActions() {
|
|||
this.notifyUserAction(accessor, context);
|
||||
}
|
||||
|
||||
private async handleTextEditor(accessor: ServicesAccessor, codeEditor: ICodeEditor, activeModel: ITextModel, context: IChatCodeBlockActionContext) {
|
||||
this.notifyUserAction(accessor, context);
|
||||
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 activeSelection = codeEditor.getSelection() ?? new Range(activeModel.getLineCount(), 1, activeModel.getLineCount(), 1);
|
||||
await bulkEditService.apply([new ResourceTextEdit(activeModel.uri, {
|
||||
range: activeSelection,
|
||||
text: context.code,
|
||||
})]);
|
||||
const mappedEditsProviders = accessor.get(ILanguageFeaturesService).mappedEditsProvider.ordered(activeModel);
|
||||
|
||||
// try applying workspace edit that was returned by a MappedEditsProvider, else simply insert at selection
|
||||
|
||||
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 {
|
||||
const activeSelection = codeEditor.getSelection() ?? new Range(activeModel.getLineCount(), 1, activeModel.getLineCount(), 1);
|
||||
await bulkEditService.apply([new ResourceTextEdit(activeModel.uri, {
|
||||
range: activeSelection,
|
||||
text: chatCodeBlockActionContext.code,
|
||||
})]);
|
||||
}
|
||||
codeEditorService.listCodeEditors().find(editor => editor.getModel()?.uri.toString() === activeModel.uri.toString())?.focus();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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 * as languages from 'vs/editor/common/languages';
|
||||
|
||||
CommandsRegistry.registerCommand(
|
||||
'_executeMappedEditsProvider',
|
||||
async (
|
||||
accessor: ServicesAccessor,
|
||||
documentUri: URI,
|
||||
codeBlocks: string[],
|
||||
context: languages.MappedEditsContext
|
||||
): Promise<languages.WorkspaceEdit | null> => {
|
||||
|
||||
const modelService = accessor.get(ITextModelService);
|
||||
const langFeaturesService = accessor.get(ILanguageFeaturesService);
|
||||
|
||||
const document = await modelService.createModelReference(documentUri);
|
||||
|
||||
let result: languages.WorkspaceEdit | null = null;
|
||||
|
||||
try {
|
||||
const providers = langFeaturesService.mappedEditsProvider.ordered(document.object.textEditorModel);
|
||||
|
||||
if (providers.length > 0) {
|
||||
const mostRelevantProvider = providers[0];
|
||||
|
||||
const cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
result = await mostRelevantProvider.provideMappedEdits(
|
||||
document.object.textEditorModel,
|
||||
codeBlocks,
|
||||
context,
|
||||
cancellationTokenSource.token
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
document.dispose();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
);
|
|
@ -59,6 +59,7 @@ export const allApiProposals = Object.freeze({
|
|||
interactiveWindow: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactiveWindow.d.ts',
|
||||
ipc: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.ipc.d.ts',
|
||||
languageConfigurationAutoClosingPairs: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.d.ts',
|
||||
mappedEditsProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts',
|
||||
notebookCellExecutionState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts',
|
||||
notebookCodeActions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCodeActions.d.ts',
|
||||
notebookControllerAffinityHidden: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookControllerAffinityHidden.d.ts',
|
||||
|
|
|
@ -228,6 +228,9 @@ import 'vs/workbench/contrib/markers/browser/markers.contribution';
|
|||
// Merge Editor
|
||||
import 'vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution';
|
||||
|
||||
// Mapped Edits
|
||||
import 'vs/workbench/contrib/mappedEdits/common/mappedEdits.contribution';
|
||||
|
||||
// Commands
|
||||
import 'vs/workbench/contrib/commands/common/commands.contribution';
|
||||
|
||||
|
|
48
src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts
vendored
Normal file
48
src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
declare module 'vscode' {
|
||||
|
||||
export interface RelatedContextItem {
|
||||
readonly uri: Uri;
|
||||
readonly range: Range;
|
||||
}
|
||||
|
||||
export interface MappedEditsContext {
|
||||
selections: Selection[];
|
||||
|
||||
/**
|
||||
* 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: it was suggested initially to be sorted from highest priority to lowest. How would it look like?
|
||||
*/
|
||||
related: RelatedContextItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for providing mapped edits for a given document.
|
||||
*/
|
||||
export interface MappedEditsProvider {
|
||||
/**
|
||||
* 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: TextDocument,
|
||||
codeBlocks: string[],
|
||||
context: MappedEditsContext,
|
||||
token: CancellationToken
|
||||
): ProviderResult<WorkspaceEdit | null>;
|
||||
}
|
||||
|
||||
namespace chat {
|
||||
export function registerMappedEditsProvider(documentSelector: DocumentSelector, provider: MappedEditsProvider): Disposable;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue