Skip to content

Commit e4f7f6a

Browse files
authored
Add PasteEditProvider (#107283)
For #30066 This adds a new `documentPaste` api proposal that lets extensions hook into copy and paste. This can be used to do things such as: - Create link when pasting an image - Bring along imports when copy and pasting code
1 parent a6724dc commit e4f7f6a

21 files changed

+505
-44
lines changed

extensions/markdown-language-features/package.json

+8-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"Programming Languages"
1717
],
1818
"enabledApiProposals": [
19-
"textEditorDrop"
19+
"textEditorDrop",
20+
"documentPaste"
2021
],
2122
"activationEvents": [
2223
"onLanguage:markdown",
@@ -414,6 +415,12 @@
414415
"markdownDescription": "%configuration.markdown.editor.drop.enabled%",
415416
"scope": "resource"
416417
},
418+
"markdown.experimental.editor.pasteLinks.enabled": {
419+
"type": "boolean",
420+
"default": false,
421+
"markdownDescription": "%configuration.markdown.editor.pasteLinks.enabled%",
422+
"scope": "resource"
423+
},
417424
"markdown.experimental.validate.enabled": {
418425
"type": "boolean",
419426
"scope": "resource",

extensions/markdown-language-features/package.nls.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
"configuration.markdown.links.openLocation.currentGroup": "Open links in the active editor group.",
2929
"configuration.markdown.links.openLocation.beside": "Open links beside the active editor.",
3030
"configuration.markdown.suggest.paths.enabled.description": "Enable/disable path suggestions for markdown links",
31-
"configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#workbench.experimental.editor.dropIntoEditor.enabled#`.",
31+
"configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#workbenck.experimental.editor.dropIntoEditor.enabled#`.",
32+
"configuration.markdown.editor.pasteLinks.enabled": "Enable/disable pasting files into a Markdown editor inserts Markdown links.",
3233
"configuration.markdown.experimental.validate.enabled.description": "Enable/disable all error reporting in Markdown files.",
3334
"configuration.markdown.experimental.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, e.g. `[link][ref]`. Requires enabling `#markdown.experimental.validate.enabled#`.",
3435
"configuration.markdown.experimental.validate.headerLinks.enabled.description": "Validate links to headers in Markdown files, e.g. `[link](#header)`. Requires enabling `#markdown.experimental.validate.enabled#`.",

extensions/markdown-language-features/src/extension.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
import * as vscode from 'vscode';
77
import { CommandManager } from './commandManager';
88
import * as commands from './commands/index';
9-
import { register as registerDiagnostics } from './languageFeatures/diagnostics';
9+
import { registerPasteProvider } from './languageFeatures/copyPaste';
1010
import { MdDefinitionProvider } from './languageFeatures/definitionProvider';
11+
import { register as registerDiagnostics } from './languageFeatures/diagnostics';
1112
import { MdLinkProvider } from './languageFeatures/documentLinkProvider';
1213
import { MdDocumentSymbolProvider } from './languageFeatures/documentSymbolProvider';
1314
import { registerDropIntoEditor } from './languageFeatures/dropIntoEditor';
@@ -78,6 +79,7 @@ function registerMarkdownLanguageFeatures(
7879
MdPathCompletionProvider.register(selector, engine, linkProvider),
7980
registerDiagnostics(selector, engine, workspaceContents, linkProvider, commandManager),
8081
registerDropIntoEditor(selector),
82+
registerPasteProvider(selector),
8183
registerFindFileReferences(commandManager, referencesProvider),
8284
);
8385
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import { tryInsertUriList } from './dropIntoEditor';
8+
9+
export function registerPasteProvider(selector: vscode.DocumentSelector) {
10+
return vscode.languages.registerDocumentPasteEditProvider(selector, new class implements vscode.DocumentPasteEditProvider {
11+
12+
async provideDocumentPasteEdits(
13+
document: vscode.TextDocument,
14+
range: vscode.Range,
15+
dataTransfer: vscode.DataTransfer,
16+
token: vscode.CancellationToken,
17+
): Promise<vscode.SnippetTextEdit | undefined> {
18+
const enabled = vscode.workspace.getConfiguration('markdown', document).get('experimental.editor.pasteLinks.enabled', false);
19+
if (!enabled) {
20+
return;
21+
}
22+
23+
return tryInsertUriList(document, range, dataTransfer, token);
24+
}
25+
});
26+
}

extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts

+32-32
Original file line numberDiff line numberDiff line change
@@ -32,45 +32,45 @@ export function registerDropIntoEditor(selector: vscode.DocumentSelector) {
3232
}
3333

3434
const replacementRange = new vscode.Range(position, position);
35-
return this.tryInsertUriList(document, replacementRange, dataTransfer, token);
35+
return tryInsertUriList(document, replacementRange, dataTransfer, token);
3636
}
37+
});
38+
}
3739

