├── .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 |
4 |
--------------------------------------------------------------------------------
/resources/light/remove.svg:
--------------------------------------------------------------------------------
1 |
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 | 
55 |
56 | 选择并粘贴:
57 |
58 | 
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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------