├── .prettierignore ├── .eslintignore ├── .DS_Store ├── .gitignore ├── src ├── types │ ├── original-fs.d.ts │ └── vscode.proposed.d.ts ├── util.ts ├── commads │ ├── common.ts │ ├── apiGetMonitor.ts │ ├── copyToHistory.ts │ ├── setClipboardValue.ts │ ├── removeClipboardHistory.ts │ ├── clearClipboardHistory.ts │ ├── historyTreeDoubleClick.ts │ ├── showClipboardInFile.ts │ └── pickAndPaste.ts ├── test │ ├── extension.test.ts │ ├── runTests.ts │ ├── index.ts │ ├── common.ts │ ├── defaultClipboard.test.ts │ ├── completion.test.ts │ ├── monitor.test.ts │ ├── pickAndPaste.test.ts │ └── istanbultestrunner.ts ├── completion.ts ├── tree │ └── history.ts ├── clipboard.ts ├── tools │ └── organize.ts ├── monitor.ts ├── extension.ts └── manager.ts ├── .gitattributes ├── media ├── clipboard.jpg ├── clipboard.png └── clipboard.svg ├── screenshots ├── copy.gif └── pick-and-paste.gif ├── .vscode ├── settings.json ├── extensions.json ├── tasks.json └── launch.json ├── .vscodeignore ├── .prettierrc.js ├── coverconfig.json ├── .release-it.yml ├── resources ├── dark │ ├── remove.svg │ ├── clear-history.svg │ └── string.svg └── light │ ├── remove.svg │ ├── clear-history.svg │ └── string.svg ├── .dependabot └── config.yml ├── .codecov.yml ├── .github └── workflows │ ├── lint.yml │ ├── deploy.yml │ └── test.yml ├── .eslintrc.js ├── tsconfig.json ├── LICENSE ├── README.md ├── CHANGELOG.md └── package.json /.prettierignore: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/**/*.d.ts 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/vscode-clipboard/master/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode-test/ 2 | *.vsix 3 | coverage/ 4 | node_modules/ 5 | out/ 6 | test-reports/ -------------------------------------------------------------------------------- /src/types/original-fs.d.ts: -------------------------------------------------------------------------------- 1 | declare module "original-fs" { 2 | export * from "fs"; 3 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically normalize line endings. 2 | * text=auto 3 | 4 | -------------------------------------------------------------------------------- /media/clipboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/vscode-clipboard/master/media/clipboard.jpg -------------------------------------------------------------------------------- /media/clipboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/vscode-clipboard/master/media/clipboard.png -------------------------------------------------------------------------------- /screenshots/copy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/vscode-clipboard/master/screenshots/copy.gif -------------------------------------------------------------------------------- /screenshots/pick-and-paste.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/vscode-clipboard/master/screenshots/pick-and-paste.gif -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "out": false 4 | }, 5 | "search.exclude": { 6 | "out": true 7 | }, 8 | "typescript.tsc.autoDetect": "off" 9 | } 10 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .* 2 | .** 3 | .**/** 4 | *.vsix 5 | bin/** 6 | coverage/** 7 | coverconfig.json 8 | out/**/*.map 9 | out/test/** 10 | out/tools/** 11 | package-lock.json 12 | screenshots/** 13 | src/** 14 | test-reports/** 15 | tsconfig.json 16 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint", // 6 | "esbenp.prettier-vscode" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: "avoid", 3 | semi: true, 4 | singleQuote: false, 5 | trailingComma: "es5", 6 | overrides: [ 7 | { 8 | files: "*.json", 9 | options: { 10 | tabWidth: 4, 11 | }, 12 | }, 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /coverconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "relativeSourcePath": "../../out", 4 | "relativeCoverageDir": "../../coverage", 5 | "ignorePatterns": ["**/node_modules/**", "test/**", "tools/**"], 6 | "includePid": false, 7 | "reports": ["json", "html", "lcov"], 8 | "verbose": false 9 | } -------------------------------------------------------------------------------- /.release-it.yml: -------------------------------------------------------------------------------- 1 | git: 2 | commitMessage: "chore(release): v${version}" 3 | tagAnnotation: "chore(release): v${version}" 4 | tagName: "v${version}" 5 | 6 | hooks: 7 | after:bump: 8 | - "npm run changelog:update" 9 | - "npm run organize" 10 | 11 | npm: 12 | publish: false 13 | private: true 14 | registry: "OMITTED" 15 | -------------------------------------------------------------------------------- /resources/dark/remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.dependabot/config.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | update_configs: 3 | - package_manager: "javascript" 4 | directory: "/" 5 | update_schedule: "live" 6 | ignored_updates: 7 | - match: 8 | dependency_name: "@types/node" 9 | - match: 10 | dependency_name: "@types/vscode" 11 | automerged_updates: 12 | - match: 13 | dependency_type: "all" 14 | -------------------------------------------------------------------------------- /resources/dark/clear-history.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/light/clear-history.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | export interface IDisposable { 2 | dispose(): void; 3 | } 4 | 5 | export function toDisposable(dispose: () => void): IDisposable { 6 | return { dispose }; 7 | } 8 | 9 | export function leftPad( 10 | value: string | number, 11 | size: number, 12 | char: string = " " 13 | ) { 14 | const chars = char.repeat(size); 15 | 16 | const paddedNumber = `${chars}${value}`.substr(-chars.length); 17 | 18 | return paddedNumber; 19 | } 20 | 21 | export function sleep(ms: number) { 22 | return new Promise(resolve => setTimeout(resolve, ms)); 23 | } 24 | -------------------------------------------------------------------------------- /src/commads/common.ts: -------------------------------------------------------------------------------- 1 | export enum commandList { 2 | apiGetMonitor = "clipboard-manager.api.getMonitor", 3 | clearClipboardHistory = "clipboard-manager.history.clear", 4 | copyToHistory = "clipboard-manager.editor.copyToHistory", 5 | historyTreeDoubleClick = "clipboard-manager.historyTree.doubleClick", 6 | pickAndPaste = "clipboard-manager.editor.pickAndPaste", 7 | removeClipboardHistory = "clipboard-manager.history.remove", 8 | setClipboardValue = "clipboard-manager.setClipboardValue", 9 | showClipboardInFile = "clipboard-manager.editor.showClipboardInFile", 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 # 2 = xx.xx%, 0 = xx% 3 | round: down # down|up|nearest 4 | range: 40...70 # default 70...90, red...green 5 | 6 | status: 7 | project: # measuring the overall project coverage 8 | default: # context, you can create multiple ones with custom titles 9 | enabled: yes # must be yes|true to enable this status 10 | threshold: 5 # allowed to drop X% and still result in a "success" commit status 11 | 12 | #Not interesting at the moment 13 | patch: off # measures lines adjusted in the pull request or single commit, if the commit is not in a pull request. 14 | -------------------------------------------------------------------------------- /src/commads/apiGetMonitor.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { Monitor } from "../monitor"; 3 | import { commandList } from "./common"; 4 | 5 | export class ApiGetMonitor implements vscode.Disposable { 6 | private _disposable: vscode.Disposable[] = []; 7 | 8 | constructor(protected monitor: Monitor) { 9 | this._disposable.push( 10 | vscode.commands.registerCommand( 11 | commandList.apiGetMonitor, 12 | this.execute, 13 | this 14 | ) 15 | ); 16 | } 17 | 18 | protected async execute() { 19 | return this.monitor; 20 | } 21 | 22 | public dispose() { 23 | this._disposable.forEach(d => d.dispose()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/commads/copyToHistory.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { commandList } from "./common"; 3 | import { Monitor } from "../monitor"; 4 | 5 | export class CopyToHistoryCommand implements vscode.Disposable { 6 | private _disposable: vscode.Disposable[] = []; 7 | 8 | constructor(protected monitor: Monitor) { 9 | this._disposable.push( 10 | vscode.commands.registerCommand( 11 | commandList.copyToHistory, 12 | this.execute, 13 | this 14 | ) 15 | ); 16 | } 17 | 18 | protected async execute() { 19 | await vscode.commands.executeCommand("editor.action.clipboardCopyAction"); 20 | await this.monitor.checkChangeText(); 21 | } 22 | 23 | public dispose() { 24 | this._disposable.forEach(d => d.dispose()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/commads/setClipboardValue.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { ClipboardManager } from "../manager"; 3 | import { commandList } from "./common"; 4 | 5 | export class SetClipboardValueCommand implements vscode.Disposable { 6 | private _disposable: vscode.Disposable[] = []; 7 | 8 | constructor(protected _manager: ClipboardManager) { 9 | this._disposable.push( 10 | vscode.commands.registerCommand( 11 | commandList.setClipboardValue, 12 | this.execute, 13 | this 14 | ) 15 | ); 16 | } 17 | 18 | protected async execute(value: string) { 19 | // Update current clip in clipboard 20 | await this._manager.setClipboardValue(value); 21 | } 22 | 23 | public dispose() { 24 | this._disposable.forEach(d => d.dispose()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - "*" 7 | pull_request: 8 | branches: 9 | - "*" 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | 18 | - name: Install Dependencies 19 | run: npm install 20 | 21 | - uses: haya14busa/action-cond@v1 22 | id: cond_report 23 | with: 24 | cond: ${{ github.event_name == 'push' }} 25 | if_true: "github-check" 26 | if_false: "github-pr-review" 27 | 28 | - name: Lint source 29 | uses: reviewdog/action-eslint@v1 30 | with: 31 | github_token: ${{ secrets.github_token }} 32 | reporter: "${{ steps.cond_report.outputs.value }}" 33 | eslint_flags: "src --ext .ts" 34 | -------------------------------------------------------------------------------- /src/test/extension.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from "assert"; 8 | import * as vscode from "vscode"; 9 | import { activateExtension } from "./common"; 10 | 11 | suiteSetup(async function () { 12 | if (!(await activateExtension())) { 13 | this.skip(); 14 | } 15 | }); 16 | 17 | suite("Extension Tests", function () { 18 | test("Active Extension", async function () { 19 | const ext = vscode.extensions.getExtension( 20 | "EdgardMessias.clipboard-manager" 21 | ) as vscode.Extension; 22 | 23 | assert.ok(ext, "Extension not found"); 24 | 25 | assert.equal(ext.isActive, true, "Extension not activated"); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | parserOptions: { 4 | project: "./tsconfig.json", 5 | }, 6 | plugins: ["@typescript-eslint"], 7 | extends: [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "prettier/@typescript-eslint", 12 | "plugin:prettier/recommended", 13 | ], 14 | rules: { 15 | "@typescript-eslint/explicit-function-return-type": "off", 16 | "@typescript-eslint/no-explicit-any": "off", 17 | "@typescript-eslint/interface-name-prefix": "off", 18 | "@typescript-eslint/no-non-null-assertion": "off", 19 | "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], 20 | "@typescript-eslint/no-inferrable-types": "off", 21 | "no-empty": ["error", { allowEmptyCatch: true }], 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | /* Strict Type-Checking Option */ 12 | "strict": true, /* enable all strict type-checking options */ 13 | /* Additional Checks */ 14 | "noUnusedLocals": true, /* Report errors on unused locals. */ 15 | "resolveJsonModule": true, 16 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 17 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 18 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 19 | }, 20 | "exclude": [ 21 | "node_modules", 22 | ".vscode-test" 23 | ] 24 | } -------------------------------------------------------------------------------- /src/test/runTests.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { downloadAndUnzipVSCode, runTests } from "vscode-test"; 3 | 4 | async function go() { 5 | if (process.argv.includes("--download-only")) { 6 | await downloadAndUnzipVSCode(process.env.CODE_VERSION); 7 | return; 8 | } 9 | 10 | const extensionDevelopmentPath = path.resolve(__dirname, "../../"); 11 | let extensionTestsPath = path.resolve(__dirname, "../../out/test"); 12 | 13 | if (process.env.CODE_TESTS_PATH) { 14 | extensionTestsPath = process.env.CODE_TESTS_PATH; 15 | } 16 | 17 | /** 18 | * Basic usage 19 | */ 20 | try { 21 | await runTests({ 22 | version: process.env.CODE_VERSION, 23 | extensionDevelopmentPath, 24 | extensionTestsPath, 25 | launchArgs: ["--disable-extensions"], 26 | }); 27 | } catch (err) { 28 | console.error("Failed to run tests"); 29 | console.error(err); 30 | process.exit(1); 31 | } 32 | } 33 | 34 | go(); 35 | -------------------------------------------------------------------------------- /src/commads/removeClipboardHistory.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { ClipboardManager } from "../manager"; 3 | import { ClipHistoryItem } from "../tree/history"; 4 | import { commandList } from "./common"; 5 | 6 | export class RemoveClipboardHistory implements vscode.Disposable { 7 | private _disposable: vscode.Disposable[] = []; 8 | 9 | constructor(protected _manager: ClipboardManager) { 10 | this._disposable.push( 11 | vscode.commands.registerCommand( 12 | commandList.removeClipboardHistory, 13 | this.execute, 14 | this 15 | ) 16 | ); 17 | } 18 | 19 | protected async execute(value: string | ClipHistoryItem) { 20 | if (value instanceof ClipHistoryItem) { 21 | value = value.clip.value; 22 | } 23 | 24 | // Update current clip in clipboard 25 | await this._manager.removeClipboardValue(value); 26 | } 27 | 28 | public dispose() { 29 | this._disposable.forEach(d => d.dispose()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | 7 | import * as IstanbulTestRunner from "./istanbultestrunner"; 8 | 9 | const testRunner = IstanbulTestRunner; 10 | 11 | const reporter = process.env.MOCHA_REPORTER || "spec"; 12 | 13 | const mochaOpts: Mocha.MochaOptions = { 14 | ui: "tdd", // the TDD UI is being used in extension.test.ts (suite, test, etc.) 15 | color: true, // colored output from test results, 16 | timeout: 10000, // default timeout: 10 seconds 17 | retries: 1, 18 | reporter: "mocha-multi-reporters", 19 | reporterOptions: { 20 | reporterEnabled: reporter, 21 | }, 22 | }; 23 | 24 | testRunner.configure( 25 | mochaOpts, 26 | // Coverage configuration options 27 | { 28 | coverConfig: "../../coverconfig.json", 29 | } 30 | ); 31 | 32 | module.exports = testRunner; 33 | -------------------------------------------------------------------------------- /src/commads/clearClipboardHistory.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { ClipboardManager } from "../manager"; 3 | import { commandList } from "./common"; 4 | 5 | export class ClearClipboardHistory implements vscode.Disposable { 6 | private _disposable: vscode.Disposable[] = []; 7 | 8 | constructor(protected _manager: ClipboardManager) { 9 | this._disposable.push( 10 | vscode.commands.registerCommand( 11 | commandList.clearClipboardHistory, 12 | this.execute, 13 | this 14 | ) 15 | ); 16 | } 17 | 18 | protected async execute() { 19 | const yes = "Yes"; 20 | const response = await vscode.window.showWarningMessage( 21 | "Do you really want to clear the history list?", 22 | { 23 | modal: true, 24 | }, 25 | yes 26 | ); 27 | 28 | if (response === yes) { 29 | this._manager.clearAll(); 30 | } 31 | } 32 | 33 | public dispose() { 34 | this._disposable.forEach(d => d.dispose()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/common.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { ClipboardCompletion } from "../completion"; 3 | import { ClipboardManager } from "../manager"; 4 | 5 | // eslint-disable-next-line @typescript-eslint/no-var-requires 6 | const pkg = require("../../package.json"); 7 | 8 | const EXTENSION_ID = `${pkg.publisher}.${pkg.name}`; 9 | 10 | interface ExtensionAPI { 11 | completion: ClipboardCompletion; 12 | manager: ClipboardManager; 13 | } 14 | 15 | export function getExtension() { 16 | return vscode.extensions.getExtension(EXTENSION_ID); 17 | } 18 | 19 | export async function activateExtension() { 20 | const ext = getExtension(); 21 | 22 | if (!ext) { 23 | return false; 24 | } 25 | 26 | if (!ext.isActive) { 27 | await ext.activate(); 28 | } 29 | 30 | return ext.isActive; 31 | } 32 | 33 | export async function showSidebar() { 34 | try { 35 | await vscode.commands.executeCommand( 36 | "workbench.view.extension.clipboard-manager" 37 | ); 38 | // tslint:disable-next-line:no-empty 39 | } catch (error) {} 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2020 Edgard Messias 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/test/defaultClipboard.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import * as sinon from "sinon"; 3 | import { BaseClipboard, getNewDefaultInstance } from "../clipboard"; 4 | import { activateExtension } from "./common"; 5 | 6 | suiteSetup(async function () { 7 | if (!(await activateExtension())) { 8 | this.skip(); 9 | } 10 | }); 11 | 12 | // Defines a Mocha test suite to group tests of similar kind together 13 | suite("Clipboard Tests", function () { 14 | let sandbox: sinon.SinonSandbox; 15 | 16 | let clipboard: BaseClipboard; 17 | 18 | setup(async function () { 19 | sandbox = sinon.createSandbox(); 20 | 21 | clipboard = getNewDefaultInstance(); 22 | 23 | await clipboard.writeText("Initial Value"); 24 | }); 25 | 26 | teardown(function () { 27 | clipboard.dispose(); 28 | 29 | sandbox.restore(); 30 | }); 31 | 32 | test("Read clipboard", async function () { 33 | const clip = await clipboard.readText(); 34 | 35 | assert.ok(clip === "Initial Value"); 36 | }); 37 | 38 | test("Read/Write Clipboard", async function () { 39 | await clipboard.writeText("test"); 40 | 41 | const actual = await clipboard.readText(); 42 | 43 | assert.equal(actual, "test"); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/out/**/*.js" 18 | ], 19 | "preLaunchTask": "npm: watch" 20 | }, 21 | { 22 | "name": "Extension Tests", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "runtimeExecutable": "${execPath}", 26 | "args": [ 27 | "--disable-extensions", 28 | "--extensionDevelopmentPath=${workspaceFolder}", 29 | "--extensionTestsPath=${workspaceFolder}/out/test" 30 | ], 31 | "outFiles": [ 32 | "${workspaceFolder}/out/test/**/*.js" 33 | ], 34 | "preLaunchTask": "npm: watch" 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 粘贴板管理 2 | 3 | 用于保留项目的复制和剪切内容,并可在面板的历史记录中重新选择粘贴,而不必再重复使用 `Ctrl + C` 和 `Ctrl + V` 的快捷键。 4 | 5 | 要选择复制的内容,只需使用 `Ctrl+Shift+V` 快捷键。 6 | 7 | ## 特性 8 | 9 | 1. 保存所有复制和剪切内容的历史记录 10 | 1. 可以检查 VSCode 之外的复制内容(`"clipboard-manager.onlyWindowFocused": false`) 11 | 1. 从历史记录中粘贴 (`Ctrl+Shift+V` => 选择并粘贴) 12 | 1. 预览粘贴的内容 13 | 1. 使用复制片段粘贴 (例如: `clip01, clip02, ...`) 14 | 1. 从历史记录中删除所选内容 15 | 1. 清除所有历史记录 16 | 1. 打开历史记录的位置 17 | 1. 在历史记录面板中双击来粘贴 18 | 19 | ## 插件设置 20 | 21 | 该插件提供以下设置(默认值): 22 | 23 | ```js 24 | { 25 | // 避免列表中出现重复的内容。 26 | "clipboard-manager.avoidDuplicates": true, 27 | // 检查剪贴板中更改的时间(以毫秒为单位),设置为零以禁用。 28 | "clipboard-manager.checkInterval": 500, 29 | // 剪贴板的最大大小(以字节为单位)。 30 | "clipboard-manager.maxClipboardSize": 1000000, 31 | // 要保存在剪贴板中的最大片段数。 32 | "clipboard-manager.maxClips": 100, 33 | // 将使用过的剪辑移到列表顶部。 34 | "clipboard-manager.moveToTop": true, 35 | // 仅从 VSCode 获取复制内容。 36 | "clipboard-manager.onlyWindowFocused": true, 37 | // 选择复制时时查看并预览。 38 | "clipboard-manager.preview": true, 39 | // 设置保存剪贴板文件的位置,设置为 false 禁用。 40 | "clipboard-manager.saveTo": null, 41 | // 启用完成的片段 42 | "clipboard-manager.snippet.enabled": true, 43 | // 片段中建议的最大片段数(全部为零)。 44 | "clipboard-manager.snippet.max": 10, 45 | // 代码段完成的默认前缀(clip1,clip2等) 46 | "clipboard-manager.snippet.prefix": "clip" 47 | } 48 | ``` 49 | 50 | ## 示例 51 | 52 | 复制到历史记录: 53 | 54 | ![Clipboard Manager - Copy](screenshots/copy.gif) 55 | 56 | 选择并粘贴: 57 | 58 | ![Clipboard Manager - Pick and Paste](screenshots/pick-and-paste.gif) 59 | 60 | ## 感谢 61 | 62 | 感谢原作者的贡献,希望能帮助到你! 63 | 64 | | [
Edgardmessias](https://github.com/edgardmessias) | 65 | |-| -------------------------------------------------------------------------------- /src/commads/historyTreeDoubleClick.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { ClipboardManager } from "../manager"; 3 | import { IClipboardTextChange } from "../monitor"; 4 | import { commandList } from "./common"; 5 | 6 | /** 7 | * Command to paste from double click on history item 8 | */ 9 | export class HistoryTreeDoubleClickCommand implements vscode.Disposable { 10 | private _disposable: vscode.Disposable[] = []; 11 | 12 | private prevClip: IClipboardTextChange | undefined; 13 | private prevTime = Date.now(); 14 | 15 | constructor(protected _manager: ClipboardManager) { 16 | this._disposable.push( 17 | vscode.commands.registerCommand( 18 | commandList.historyTreeDoubleClick, 19 | this.execute, 20 | this 21 | ) 22 | ); 23 | } 24 | 25 | /** 26 | * Emulate double click on tree view history 27 | * @param clip 28 | */ 29 | protected async execute(clip: IClipboardTextChange) { 30 | const now = Date.now(); 31 | if (this.prevClip !== clip) { 32 | this.prevClip = clip; 33 | this.prevTime = now; 34 | return; 35 | } 36 | 37 | const diff = now - this.prevTime; 38 | this.prevTime = now; 39 | 40 | if (diff > 500) { 41 | return; 42 | } 43 | 44 | // Reset double click 45 | this.prevClip = undefined; 46 | 47 | // Update current clip in clipboard 48 | await this._manager.setClipboardValue(clip.value); 49 | 50 | // Force to focus on editor to paste command works 51 | await vscode.commands.executeCommand( 52 | "workbench.action.focusActiveEditorGroup" 53 | ); 54 | 55 | // Run default paste 56 | return await vscode.commands.executeCommand( 57 | "editor.action.clipboardPasteAction" 58 | ); 59 | } 60 | 61 | public dispose() { 62 | this._disposable.forEach(d => d.dispose()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/commads/showClipboardInFile.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { ClipboardManager } from "../manager"; 3 | import { ClipHistoryItem } from "../tree/history"; 4 | import { commandList } from "./common"; 5 | 6 | export class ShowClipboardInFile implements vscode.Disposable { 7 | private _disposable: vscode.Disposable[] = []; 8 | 9 | constructor(protected _manager: ClipboardManager) { 10 | this._disposable.push( 11 | vscode.commands.registerCommand( 12 | commandList.showClipboardInFile, 13 | this.execute, 14 | this 15 | ) 16 | ); 17 | } 18 | 19 | protected async execute(item: ClipHistoryItem) { 20 | const clip = item.clip; 21 | 22 | if (!clip.createdLocation) { 23 | return; 24 | } 25 | 26 | const uri = clip.createdLocation.uri; 27 | 28 | const document = await vscode.workspace.openTextDocument(uri); 29 | 30 | const opts: vscode.TextDocumentShowOptions = { 31 | viewColumn: vscode.ViewColumn.Active, 32 | }; 33 | 34 | if (document.getText(clip.createdLocation.range) === clip.value) { 35 | opts.selection = clip.createdLocation.range; 36 | } else { 37 | // Find current position of value 38 | const indexes: number[] = []; 39 | const text = document.getText(); 40 | let lastIndex = text.indexOf(clip.value); 41 | 42 | while (lastIndex >= 0) { 43 | indexes.push(lastIndex); 44 | lastIndex = text.indexOf(clip.value, lastIndex + 1); 45 | } 46 | 47 | if (indexes.length >= 0) { 48 | const offset = document.offsetAt(clip.createdLocation.range.start); 49 | 50 | // Sort by distance of initial location 51 | indexes.sort((a, b) => Math.abs(a - offset) - Math.abs(b - offset)); 52 | 53 | const index = indexes[0]; 54 | if (index >= 0) { 55 | const range = new vscode.Range( 56 | document.positionAt(index), 57 | document.positionAt(index + clip.value.length) 58 | ); 59 | opts.selection = range; 60 | } 61 | } 62 | } 63 | 64 | await vscode.window.showTextDocument(document, opts); 65 | } 66 | 67 | public dispose() { 68 | this._disposable.forEach(d => d.dispose()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Extension 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Fetching tags 18 | run: git fetch --tags -f || true 19 | 20 | - name: Install Dependencies 21 | run: npm install 22 | 23 | - name: Generate Changelog 24 | id: generate_changelog 25 | run: | 26 | changelog=$(npm run changelog:last --silent) 27 | changelog="${changelog//$'\n'/'%0A'}" 28 | changelog="${changelog//$'\r'/'%0D'}" 29 | echo -e "set-output name=changelog::${changelog-}\n" 30 | echo -e "::set-output name=changelog::${changelog}\n" 31 | 32 | - name: Package extension 33 | uses: lannonbr/vsce-action@master 34 | with: 35 | args: "package -o clipboard-manager.vsix" 36 | 37 | - name: Create Release 38 | id: create_release 39 | uses: actions/create-release@v1 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | with: 43 | tag_name: ${{ github.ref }} 44 | release_name: ${{ github.ref }} 45 | body: ${{ steps.generate_changelog.outputs.changelog }} 46 | draft: false 47 | prerelease: false 48 | 49 | - name: Upload Release Asset 50 | id: upload-release-asset 51 | uses: actions/upload-release-asset@v1.0.1 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | with: 55 | upload_url: ${{ steps.create_release.outputs.upload_url }} 56 | asset_path: ./clipboard-manager.vsix 57 | asset_name: clipboard-manager.vsix 58 | asset_content_type: application/zip 59 | 60 | - name: Publish in marketplace 61 | uses: lannonbr/vsce-action@master 62 | with: 63 | args: "publish -p $VSCE_TOKEN" 64 | env: 65 | VSCE_TOKEN: ${{ secrets.VSCE_TOKEN }} 66 | 67 | - name: Publish in Open VSX Registry 68 | run: npx ovsx publish clipboard-manager.vsix -p $OVSX_TOKEN 69 | env: 70 | OVSX_TOKEN: ${{ secrets.OVSX_TOKEN }} 71 | -------------------------------------------------------------------------------- /src/completion.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { commandList } from "./commads/common"; 3 | import { ClipboardManager } from "./manager"; 4 | import { leftPad } from "./util"; 5 | 6 | export class ClipboardCompletion implements vscode.CompletionItemProvider { 7 | constructor(protected manager: ClipboardManager) {} 8 | 9 | public provideCompletionItems( 10 | document: vscode.TextDocument, 11 | _position: vscode.Position, 12 | _token: vscode.CancellationToken, 13 | _context: vscode.CompletionContext 14 | ): vscode.ProviderResult { 15 | const config = vscode.workspace.getConfiguration( 16 | "clipboard-manager", 17 | document.uri 18 | ); 19 | 20 | const enabled = config.get("snippet.enabled", true); 21 | 22 | if (!enabled) { 23 | return null; 24 | } 25 | 26 | const prefix = config.get("snippet.prefix", "clip"); 27 | const maxSnippets = config.get("snippet.max", 10); 28 | 29 | const clips = 30 | maxSnippets > 0 31 | ? this.manager.clips.slice(0, maxSnippets) 32 | : this.manager.clips; 33 | 34 | const maxLength = `${clips.length}`.length; 35 | 36 | const completions: vscode.CompletionItem[] = clips.map((clip, index) => { 37 | // Add left zero pad from max number of clips 38 | const indexNumber = leftPad(index + 1, maxLength, "0"); 39 | 40 | const c: vscode.CompletionItem = { 41 | label: `${prefix}${indexNumber}`, 42 | detail: `Clipboard ${indexNumber}`, 43 | insertText: clip.value, 44 | kind: vscode.CompletionItemKind.Text, 45 | filterText: `${prefix}${indexNumber} ${clip.value}`, 46 | }; 47 | 48 | // Highlight the syntax of clip 49 | c.documentation = new vscode.MarkdownString(); 50 | c.documentation.appendCodeblock(clip.value, clip.language); 51 | 52 | if (clip.createdAt) { 53 | const date = new Date(clip.createdAt); 54 | c.detail += " - " + date.toLocaleString(); 55 | } 56 | 57 | c.command = { 58 | command: commandList.setClipboardValue, 59 | title: "Paste", 60 | tooltip: "Paste", 61 | arguments: [clip.value], 62 | }; 63 | 64 | return c; 65 | }); 66 | 67 | return completions; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/tree/history.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as vscode from "vscode"; 3 | import { commandList } from "../commads/common"; 4 | import { ClipboardManager, IClipboardItem } from "../manager"; 5 | import { leftPad } from "../util"; 6 | 7 | export class ClipHistoryItem extends vscode.TreeItem { 8 | constructor(readonly clip: IClipboardItem) { 9 | super(clip.value); 10 | 11 | this.contextValue = "clipHistoryItem:"; 12 | this.label = this.clip.value.replace(/\s+/g, " ").trim(); 13 | this.tooltip = this.clip.value; 14 | 15 | this.command = { 16 | command: commandList.historyTreeDoubleClick, 17 | title: "Paste", 18 | tooltip: "Paste", 19 | arguments: [this.clip], 20 | }; 21 | 22 | if (this.clip.createdLocation) { 23 | this.resourceUri = this.clip.createdLocation.uri; 24 | this.contextValue += "file"; 25 | 26 | this.tooltip = `File: ${this.resourceUri.fsPath}\nValue: ${this.tooltip}\n`; 27 | } else { 28 | const basePath = path.join(__filename, "..", "..", "..", "resources"); 29 | 30 | this.iconPath = { 31 | light: path.join(basePath, "light", "string.svg"), 32 | dark: path.join(basePath, "dark", "string.svg"), 33 | }; 34 | } 35 | } 36 | } 37 | 38 | export class ClipboardTreeDataProvider 39 | implements vscode.TreeDataProvider, vscode.Disposable { 40 | private _disposables: vscode.Disposable[] = []; 41 | 42 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 43 | public readonly onDidChangeTreeData: vscode.Event = this 44 | ._onDidChangeTreeData.event; 45 | 46 | constructor(protected _manager: ClipboardManager) { 47 | this._manager.onDidChangeClipList(() => { 48 | this._onDidChangeTreeData.fire(); 49 | }); 50 | } 51 | 52 | public getTreeItem( 53 | element: ClipHistoryItem 54 | ): vscode.TreeItem | Thenable { 55 | return element; 56 | } 57 | 58 | public getChildren( 59 | _element?: ClipHistoryItem | undefined 60 | ): vscode.ProviderResult { 61 | const clips = this._manager.clips; 62 | 63 | const maxLength = `${clips.length}`.length; 64 | 65 | const childs = clips.map((c, index) => { 66 | const item = new ClipHistoryItem(c); 67 | const indexNumber = leftPad(index + 1, maxLength, "0"); 68 | 69 | item.label = `${indexNumber}) ${item.label}`; 70 | 71 | return item; 72 | }); 73 | 74 | return childs; 75 | } 76 | 77 | public dispose() { 78 | this._disposables.forEach(d => d.dispose()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/clipboard.ts: -------------------------------------------------------------------------------- 1 | import * as clipboardy from "clipboardy"; 2 | import * as vscode from "vscode"; 3 | 4 | /** 5 | * Clipboard base class to read and write text and detect changes 6 | */ 7 | export abstract class BaseClipboard { 8 | protected _disposables: vscode.Disposable[] = []; 9 | 10 | private _onDidWillWriteText = new vscode.EventEmitter(); 11 | public readonly onDidWillWriteText = this._onDidWillWriteText.event; 12 | 13 | private _onDidWriteText = new vscode.EventEmitter(); 14 | public readonly onDidWriteText = this._onDidWriteText.event; 15 | 16 | constructor() { 17 | this._disposables.push(this._onDidWillWriteText); 18 | this._disposables.push(this._onDidWriteText); 19 | } 20 | 21 | public readText(): Thenable { 22 | return this.readTextInternal(); 23 | } 24 | 25 | public async writeText(value: string) { 26 | this._onDidWillWriteText.fire(value); 27 | 28 | await this.writeTextInternal(value); 29 | 30 | this._onDidWriteText.fire(value); 31 | } 32 | 33 | protected abstract readTextInternal(): Thenable; 34 | protected abstract writeTextInternal(value: string): Thenable; 35 | 36 | public dispose() { 37 | this._disposables.forEach(d => d.dispose()); 38 | } 39 | } 40 | 41 | export class VSCodeClipboard extends BaseClipboard { 42 | protected readTextInternal(): Thenable { 43 | return vscode.env.clipboard.readText(); 44 | } 45 | protected writeTextInternal(value: string): Thenable { 46 | return vscode.env.clipboard.writeText(value); 47 | } 48 | } 49 | 50 | export class ClipboardyClipboard extends BaseClipboard { 51 | protected readTextInternal(): Thenable { 52 | let promise = clipboardy.read(); 53 | 54 | /** 55 | * Fix problem in `clipboardy` when clipboard text is empty on windows 56 | * Example: After power up or after a print screen 57 | */ 58 | if (process.platform === "win32") { 59 | promise = promise.then(null, (reason: any) => { 60 | const ignoreMessage = 61 | "thread 'main' panicked at 'Error: Could not paste from clipboard: Error { repr: Os { code: 0, message:"; 62 | 63 | if (reason.stderr && reason.stderr.startsWith(ignoreMessage)) { 64 | // return empty content 65 | return ""; 66 | } 67 | 68 | throw reason; 69 | }); 70 | } 71 | 72 | return promise; 73 | } 74 | protected writeTextInternal(value: string): Thenable { 75 | return clipboardy.write(value); 76 | } 77 | } 78 | 79 | export function getNewDefaultInstance() { 80 | let clipboard; 81 | 82 | try { 83 | vscode.env.clipboard.readText(); 84 | clipboard = new VSCodeClipboard(); 85 | // tslint:disable-next-line:no-empty 86 | } catch (error) {} 87 | 88 | if (!clipboard) { 89 | clipboard = new ClipboardyClipboard(); 90 | } 91 | 92 | return clipboard; 93 | } 94 | 95 | export const defaultClipboard: BaseClipboard = getNewDefaultInstance(); 96 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - "*" 7 | pull_request: 8 | branches: 9 | - "*" 10 | 11 | jobs: 12 | test: 13 | if: "!contains(github.event.head_commit.message, 'skip ci')" 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [ubuntu, macos, windows] 18 | version: ["1.26.0", "stable", "insiders"] 19 | env: 20 | MOCHA_COLORS: 1 21 | MOCHA_REPORTER: "mocha-github-actions-reporter" 22 | runs-on: ${{ matrix.os }}-latest 23 | name: ${{ matrix.os }} (VSCode ${{ matrix.version }}) 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v2 27 | 28 | - name: Install Node.js 29 | uses: actions/setup-node@v1 30 | with: 31 | node-version: 10.x 32 | 33 | - name: Install Dependencies 34 | run: npm ci 35 | 36 | - name: Enable full coverage 37 | run: sed -i.bak 's/"\*"//g' package.json 38 | shell: bash 39 | 40 | - name: Cache VSCode binary 41 | id: cache-vscode 42 | uses: actions/cache@v1 43 | with: 44 | path: .vscode-test 45 | key: vscode-${{ runner.os }}-${{ matrix.version }}-${{ github.run_id }} 46 | restore-keys: | 47 | vscode-${{ runner.os }}-${{ matrix.version }}- 48 | vscode-${{ runner.os }}- 49 | 50 | - name: Run tests 51 | uses: GabrielBB/xvfb-action@v1.0 52 | with: 53 | run: npm test 54 | env: 55 | CODE_VERSION: ${{ matrix.version }} 56 | continue-on-error: ${{ matrix.version == 'insiders' }} 57 | 58 | - name: Upload coverage 59 | run: | 60 | cover() { 61 | curl -s https://codecov.io/bash -o codecov.sh 62 | bash codecov.sh -X gcov -f ./coverage/coverage-final.json -f ./coverage/lcov.info 63 | } 64 | for i in {1..5}; do cover && break || sleep 5; done 65 | shell: bash 66 | env: 67 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 68 | CODECOV_NAME: ${{ matrix.os }} ${{ matrix.version }} 69 | 70 | - name: Remove old versions of VSCode 71 | run: ls -1 -d .vscode-test/vscode-* | sed -e '$ d' | xargs -I {} rm -rf {} || true 72 | shell: bash 73 | 74 | artifact: 75 | runs-on: ubuntu-latest 76 | steps: 77 | - name: Checkout 78 | uses: actions/checkout@v2 79 | 80 | - name: Install Dependencies 81 | run: npm install 82 | 83 | - name: Update changelog with unreleased changes 84 | run: npm run changelog:update -- -u 85 | 86 | - name: Package extension 87 | uses: lannonbr/vsce-action@master 88 | with: 89 | args: "package -o clipboard-manager.vsix" 90 | 91 | - name: Upload artifact 92 | uses: actions/upload-artifact@v1 93 | with: 94 | name: "clipboard-manager-${{ github.sha }}.vsix" 95 | path: "clipboard-manager.vsix" 96 | -------------------------------------------------------------------------------- /src/tools/organize.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | 4 | interface IPackage { 5 | [key: string]: any; 6 | contributes: { 7 | [key: string]: any; 8 | commands: Array<{ 9 | command: string; 10 | }>; 11 | configuration: Array<{ 12 | title: string; 13 | properties: { 14 | [key: string]: any; 15 | }; 16 | }>; 17 | }; 18 | } 19 | 20 | function sortObjectKeys(obj: { [key: string]: any }) { 21 | const clone = Object.assign({}, obj); 22 | 23 | for (const key of Object.keys(clone).sort()) { 24 | delete obj[key]; 25 | obj[key] = clone[key]; 26 | } 27 | } 28 | 29 | function replaceStringRange( 30 | s: string, 31 | start: number, 32 | end: number, 33 | substitute: string 34 | ) { 35 | return s.substring(0, start) + substitute + s.substring(end); 36 | } 37 | 38 | /** 39 | * Format package.json 40 | */ 41 | const packageFile = path.join(__dirname, "..", "..", "package.json"); 42 | 43 | let packageJson = fs.readFileSync(packageFile, { encoding: "utf8" }); 44 | 45 | const packageData = JSON.parse(packageJson) as IPackage; 46 | 47 | const sortByCommand = (a: any, b: any) => a.command.localeCompare(b.command); 48 | 49 | packageData.contributes.commands.sort(sortByCommand); 50 | packageData.contributes.menus.commandPalette.sort(sortByCommand); 51 | packageData.contributes.keybindings.sort(sortByCommand); 52 | 53 | sortObjectKeys(packageData.contributes.configuration[0].properties); 54 | sortObjectKeys(packageData.scripts); 55 | sortObjectKeys(packageData.devDependencies); 56 | sortObjectKeys(packageData.dependencies); 57 | 58 | packageJson = JSON.stringify(packageData, null, 4) + "\n"; 59 | 60 | fs.writeFileSync(packageFile, packageJson, { encoding: "utf8" }); 61 | 62 | /** 63 | * Format README.md settings part 64 | */ 65 | 66 | const settings: string[] = []; 67 | const settingKeys = Object.keys( 68 | packageData.contributes.configuration[0].properties 69 | ); 70 | 71 | for (const key of settingKeys) { 72 | const s = packageData.contributes.configuration[0].properties[key]; 73 | 74 | let desc = ""; 75 | 76 | // Turn description to comment 77 | if (s.description) { 78 | desc += " // " + s.description.replace(/\n/g, "\n // ") + "\n"; 79 | } 80 | 81 | desc += " " + JSON.stringify(key) + ": " + JSON.stringify(s.default || null); 82 | 83 | settings.push(desc); 84 | } 85 | 86 | const readmeFile = path.join(__dirname, "..", "..", "README.md"); 87 | let readmeContent = fs.readFileSync(readmeFile, { encoding: "utf8" }); 88 | 89 | const settingsBegin = readmeContent.indexOf("") + 21; 90 | const settingsEnd = readmeContent.indexOf(""); 91 | 92 | readmeContent = replaceStringRange( 93 | readmeContent, 94 | settingsBegin, 95 | settingsEnd, 96 | "\n```js\n{\n" + settings.join(",\n\n") + "\n}\n```\n" 97 | ); 98 | 99 | fs.writeFileSync(readmeFile, readmeContent, { encoding: "utf8" }); 100 | -------------------------------------------------------------------------------- /src/test/completion.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import * as sinon from "sinon"; 3 | import * as vscode from "vscode"; 4 | import { 5 | BaseClipboard, 6 | defaultClipboard, 7 | getNewDefaultInstance, 8 | } from "../clipboard"; 9 | import { commandList } from "../commads/common"; 10 | import { Monitor } from "../monitor"; 11 | import { sleep } from "../util"; 12 | import * as common from "./common"; 13 | 14 | suiteSetup(async function () { 15 | if (!(await common.activateExtension())) { 16 | this.skip(); 17 | } 18 | }); 19 | 20 | // Defines a Mocha test suite to group tests of similar kind together 21 | suite("Completion Tests", function () { 22 | let sandbox: sinon.SinonSandbox; 23 | 24 | let externalClipboard: BaseClipboard; 25 | let monitor: Monitor; 26 | 27 | setup(async function () { 28 | sandbox = sinon.createSandbox(); 29 | 30 | externalClipboard = getNewDefaultInstance(); 31 | 32 | monitor = (await vscode.commands.executeCommand( 33 | commandList.apiGetMonitor 34 | )) as Monitor; 35 | 36 | monitor.checkInterval = 300; 37 | monitor.onlyWindowFocused = false; 38 | 39 | // Reset initial value 40 | await defaultClipboard.writeText("Initial Value"); 41 | 42 | // Show sidebar 43 | common.showSidebar(); 44 | 45 | // Clear clipboard history 46 | common.getExtension()?.exports.manager.clearAll(); 47 | await sleep(500); 48 | }); 49 | 50 | teardown(function () { 51 | externalClipboard.dispose(); 52 | 53 | sandbox.restore(); 54 | }); 55 | 56 | test("Completion List", async function () { 57 | const completion = common.getExtension()?.exports.completion; 58 | 59 | if (!completion) { 60 | return this.skip(); 61 | } 62 | 63 | this.timeout(60000); 64 | 65 | const provideCompletionItemsSpy = sandbox.spy( 66 | completion, 67 | "provideCompletionItems" 68 | ); 69 | 70 | await externalClipboard.writeText("alpha"); 71 | await sleep(monitor.checkInterval + 300); 72 | await externalClipboard.writeText("beta"); 73 | await sleep(monitor.checkInterval + 300); 74 | await externalClipboard.writeText("gamma"); 75 | await sleep(monitor.checkInterval + 300); 76 | 77 | const document = await vscode.workspace.openTextDocument({ 78 | content: 79 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nclip", 80 | }); 81 | 82 | const editor = await vscode.window.showTextDocument(document); 83 | 84 | editor.selections = [new vscode.Selection(1, 4, 1, 4)]; 85 | 86 | await vscode.commands.executeCommand("editor.action.triggerSuggest"); 87 | await sleep(500); 88 | assert.ok(provideCompletionItemsSpy.called); 89 | 90 | await vscode.commands.executeCommand("selectNextSuggestion"); 91 | await sleep(500); 92 | 93 | await vscode.commands.executeCommand("selectPrevSuggestion"); 94 | await sleep(500); 95 | 96 | await vscode.commands.executeCommand("acceptSelectedSuggestion"); 97 | await sleep(500); 98 | 99 | assert.ok(!editor.document.getText().includes("clip")); 100 | assert.ok(editor.document.getText().includes("gamma")); 101 | 102 | await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /resources/dark/string.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/light/string.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /media/clipboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Layer 1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/commads/pickAndPaste.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { ClipboardManager, IClipboardItem } from "../manager"; 3 | import { leftPad } from "../util"; 4 | import { commandList } from "./common"; 5 | 6 | class ClipPickItem implements vscode.QuickPickItem { 7 | public label: string; 8 | 9 | get description() { 10 | if (this.clip.createdAt) { 11 | const date = new Date(this.clip.createdAt); 12 | return date.toLocaleString(); 13 | } 14 | } 15 | 16 | constructor(readonly clip: IClipboardItem) { 17 | this.label = this.clip.value.replace(/\s+/g, " ").trim(); 18 | } 19 | } 20 | 21 | export class PickAndPasteCommand implements vscode.Disposable { 22 | private _disposable: vscode.Disposable[] = []; 23 | 24 | constructor(protected _manager: ClipboardManager) { 25 | this._disposable.push( 26 | vscode.commands.registerCommand( 27 | commandList.pickAndPaste, 28 | this.execute, 29 | this 30 | ) 31 | ); 32 | } 33 | 34 | protected async execute() { 35 | const config = vscode.workspace.getConfiguration("clipboard-manager"); 36 | const preview = config.get("preview", true); 37 | 38 | const clips = this._manager.clips; 39 | 40 | const maxLength = `${clips.length}`.length; 41 | 42 | const picks = clips.map((c, index) => { 43 | const item = new ClipPickItem(c); 44 | const indexNumber = leftPad(index + 1, maxLength, "0"); 45 | 46 | item.label = `${indexNumber}) ${item.label}`; 47 | 48 | return item; 49 | }); 50 | 51 | // Variable to check changes in document by preview 52 | let needUndo = false; 53 | 54 | const options: vscode.QuickPickOptions = { 55 | placeHolder: "Select one clip to paste. ESC to cancel.", 56 | }; 57 | 58 | /** 59 | * If preview is enabled, get current text editor and replace 60 | * current selecion. 61 | * NOTE: not need paste if the text is replaced 62 | */ 63 | if (preview) { 64 | options.onDidSelectItem = async (selected: ClipPickItem) => { 65 | const editor = vscode.window.activeTextEditor; 66 | if (editor) { 67 | editor.edit( 68 | edit => { 69 | for (const selection of editor.selections) { 70 | edit.replace(selection, selected.clip.value); 71 | } 72 | needUndo = true; 73 | }, 74 | { 75 | undoStopAfter: false, 76 | undoStopBefore: false, 77 | } 78 | ); 79 | } 80 | }; 81 | } 82 | 83 | const pick = await vscode.window.showQuickPick(picks, options); 84 | 85 | if (!pick) { 86 | if (needUndo) { 87 | return await vscode.commands.executeCommand("undo"); 88 | } 89 | return; 90 | } 91 | 92 | // Update current clip in clipboard 93 | await this._manager.setClipboardValue(pick.clip.value); 94 | 95 | // If text changed, only need remove selecion 96 | // If a error occur on replace, run paste command for fallback 97 | if (needUndo) { 98 | // Fix editor selection 99 | const editor = vscode.window.activeTextEditor; 100 | if (editor) { 101 | const selecions = editor.selections.map( 102 | s => new vscode.Selection(s.end, s.end) 103 | ); 104 | editor.selections = selecions; 105 | } else { 106 | return await vscode.commands.executeCommand("cancelSelection"); 107 | } 108 | } else { 109 | return await vscode.commands.executeCommand( 110 | "editor.action.clipboardPasteAction" 111 | ); 112 | } 113 | } 114 | 115 | public dispose() { 116 | this._disposable.forEach(d => d.dispose()); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.4.2](https://github.com/edgardmessias/vscode.clipboard-manager/compare/v1.4.1...v1.4.2) (2020-04-03) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * Added inline button to remove item (close [#48](https://github.com/edgardmessias/vscode.clipboard-manager/issues/48)) ([9062ea0](https://github.com/edgardmessias/vscode.clipboard-manager/commit/9062ea0eadec8aedab4ddfdecf72ee651848a615)) 7 | * Added prompt before clear all history ([d3aab06](https://github.com/edgardmessias/vscode.clipboard-manager/commit/d3aab06fb3e8ff62c5ef55209a8473c86698fa6c)) 8 | 9 | 10 | 11 | ## [1.4.1](https://github.com/edgardmessias/vscode.clipboard-manager/compare/v1.4.0...v1.4.1) (2020-02-11) 12 | 13 | 14 | ### Bug Fixes 15 | 16 | * Allow only to run on local OS (close [#40](https://github.com/edgardmessias/vscode.clipboard-manager/issues/40)) ([e6d9e9a](https://github.com/edgardmessias/vscode.clipboard-manager/commit/e6d9e9add9168e51bc12293fb0888631c94c299c)) 17 | 18 | 19 | 20 | # [1.4.0](https://github.com/edgardmessias/vscode.clipboard-manager/compare/v1.3.0...v1.4.0) (2020-02-10) 21 | 22 | 23 | ### Features 24 | 25 | * Added option to limit size of clipboard ([3ae3427](https://github.com/edgardmessias/vscode.clipboard-manager/commit/3ae3427f94518451d5f4604193537cf7eb2b885e)) 26 | 27 | 28 | 29 | # [1.3.0](https://github.com/edgardmessias/vscode.clipboard-manager/compare/v1.2.1...v1.3.0) (2020-02-07) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * Fixed configuration reload for monitor (checkInterval and onlyWindowFocused) ([5a3e3a7](https://github.com/edgardmessias/vscode.clipboard-manager/commit/5a3e3a7ad215c3576984703a29d566a8b865f5f1)) 35 | 36 | 37 | ### Features 38 | 39 | * Added shortcut key to copy to clipboard history (close [#26](https://github.com/edgardmessias/vscode.clipboard-manager/issues/26)) ([0d24eab](https://github.com/edgardmessias/vscode.clipboard-manager/commit/0d24eabd6c7c03acafb54e46f41b3b02bb030ac1)) 40 | 41 | 42 | 43 | ## [1.2.1](https://github.com/edgardmessias/vscode.clipboard-manager/compare/v1.2.0...v1.2.1) (2020-02-06) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * Fixed shortcut for MacOs (close [#27](https://github.com/edgardmessias/vscode.clipboard-manager/issues/27)) ([3be9b73](https://github.com/edgardmessias/vscode.clipboard-manager/commit/3be9b73a403c4f365d5a2dcfa6bbecd119155587)) 49 | 50 | 51 | 52 | # [1.2.0](https://github.com/edgardmessias/vscode.clipboard-manager/compare/v1.1.0...v1.2.0) (2020-02-06) 53 | 54 | 55 | ### Bug Fixes 56 | 57 | * Fixed pick and paste for multi cursor selection (close [#23](https://github.com/edgardmessias/vscode.clipboard-manager/issues/23)) ([5205112](https://github.com/edgardmessias/vscode.clipboard-manager/commit/5205112d642396ff973e0861fc3ec7599b42ae68)) 58 | * Show command to clear clipboard history ([9fc3fe2](https://github.com/edgardmessias/vscode.clipboard-manager/commit/9fc3fe289e233301315bf34fa066e1c869cf159b)) 59 | * **package:** update clipboardy to version 2.0.0 ([0f80945](https://github.com/edgardmessias/vscode.clipboard-manager/commit/0f809450424f53be80a6e2cc55eba7dcacd4f561)) 60 | * Fix travis/appveyor error on downloading VS Code ([8afa9bc](https://github.com/edgardmessias/vscode.clipboard-manager/commit/8afa9bc79caf4cccbda26107c3519cbec1a45084)) 61 | 62 | 63 | ### Features 64 | 65 | * Added option to set path for clipboard file ([0039f84](https://github.com/edgardmessias/vscode.clipboard-manager/commit/0039f84cdc7301cdf2c5642f697127aa5832f667)) 66 | * Added option to set path for clipboard file (close [#25](https://github.com/edgardmessias/vscode.clipboard-manager/issues/25)) ([bedd470](https://github.com/edgardmessias/vscode.clipboard-manager/commit/bedd4707d551fed57847e4d3dbe4d767c5a03568)) 67 | 68 | 69 | 70 | # [1.1.0](https://github.com/edgardmessias/vscode.clipboard-manager/compare/v1.0.2...v1.1.0) (2018-12-11) 71 | 72 | 73 | 74 | ## [1.0.2](https://github.com/edgardmessias/vscode.clipboard-manager/compare/v1.0.1...v1.0.2) (2018-12-10) 75 | 76 | 77 | 78 | ## [1.0.1](https://github.com/edgardmessias/vscode.clipboard-manager/compare/v1.0.0...v1.0.1) (2018-12-05) 79 | 80 | 81 | 82 | # 1.0.0 (2018-11-28) 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/monitor.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { BaseClipboard } from "./clipboard"; 3 | import { toDisposable } from "./util"; 4 | 5 | export interface IClipboardTextChange { 6 | value: string; 7 | timestamp: number; 8 | language?: string; 9 | location?: vscode.Location; 10 | } 11 | 12 | export class Monitor implements vscode.Disposable { 13 | protected _disposables: vscode.Disposable[] = []; 14 | 15 | protected _previousText: string = ""; 16 | 17 | protected _windowFocused: boolean = true; 18 | 19 | public onlyWindowFocused: boolean = true; 20 | 21 | private _onDidChangeText = new vscode.EventEmitter(); 22 | public readonly onDidChangeText = this._onDidChangeText.event; 23 | 24 | protected _timer: NodeJS.Timer | undefined; 25 | 26 | public maxClipboardSize: number = 1000000; 27 | 28 | protected _checkInterval: number = 500; 29 | get checkInterval() { 30 | return this._checkInterval; 31 | } 32 | set checkInterval(timeout: number) { 33 | this._checkInterval = timeout; 34 | if (this._timer) { 35 | clearInterval(this._timer); 36 | this._timer = undefined; 37 | } 38 | // Minimum timeout to avoid cpu high usage 39 | if (timeout >= 100) { 40 | this._timer = setInterval(() => this.checkChangeText(), timeout); 41 | } 42 | } 43 | 44 | constructor(readonly clipboard: BaseClipboard) { 45 | // Update current clipboard to check changes after init 46 | this.readText().then(value => { 47 | this._previousText = value; 48 | 49 | // Initialize the checkInterval 50 | this.checkInterval = this._checkInterval; 51 | 52 | return value; 53 | }); 54 | 55 | // Updates the previous value if you change it manually 56 | this._disposables.push( 57 | this.clipboard.onDidWriteText(value => { 58 | this._previousText = value; 59 | }) 60 | ); 61 | 62 | this._disposables.push( 63 | toDisposable(() => { 64 | if (this._timer) { 65 | clearInterval(this._timer); 66 | } 67 | }) 68 | ); 69 | 70 | this._windowFocused = vscode.window.state.focused; 71 | // Update current clip when window if focused again 72 | vscode.window.onDidChangeWindowState( 73 | this.onDidChangeWindowState, 74 | this, 75 | this._disposables 76 | ); 77 | } 78 | 79 | protected async readText(): Promise { 80 | const text = await this.clipboard.readText(); 81 | if (text.length > this.maxClipboardSize) { 82 | return ""; 83 | } 84 | return text; 85 | } 86 | 87 | protected async onDidChangeWindowState(state: vscode.WindowState) { 88 | // Prevent detect change from external copy 89 | if (this.onlyWindowFocused && state.focused) { 90 | this._previousText = await this.readText(); 91 | } 92 | 93 | this._windowFocused = state.focused; 94 | } 95 | 96 | public async checkChangeText() { 97 | // Don't check the clipboard when windows is not focused 98 | if (this.onlyWindowFocused && !this._windowFocused) { 99 | return; 100 | } 101 | 102 | const newText = await this.readText(); 103 | if (newText === this._previousText) { 104 | return; 105 | } 106 | 107 | const change: IClipboardTextChange = { 108 | value: newText, 109 | timestamp: Date.now(), 110 | }; 111 | 112 | const editor = vscode.window.activeTextEditor; 113 | 114 | if (this._windowFocused && editor && editor.document) { 115 | // Set current language of copied clip 116 | change.language = editor.document.languageId; 117 | 118 | // Try get position of clip 119 | if (editor.selection) { 120 | const selection = editor.selection; 121 | change.location = { 122 | range: new vscode.Range(selection.start, selection.end), 123 | uri: editor.document.uri, 124 | }; 125 | } 126 | } 127 | 128 | this._onDidChangeText.fire(change); 129 | this._previousText = newText; 130 | } 131 | 132 | public dispose() { 133 | this._disposables.forEach(d => d.dispose()); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import * as vscode from "vscode"; 3 | import { defaultClipboard } from "./clipboard"; 4 | import { ApiGetMonitor } from "./commads/apiGetMonitor"; 5 | import { ClearClipboardHistory } from "./commads/clearClipboardHistory"; 6 | import { HistoryTreeDoubleClickCommand } from "./commads/historyTreeDoubleClick"; 7 | import { PickAndPasteCommand } from "./commads/pickAndPaste"; 8 | import { RemoveClipboardHistory } from "./commads/removeClipboardHistory"; 9 | import { SetClipboardValueCommand } from "./commads/setClipboardValue"; 10 | import { ShowClipboardInFile } from "./commads/showClipboardInFile"; 11 | import { ClipboardCompletion } from "./completion"; 12 | import { ClipboardManager } from "./manager"; 13 | import { Monitor } from "./monitor"; 14 | import { ClipboardTreeDataProvider } from "./tree/history"; 15 | import { CopyToHistoryCommand } from "./commads/copyToHistory"; 16 | 17 | let manager: ClipboardManager; 18 | 19 | // this method is called when your extension is activated 20 | export async function activate(context: vscode.ExtensionContext) { 21 | const disposable: vscode.Disposable[] = []; 22 | 23 | // Check the clipboard is working 24 | try { 25 | await defaultClipboard.readText(); // Read test 26 | } catch (error) { 27 | console.log(error); 28 | // Small delay to force show error 29 | setTimeout(() => { 30 | if (error.message) { 31 | vscode.window.showErrorMessage(error.message); 32 | } else { 33 | vscode.window.showErrorMessage( 34 | "Failed to read value from clipboard, check the console log" 35 | ); 36 | } 37 | }, 2000); 38 | // Disable clipboard listening 39 | defaultClipboard.dispose(); 40 | return; 41 | } 42 | 43 | // Add to disposable list the default clipboard 44 | disposable.push(defaultClipboard); 45 | 46 | const monitor = new Monitor(defaultClipboard); 47 | disposable.push(monitor); 48 | 49 | manager = new ClipboardManager(context, monitor); 50 | disposable.push(manager); 51 | 52 | // API Commands 53 | disposable.push(new ApiGetMonitor(monitor)); 54 | 55 | // Commands 56 | disposable.push(new PickAndPasteCommand(manager)); 57 | disposable.push(new HistoryTreeDoubleClickCommand(manager)); 58 | disposable.push(new SetClipboardValueCommand(manager)); 59 | disposable.push(new RemoveClipboardHistory(manager)); 60 | disposable.push(new ShowClipboardInFile(manager)); 61 | disposable.push(new ClearClipboardHistory(manager)); 62 | disposable.push(new CopyToHistoryCommand(monitor)); 63 | 64 | const completion = new ClipboardCompletion(manager); 65 | // disposable.push(completion); 66 | 67 | // All files types 68 | disposable.push( 69 | vscode.languages.registerCompletionItemProvider( 70 | { 71 | scheme: "file", 72 | }, 73 | completion 74 | ) 75 | ); 76 | 77 | // All files types (New file) 78 | disposable.push( 79 | vscode.languages.registerCompletionItemProvider( 80 | { 81 | scheme: "untitled", 82 | }, 83 | completion 84 | ) 85 | ); 86 | 87 | const clipboardTreeDataProvider = new ClipboardTreeDataProvider(manager); 88 | disposable.push(clipboardTreeDataProvider); 89 | 90 | disposable.push( 91 | vscode.window.registerTreeDataProvider( 92 | "clipboardHistory", 93 | clipboardTreeDataProvider 94 | ) 95 | ); 96 | 97 | const updateConfig = () => { 98 | const config = vscode.workspace.getConfiguration("clipboard-manager"); 99 | monitor.checkInterval = config.get("checkInterval", 500); 100 | monitor.onlyWindowFocused = config.get("onlyWindowFocused", true); 101 | monitor.maxClipboardSize = config.get("maxClipboardSize", 1000000); 102 | }; 103 | updateConfig(); 104 | 105 | disposable.push( 106 | vscode.workspace.onDidChangeConfiguration( 107 | e => e.affectsConfiguration("clipboard-manager") && updateConfig() 108 | ) 109 | ); 110 | 111 | context.subscriptions.push(...disposable); 112 | 113 | return { 114 | completion, 115 | manager, 116 | }; 117 | } 118 | 119 | // this method is called when your extension is deactivated 120 | export function deactivate() { 121 | if (manager) { 122 | manager.saveClips(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/test/monitor.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import * as sinon from "sinon"; 3 | import * as vscode from "vscode"; 4 | import { BaseClipboard, getNewDefaultInstance } from "../clipboard"; 5 | import { IClipboardTextChange, Monitor } from "../monitor"; 6 | import { sleep } from "../util"; 7 | import { activateExtension } from "./common"; 8 | 9 | suiteSetup(async function () { 10 | if (!(await activateExtension())) { 11 | this.skip(); 12 | } 13 | }); 14 | 15 | // Defines a Mocha test suite to group tests of similar kind together 16 | suite("Monitor Tests", function () { 17 | let sandbox: sinon.SinonSandbox; 18 | let onDidChangeWindowState: sinon.SinonStub; 19 | 20 | let disposables: vscode.Disposable[] = []; 21 | let externalClipboard: BaseClipboard; 22 | let clipboard: BaseClipboard; 23 | let monitor: Monitor; 24 | 25 | // Emulate windows focus events 26 | async function setWindowsFocus(focused: boolean) { 27 | for (const call of onDidChangeWindowState.getCalls()) { 28 | const focusFunction = call.args[0]; 29 | const focusObj = call.args[1]; 30 | await focusFunction.call(focusObj, { focused }); 31 | } 32 | } 33 | 34 | setup(async function () { 35 | sandbox = sinon.createSandbox(); 36 | 37 | // Stubs 38 | onDidChangeWindowState = sandbox.stub( 39 | vscode.window, 40 | "onDidChangeWindowState" 41 | ); 42 | 43 | // Inits 44 | disposables = []; 45 | 46 | externalClipboard = getNewDefaultInstance(); 47 | 48 | clipboard = getNewDefaultInstance(); 49 | 50 | monitor = new Monitor(clipboard); 51 | 52 | monitor.checkInterval = 300; 53 | monitor.onlyWindowFocused = true; 54 | 55 | disposables.push(externalClipboard, clipboard, monitor); 56 | 57 | await clipboard.writeText("Initial Value"); 58 | 59 | await setWindowsFocus(true); 60 | }); 61 | 62 | teardown(function () { 63 | disposables.forEach(d => d.dispose()); 64 | 65 | sandbox.restore(); 66 | }); 67 | 68 | test("Check changes interval", async function () { 69 | const readTextSpy = sandbox.spy(monitor.clipboard, "readText"); 70 | 71 | monitor.checkInterval = 1000; 72 | const initialCount = readTextSpy.callCount; 73 | 74 | await sleep(500); 75 | assert.equal(readTextSpy.callCount, initialCount); 76 | 77 | await sleep(600); // after 1100ms 78 | assert.equal(readTextSpy.callCount, initialCount + 1); 79 | 80 | await sleep(monitor.checkInterval); 81 | assert.equal(readTextSpy.callCount, initialCount + 2); 82 | }); 83 | 84 | test("Check changes content", async function () { 85 | const onDidChangeTextSpy = sandbox.spy(); 86 | 87 | disposables.push(monitor.onDidChangeText(onDidChangeTextSpy)); 88 | monitor.checkInterval = 200; // Reset setInterval 89 | 90 | await externalClipboard.writeText("new value"); 91 | await sleep(monitor.checkInterval + 300); 92 | 93 | assert.equal(onDidChangeTextSpy.callCount, 1); 94 | }); 95 | 96 | test("Check changes external", async function () { 97 | const onDidChangeTextSpy = sandbox.spy(); 98 | 99 | disposables.push(monitor.onDidChangeText(onDidChangeTextSpy)); 100 | monitor.checkInterval = 200; // Reset setInterval 101 | 102 | await externalClipboard.writeText("new value"); 103 | await sleep(monitor.checkInterval + 300); 104 | assert.equal(onDidChangeTextSpy.callCount, 1); 105 | 106 | // Not emit the event when window is not focused 107 | await setWindowsFocus(false); 108 | await externalClipboard.writeText("external change"); 109 | await sleep(monitor.checkInterval + 300); 110 | assert.equal(onDidChangeTextSpy.callCount, 1); 111 | 112 | // Not emit the event when window regains focus 113 | await setWindowsFocus(true); 114 | await sleep(monitor.checkInterval + 300); 115 | assert.equal(onDidChangeTextSpy.callCount, 1); 116 | 117 | await externalClipboard.writeText("internal change"); 118 | await sleep(monitor.checkInterval + 300); 119 | assert.equal(onDidChangeTextSpy.callCount, 2); 120 | }); 121 | 122 | test("Editor Position", async function () { 123 | const onDidChangeTextSpy = sandbox.spy(); 124 | disposables.push(monitor.onDidChangeText(onDidChangeTextSpy)); 125 | 126 | const document = await vscode.workspace.openTextDocument({ 127 | content: 128 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 129 | }); 130 | 131 | const editor = await vscode.window.showTextDocument(document); 132 | 133 | editor.selections = [new vscode.Selection(0, 0, 0, 11)]; 134 | 135 | await vscode.commands.executeCommand("editor.action.clipboardCopyAction"); 136 | 137 | await sleep(monitor.checkInterval + 300); 138 | 139 | // Check clipboard content 140 | const current = await clipboard.readText(); 141 | assert.equal(current, "Lorem ipsum"); 142 | 143 | // Check clipboard event 144 | assert.equal(onDidChangeTextSpy.called, true); 145 | 146 | const firstCall = onDidChangeTextSpy.firstCall 147 | .args[0] as IClipboardTextChange; 148 | 149 | assert.equal(firstCall.value, "Lorem ipsum"); 150 | assert.ok(firstCall.language); 151 | assert.ok(firstCall.location); 152 | 153 | const location: vscode.Location = firstCall.location as vscode.Location; 154 | const range: vscode.Range = location.range as vscode.Range; 155 | 156 | assert.equal(range.start.line, 0); 157 | assert.equal(range.start.character, 0); 158 | assert.equal(range.end.line, 0); 159 | assert.equal(range.end.character, 11); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /src/test/pickAndPaste.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import * as sinon from "sinon"; 3 | import * as vscode from "vscode"; 4 | import { 5 | BaseClipboard, 6 | defaultClipboard, 7 | getNewDefaultInstance, 8 | } from "../clipboard"; 9 | import { commandList } from "../commads/common"; 10 | import { Monitor } from "../monitor"; 11 | import { sleep } from "../util"; 12 | import * as common from "./common"; 13 | 14 | suiteSetup(async function () { 15 | if (!(await common.activateExtension())) { 16 | this.skip(); 17 | } 18 | }); 19 | 20 | // Defines a Mocha test suite to group tests of similar kind together 21 | suite("Pick and Paste Tests", function () { 22 | let sandbox: sinon.SinonSandbox; 23 | let showQuickPickStub: sinon.SinonStub; 24 | 25 | let externalClipboard: BaseClipboard; 26 | let monitor: Monitor; 27 | let editor: vscode.TextEditor; 28 | 29 | setup(async function () { 30 | sandbox = sinon.createSandbox(); 31 | 32 | showQuickPickStub = sandbox.stub(vscode.window, "showQuickPick"); 33 | 34 | externalClipboard = getNewDefaultInstance(); 35 | 36 | monitor = (await vscode.commands.executeCommand( 37 | commandList.apiGetMonitor 38 | )) as Monitor; 39 | 40 | monitor.checkInterval = 300; 41 | monitor.onlyWindowFocused = false; 42 | 43 | // Reset initial value 44 | await defaultClipboard.writeText("Initial Value"); 45 | 46 | // Show sidebar 47 | common.showSidebar(); 48 | 49 | // Clear clipboard history 50 | common.getExtension()?.exports.manager.clearAll(); 51 | await sleep(500); 52 | 53 | await externalClipboard.writeText("alpha"); 54 | await sleep(monitor.checkInterval + 300); 55 | await externalClipboard.writeText("beta"); 56 | await sleep(monitor.checkInterval + 300); 57 | await externalClipboard.writeText("gamma"); 58 | await sleep(monitor.checkInterval + 300); 59 | 60 | const document = await vscode.workspace.openTextDocument({ 61 | content: 62 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nclip", 63 | }); 64 | 65 | editor = await vscode.window.showTextDocument(document); 66 | 67 | editor.selections = [new vscode.Selection(1, 0, 1, 4)]; 68 | }); 69 | 70 | teardown(async function () { 71 | externalClipboard.dispose(); 72 | 73 | await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); 74 | 75 | sandbox.restore(); 76 | }); 77 | 78 | test("Select with preview", async function () { 79 | this.timeout(60000); 80 | 81 | showQuickPickStub.callsFake( 82 | async ( 83 | picks: vscode.QuickPickItem[], 84 | options: vscode.QuickPickOptions 85 | ) => { 86 | if (options.onDidSelectItem) { 87 | options.onDidSelectItem(picks[0]); 88 | await sleep(100); 89 | options.onDidSelectItem(picks[1]); 90 | await sleep(100); 91 | options.onDidSelectItem(picks[2]); 92 | await sleep(100); 93 | options.onDidSelectItem(picks[0]); 94 | await sleep(100); 95 | } 96 | return picks[0]; 97 | } 98 | ); 99 | 100 | assert.ok(!showQuickPickStub.called); 101 | 102 | await vscode.commands.executeCommand(commandList.pickAndPaste); 103 | 104 | assert.ok(showQuickPickStub.called); 105 | 106 | await sleep(100); 107 | 108 | assert.ok(!editor.document.getText().includes("clip")); 109 | assert.ok(editor.document.getText().includes("gamma")); 110 | }); 111 | 112 | test("Select without preview", async function () { 113 | this.timeout(60000); 114 | 115 | showQuickPickStub.callsFake( 116 | async ( 117 | picks: vscode.QuickPickItem[], 118 | _options: vscode.QuickPickOptions 119 | ) => { 120 | return picks[0]; 121 | } 122 | ); 123 | 124 | assert.ok(!showQuickPickStub.called); 125 | 126 | await vscode.commands.executeCommand(commandList.pickAndPaste); 127 | 128 | assert.ok(showQuickPickStub.called); 129 | 130 | await sleep(100); 131 | 132 | assert.ok(!editor.document.getText().includes("clip")); 133 | assert.ok(editor.document.getText().includes("gamma")); 134 | }); 135 | 136 | test("Cancel with preview", async function () { 137 | this.timeout(60000); 138 | 139 | showQuickPickStub.callsFake( 140 | async ( 141 | picks: vscode.QuickPickItem[], 142 | options: vscode.QuickPickOptions 143 | ) => { 144 | if (options.onDidSelectItem) { 145 | options.onDidSelectItem(picks[0]); 146 | await sleep(100); 147 | options.onDidSelectItem(picks[1]); 148 | await sleep(100); 149 | options.onDidSelectItem(picks[2]); 150 | await sleep(100); 151 | options.onDidSelectItem(picks[0]); 152 | await sleep(100); 153 | } 154 | return undefined; 155 | } 156 | ); 157 | 158 | assert.ok(!showQuickPickStub.called); 159 | 160 | await vscode.commands.executeCommand(commandList.pickAndPaste); 161 | 162 | assert.ok(showQuickPickStub.called); 163 | 164 | await sleep(100); 165 | 166 | assert.ok(editor.document.getText().includes("clip")); 167 | assert.ok(!editor.document.getText().includes("alpha")); 168 | assert.ok(!editor.document.getText().includes("beta")); 169 | assert.ok(!editor.document.getText().includes("gamma")); 170 | }); 171 | 172 | test("Cancel without preview", async function () { 173 | this.timeout(60000); 174 | 175 | showQuickPickStub.callsFake( 176 | async ( 177 | _picks: vscode.QuickPickItem[], 178 | _options: vscode.QuickPickOptions 179 | ) => { 180 | return undefined; 181 | } 182 | ); 183 | 184 | assert.ok(!showQuickPickStub.called); 185 | 186 | await vscode.commands.executeCommand(commandList.pickAndPaste); 187 | 188 | assert.ok(showQuickPickStub.called); 189 | 190 | await sleep(100); 191 | 192 | assert.ok(editor.document.getText().includes("clip")); 193 | assert.ok(!editor.document.getText().includes("alpha")); 194 | assert.ok(!editor.document.getText().includes("beta")); 195 | assert.ok(!editor.document.getText().includes("gamma")); 196 | }); 197 | }); 198 | -------------------------------------------------------------------------------- /src/manager.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as os from "os"; 3 | import * as path from "path"; 4 | import * as vscode from "vscode"; 5 | import { IClipboardTextChange, Monitor } from "./monitor"; 6 | 7 | export interface IClipboardItem { 8 | value: string; 9 | createdAt: number; 10 | lastUse?: number; 11 | copyCount: number; 12 | useCount: number; 13 | language?: string; 14 | createdLocation?: vscode.Location; 15 | } 16 | 17 | export class ClipboardManager implements vscode.Disposable { 18 | protected _disposable: vscode.Disposable[] = []; 19 | 20 | protected _clips: IClipboardItem[] = []; 21 | get clips() { 22 | return this._clips; 23 | } 24 | 25 | protected lastUpdate: number = 0; 26 | 27 | // get clipboard() { 28 | // return this._clipboard; 29 | // } 30 | 31 | private _onDidClipListChange = new vscode.EventEmitter(); 32 | public readonly onDidChangeClipList = this._onDidClipListChange.event; 33 | 34 | constructor( 35 | protected context: vscode.ExtensionContext, 36 | protected _monitor: Monitor 37 | ) { 38 | this._monitor.onDidChangeText(this.updateClipList, this, this._disposable); 39 | 40 | this.loadClips(); 41 | 42 | vscode.window.onDidChangeWindowState( 43 | state => { 44 | if (state.focused) { 45 | this.checkClipsUpdate(); 46 | } 47 | }, 48 | this, 49 | this._disposable 50 | ); 51 | 52 | vscode.workspace.onDidChangeConfiguration( 53 | e => e.affectsConfiguration("clipboard-manager") && this.saveClips() 54 | ); 55 | } 56 | 57 | protected updateClipList(change: IClipboardTextChange) { 58 | this.checkClipsUpdate(); 59 | 60 | const config = vscode.workspace.getConfiguration("clipboard-manager"); 61 | const maxClips = config.get("maxClips", 100); 62 | const avoidDuplicates = config.get("avoidDuplicates", true); 63 | 64 | let item: IClipboardItem = { 65 | value: change.value, 66 | createdAt: change.timestamp, 67 | copyCount: 1, 68 | useCount: 0, 69 | language: change.language, 70 | createdLocation: change.location, 71 | }; 72 | 73 | if (avoidDuplicates) { 74 | const index = this._clips.findIndex(c => c.value === change.value); 75 | 76 | // Remove same clips and move recent to top 77 | if (index >= 0) { 78 | this._clips[index].copyCount++; 79 | item = this._clips[index]; 80 | this._clips = this._clips.filter(c => c.value !== change.value); 81 | } 82 | } 83 | 84 | // Add to top 85 | this._clips.unshift(item); 86 | 87 | // Max clips to store 88 | if (maxClips > 0) { 89 | this._clips = this._clips.slice(0, maxClips); 90 | } 91 | 92 | this._onDidClipListChange.fire(); 93 | 94 | this.saveClips(); 95 | } 96 | 97 | public async setClipboardValue(value: string) { 98 | this.checkClipsUpdate(); 99 | 100 | const config = vscode.workspace.getConfiguration("clipboard-manager"); 101 | const moveToTop = config.get("moveToTop", true); 102 | 103 | const index = this._clips.findIndex(c => c.value === value); 104 | 105 | if (index >= 0) { 106 | this._clips[index].useCount++; 107 | 108 | if (moveToTop) { 109 | const clips = this.clips.splice(index, 1); 110 | this._clips.unshift(...clips); 111 | this._onDidClipListChange.fire(); 112 | this.saveClips(); 113 | } 114 | } 115 | 116 | return await this._monitor.clipboard.writeText(value); 117 | } 118 | 119 | public removeClipboardValue(value: string) { 120 | this.checkClipsUpdate(); 121 | 122 | const prevLength = this._clips.length; 123 | 124 | this._clips = this._clips.filter(c => c.value !== value); 125 | this._onDidClipListChange.fire(); 126 | this.saveClips(); 127 | 128 | return prevLength !== this._clips.length; 129 | } 130 | 131 | public clearAll() { 132 | this.checkClipsUpdate(); 133 | 134 | this._clips = []; 135 | this._onDidClipListChange.fire(); 136 | this.saveClips(); 137 | 138 | return true; 139 | } 140 | 141 | /** 142 | * `clipboard.history.json` 143 | */ 144 | protected getStoreFile() { 145 | let folder = os.tmpdir(); 146 | 147 | if (this.context.storagePath) { 148 | const parts = this.context.storagePath.split( 149 | /[\\/]workspaceStorage[\\/]/ 150 | ); 151 | folder = parts[0]; 152 | } 153 | 154 | const filePath = path.join(folder, "clipboard.history.json"); 155 | 156 | const config = vscode.workspace.getConfiguration("clipboard-manager"); 157 | const saveTo = config.get("saveTo"); 158 | 159 | if (typeof saveTo === "string") { 160 | return saveTo; 161 | } 162 | 163 | if (saveTo === false) { 164 | return false; 165 | } 166 | 167 | return filePath; 168 | } 169 | 170 | protected jsonReplacer(key: string, value: any) { 171 | if (key === "createdLocation" && value) { 172 | value = { 173 | range: { 174 | start: value.range.start, 175 | end: value.range.end, 176 | }, 177 | uri: value.uri.toString(), 178 | }; 179 | } else if (value instanceof vscode.Uri) { 180 | value = value.toString(); 181 | } 182 | 183 | return value; 184 | } 185 | 186 | public saveClips() { 187 | const file = this.getStoreFile(); 188 | if (!file) { 189 | return; 190 | } 191 | 192 | let json = "[]"; 193 | try { 194 | json = JSON.stringify( 195 | { 196 | version: 2, 197 | clips: this._clips, 198 | }, 199 | this.jsonReplacer, 200 | 2 201 | ); 202 | } catch (error) { 203 | console.error(error); 204 | return; 205 | } 206 | 207 | try { 208 | fs.writeFileSync(file, json); 209 | this.lastUpdate = fs.statSync(file).mtimeMs; 210 | } catch (error) { 211 | switch (error.code) { 212 | case "EPERM": 213 | vscode.window.showErrorMessage( 214 | `Not permitted to save clipboards on "${file}"` 215 | ); 216 | break; 217 | case "EISDIR": 218 | vscode.window.showErrorMessage( 219 | `Failed to save clipboards on "${file}", because the path is a directory` 220 | ); 221 | break; 222 | default: 223 | console.error(error); 224 | } 225 | } 226 | } 227 | 228 | /** 229 | * Check the clip history changed from another workspace 230 | */ 231 | public checkClipsUpdate() { 232 | const file = this.getStoreFile(); 233 | 234 | if (!file) { 235 | return; 236 | } 237 | 238 | if (!fs.existsSync(file)) { 239 | return; 240 | } 241 | 242 | const stat = fs.statSync(file); 243 | 244 | if (this.lastUpdate < stat.mtimeMs) { 245 | this.lastUpdate = stat.mtimeMs; 246 | this.loadClips(); 247 | } 248 | } 249 | 250 | public loadClips() { 251 | let json; 252 | 253 | const file = this.getStoreFile(); 254 | 255 | if (file && fs.existsSync(file)) { 256 | try { 257 | json = fs.readFileSync(file); 258 | this.lastUpdate = fs.statSync(file).mtimeMs; 259 | } catch (error) { 260 | // ignore 261 | } 262 | } else { 263 | // Read from old storage 264 | json = this.context.globalState.get("clips"); 265 | } 266 | 267 | if (!json) { 268 | return; 269 | } 270 | 271 | let stored: any = {}; 272 | 273 | try { 274 | stored = JSON.parse(json); 275 | } catch (error) { 276 | console.log(error); 277 | return; 278 | } 279 | 280 | if (!stored.version || !stored.clips) { 281 | return; 282 | } 283 | 284 | let clips = stored.clips as any[]; 285 | 286 | if (stored.version === 1) { 287 | clips = clips.map(c => { 288 | c.createdAt = c.timestamp; 289 | c.copyCount = 1; 290 | c.useCount = 0; 291 | c.createdLocation = c.location; 292 | return c; 293 | }); 294 | stored.version = 2; 295 | } 296 | 297 | this._clips = clips.map(c => { 298 | const clip: IClipboardItem = { 299 | value: c.value, 300 | createdAt: c.createdAt, 301 | copyCount: c.copyCount, 302 | useCount: c.copyCount, 303 | language: c.language, 304 | }; 305 | 306 | if (c.createdLocation) { 307 | const uri = vscode.Uri.parse(c.createdLocation.uri); 308 | const range = new vscode.Range( 309 | c.createdLocation.range.start.line, 310 | c.createdLocation.range.start.character, 311 | c.createdLocation.range.end.line, 312 | c.createdLocation.range.end.character 313 | ); 314 | clip.createdLocation = new vscode.Location(uri, range); 315 | } 316 | 317 | return clip; 318 | }); 319 | 320 | this._onDidClipListChange.fire(); 321 | } 322 | 323 | public dispose() { 324 | this._disposable.forEach(d => d.dispose()); 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /src/test/istanbultestrunner.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------- 2 | * Copyright (C) Microsoft Corporation. All rights reserved. 3 | *--------------------------------------------------------*/ 4 | 5 | "use strict"; 6 | import * as glob from "glob"; 7 | import * as istanbulCoverage from "istanbul-lib-coverage"; 8 | import * as istanbulHook from "istanbul-lib-hook"; 9 | import * as istanbulInstrument from "istanbul-lib-instrument"; 10 | import * as istanbulReport from "istanbul-lib-report"; 11 | import * as istanbulSourceMaps from "istanbul-lib-source-maps"; 12 | import * as istanbulReports from "istanbul-reports"; 13 | import * as Mocha from "mocha"; 14 | import * as fs from "original-fs"; 15 | import * as paths from "path"; 16 | 17 | // tslint:disable-next-line:no-var-requires 18 | 19 | declare let global: { 20 | [key: string]: any; // missing index defintion 21 | }; 22 | 23 | // Linux: prevent a weird NPE when mocha on Linux requires the window size from the TTY 24 | // Since we are not running in a tty environment, we just implementt he method statically 25 | // eslint-disable-next-line @typescript-eslint/no-var-requires 26 | const tty = require("tty"); 27 | if (!tty.getWindowSize) { 28 | tty.getWindowSize = function (): number[] { 29 | return [80, 75]; 30 | }; 31 | } 32 | 33 | let mocha = new Mocha({ 34 | ui: "tdd", 35 | color: true, 36 | }); 37 | 38 | let testOptions: any; 39 | 40 | export function configure(mochaOpts: Mocha.MochaOptions, testOpts: any): void { 41 | mocha = new Mocha(mochaOpts); 42 | testOptions = testOpts; 43 | } 44 | 45 | function _mkDirIfExists(dir: string): void { 46 | if (!fs.existsSync(dir)) { 47 | fs.mkdirSync(dir); 48 | } 49 | } 50 | 51 | function _readCoverOptions(testsRoot: string): ITestRunnerOptions | undefined { 52 | const coverConfigPath = paths.join(testsRoot, testOptions.coverConfig); 53 | let coverConfig: ITestRunnerOptions | undefined; 54 | if (fs.existsSync(coverConfigPath)) { 55 | const configContent = fs.readFileSync(coverConfigPath, { 56 | encoding: "utf8", 57 | }); 58 | coverConfig = JSON.parse(configContent); 59 | } 60 | return coverConfig; 61 | } 62 | 63 | export function run(testsRoot: string, clb: Function): any { 64 | // Enable source map support 65 | require("source-map-support").install(); 66 | 67 | // Read configuration for the coverage file 68 | const coverOptions: ITestRunnerOptions | undefined = _readCoverOptions( 69 | testsRoot 70 | ); 71 | let coverageRunner: CoverageRunner; 72 | if (coverOptions && coverOptions.enabled) { 73 | // Setup coverage pre-test, including post-test hook to report 74 | // eslint-disable-next-line @typescript-eslint/no-use-before-define 75 | coverageRunner = new CoverageRunner(coverOptions, testsRoot, clb); 76 | coverageRunner.setupCoverage(); 77 | } 78 | 79 | // Glob test files 80 | glob("**/**.test.js", { cwd: testsRoot }, function (error, files): any { 81 | if (error) { 82 | return clb(error); 83 | } 84 | try { 85 | // Fill into Mocha 86 | files.forEach(function (f): Mocha { 87 | return mocha.addFile(paths.join(testsRoot, f)); 88 | }); 89 | // Run the tests 90 | let failureCount = 0; 91 | 92 | mocha 93 | .run() 94 | .on("fail", function (_test, _err): void { 95 | failureCount++; 96 | }) 97 | .on("end", function (): void { 98 | if (coverageRunner) { 99 | coverageRunner.reportCoverage(); 100 | } 101 | clb(undefined, failureCount); 102 | }); 103 | } catch (error) { 104 | return clb(error); 105 | } 106 | }); 107 | } 108 | interface ITestRunnerOptions { 109 | enabled?: boolean; 110 | relativeCoverageDir: string; 111 | relativeSourcePath: string; 112 | ignorePatterns: string[]; 113 | includePid?: boolean; 114 | reports?: string[]; 115 | verbose?: boolean; 116 | } 117 | 118 | class CoverageRunner { 119 | private coverageVar: string = "$$cov_" + new Date().getTime() + "$$"; 120 | private transformer!: istanbulHook.Transformer; 121 | private matchFn: any = undefined; 122 | private instrumenter!: istanbulInstrument.Instrumenter; 123 | private unhookRequire!: Function; 124 | 125 | constructor( 126 | private options: ITestRunnerOptions, 127 | private testsRoot: string, 128 | private endRunCallback: Function 129 | ) { 130 | if (!options.relativeSourcePath) { 131 | return this.endRunCallback( 132 | "Error - relativeSourcePath must be defined for code coverage to work" 133 | ); 134 | } 135 | } 136 | 137 | public setupCoverage(): void { 138 | // Set up Code Coverage, hooking require so that instrumented code is returned 139 | // eslint-disable-next-line @typescript-eslint/no-this-alias 140 | const self = this; 141 | self.instrumenter = istanbulInstrument.createInstrumenter({ 142 | coverageVariable: self.coverageVar, 143 | }); 144 | const sourceRoot = paths.join( 145 | self.testsRoot, 146 | self.options.relativeSourcePath 147 | ); 148 | 149 | // Glob source files 150 | const srcFiles = glob.sync("**/**.js", { 151 | ignore: self.options.ignorePatterns, 152 | cwd: sourceRoot, 153 | }); 154 | 155 | // Create a match function - taken from the run-with-cover.js in istanbul. 156 | // eslint-disable-next-line @typescript-eslint/no-var-requires 157 | const decache = require("decache"); 158 | const fileMap: { [key: string]: any } = {}; 159 | srcFiles.forEach(file => { 160 | const fullPath = paths.join(sourceRoot, file); 161 | fileMap[fullPath] = true; 162 | 163 | // On Windows, extension is loaded pre-test hooks and this mean we lose 164 | // our chance to hook the Require call. In order to instrument the code 165 | // we have to decache the JS file so on next load it gets instrumented. 166 | // This doesn't impact tests, but is a concern if we had some integration 167 | // tests that relied on VSCode accessing our module since there could be 168 | // some shared global state that we lose. 169 | decache(fullPath); 170 | }); 171 | 172 | self.matchFn = function (file: string): boolean { 173 | return fileMap[file]; 174 | }; 175 | self.matchFn.files = Object.keys(fileMap); 176 | 177 | // Hook up to the Require function so that when this is called, if any of our source files 178 | // are required, the instrumented version is pulled in instead. These instrumented versions 179 | // write to a global coverage variable with hit counts whenever they are accessed 180 | self.transformer = (code, options) => 181 | self.instrumenter!.instrumentSync(code, options.filename); 182 | const hookOpts = { verbose: false, extensions: [".js"] }; 183 | self.unhookRequire = istanbulHook.hookRequire( 184 | self.matchFn, 185 | self.transformer, 186 | hookOpts 187 | ); 188 | 189 | // initialize the global variable to stop mocha from complaining about leaks 190 | global[self.coverageVar] = {}; 191 | } 192 | 193 | /** 194 | * Writes a coverage report. Note that as this is called in the process exit callback, all calls must be synchronous. 195 | * 196 | * @returns {void} 197 | * 198 | * @memberOf CoverageRunner 199 | */ 200 | public async reportCoverage(): Promise { 201 | // eslint-disable-next-line @typescript-eslint/no-this-alias 202 | const self = this; 203 | self.unhookRequire(); 204 | let cov: any; 205 | if ( 206 | typeof global[self.coverageVar] === "undefined" || 207 | Object.keys(global[self.coverageVar]).length === 0 208 | ) { 209 | console.error( 210 | "No coverage information was collected, exit without writing coverage information" 211 | ); 212 | return; 213 | } else { 214 | cov = global[self.coverageVar]; 215 | } 216 | 217 | const map = istanbulCoverage.createCoverageMap(); 218 | const mapStore = istanbulSourceMaps.createSourceMapStore(); 219 | 220 | for (const file of self.matchFn.files) { 221 | // TODO consider putting this under a conditional flag 222 | // Files that are not touched by code ran by the test runner is manually instrumented, to 223 | // illustrate the missing coverage. 224 | if (!cov[file]) { 225 | const code = fs.readFileSync(file, { encoding: "utf8" }); 226 | self.instrumenter!.instrumentSync(code, file); 227 | cov[file] = self.instrumenter!.lastFileCoverage(); 228 | } 229 | 230 | mapStore.registerURL(file, `${file}.map`); // Load sourceMap 231 | map.addFileCoverage(cov[file]); 232 | } 233 | 234 | // TODO Allow config of reporting directory with 235 | const reportingDir = paths.join( 236 | self.testsRoot, 237 | self.options.relativeCoverageDir 238 | ); 239 | const includePid = self.options.includePid; 240 | const pidExt = includePid ? "-" + process.pid : ""; 241 | const coverageFile = paths.join( 242 | reportingDir, 243 | "coverage" + pidExt + ".json" 244 | ); 245 | 246 | _mkDirIfExists(reportingDir); // yes, do this again since some test runners could clean the dir initially created 247 | 248 | fs.writeFileSync(coverageFile, JSON.stringify(cov), "utf8"); 249 | 250 | const tsMap = ((await mapStore.transformCoverage( 251 | map 252 | )) as any) as istanbulCoverage.CoverageMap; 253 | 254 | const context = istanbulReport.createContext({ 255 | coverageMap: tsMap, 256 | dir: reportingDir, 257 | }); 258 | 259 | const reportTypes = 260 | self.options.reports instanceof Array ? self.options.reports : ["lcov"]; 261 | 262 | reportTypes.forEach(reporter => 263 | (istanbulReports.create(reporter as any, { 264 | projectRoot: paths.join(__dirname, "..", ".."), 265 | }) as any).execute(context) 266 | ); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "publisher": "Wscats", 3 | "name": "clipboard", 4 | "displayName": "Clipboard", 5 | "description": "📚粘贴板管理", 6 | "version": "1.0.2", 7 | "author": { 8 | "name": "Eno Yao", 9 | "email": "kalone.cool@gmail.com", 10 | "url": "https://github.com/Wscats" 11 | }, 12 | "icon": "media/clipboard.jpg", 13 | "galleryBanner": { 14 | "color": "#58bc58", 15 | "theme": "dark" 16 | }, 17 | "engines": { 18 | "vscode": "^1.26.0" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/Wscats/vscode-clipboard/issues/new" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/Wscats/vscode-clipboard.git" 26 | }, 27 | "homepage": "https://github.com/Wscats/vscode-clipboard", 28 | "categories": [ 29 | "Other" 30 | ], 31 | "keywords": [ 32 | "clipboard", 33 | "copy", 34 | "paste", 35 | "history" 36 | ], 37 | "activationEvents": [ 38 | "*" 39 | ], 40 | "main": "./out/extension", 41 | "contributes": { 42 | "viewsContainers": { 43 | "activitybar": [ 44 | { 45 | "id": "clipboard-manager", 46 | "title": "Clipboard Manager", 47 | "icon": "media/clipboard.svg" 48 | } 49 | ] 50 | }, 51 | "views": { 52 | "clipboard-manager": [ 53 | { 54 | "id": "clipboardHistory", 55 | "name": "Clipboard History" 56 | } 57 | ] 58 | }, 59 | "commands": [ 60 | { 61 | "command": "clipboard-manager.editor.copyToHistory", 62 | "title": "Copy to Clipboard History", 63 | "category": "Clipboard Manager" 64 | }, 65 | { 66 | "command": "clipboard-manager.editor.pickAndPaste", 67 | "title": "Pick and Paste", 68 | "category": "Clipboard Manager" 69 | }, 70 | { 71 | "command": "clipboard-manager.editor.showClipboardInFile", 72 | "title": "Show in the file", 73 | "category": "Clipboard Manager" 74 | }, 75 | { 76 | "command": "clipboard-manager.history.clear", 77 | "title": "Clear History", 78 | "category": "Clipboard Manager", 79 | "icon": { 80 | "dark": "resources/dark/clear-history.svg", 81 | "light": "resources/light/clear-history.svg" 82 | } 83 | }, 84 | { 85 | "command": "clipboard-manager.history.remove", 86 | "title": "Remove", 87 | "category": "Clipboard Manager", 88 | "icon": { 89 | "dark": "resources/dark/remove.svg", 90 | "light": "resources/light/remove.svg" 91 | } 92 | } 93 | ], 94 | "menus": { 95 | "commandPalette": [ 96 | { 97 | "command": "clipboard-manager.editor.pickAndPaste", 98 | "when": "editorFocus && !editorReadonly" 99 | }, 100 | { 101 | "command": "clipboard-manager.editor.showClipboardInFile", 102 | "when": "false" 103 | }, 104 | { 105 | "command": "clipboard-manager.history.clear" 106 | }, 107 | { 108 | "command": "clipboard-manager.history.remove", 109 | "when": "false" 110 | } 111 | ], 112 | "view/item/context": [ 113 | { 114 | "command": "clipboard-manager.editor.showClipboardInFile", 115 | "when": "viewItem =~ /^clipHistoryItem:file/", 116 | "group": "0_navigation" 117 | }, 118 | { 119 | "command": "clipboard-manager.history.remove", 120 | "when": "viewItem =~ /^clipHistoryItem:/", 121 | "group": "1_modification" 122 | }, 123 | { 124 | "command": "clipboard-manager.history.remove", 125 | "when": "viewItem =~ /^clipHistoryItem:/", 126 | "group": "inline" 127 | } 128 | ], 129 | "view/title": [ 130 | { 131 | "command": "clipboard-manager.history.clear", 132 | "when": "view == clipboardHistory", 133 | "group": "navigation" 134 | } 135 | ] 136 | }, 137 | "keybindings": [ 138 | { 139 | "command": "clipboard-manager.editor.copyToHistory", 140 | "key": "Ctrl+Shift+C", 141 | "mac": "Cmd+Shift+C", 142 | "when": "textInputFocus && !editorReadonly" 143 | }, 144 | { 145 | "command": "clipboard-manager.editor.pickAndPaste", 146 | "key": "Ctrl+Shift+V", 147 | "mac": "Cmd+Shift+V", 148 | "when": "textInputFocus && !editorReadonly" 149 | } 150 | ], 151 | "configuration": [ 152 | { 153 | "title": "Clipboard Manager", 154 | "properties": { 155 | "clipboard-manager.avoidDuplicates": { 156 | "type": "boolean", 157 | "default": true, 158 | "description": "Avoid duplicate clips in the list" 159 | }, 160 | "clipboard-manager.checkInterval": { 161 | "type": "integer", 162 | "default": 500, 163 | "description": "Time in milliseconds to check changes in clipboard. Set zero to disable." 164 | }, 165 | "clipboard-manager.maxClipboardSize": { 166 | "type": "integer", 167 | "default": 1000000, 168 | "description": "Maximum clipboard size in bytes." 169 | }, 170 | "clipboard-manager.maxClips": { 171 | "type": "integer", 172 | "default": 100, 173 | "description": "Maximum number of clips to save in clipboard" 174 | }, 175 | "clipboard-manager.moveToTop": { 176 | "type": "boolean", 177 | "default": true, 178 | "description": "Move used clip to top in the list" 179 | }, 180 | "clipboard-manager.onlyWindowFocused": { 181 | "type": "boolean", 182 | "default": true, 183 | "description": "Get clips only from VSCode" 184 | }, 185 | "clipboard-manager.preview": { 186 | "type": "boolean", 187 | "default": true, 188 | "description": "View a preview while you are choosing the clip" 189 | }, 190 | "clipboard-manager.saveTo": { 191 | "type": [ 192 | "string", 193 | "null", 194 | "boolean" 195 | ], 196 | "default": null, 197 | "description": "Set location to save the clipboard file, set false to disable", 198 | "scope": "application" 199 | }, 200 | "clipboard-manager.snippet.enabled": { 201 | "scope": "resource", 202 | "type": "boolean", 203 | "default": true, 204 | "description": "Enable completion snippets" 205 | }, 206 | "clipboard-manager.snippet.max": { 207 | "scope": "resource", 208 | "type": "integer", 209 | "default": 10, 210 | "description": "Maximum number of clips to suggests in snippets (Zero for all)" 211 | }, 212 | "clipboard-manager.snippet.prefix": { 213 | "scope": "resource", 214 | "type": "string", 215 | "default": "clip", 216 | "description": "Default prefix for snippets completion (clip1, clip2, ...)" 217 | } 218 | } 219 | } 220 | ] 221 | }, 222 | "scripts": { 223 | "build": "vsce package", 224 | "changelog:last": "conventional-changelog -p angular -r 2", 225 | "changelog:preview": "conventional-changelog -p angular -u", 226 | "changelog:update": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0", 227 | "clean": "rimraf ./out ./coverage ./test-reports", 228 | "compile": "tsc -p ./", 229 | "lint": "eslint -c .eslintrc.js --ext .ts src", 230 | "organize": "node ./out/tools/organize.js", 231 | "release": "release-it", 232 | "test": "npm run compile && node ./out/test/runTests.js", 233 | "vscode:prepublish": "npm run clean && npm run compile", 234 | "watch": "tsc -watch -p ./" 235 | }, 236 | "devDependencies": { 237 | "@types/clipboardy": "^2.0.1", 238 | "@types/glob": "^7.1.3", 239 | "@types/istanbul-lib-coverage": "^2.0.3", 240 | "@types/istanbul-lib-hook": "^2.0.1", 241 | "@types/istanbul-lib-instrument": "^1.7.4", 242 | "@types/istanbul-lib-report": "^3.0.0", 243 | "@types/istanbul-lib-source-maps": "^4.0.1", 244 | "@types/istanbul-reports": "^3.0.0", 245 | "@types/mocha": "^8.0.3", 246 | "@types/node": "~8.10.59", 247 | "@types/sinon": "^9.0.5", 248 | "@types/vscode": "1.26.0", 249 | "@typescript-eslint/eslint-plugin": "^2.34.0", 250 | "@typescript-eslint/parser": "^2.34.0", 251 | "conventional-changelog-cli": "^2.1.0", 252 | "decache": "^4.6.0", 253 | "eslint": "^6.8.0", 254 | "eslint-config-prettier": "^6.11.0", 255 | "eslint-plugin-prettier": "^3.1.4", 256 | "glob": "^7.1.6", 257 | "istanbul-lib-coverage": "^3.0.0", 258 | "istanbul-lib-hook": "^3.0.0", 259 | "istanbul-lib-instrument": "^4.0.3", 260 | "istanbul-lib-report": "^3.0.0", 261 | "istanbul-lib-source-maps": "^4.0.0", 262 | "istanbul-reports": "^3.0.2", 263 | "mocha": "^8.1.3", 264 | "mocha-github-actions-reporter": "^0.2.1", 265 | "mocha-multi-reporters": "^1.1.7", 266 | "original-fs": "^1.1.0", 267 | "prettier": "^2.1.1", 268 | "release-it": "^14.0.2", 269 | "rimraf": "^3.0.2", 270 | "sinon": "^9.0.3", 271 | "source-map-support": "^0.5.19", 272 | "typescript": "^4.0.2", 273 | "vscode-test": "^1.4.0" 274 | }, 275 | "dependencies": { 276 | "clipboardy": "^2.3.0" 277 | } 278 | } -------------------------------------------------------------------------------- /src/types/vscode.proposed.d.ts: -------------------------------------------------------------------------------- 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 | * This is the place for API experiments and proposals. 7 | * These API are NOT stable and subject to change. They are only available in the Insiders 8 | * distribution and CANNOT be used in published extensions. 9 | * 10 | * To test these API in local environment: 11 | * - Use Insiders release of VS Code. 12 | * - Add `"enableProposedApi": true` to your package.json. 13 | * - Copy this file to your project. 14 | */ 15 | declare module 'vscode' { 16 | export namespace window { 17 | export function sampleFunction(): Thenable < any > ; 18 | } 19 | //#region Joh - https://github.com/Microsoft/vscode/issues/57093 20 | /** 21 | * An insert text rule defines how the [`insertText`](#CompletionItem.insertText) of a 22 | * completion item should be modified. 23 | */ 24 | export enum CompletionItemInsertTextRule { 25 | /** 26 | * Keep whitespace as is. By default, the editor adjusts leading 27 | * whitespace of new lines so that they match the indentation of 28 | * the line for which the item is accepeted. 29 | */ 30 | KeepWhitespace = 0b01 31 | } 32 | export interface CompletionItem { 33 | /** 34 | * Rules about how/if the `insertText` should be modified by the 35 | * editor. Can be a bit mask of many rules. 36 | */ 37 | insertTextRules ? : CompletionItemInsertTextRule; 38 | } 39 | //#endregion 40 | //#region Joh - clipboard https://github.com/Microsoft/vscode/issues/217 41 | export interface Clipboard { 42 | readText(): Thenable < string > ; 43 | writeText(value: string): Thenable < void > ; 44 | } 45 | export namespace env { 46 | export const clipboard: Clipboard; 47 | } 48 | //#endregion 49 | //#region Joh - read/write in chunks 50 | export interface FileSystemProvider { 51 | open ? (resource: Uri) : number | Thenable < number > ; 52 | close ? (fd: number) : void | Thenable < void > ; 53 | read ? (fd: number, pos: number, data: Uint8Array, offset: number, length: number) : number | Thenable < number > ; 54 | write ? (fd: number, pos: number, data: Uint8Array, offset: number, length: number) : number | Thenable < number > ; 55 | } 56 | //#endregion 57 | //#region Rob: search provider 58 | /** 59 | * The parameters of a query for text search. 60 | */ 61 | export interface TextSearchQuery { 62 | /** 63 | * The text pattern to search for. 64 | */ 65 | pattern: string; 66 | /** 67 | * Whether or not `pattern` should match multiple lines of text. 68 | */ 69 | isMultiline ? : boolean; 70 | /** 71 | * Whether or not `pattern` should be interpreted as a regular expression. 72 | */ 73 | isRegExp ? : boolean; 74 | /** 75 | * Whether or not the search should be case-sensitive. 76 | */ 77 | isCaseSensitive ? : boolean; 78 | /** 79 | * Whether or not to search for whole word matches only. 80 | */ 81 | isWordMatch ? : boolean; 82 | } 83 | /** 84 | * A file glob pattern to match file paths against. 85 | * TODO@roblou - merge this with the GlobPattern docs/definition in vscode.d.ts. 86 | * @see [GlobPattern](#GlobPattern) 87 | */ 88 | export type GlobString = string; 89 | /** 90 | * Options common to file and text search 91 | */ 92 | export interface SearchOptions { 93 | /** 94 | * The root folder to search within. 95 | */ 96 | folder: Uri; 97 | /** 98 | * Files that match an `includes` glob pattern should be included in the search. 99 | */ 100 | includes: GlobString[]; 101 | /** 102 | * Files that match an `excludes` glob pattern should be excluded from the search. 103 | */ 104 | excludes: GlobString[]; 105 | /** 106 | * Whether external files that exclude files, like .gitignore, should be respected. 107 | * See the vscode setting `"search.useIgnoreFiles"`. 108 | */ 109 | useIgnoreFiles: boolean; 110 | /** 111 | * Whether symlinks should be followed while searching. 112 | * See the vscode setting `"search.followSymlinks"`. 113 | */ 114 | followSymlinks: boolean; 115 | /** 116 | * Whether global files that exclude files, like .gitignore, should be respected. 117 | * See the vscode setting `"search.useGlobalIgnoreFiles"`. 118 | */ 119 | useGlobalIgnoreFiles: boolean; 120 | } 121 | /** 122 | * Options to specify the size of the result text preview. 123 | * These options don't affect the size of the match itself, just the amount of preview text. 124 | */ 125 | export interface TextSearchPreviewOptions { 126 | /** 127 | * The maximum number of lines in the preview. 128 | * Only search providers that support multiline search will ever return more than one line in the match. 129 | */ 130 | matchLines: number; 131 | /** 132 | * The maximum number of characters included per line. 133 | */ 134 | charsPerLine: number; 135 | } 136 | /** 137 | * Options that apply to text search. 138 | */ 139 | export interface TextSearchOptions extends SearchOptions { 140 | /** 141 | * The maximum number of results to be returned. 142 | */ 143 | maxResults: number; 144 | /** 145 | * Options to specify the size of the result text preview. 146 | */ 147 | previewOptions ? : TextSearchPreviewOptions; 148 | /** 149 | * Exclude files larger than `maxFileSize` in bytes. 150 | */ 151 | maxFileSize ? : number; 152 | /** 153 | * Interpret files using this encoding. 154 | * See the vscode setting `"files.encoding"` 155 | */ 156 | encoding ? : string; 157 | /** 158 | * Number of lines of context to include before each match. 159 | */ 160 | beforeContext ? : number; 161 | /** 162 | * Number of lines of context to include after each match. 163 | */ 164 | afterContext ? : number; 165 | } 166 | /** 167 | * Information collected when text search is complete. 168 | */ 169 | export interface TextSearchComplete { 170 | /** 171 | * Whether the search hit the limit on the maximum number of search results. 172 | * `maxResults` on [`TextSearchOptions`](#TextSearchOptions) specifies the max number of results. 173 | * - If exactly that number of matches exist, this should be false. 174 | * - If `maxResults` matches are returned and more exist, this should be true. 175 | * - If search hits an internal limit which is less than `maxResults`, this should be true. 176 | */ 177 | limitHit ? : boolean; 178 | } 179 | /** 180 | * The parameters of a query for file search. 181 | */ 182 | export interface FileSearchQuery { 183 | /** 184 | * The search pattern to match against file paths. 185 | */ 186 | pattern: string; 187 | } 188 | /** 189 | * Options that apply to file search. 190 | */ 191 | export interface FileSearchOptions extends SearchOptions { 192 | /** 193 | * The maximum number of results to be returned. 194 | */ 195 | maxResults: number; 196 | } 197 | /** 198 | * Options that apply to requesting the file index. 199 | */ 200 | export interface FileIndexOptions extends SearchOptions {} 201 | /** 202 | * A preview of the text result. 203 | */ 204 | export interface TextSearchMatchPreview { 205 | /** 206 | * The matching lines of text, or a portion of the matching line that contains the match. 207 | */ 208 | text: string; 209 | /** 210 | * The Range within `text` corresponding to the text of the match. 211 | * The number of matches must match the TextSearchMatch's range property. 212 | */ 213 | matches: Range | Range[]; 214 | } 215 | /** 216 | * A match from a text search 217 | */ 218 | export interface TextSearchMatch { 219 | /** 220 | * The uri for the matching document. 221 | */ 222 | uri: Uri; 223 | /** 224 | * The range of the match within the document, or multiple ranges for multiple matches. 225 | */ 226 | ranges: Range | Range[]; 227 | /** 228 | * A preview of the text match. 229 | */ 230 | preview: TextSearchMatchPreview; 231 | } 232 | /** 233 | * A line of context surrounding a TextSearchMatch. 234 | */ 235 | export interface TextSearchContext { 236 | /** 237 | * The uri for the matching document. 238 | */ 239 | uri: Uri; 240 | /** 241 | * One line of text. 242 | * previewOptions.charsPerLine applies to this 243 | */ 244 | text: string; 245 | /** 246 | * The line number of this line of context. 247 | */ 248 | lineNumber: number; 249 | } 250 | export type TextSearchResult = TextSearchMatch | TextSearchContext; 251 | /** 252 | * A FileIndexProvider provides a list of files in the given folder. VS Code will filter that list for searching with quickopen or from other extensions. 253 | * 254 | * A FileIndexProvider is the simpler of two ways to implement file search in VS Code. Use a FileIndexProvider if you are able to provide a listing of all files 255 | * in a folder, and want VS Code to filter them according to the user's search query. 256 | * 257 | * The FileIndexProvider will be invoked once when quickopen is opened, and VS Code will filter the returned list. It will also be invoked when 258 | * `workspace.findFiles` is called. 259 | * 260 | * If a [`FileSearchProvider`](#FileSearchProvider) is registered for the scheme, that provider will be used instead. 261 | */ 262 | export interface FileIndexProvider { 263 | /** 264 | * Provide the set of files in the folder. 265 | * @param options A set of options to consider while searching. 266 | * @param token A cancellation token. 267 | */ 268 | provideFileIndex(options: FileIndexOptions, token: CancellationToken): ProviderResult < Uri[] > ; 269 | } 270 | /** 271 | * A FileSearchProvider provides search results for files in the given folder that match a query string. It can be invoked by quickopen or other extensions. 272 | * 273 | * A FileSearchProvider is the more powerful of two ways to implement file search in VS Code. Use a FileSearchProvider if you wish to search within a folder for 274 | * all files that match the user's query. 275 | * 276 | * The FileSearchProvider will be invoked on every keypress in quickopen. When `workspace.findFiles` is called, it will be invoked with an empty query string, 277 | * and in that case, every file in the folder should be returned. 278 | * 279 | * @see [FileIndexProvider](#FileIndexProvider) 280 | */ 281 | export interface FileSearchProvider { 282 | /** 283 | * Provide the set of files that match a certain file path pattern. 284 | * @param query The parameters for this query. 285 | * @param options A set of options to consider while searching files. 286 | * @param progress A progress callback that must be invoked for all results. 287 | * @param token A cancellation token. 288 | */ 289 | provideFileSearchResults(query: FileSearchQuery, options: FileSearchOptions, token: CancellationToken): ProviderResult < Uri[] > ; 290 | } 291 | /** 292 | * A TextSearchProvider provides search results for text results inside files in the workspace. 293 | */ 294 | export interface TextSearchProvider { 295 | /** 296 | * Provide results that match the given text pattern. 297 | * @param query The parameters for this query. 298 | * @param options A set of options to consider while searching. 299 | * @param progress A progress callback that must be invoked for all results. 300 | * @param token A cancellation token. 301 | */ 302 | provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress < TextSearchResult > , token: CancellationToken): ProviderResult < TextSearchComplete > ; 303 | } 304 | /** 305 | * Options that can be set on a findTextInFiles search. 306 | */ 307 | export interface FindTextInFilesOptions { 308 | /** 309 | * A [glob pattern](#GlobPattern) that defines the files to search for. The glob pattern 310 | * will be matched against the file paths of files relative to their workspace. Use a [relative pattern](#RelativePattern) 311 | * to restrict the search results to a [workspace folder](#WorkspaceFolder). 312 | */ 313 | include ? : GlobPattern; 314 | /** 315 | * A [glob pattern](#GlobPattern) that defines files and folders to exclude. The glob pattern 316 | * will be matched against the file paths of resulting matches relative to their workspace. When `undefined` only default excludes will 317 | * apply, when `null` no excludes will apply. 318 | */ 319 | exclude ? : GlobPattern | null; 320 | /** 321 | * The maximum number of results to search for 322 | */ 323 | maxResults ? : number; 324 | /** 325 | * Whether external files that exclude files, like .gitignore, should be respected. 326 | * See the vscode setting `"search.useIgnoreFiles"`. 327 | */ 328 | useIgnoreFiles ? : boolean; 329 | /** 330 | * Whether global files that exclude files, like .gitignore, should be respected. 331 | * See the vscode setting `"search.useGlobalIgnoreFiles"`. 332 | */ 333 | useGlobalIgnoreFiles ? : boolean; 334 | /** 335 | * Whether symlinks should be followed while searching. 336 | * See the vscode setting `"search.followSymlinks"`. 337 | */ 338 | followSymlinks ? : boolean; 339 | /** 340 | * Interpret files using this encoding. 341 | * See the vscode setting `"files.encoding"` 342 | */ 343 | encoding ? : string; 344 | /** 345 | * Options to specify the size of the result text preview. 346 | */ 347 | previewOptions ? : TextSearchPreviewOptions; 348 | /** 349 | * Number of lines of context to include before each match. 350 | */ 351 | beforeContext ? : number; 352 | /** 353 | * Number of lines of context to include after each match. 354 | */ 355 | afterContext ? : number; 356 | } 357 | export namespace workspace { 358 | /** 359 | * DEPRECATED 360 | */ 361 | export function registerSearchProvider(): Disposable; 362 | /** 363 | * Register a file index provider. 364 | * 365 | * Only one provider can be registered per scheme. 366 | * 367 | * @param scheme The provider will be invoked for workspace folders that have this file scheme. 368 | * @param provider The provider. 369 | * @return A [disposable](#Disposable) that unregisters this provider when being disposed. 370 | */ 371 | export function registerFileIndexProvider(scheme: string, provider: FileIndexProvider): Disposable; 372 | /** 373 | * Register a search provider. 374 | * 375 | * Only one provider can be registered per scheme. 376 | * 377 | * @param scheme The provider will be invoked for workspace folders that have this file scheme. 378 | * @param provider The provider. 379 | * @return A [disposable](#Disposable) that unregisters this provider when being disposed. 380 | */ 381 | export function registerFileSearchProvider(scheme: string, provider: FileSearchProvider): Disposable; 382 | /** 383 | * Register a text search provider. 384 | * 385 | * Only one provider can be registered per scheme. 386 | * 387 | * @param scheme The provider will be invoked for workspace folders that have this file scheme. 388 | * @param provider The provider. 389 | * @return A [disposable](#Disposable) that unregisters this provider when being disposed. 390 | */ 391 | export function registerTextSearchProvider(scheme: string, provider: TextSearchProvider): Disposable; 392 | /** 393 | * Search text in files across all [workspace folders](#workspace.workspaceFolders) in the workspace. 394 | * @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words. 395 | * @param callback A callback, called for each result 396 | * @param token A token that can be used to signal cancellation to the underlying search engine. 397 | * @return A thenable that resolves when the search is complete. 398 | */ 399 | export function findTextInFiles(query: TextSearchQuery, callback: (result: TextSearchResult) => void, token ? : CancellationToken): Thenable < TextSearchComplete > ; 400 | /** 401 | * Search text in files across all [workspace folders](#workspace.workspaceFolders) in the workspace. 402 | * @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words. 403 | * @param options An optional set of query options. Include and exclude patterns, maxResults, etc. 404 | * @param callback A callback, called for each result 405 | * @param token A token that can be used to signal cancellation to the underlying search engine. 406 | * @return A thenable that resolves when the search is complete. 407 | */ 408 | export function findTextInFiles(query: TextSearchQuery, options: FindTextInFilesOptions, callback: (result: TextSearchResult) => void, token ? : CancellationToken): Thenable < TextSearchComplete > ; 409 | } 410 | //#endregion 411 | //#region Joao: diff command 412 | /** 413 | * The contiguous set of modified lines in a diff. 414 | */ 415 | export interface LineChange { 416 | readonly originalStartLineNumber: number; 417 | readonly originalEndLineNumber: number; 418 | readonly modifiedStartLineNumber: number; 419 | readonly modifiedEndLineNumber: number; 420 | } 421 | export namespace commands { 422 | /** 423 | * Registers a diff information command that can be invoked via a keyboard shortcut, 424 | * a menu item, an action, or directly. 425 | * 426 | * Diff information commands are different from ordinary [commands](#commands.registerCommand) as 427 | * they only execute when there is an active diff editor when the command is called, and the diff 428 | * information has been computed. Also, the command handler of an editor command has access to 429 | * the diff information. 430 | * 431 | * @param command A unique identifier for the command. 432 | * @param callback A command handler function with access to the [diff information](#LineChange). 433 | * @param thisArg The `this` context used when invoking the handler function. 434 | * @return Disposable which unregisters this command on disposal. 435 | */ 436 | export function registerDiffInformationCommand(command: string, callback: (diff: LineChange[], ...args: any[]) => any, thisArg ? : any): Disposable; 437 | } 438 | //#endregion 439 | //#region Joh: decorations 440 | //todo@joh -> make class 441 | export interface DecorationData { 442 | letter ? : string; 443 | title ? : string; 444 | color ? : ThemeColor; 445 | priority ? : number; 446 | bubble ? : boolean; 447 | source ? : string; // hacky... we should remove it and use equality under the hood 448 | } 449 | export interface SourceControlResourceDecorations { 450 | source ? : string; 451 | letter ? : string; 452 | color ? : ThemeColor; 453 | } 454 | export interface DecorationProvider { 455 | onDidChangeDecorations: Event < undefined | Uri | Uri[] > ; 456 | provideDecoration(uri: Uri, token: CancellationToken): ProviderResult < DecorationData > ; 457 | } 458 | export namespace window { 459 | export function registerDecorationProvider(provider: DecorationProvider): Disposable; 460 | } 461 | //#endregion 462 | //#region André: debug 463 | /** 464 | * Represents a debug adapter executable and optional arguments passed to it. 465 | */ 466 | export class DebugAdapterExecutable { 467 | readonly type: 'executable'; 468 | /** 469 | * The command path of the debug adapter executable. 470 | * A command must be either an absolute path or the name of an executable looked up via the PATH environment variable. 471 | * The special value 'node' will be mapped to VS Code's built-in node runtime. 472 | */ 473 | readonly command: string; 474 | /** 475 | * Optional arguments passed to the debug adapter executable. 476 | */ 477 | readonly args: string[]; 478 | /** 479 | * The additional environment of the executed program or shell. If omitted 480 | * the parent process' environment is used. If provided it is merged with 481 | * the parent process' environment. 482 | */ 483 | readonly env ? : { 484 | [key: string]: string 485 | }; 486 | /** 487 | * The working directory for the debug adapter. 488 | */ 489 | readonly cwd ? : string; 490 | /** 491 | * Create a description for a debug adapter based on an executable program. 492 | */ 493 | constructor(command: string, args ? : string[], env ? : { 494 | [key: string]: string 495 | }, cwd ? : string); 496 | } 497 | /** 498 | * Represents a debug adapter running as a socket based server. 499 | */ 500 | export class DebugAdapterServer { 501 | readonly type: 'server'; 502 | /** 503 | * The port. 504 | */ 505 | readonly port: number; 506 | /** 507 | * The host. 508 | */ 509 | readonly host ? : string; 510 | /** 511 | * Create a description for a debug adapter running as a socket based server. 512 | */ 513 | constructor(port: number, host ? : string); 514 | } 515 | /** 516 | * Represents a debug adapter that is implemented in the extension. 517 | */ 518 | export class DebugAdapterImplementation { 519 | readonly type: 'implementation'; 520 | readonly implementation: any; 521 | /** 522 | * Create a description for a debug adapter directly implemented in the extension. 523 | * The implementation's "type": TBD 524 | */ 525 | constructor(implementation: any); 526 | } 527 | export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer | DebugAdapterImplementation; 528 | /** 529 | * A Debug Adapter Tracker is a means to track the communication between VS Code and a Debug Adapter. 530 | */ 531 | export interface DebugAdapterTracker { 532 | // VS Code -> Debug Adapter 533 | startDebugAdapter ? () : void; 534 | toDebugAdapter ? (message: any) : void; 535 | stopDebugAdapter ? () : void; 536 | // Debug Adapter -> VS Code 537 | fromDebugAdapter ? (message: any) : void; 538 | debugAdapterError ? (error: Error) : void; 539 | debugAdapterExit ? (code ? : number, signal ? : string) : void; 540 | } 541 | export interface DebugConfigurationProvider { 542 | /** 543 | * The optional method 'provideDebugAdapter' is called at the start of a debug session to provide details about the debug adapter to use. 544 | * These details must be returned as objects of type DebugAdapterDescriptor. 545 | * Currently two types of debug adapters are supported: 546 | * - a debug adapter executable specified as a command path and arguments (see DebugAdapterExecutable), 547 | * - a debug adapter server reachable via a communication port (see DebugAdapterServer). 548 | * If the method is not implemented the default behavior is this: 549 | * provideDebugAdapter(session: DebugSession, folder: WorkspaceFolder | undefined, executable: DebugAdapterExecutable, config: DebugConfiguration, token?: CancellationToken) { 550 | * if (typeof config.debugServer === 'number') { 551 | * return new DebugAdapterServer(config.debugServer); 552 | * } 553 | * return executable; 554 | * } 555 | * An extension is only allowed to register a DebugConfigurationProvider with a provideDebugAdapter method if the extension defines the debug type. Otherwise an error is thrown. 556 | * Registering more than one DebugConfigurationProvider with a provideDebugAdapter method for a type results in an error. 557 | * @param session The [debug session](#DebugSession) for which the debug adapter will be used. 558 | * @param folder The workspace folder from which the configuration originates from or undefined for a folderless setup. 559 | * @param executable The debug adapter's executable information as specified in the package.json (or undefined if no such information exists). 560 | * @param config The resolved debug configuration. 561 | * @param token A cancellation token. 562 | * @return a [debug adapter's descriptor](#DebugAdapterDescriptor) or undefined. 563 | */ 564 | provideDebugAdapter ? (session: DebugSession, folder: WorkspaceFolder | undefined, executable: DebugAdapterExecutable | undefined, config: DebugConfiguration, token ? : CancellationToken) : ProviderResult < DebugAdapterDescriptor > ; 565 | /** 566 | * The optional method 'provideDebugAdapterTracker' is called at the start of a debug session to provide a tracker that gives access to the communication between VS Code and a Debug Adapter. 567 | * @param session The [debug session](#DebugSession) for which the tracker will be used. 568 | * @param folder The workspace folder from which the configuration originates from or undefined for a folderless setup. 569 | * @param config The resolved debug configuration. 570 | * @param token A cancellation token. 571 | */ 572 | provideDebugAdapterTracker ? (session: DebugSession, folder: WorkspaceFolder | undefined, config: DebugConfiguration, token ? : CancellationToken) : ProviderResult < DebugAdapterTracker > ; 573 | /** 574 | * Deprecated, use DebugConfigurationProvider.provideDebugAdapter instead. 575 | * @deprecated Use DebugConfigurationProvider.provideDebugAdapter instead 576 | */ 577 | debugAdapterExecutable ? (folder: WorkspaceFolder | undefined, token ? : CancellationToken) : ProviderResult < DebugAdapterExecutable > ; 578 | } 579 | //#endregion 580 | //#region Rob, Matt: logging 581 | /** 582 | * The severity level of a log message 583 | */ 584 | export enum LogLevel { 585 | Trace = 1, 586 | Debug = 2, 587 | Info = 3, 588 | Warning = 4, 589 | Error = 5, 590 | Critical = 6, 591 | Off = 7 592 | } 593 | export namespace env { 594 | /** 595 | * Current logging level. 596 | */ 597 | export const logLevel: LogLevel; 598 | /** 599 | * An [event](#Event) that fires when the log level has changed. 600 | */ 601 | export const onDidChangeLogLevel: Event < LogLevel > ; 602 | } 603 | //#endregion 604 | //#region Joao: SCM validation 605 | /** 606 | * Represents the validation type of the Source Control input. 607 | */ 608 | export enum SourceControlInputBoxValidationType { 609 | /** 610 | * Something not allowed by the rules of a language or other means. 611 | */ 612 | Error = 0, 613 | /** 614 | * Something suspicious but allowed. 615 | */ 616 | Warning = 1, 617 | /** 618 | * Something to inform about but not a problem. 619 | */ 620 | Information = 2 621 | } 622 | export interface SourceControlInputBoxValidation { 623 | /** 624 | * The validation message to display. 625 | */ 626 | readonly message: string; 627 | /** 628 | * The validation type. 629 | */ 630 | readonly type: SourceControlInputBoxValidationType; 631 | } 632 | /** 633 | * Represents the input box in the Source Control viewlet. 634 | */ 635 | export interface SourceControlInputBox { 636 | /** 637 | * A validation function for the input box. It's possible to change 638 | * the validation provider simply by setting this property to a different function. 639 | */ 640 | validateInput ? (value: string, cursorPosition: number) : ProviderResult < SourceControlInputBoxValidation | undefined | null > ; 641 | } 642 | //#endregion 643 | //#region Joao: SCM selected provider 644 | export interface SourceControl { 645 | /** 646 | * Whether the source control is selected. 647 | */ 648 | readonly selected: boolean; 649 | /** 650 | * An event signaling when the selection state changes. 651 | */ 652 | readonly onDidChangeSelection: Event < boolean > ; 653 | } 654 | //#endregion 655 | //#region Joao: SCM Input Box 656 | /** 657 | * Represents the input box in the Source Control viewlet. 658 | */ 659 | export interface SourceControlInputBox { 660 | /** 661 | * Controls whether the input box is visible (default is `true`). 662 | */ 663 | visible: boolean; 664 | } 665 | //#endregion 666 | //#region Comments 667 | /** 668 | * Comments provider related APIs are still in early stages, they may be changed significantly during our API experiments. 669 | */ 670 | interface CommentInfo { 671 | /** 672 | * All of the comment threads associated with the document. 673 | */ 674 | threads: CommentThread[]; 675 | /** 676 | * The ranges of the document which support commenting. 677 | */ 678 | commentingRanges ? : Range[]; 679 | } 680 | export enum CommentThreadCollapsibleState { 681 | /** 682 | * Determines an item is collapsed 683 | */ 684 | Collapsed = 0, 685 | /** 686 | * Determines an item is expanded 687 | */ 688 | Expanded = 1 689 | } 690 | /** 691 | * A collection of comments representing a conversation at a particular range in a document. 692 | */ 693 | interface CommentThread { 694 | /** 695 | * A unique identifier of the comment thread. 696 | */ 697 | threadId: string; 698 | /** 699 | * The uri of the document the thread has been created on. 700 | */ 701 | resource: Uri; 702 | /** 703 | * The range the comment thread is located within the document. The thread icon will be shown 704 | * at the first line of the range. 705 | */ 706 | range: Range; 707 | /** 708 | * The ordered comments of the thread. 709 | */ 710 | comments: Comment[]; 711 | /** 712 | * Whether the thread should be collapsed or expanded when opening the document. Defaults to Collapsed. 713 | */ 714 | collapsibleState ? : CommentThreadCollapsibleState; 715 | } 716 | /** 717 | * A comment is displayed within the editor or the Comments Panel, depending on how it is provided. 718 | */ 719 | interface Comment { 720 | /** 721 | * The id of the comment 722 | */ 723 | commentId: string; 724 | /** 725 | * The text of the comment 726 | */ 727 | body: MarkdownString; 728 | /** 729 | * The display name of the user who created the comment 730 | */ 731 | userName: string; 732 | /** 733 | * The icon path for the user who created the comment 734 | */ 735 | userIconPath ? : Uri; 736 | /** 737 | * @deprecated Use userIconPath instead. The avatar src of the user who created the comment 738 | */ 739 | gravatar ? : string; 740 | /** 741 | * Whether the current user has permission to edit the comment. 742 | * 743 | * This will be treated as false if the comment is provided by a `WorkspaceCommentProvider`, or 744 | * if it is provided by a `DocumentCommentProvider` and no `editComment` method is given. 745 | */ 746 | canEdit ? : boolean; 747 | /** 748 | * Whether the current user has permission to delete the comment. 749 | * 750 | * This will be treated as false if the comment is provided by a `WorkspaceCommentProvider`, or 751 | * if it is provided by a `DocumentCommentProvider` and no `deleteComment` method is given. 752 | */ 753 | canDelete ? : boolean; 754 | /** 755 | * The command to be executed if the comment is selected in the Comments Panel 756 | */ 757 | command ? : Command; 758 | } 759 | export interface CommentThreadChangedEvent { 760 | /** 761 | * Added comment threads. 762 | */ 763 | readonly added: CommentThread[]; 764 | /** 765 | * Removed comment threads. 766 | */ 767 | readonly removed: CommentThread[]; 768 | /** 769 | * Changed comment threads. 770 | */ 771 | readonly changed: CommentThread[]; 772 | } 773 | interface DocumentCommentProvider { 774 | /** 775 | * Provide the commenting ranges and comment threads for the given document. The comments are displayed within the editor. 776 | */ 777 | provideDocumentComments(document: TextDocument, token: CancellationToken): Promise < CommentInfo > ; 778 | /** 779 | * Called when a user adds a new comment thread in the document at the specified range, with body text. 780 | */ 781 | createNewCommentThread(document: TextDocument, range: Range, text: string, token: CancellationToken): Promise < CommentThread > ; 782 | /** 783 | * Called when a user replies to a new comment thread in the document at the specified range, with body text. 784 | */ 785 | replyToCommentThread(document: TextDocument, range: Range, commentThread: CommentThread, text: string, token: CancellationToken): Promise < CommentThread > ; 786 | /** 787 | * Called when a user edits the comment body to the be new text. 788 | */ 789 | editComment ? (document: TextDocument, comment: Comment, text: string, token: CancellationToken) : Promise < void > ; 790 | /** 791 | * Called when a user deletes the comment. 792 | */ 793 | deleteComment ? (document: TextDocument, comment: Comment, token: CancellationToken) : Promise < void > ; 794 | /** 795 | * Notify of updates to comment threads. 796 | */ 797 | onDidChangeCommentThreads: Event < CommentThreadChangedEvent > ; 798 | } 799 | interface WorkspaceCommentProvider { 800 | /** 801 | * Provide all comments for the workspace. Comments are shown within the comments panel. Selecting a comment 802 | * from the panel runs the comment's command. 803 | */ 804 | provideWorkspaceComments(token: CancellationToken): Promise < CommentThread[] > ; 805 | /** 806 | * Notify of updates to comment threads. 807 | */ 808 | onDidChangeCommentThreads: Event < CommentThreadChangedEvent > ; 809 | } 810 | namespace workspace { 811 | export function registerDocumentCommentProvider(provider: DocumentCommentProvider): Disposable; 812 | export function registerWorkspaceCommentProvider(provider: WorkspaceCommentProvider): Disposable; 813 | } 814 | //#endregion 815 | //#region Terminal 816 | export interface Terminal { 817 | /** 818 | * Fires when the terminal's pty slave pseudo-device is written to. In other words, this 819 | * provides access to the raw data stream from the process running within the terminal, 820 | * including VT sequences. 821 | */ 822 | onDidWriteData: Event < string > ; 823 | } 824 | /** 825 | * Represents the dimensions of a terminal. 826 | */ 827 | export interface TerminalDimensions { 828 | /** 829 | * The number of columns in the terminal. 830 | */ 831 | readonly columns: number; 832 | /** 833 | * The number of rows in the terminal. 834 | */ 835 | readonly rows: number; 836 | } 837 | /** 838 | * Represents a terminal without a process where all interaction and output in the terminal is 839 | * controlled by an extension. This is similar to an output window but has the same VT sequence 840 | * compatility as the regular terminal. 841 | * 842 | * Note that an instance of [Terminal](#Terminal) will be created when a TerminalRenderer is 843 | * created with all its APIs available for use by extensions. When using the Terminal object 844 | * of a TerminalRenderer it acts just like normal only the extension that created the 845 | * TerminalRenderer essentially acts as a process. For example when an 846 | * [Terminal.onDidWriteData](#Terminal.onDidWriteData) listener is registered, that will fire 847 | * when [TerminalRenderer.write](#TerminalRenderer.write) is called. Similarly when 848 | * [Terminal.sendText](#Terminal.sendText) is triggered that will fire the 849 | * [TerminalRenderer.onDidAcceptInput](#TerminalRenderer.onDidAcceptInput) event. 850 | * 851 | * **Example:** Create a terminal renderer, show it and write hello world in red 852 | * ```typescript 853 | * const renderer = window.createTerminalRenderer('foo'); 854 | * renderer.terminal.then(t => t.show()); 855 | * renderer.write('\x1b[31mHello world\x1b[0m'); 856 | * ``` 857 | */ 858 | export interface TerminalRenderer { 859 | /** 860 | * The name of the terminal, this will appear in the terminal selector. 861 | */ 862 | name: string; 863 | /** 864 | * The dimensions of the terminal, the rows and columns of the terminal can only be set to 865 | * a value smaller than the maximum value, if this is undefined the terminal will auto fit 866 | * to the maximum value [maximumDimensions](TerminalRenderer.maximumDimensions). 867 | * 868 | * **Example:** Override the dimensions of a TerminalRenderer to 20 columns and 10 rows 869 | * ```typescript 870 | * terminalRenderer.dimensions = { 871 | * cols: 20, 872 | * rows: 10 873 | * }; 874 | * ``` 875 | */ 876 | dimensions: TerminalDimensions | undefined; 877 | /** 878 | * The maximum dimensions of the terminal, this will be undefined immediately after a 879 | * terminal renderer is created and also until the terminal becomes visible in the UI. 880 | * Listen to [onDidChangeMaximumDimensions](TerminalRenderer.onDidChangeMaximumDimensions) 881 | * to get notified when this value changes. 882 | */ 883 | readonly maximumDimensions: TerminalDimensions | undefined; 884 | /** 885 | * The corressponding [Terminal](#Terminal) for this TerminalRenderer. 886 | */ 887 | readonly terminal: Terminal; 888 | /** 889 | * Write text to the terminal. Unlike [Terminal.sendText](#Terminal.sendText) which sends 890 | * text to the underlying _process_, this will write the text to the terminal itself. 891 | * 892 | * **Example:** Write red text to the terminal 893 | * ```typescript 894 | * terminalRenderer.write('\x1b[31mHello world\x1b[0m'); 895 | * ``` 896 | * 897 | * **Example:** Move the cursor to the 10th row and 20th column and write an asterisk 898 | * ```typescript 899 | * terminalRenderer.write('\x1b[10;20H*'); 900 | * ``` 901 | * 902 | * @param text The text to write. 903 | */ 904 | write(text: string): void; 905 | /** 906 | * An event which fires on keystrokes in the terminal or when an extension calls 907 | * [Terminal.sendText](#Terminal.sendText). Keystrokes are converted into their 908 | * corresponding VT sequence representation. 909 | * 910 | * **Example:** Simulate interaction with the terminal from an outside extension or a 911 | * workbench command such as `workbench.action.terminal.runSelectedText` 912 | * ```typescript 913 | * const terminalRenderer = window.createTerminalRenderer('test'); 914 | * terminalRenderer.onDidAcceptInput(data => { 915 | * cosole.log(data); // 'Hello world' 916 | * }); 917 | * terminalRenderer.terminal.then(t => t.sendText('Hello world')); 918 | * ``` 919 | */ 920 | readonly onDidAcceptInput: Event < string > ; 921 | /** 922 | * An event which fires when the [maximum dimensions](#TerminalRenderer.maimumDimensions) of 923 | * the terminal renderer change. 924 | */ 925 | readonly onDidChangeMaximumDimensions: Event < TerminalDimensions > ; 926 | } 927 | export namespace window { 928 | /** 929 | * Create a [TerminalRenderer](#TerminalRenderer). 930 | * 931 | * @param name The name of the terminal renderer, this shows up in the terminal selector. 932 | */ 933 | export function createTerminalRenderer(name: string): TerminalRenderer; 934 | } 935 | //#endregion 936 | //#region Joh -> exclusive document filters 937 | export interface DocumentFilter { 938 | exclusive ? : boolean; 939 | } 940 | //#endregion 941 | //#region mjbvz,joh: https://github.com/Microsoft/vscode/issues/43768 942 | export interface FileRenameEvent { 943 | readonly oldUri: Uri; 944 | readonly newUri: Uri; 945 | } 946 | export interface FileWillRenameEvent { 947 | readonly oldUri: Uri; 948 | readonly newUri: Uri; 949 | waitUntil(thenable: Thenable < WorkspaceEdit > ): void; 950 | } 951 | export namespace workspace { 952 | export const onWillRenameFile: Event < FileWillRenameEvent > ; 953 | export const onDidRenameFile: Event < FileRenameEvent > ; 954 | } 955 | //#endregion 956 | //#region Signature Help 957 | /** 958 | * How a [Signature provider](#SignatureHelpProvider) was triggered 959 | */ 960 | export enum SignatureHelpTriggerReason { 961 | /** 962 | * Signature help was invoked manually by the user or by a command. 963 | */ 964 | Invoke = 1, 965 | /** 966 | * Signature help was triggered by a trigger character. 967 | */ 968 | TriggerCharacter = 2, 969 | /** 970 | * Signature help was triggered by the cursor moving or by the document content changing. 971 | */ 972 | ContentChange = 3, 973 | } 974 | /** 975 | * Contains additional information about the context in which a 976 | * [signature help provider](#SignatureHelpProvider.provideSignatureHelp) is triggered. 977 | */ 978 | export interface SignatureHelpContext { 979 | /** 980 | * Action that caused signature help to be requested. 981 | */ 982 | readonly triggerReason: SignatureHelpTriggerReason; 983 | /** 984 | * Character that caused signature help to be requested. 985 | * 986 | * This is `undefined` when signature help is not triggered by typing, such as when invoking signature help 987 | * or when moving the cursor. 988 | */ 989 | readonly triggerCharacter ? : string; 990 | /** 991 | * Whether or not signature help was previously showing when triggered. 992 | * 993 | * Retriggers occur when the signature help is already active and can be caused by typing a trigger character 994 | * or by a cursor move. 995 | */ 996 | readonly isRetrigger: boolean; 997 | } 998 | export interface SignatureHelpProvider { 999 | provideSignatureHelp(document: TextDocument, position: Position, token: CancellationToken, context: SignatureHelpContext): ProviderResult < SignatureHelp > ; 1000 | } 1001 | export interface SignatureHelpProviderMetadata { 1002 | readonly triggerCharacters: ReadonlyArray < string > ; 1003 | readonly retriggerCharacters: ReadonlyArray < string > ; 1004 | } 1005 | namespace languages { 1006 | export function registerSignatureHelpProvider( 1007 | selector: DocumentSelector, 1008 | provider: SignatureHelpProvider, 1009 | metadata: SignatureHelpProviderMetadata 1010 | ): Disposable; 1011 | } 1012 | //#endregion 1013 | //#region Alex - OnEnter enhancement 1014 | export interface OnEnterRule { 1015 | /** 1016 | * This rule will only execute if the text above the this line matches this regular expression. 1017 | */ 1018 | oneLineAboveText ? : RegExp; 1019 | } 1020 | //#endregion 1021 | //#region Tree View 1022 | /** 1023 | * Options for creating a [TreeView](#TreeView] 1024 | */ 1025 | export interface TreeViewOptions < T > { 1026 | /** 1027 | * A data provider that provides tree data. 1028 | */ 1029 | treeDataProvider: TreeDataProvider < T > ; 1030 | /** 1031 | * Whether to show collapse all action or not. 1032 | */ 1033 | showCollapseAll ? : boolean; 1034 | } 1035 | namespace window { 1036 | export function createTreeView < T > (viewId: string, options: TreeViewOptions < T > ): TreeView < T > ; 1037 | } 1038 | /** 1039 | * Label describing the [Tree item](#TreeItem) 1040 | */ 1041 | export interface TreeItemLabel { 1042 | /** 1043 | * A human-readable string describing the [Tree item](#TreeItem). 1044 | */ 1045 | label: string; 1046 | /** 1047 | * Ranges in the label to highlight. A range is defined as a tuple of two number where the 1048 | * first is the inclusive start index and the second the exclusive end index 1049 | */ 1050 | highlights ? : [number, number][]; 1051 | } 1052 | export class TreeItem2 extends TreeItem { 1053 | /** 1054 | * Label describing this item. When `falsy`, it is derived from [resourceUri](#TreeItem.resourceUri). 1055 | */ 1056 | label ? : string | TreeItemLabel | /* for compilation */ any; 1057 | /** 1058 | * @param label Label describing this item 1059 | * @param collapsibleState [TreeItemCollapsibleState](#TreeItemCollapsibleState) of the tree item. Default is [TreeItemCollapsibleState.None](#TreeItemCollapsibleState.None) 1060 | */ 1061 | constructor(label: TreeItemLabel, collapsibleState ? : TreeItemCollapsibleState); 1062 | } 1063 | //#endregion 1064 | //#region Task 1065 | /** 1066 | * Controls how the task is presented in the UI. 1067 | */ 1068 | export interface TaskPresentationOptions { 1069 | /** 1070 | * Controls whether the terminal is cleared before executing the task. 1071 | */ 1072 | clear ? : boolean; 1073 | } 1074 | //#endregion 1075 | } --------------------------------------------------------------------------------