38-
private async tryInsertUriList(document: vscode.TextDocument, replacementRange: vscode.Range, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.SnippetTextEdit | undefined> {
39-
const urlList = await dataTransfer.get('text/uri-list')?.asString();
40-
if (!urlList || token.isCancellationRequested) {
41-
return undefined;
42-
}
43-
44-
const uris: vscode.Uri[] = [];
45-
for (const resource of urlList.split('\n')) {
46-
try {
47-
uris.push(vscode.Uri.parse(resource));
48-
} catch {
49-
// noop
50-
}
51-
}
40+
export async function tryInsertUriList(document: vscode.TextDocument, replacementRange: vscode.Range, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.SnippetTextEdit | undefined> {
41+
const urlList = await dataTransfer.get('text/uri-list')?.asString();
42+
if (!urlList || token.isCancellationRequested) {
43+
return undefined;
44+
}
5245

53-
if (!uris.length) {
54-
return;
55-
}
46+
const uris: vscode.Uri[] = [];
47+
for (const resource of urlList.split('\n')) {
48+
try {
49+
uris.push(vscode.Uri.parse(resource));
50+
} catch {
51+
// noop
52+
}
53+
}
5654

57-
const snippet = new vscode.SnippetString();
58-
uris.forEach((uri, i) => {
59-
const mdPath = document.uri.scheme === uri.scheme
60-
? encodeURI(path.relative(URI.Utils.dirname(document.uri).fsPath, uri.fsPath).replace(/\\/g, '/'))
61-
: uri.toString(false);
55+
if (!uris.length) {
56+
return;
57+
}
6258

63-
const ext = URI.Utils.extname(uri).toLowerCase();
64-
snippet.appendText(imageFileExtensions.has(ext) ? '![' : '[');
65-
snippet.appendTabstop();
66-
snippet.appendText(`](${mdPath})`);
59+
const snippet = new vscode.SnippetString();
60+
uris.forEach((uri, i) => {
61+
const mdPath = document.uri.scheme === uri.scheme
62+
? encodeURI(path.relative(URI.Utils.dirname(document.uri).fsPath, uri.fsPath).replace(/\\/g, '/'))
63+
: uri.toString(false);
6764

68-
if (i <= uris.length - 1 && uris.length > 1) {
69-
snippet.appendText(' ');
70-
}
71-
});
65+
const ext = URI.Utils.extname(uri).toLowerCase();
66+
snippet.appendText(imageFileExtensions.has(ext) ? '![' : '[');
67+
snippet.appendTabstop();
68+
snippet.appendText(`](${mdPath})`);
7269

73-
return new vscode.SnippetTextEdit(replacementRange, snippet);
70+
if (i <= uris.length - 1 && uris.length > 1) {
71+
snippet.appendText(' ');
7472
}
7573
});
74+
75+
return new vscode.SnippetTextEdit(replacementRange, snippet);
7676
}

extensions/markdown-language-features/tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"src/**/*",
88
"../../src/vscode-dts/vscode.d.ts",
99
"../../src/vscode-dts/vscode.proposed.textEditorDrop.d.ts",
10-
"../../src/vscode-dts/vscode.proposed.dataTransferFiles.d.ts"
10+
"../../src/vscode-dts/vscode.proposed.dataTransferFiles.d.ts",
11+
"../../src/vscode-dts/vscode.proposed.documentPaste.d.ts"
1112
]
1213
}

src/vs/base/common/dataTransfer.ts

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ export class VSDataTransfer {
3737
this._data.set(mimeType, value);
3838
}
3939

40+
public delete(mimeType: string) {
41+
this._data.delete(mimeType);
42+
}
43+
4044
public setString(mimeType: string, stringOrPromise: string | Promise<string>) {
4145
this.set(mimeType, {
4246
asString: async () => stringOrPromise,

src/vs/editor/browser/dnd.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { VSDataTransfer } from 'vs/base/common/dataTransfer';
7+
import { URI } from 'vs/base/common/uri';
8+
9+
10+
export function toVSDataTransfer(dataTransfer: DataTransfer) {
11+
const vsDataTransfer = new VSDataTransfer();
12+
for (const item of dataTransfer.items) {
13+
const type = item.type;
14+
if (item.kind === 'string') {
15+
const asStringValue = new Promise<string>(resolve => item.getAsString(resolve));
16+
vsDataTransfer.setString(type, asStringValue);
17+
} else if (item.kind === 'file') {
18+
const file = item.getAsFile() as null | (File & { path?: string });
19+
if (file) {
20+
const uri = file.path ? URI.parse(file.path) : undefined;
21+
vsDataTransfer.setFile(type, file.name, uri, async () => {
22+
return new Uint8Array(await file.arrayBuffer());
23+
});
24+
}
25+
}
26+
}
27+
return vsDataTransfer;
28+
}

src/vs/editor/common/languages.ts

+9
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,15 @@ export interface CodeActionProvider {
721721
_getAdditionalMenuItems?(context: CodeActionContext, actions: readonly CodeAction[]): Command[];
722722
}
723723

724+
/**
725+
* @internal
726+
*/
727+
export interface DocumentPasteEditProvider {
728+
prepareDocumentPaste?(model: model.ITextModel, selection: Selection, dataTransfer: VSDataTransfer, token: CancellationToken): Promise<undefined | VSDataTransfer>;
729+
730+
provideDocumentPasteEdits(model: model.ITextModel, selection: Selection, dataTransfer: VSDataTransfer, token: CancellationToken): Promise<WorkspaceEdit | SnippetTextEdit | undefined>;
731+
}
732+
724733
/**
725734
* Represents a parameter of a callable-signature. A parameter can
726735
* have a label and a doc-comment.

src/vs/editor/common/services/languageFeatures.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { LanguageFeatureRegistry, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry';
7-
import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, 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';
7+
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';
88
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
99

1010
export const ILanguageFeaturesService = createDecorator<ILanguageFeaturesService>('ILanguageFeaturesService');
@@ -25,6 +25,8 @@ export interface ILanguageFeaturesService {
2525

2626
readonly codeActionProvider: LanguageFeatureRegistry<CodeActionProvider>;
2727

28+
readonly documentPasteEditProvider: LanguageFeatureRegistry<DocumentPasteEditProvider>;
29+
2830
readonly renameProvider: LanguageFeatureRegistry<RenameProvider>;
2931

3032
readonly documentFormattingEditProvider: LanguageFeatureRegistry<DocumentFormattingEditProvider>;

src/vs/editor/common/services/languageFeaturesService.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { URI } from 'vs/base/common/uri';
77
import { LanguageFeatureRegistry, NotebookInfo, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry';
8-
import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, 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';
8+
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';
99
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
1010
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
1111

@@ -41,6 +41,7 @@ export class LanguageFeaturesService implements ILanguageFeaturesService {
4141
readonly documentRangeSemanticTokensProvider = new LanguageFeatureRegistry<DocumentRangeSemanticTokensProvider>(this._score.bind(this));
4242
readonly documentSemanticTokensProvider = new LanguageFeatureRegistry<DocumentSemanticTokensProvider>(this._score.bind(this));
4343
readonly documentOnDropEditProvider = new LanguageFeatureRegistry<DocumentOnDropEditProvider>(this._score.bind(this));
44+
readonly documentPasteEditProvider = new LanguageFeatureRegistry<DocumentPasteEditProvider>(this._score.bind(this));
4445

4546
private _notebookTypeResolver?: NotebookInfoResolver;
4647

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
7+
import { editorConfigurationBaseNode } from 'vs/editor/common/config/editorConfigurationSchema';
8+
import { CopyPasteController } from 'vs/editor/contrib/copyPaste/browser/copyPasteController';
9+
import * as nls from 'vs/nls';
10+
import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
11+
import { Registry } from 'vs/platform/registry/common/platform';
12+
13+
registerEditorContribution(CopyPasteController.ID, CopyPasteController);
14+
15+
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
16+
...editorConfigurationBaseNode,
17+
properties: {
18+
'editor.experimental.pasteActions.enabled': {
19+
type: 'boolean',
20+
scope: ConfigurationScope.LANGUAGE_OVERRIDABLE,
21+
description: nls.localize('pasteActions', "Enable/disable running edits from extensions on paste."),
22+
default: false,
23+
},
24+
}
25+
});

0 commit comments

Comments
 (0)