diff --git a/.scripts/samples.js b/.scripts/samples.js index 820e7408..4bc6e64e 100644 --- a/.scripts/samples.js +++ b/.scripts/samples.js @@ -264,6 +264,7 @@ const samples = [ { description: 'virtual-document-sample', excludeFromReadme: true, path: 'virtual-document-sample', guide: null, apis: [], contributions: [] }, { description: 'webpack-sample', excludeFromReadme: true, path: 'webpack-sample', guide: null, apis: [], contributions: [] }, { description: 'welcome-view-content-sample', excludeFromReadme: true, path: 'welcome-view-content-sample', guide: null, apis: [], contributions: [] }, + { description: 'document-paste', excludeFromReadme: true, path: 'document-paste', guide: null, apis: [], contributions: [] }, { description: 'drop-on-document', excludeFromReadme: true, path: 'drop-on-document', guide: null, apis: [], contributions: [] }, { description: 'uri-handler-sample', excludeFromReadme: true, path: 'uri-handler-sample', guide: null, apis: [], contributions: [] }, { description: 'authenticationprovider-sample', excludeFromReadme: true, path: 'authenticationprovider-sample', guide: null, apis: [], contributions: [] }, diff --git a/document-paste/.eslintrc.js b/document-paste/.eslintrc.js new file mode 100644 index 00000000..f660e395 --- /dev/null +++ b/document-paste/.eslintrc.js @@ -0,0 +1,20 @@ +/**@type {import('eslint').Linter.Config} */ +// eslint-disable-next-line no-undef +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + plugins: [ + '@typescript-eslint', + ], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + ], + rules: { + 'semi': [2, "always"], + '@typescript-eslint/no-unused-vars': 0, + '@typescript-eslint/no-explicit-any': 0, + '@typescript-eslint/explicit-module-boundary-types': 0, + '@typescript-eslint/no-non-null-assertion': 0, + } +}; \ No newline at end of file diff --git a/document-paste/.gitignore b/document-paste/.gitignore new file mode 100644 index 00000000..5fe00fea --- /dev/null +++ b/document-paste/.gitignore @@ -0,0 +1,4 @@ +out +node_modules +.vscode-test/ +*.vsix diff --git a/document-paste/.vscode/extensions.json b/document-paste/.vscode/extensions.json new file mode 100644 index 00000000..af515502 --- /dev/null +++ b/document-paste/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "dbaeumer.vscode-eslint" + ] +} \ No newline at end of file diff --git a/document-paste/.vscode/launch.json b/document-paste/.vscode/launch.json new file mode 100644 index 00000000..527cbf4b --- /dev/null +++ b/document-paste/.vscode/launch.json @@ -0,0 +1,35 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +// Use IntelliSense to learn about possible attributes. +// Hover to view descriptions of existing attributes. +// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +{ + "version": "0.2.0", + "configurations": [{ + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "preLaunchTask": "npm: watch" + }, + { + "name": "Run Extension Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test" + ], + "outFiles": [ + "${workspaceFolder}/out/test/**/*.js" + ], + "preLaunchTask": "npm: watch" + } + ] +} diff --git a/document-paste/.vscode/settings.json b/document-paste/.vscode/settings.json new file mode 100644 index 00000000..e46111f1 --- /dev/null +++ b/document-paste/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.insertSpaces": false +} \ No newline at end of file diff --git a/document-paste/.vscode/tasks.json b/document-paste/.vscode/tasks.json new file mode 100644 index 00000000..241aa6d9 --- /dev/null +++ b/document-paste/.vscode/tasks.json @@ -0,0 +1,20 @@ +// See https://go.microsoft.com/fwlink/?LinkId=733558 +// for the documentation about the tasks.json format +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never" + }, + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/document-paste/.vscodeignore b/document-paste/.vscodeignore new file mode 100644 index 00000000..ea75c676 --- /dev/null +++ b/document-paste/.vscodeignore @@ -0,0 +1,8 @@ +.vscode/** +.vscode-test/** +out/test/** +out/**/*.map +src/** +.gitignore +tsconfig.json +vsc-extension-quickstart.md diff --git a/document-paste/README.md b/document-paste/README.md new file mode 100644 index 00000000..4e0111ce --- /dev/null +++ b/document-paste/README.md @@ -0,0 +1,5 @@ +# Document Paste Edit Sample + +This sample shows usage of the [document paste edit proposal](https://github.com/microsoft/vscode/issues/30066). + +Requires enabling `editor.experimental.pasteActions.enabled` \ No newline at end of file diff --git a/document-paste/package.json b/document-paste/package.json new file mode 100644 index 00000000..f187fa56 --- /dev/null +++ b/document-paste/package.json @@ -0,0 +1,42 @@ +{ + "name": "document-paste-sample", + "displayName": "Document paste Sample", + "description": "Shows use of the document paste API proposal", + "version": "0.0.1", + "publisher": "vscode-samples", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-extension-samples" + }, + "enabledApiProposals": [ + "textEditorDrop", + "documentPaste" + ], + "engines": { + "vscode": "^1.67.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "onLanguage:plaintext" + ], + "main": "./out/extension.js", + "contributes": {}, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "lint": "eslint . --ext .ts,.tsx", + "watch": "tsc -watch -p ./" + }, + "devDependencies": { + "@types/node": "^16.11.7", + "@types/vscode": "^1.32.0", + "@typescript-eslint/eslint-plugin": "^5.19.0", + "@typescript-eslint/parser": "^5.19.0", + "eslint": "^8.13.0", + "typescript": "^4.7.2" + } +} diff --git a/document-paste/src/extension.ts b/document-paste/src/extension.ts new file mode 100644 index 00000000..58c49cb2 --- /dev/null +++ b/document-paste/src/extension.ts @@ -0,0 +1,55 @@ +import * as vscode from 'vscode'; + +/** + * Provider that maintains a count of the number of times it has copied text. + */ +class CopyCountPasteEditProvider implements vscode.DocumentPasteEditProvider { + + private readonly countMimeTypes = 'application/vnd.code.copydemo-copy-count'; + + private count = 0; + + prepareDocumentPaste?( + _document: vscode.TextDocument, + _range: vscode.Range, + dataTransfer: vscode.DataTransfer, + _token: vscode.CancellationToken + ): void | Thenable { + dataTransfer.set(this.countMimeTypes, new vscode.DataTransferItem(this.count++)); + } + + async provideDocumentPasteEdits( + _document: vscode.TextDocument, + range: vscode.Range, + dataTransfer: vscode.DataTransfer, + token: vscode.CancellationToken + ) { + const countDataTransferItem = dataTransfer.get(this.countMimeTypes) + if (!countDataTransferItem) { + return undefined; + } + + const textDataTransferItem = dataTransfer.get('text') ?? dataTransfer.get('text/plain'); + if (!textDataTransferItem) { + return undefined; + } + + const count = await countDataTransferItem.asString(); + const text = await textDataTransferItem.asString(); + + + // Build a snippet to insert + const snippet = new vscode.SnippetString(); + snippet.appendText(`(copy #${count}) ${text}`); + + return new vscode.SnippetTextEdit(range, snippet); + } +} + +export function activate(context: vscode.ExtensionContext) { + // Enable our provider in plaintext files + const selector: vscode.DocumentSelector = { language: 'plaintext' }; + + // Register our provider + context.subscriptions.push(vscode.languages.registerDocumentPasteEditProvider(selector, new CopyCountPasteEditProvider())); +} diff --git a/document-paste/src/vscode.proposed.documentPaste.d.ts b/document-paste/src/vscode.proposed.documentPaste.d.ts new file mode 100644 index 00000000..1777c8d4 --- /dev/null +++ b/document-paste/src/vscode.proposed.documentPaste.d.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * 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' { + + // https://github.com/microsoft/vscode/issues/30066/ + + /** + * Provider invoked when the user copies and pastes code. + */ + interface DocumentPasteEditProvider { + + /** + * Optional method invoked after the user copies text in a file. + * + * During {@link prepareDocumentPaste}, an extension can compute metadata that is attached to + * a {@link DataTransfer} and is passed back to the provider in {@link provideDocumentPasteEdits}. + * + * @param document Document where the copy took place. + * @param range Range being copied in the `document`. + * @param dataTransfer The data transfer associated with the copy. You can store additional values on this for later use in {@link provideDocumentPasteEdits}. + * @param token A cancellation token. + */ + prepareDocumentPaste?(document: TextDocument, range: Range, dataTransfer: DataTransfer, token: CancellationToken): void | Thenable; + + /** + * Invoked before the user pastes into a document. + * + * In this method, extensions can return a workspace edit that replaces the standard pasting behavior. + * + * @param document Document being pasted into + * @param range Currently selected range in the document. + * @param dataTransfer The data transfer associated with the paste. + * @param token A cancellation token. + * + * @return Optional workspace edit that applies the paste. Return undefined to use standard pasting. + */ + provideDocumentPasteEdits(document: TextDocument, range: Range, dataTransfer: DataTransfer, token: CancellationToken): ProviderResult; + } + + namespace languages { + export function registerDocumentPasteEditProvider(selector: DocumentSelector, provider: DocumentPasteEditProvider): Disposable; + } +} diff --git a/document-paste/src/vscode.proposed.textEditorDrop.d.ts b/document-paste/src/vscode.proposed.textEditorDrop.d.ts new file mode 100644 index 00000000..5468bc21 --- /dev/null +++ b/document-paste/src/vscode.proposed.textEditorDrop.d.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * 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' { + + // https://github.com/microsoft/vscode/issues/142990 + + export class SnippetTextEdit { + snippet: SnippetString; + range: Range; + constructor(range: Range, snippet: SnippetString); + } + + /** + * Provider which handles dropping of resources into a text editor. + * + * The user can drop into a text editor by holding down `shift` while dragging. Requires `workbench.experimental.editor.dropIntoEditor.enabled` to be on. + */ + export interface DocumentOnDropEditProvider { + /** + * Provide edits which inserts the content being dragged and dropped into the document. + * + * @param document The document in which the drop occurred. + * @param position The position in the document where the drop occurred. + * @param dataTransfer A {@link DataTransfer} object that holds data about what is being dragged and dropped. + * @param token A cancellation token. + * + * @return A {@link SnippetTextEdit} or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideDocumentOnDropEdits(document: TextDocument, position: Position, dataTransfer: DataTransfer, token: CancellationToken): ProviderResult; + } + + export namespace languages { + /** + * Registers a new {@link DocumentOnDropEditProvider}. + * + * @param selector A selector that defines the documents this provider applies to. + * @param provider A drop provider. + * + * @return A {@link Disposable} that unregisters this provider when disposed of. + */ + export function registerDocumentOnDropEditProvider(selector: DocumentSelector, provider: DocumentOnDropEditProvider): Disposable; + } +} diff --git a/document-paste/tsconfig.json b/document-paste/tsconfig.json new file mode 100644 index 00000000..f2ab0651 --- /dev/null +++ b/document-paste/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "lib": ["es2020"], + "outDir": "out", + "sourceMap": true, + "rootDir": "src", + "strict": true + }, + "exclude": ["node_modules", ".vscode-test"] +}