mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
add vscode.executeMappedEditsProvider
command & use it to have integration tests for mapped-edits service
This commit is contained in:
parent
3e9e7b3b5a
commit
c3a4fbbe8f
|
@ -58,6 +58,10 @@
|
||||||
"name": "vs/workbench/contrib/commands",
|
"name": "vs/workbench/contrib/commands",
|
||||||
"project": "vscode-workbench"
|
"project": "vscode-workbench"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "vs/workbench/contrib/mappedEdits",
|
||||||
|
"project": "vscode-workbench"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "vs/workbench/contrib/comments",
|
"name": "vs/workbench/contrib/comments",
|
||||||
"project": "vscode-workbench"
|
"project": "vscode-workbench"
|
||||||
|
|
|
@ -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
|
|
@ -6,7 +6,7 @@
|
||||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||||
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
|
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
|
||||||
import { reviveWorkspaceEditDto } from 'vs/workbench/api/browser/mainThreadBulkEdits';
|
import { reviveWorkspaceEditDto } from 'vs/workbench/api/browser/mainThreadBulkEdits';
|
||||||
import { ExtHostContext, ExtHostMappedEditsShape, IDocumentFilterDto, IMappedEditsContextDto, MainContext, MainThreadMappedEditsShape } from 'vs/workbench/api/common/extHost.protocol';
|
import { ExtHostContext, ExtHostMappedEditsShape, IDocumentFilterDto, MainContext, MainThreadMappedEditsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||||
import { IMappedEditsProvider, IMappedEditsService } from 'vs/workbench/services/mappedEdits/common/mappedEdits';
|
import { IMappedEditsProvider, IMappedEditsService } from 'vs/workbench/services/mappedEdits/common/mappedEdits';
|
||||||
import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||||
|
|
||||||
|
|
|
@ -464,7 +464,26 @@ const newCommands: ApiCommand[] = [
|
||||||
new ApiCommandArgument('value', 'The context key value', () => true, v => v),
|
new ApiCommandArgument('value', 'The context key value', () => true, v => v),
|
||||||
],
|
],
|
||||||
ApiCommandResult.Void
|
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, vscode.WorkspaceEdit>(
|
||||||
|
'A promise that resolves to a workspace edit or null',
|
||||||
|
(value) => {
|
||||||
|
return typeConverters.WorkspaceEdit.to(value);
|
||||||
|
})
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
//#endregion
|
//#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 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 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 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 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);
|
static readonly TypeHierarchyItem = new ApiCommandArgument('item', 'A type hierarchy item', v => v instanceof extHostTypes.TypeHierarchyItem, extHostTypeConverter.TypeHierarchyItem.from);
|
||||||
|
|
|
@ -32,6 +32,7 @@ import { IMarkerData, IRelatedInformation, MarkerSeverity, MarkerTag } from 'vs/
|
||||||
import { ProgressLocation as MainProgressLocation } from 'vs/platform/progress/common/progress';
|
import { ProgressLocation as MainProgressLocation } from 'vs/platform/progress/common/progress';
|
||||||
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
|
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
|
||||||
import { getPrivateApiFor } from 'vs/workbench/api/common/extHostTestingPrivateApi';
|
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 { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/editor';
|
||||||
import { IViewBadge } from 'vs/workbench/common/views';
|
import { IViewBadge } from 'vs/workbench/common/views';
|
||||||
import { IChatFollowup, IChatReplyFollowup, IChatResponseCommandFollowup } from 'vs/workbench/contrib/chat/common/chatService';
|
import { IChatFollowup, IChatReplyFollowup, IChatResponseCommandFollowup } from 'vs/workbench/contrib/chat/common/chatService';
|
||||||
|
@ -1563,6 +1564,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(context: vscode.MappedEditsContext): mappedEdits.MappedEditsContext {
|
||||||
|
return {
|
||||||
|
selections: context.selections.map(s => Selection.from(s)),
|
||||||
|
related: context.related.map(r => ({
|
||||||
|
uri: URI.from(r.uri),
|
||||||
|
range: Range.from(r.range)
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export namespace NotebookRange {
|
export namespace NotebookRange {
|
||||||
|
|
||||||
export function from(range: vscode.NotebookRange): ICellRange {
|
export function from(range: vscode.NotebookRange): ICellRange {
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||||
|
import * as nls from 'vs/nls';
|
||||||
|
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||||
|
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { IMappedEditsService, MappedEditsContext } from 'vs/workbench/services/mappedEdits/common/mappedEdits';
|
||||||
|
|
||||||
|
class ExecuteMappedEditsProvider extends Action2 {
|
||||||
|
|
||||||
|
static readonly ID = '_executeMappedEditsProvider';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: ExecuteMappedEditsProvider.ID,
|
||||||
|
title: { value: nls.localize('executeMappedEditsProvider', "Execute Mapped Edits Provider"), original: '' },
|
||||||
|
f1: false,
|
||||||
|
description: {
|
||||||
|
description: nls.localize('executeMappedEditsProvider.description', "Executes Mapped Edits Provider and returns the corresponding WorkspaceEdit or null if no edits are provided."),
|
||||||
|
args: [
|
||||||
|
// FIXME@ulugbekna
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor, documentUri: URI, codeBlocks: string[], context: MappedEditsContext) {
|
||||||
|
|
||||||
|
const mappedEditsService = accessor.get(IMappedEditsService);
|
||||||
|
const modelService = accessor.get(ITextModelService);
|
||||||
|
|
||||||
|
const document = await modelService.createModelReference(documentUri);
|
||||||
|
|
||||||
|
const cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
const res = await mappedEditsService.provideMappedEdits(document.object.textEditorModel, codeBlocks, context, cancellationTokenSource.token);
|
||||||
|
|
||||||
|
document.dispose();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerAction2(ExecuteMappedEditsProvider);
|
|
@ -10,16 +10,16 @@ import { LanguageSelector } from 'vs/editor/common/languageSelector';
|
||||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { WorkspaceEdit } from 'vs/editor/common/languages';
|
import { WorkspaceEdit } from 'vs/editor/common/languages';
|
||||||
import { Selection } from 'vs/editor/common/core/selection';
|
import { ISelection } from 'vs/editor/common/core/selection';
|
||||||
import { Range } from 'vs/editor/common/core/range';
|
import { IRange } from 'vs/editor/common/core/range';
|
||||||
|
|
||||||
export interface RelatedContextItem {
|
export interface RelatedContextItem {
|
||||||
readonly uri: URI;
|
readonly uri: URI;
|
||||||
readonly range: Range;
|
readonly range: IRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MappedEditsContext {
|
export interface MappedEditsContext {
|
||||||
selections: Selection[];
|
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)
|
* 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)
|
||||||
|
|
|
@ -227,6 +227,9 @@ import 'vs/workbench/contrib/markers/browser/markers.contribution';
|
||||||
// Merge Editor
|
// Merge Editor
|
||||||
import 'vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution';
|
import 'vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution';
|
||||||
|
|
||||||
|
// Mapped Edits
|
||||||
|
import 'vs/workbench/contrib/mappedEdits/common/mappedEdits.contribution';
|
||||||
|
|
||||||
// Commands
|
// Commands
|
||||||
import 'vs/workbench/contrib/commands/common/commands.contribution';
|
import 'vs/workbench/contrib/commands/common/commands.contribution';
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue