Skip to content

Commit bdd3721

Browse files
committed
Add custom editor test extension
Adds a simple set of tests for custom editors in a new extension. This is currently not run during CI since we want more testing to make sure it is reliable
1 parent d4d1e3b commit bdd3721

15 files changed

Lines changed: 1231 additions & 1 deletion

File tree

.vscode/launch.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,24 @@
176176
"order": 6
177177
}
178178
},
179+
{
180+
"type": "extensionHost",
181+
"request": "launch",
182+
"name": "VS Code Custom Editor Tests",
183+
"runtimeExecutable": "${execPath}",
184+
"args": [
185+
"${workspaceFolder}/extensions/vscode-custom-editor-tests/test-workspace",
186+
"--extensionDevelopmentPath=${workspaceFolder}/extensions/vscode-custom-editor-tests",
187+
"--extensionTestsPath=${workspaceFolder}/extensions/vscode-custom-editor-tests/out/test"
188+
],
189+
"outFiles": [
190+
"${workspaceFolder}/out/**/*.js"
191+
],
192+
"presentation": {
193+
"group": "5_tests",
194+
"order": 6
195+
}
196+
},
179197
{
180198
"type": "chrome",
181199
"request": "attach",

build/lib/extensions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,8 @@ const excludedCommonExtensions = [
230230
'vscode-test-resolver',
231231
'ms-vscode.node-debug',
232232
'ms-vscode.node-debug2',
233-
'vscode-notebook-tests'
233+
'vscode-notebook-tests',
234+
'vscode-custom-editor-tests',
234235
];
235236
const excludedDesktopExtensions = excludedCommonExtensions.concat([
236237
'vscode-web-playground',
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
// @ts-check
6+
(function () {
7+
// @ts-ignore
8+
const vscode = acquireVsCodeApi();
9+
10+
const textArea = document.querySelector('textarea');
11+
12+
const initialState = vscode.getState();
13+
if (initialState) {
14+
textArea.value = initialState.value;
15+
}
16+
17+
window.addEventListener('message', e => {
18+
switch (e.data.type) {
19+
case 'fakeInput':
20+
{
21+
const value = e.data.value;
22+
textArea.value = value;
23+
onInput();
24+
break;
25+
}
26+
27+
case 'setValue':
28+
{
29+
const value = e.data.value;
30+
textArea.value = value;
31+
vscode.setState({ value });
32+
33+
vscode.postMessage({
34+
type: 'didChangeContent',
35+
value: value
36+
});
37+
break;
38+
}
39+
}
40+
});
41+
42+
const onInput = () => {
43+
const value = textArea.value;
44+
vscode.setState({ value });
45+
vscode.postMessage({
46+
type: 'edit',
47+
value: value
48+
});
49+
vscode.postMessage({
50+
type: 'didChangeContent',
51+
value: value
52+
});
53+
};
54+
55+
textArea.addEventListener('input', onInput);
56+
}());
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"name": "vscode-custom-editor-tests",
3+
"description": "Custom editor tests for VS Code",
4+
"version": "0.0.1",
5+
"publisher": "vscode",
6+
"license": "MIT",
7+
"private": true,
8+
"activationEvents": [
9+
"onCustomEditor:testWebviewEditor.abc"
10+
],
11+
"main": "./out/extension",
12+
"enableProposedApi": true,
13+
"engines": {
14+
"vscode": "^1.48.0"
15+
},
16+
"scripts": {
17+
"compile": "node ./node_modules/vscode/bin/compile -watch -p ./",
18+
"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-notebook-tests ./tsconfig.json"
19+
},
20+
"dependencies": {
21+
"p-limit": "^3.0.2"
22+
},
23+
"devDependencies": {
24+
"@types/node": "^12.11.7",
25+
"@types/p-limit": "^2.2.0",
26+
"mocha": "^2.3.3",
27+
"mocha-junit-reporter": "^1.17.0",
28+
"mocha-multi-reporters": "^1.1.7",
29+
"vscode": "^1.1.36"
30+
},
31+
"contributes": {
32+
"customEditors": [
33+
{
34+
"viewType": "testWebviewEditor.abc",
35+
"displayName": "Test ABC editor",
36+
"selector": [
37+
{
38+
"filenamePattern": "*.abc"
39+
}
40+
]
41+
}
42+
]
43+
}
44+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as pLimit from 'p-limit';
7+
import * as path from 'path';
8+
import * as vscode from 'vscode';
9+
import { Disposable } from './dispose';
10+
11+
export namespace Testing {
12+
export const abcEditorContentChangeCommand = '_abcEditor.contentChange';
13+
export const abcEditorTypeCommand = '_abcEditor.type';
14+
15+
export interface CustomEditorContentChangeEvent {
16+
readonly content: string;
17+
readonly source: vscode.Uri;
18+
}
19+
}
20+
21+
export class AbcTextEditorProvider implements vscode.CustomTextEditorProvider {
22+
23+
public static readonly viewType = 'testWebviewEditor.abc';
24+
25+
private activeEditor?: AbcEditor;
26+
27+
public constructor(
28+
private readonly context: vscode.ExtensionContext,
29+
) { }
30+
31+
public register(): vscode.Disposable {
32+
const provider = vscode.window.registerCustomEditorProvider(AbcTextEditorProvider.viewType, this);
33+
34+
const commands: vscode.Disposable[] = [];
35+
commands.push(vscode.commands.registerCommand(Testing.abcEditorTypeCommand, (content: string) => {
36+
this.activeEditor?.testing_fakeInput(content);
37+
}));
38+
39+
return vscode.Disposable.from(provider, ...commands);
40+
}
41+
42+
public async resolveCustomTextEditor(document: vscode.TextDocument, panel: vscode.WebviewPanel) {
43+
const editor = new AbcEditor(document, this.context.extensionPath, panel);
44+
45+
this.activeEditor = editor;
46+
47+
panel.onDidChangeViewState(({ webviewPanel }) => {
48+
if (this.activeEditor === editor && !webviewPanel.active) {
49+
this.activeEditor = undefined;
50+
}
51+
if (webviewPanel.active) {
52+
this.activeEditor = editor;
53+
}
54+
});
55+
}
56+
}
57+
58+
class AbcEditor extends Disposable {
59+
60+
public readonly _onDispose = this._register(new vscode.EventEmitter<void>());
61+
public readonly onDispose = this._onDispose.event;
62+
63+
private readonly limit = pLimit(1);
64+
private syncedVersion: number = -1;
65+
private currentWorkspaceEdit?: Thenable<void>;
66+
67+
constructor(
68+
private readonly document: vscode.TextDocument,
69+
private readonly _extensionPath: string,
70+
private readonly panel: vscode.WebviewPanel,
71+
) {
72+
super();
73+
74+
panel.webview.options = {
75+
enableScripts: true,
76+
};
77+
panel.webview.html = this.html;
78+
79+
this._register(vscode.workspace.onDidChangeTextDocument(e => {
80+
if (e.document === this.document) {
81+
this.update();
82+
}
83+
}));
84+
85+
this._register(panel.webview.onDidReceiveMessage(message => {
86+
switch (message.type) {
87+
case 'edit':
88+
this.doEdit(message.value);
89+
break;
90+
91+
case 'didChangeContent':
92+
vscode.commands.executeCommand(Testing.abcEditorContentChangeCommand, {
93+
content: message.value,
94+
source: document.uri,
95+
} as Testing.CustomEditorContentChangeEvent);
96+
break;
97+
}
98+
}));
99+
100+
this._register(panel.onDidDispose(() => { this.dispose(); }));
101+
102+
this.update();
103+
}
104+
105+
public testing_fakeInput(value: string) {
106+
this.panel.webview.postMessage({
107+
type: 'fakeInput',
108+
value: value,
109+
});
110+
}
111+
112+
private async doEdit(value: string) {
113+
const edit = new vscode.WorkspaceEdit();
114+
edit.replace(this.document.uri, this.document.validateRange(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(999999, 999999))), value);
115+
this.limit(() => {
116+
this.currentWorkspaceEdit = vscode.workspace.applyEdit(edit).then(() => {
117+
this.syncedVersion = this.document.version;
118+
this.currentWorkspaceEdit = undefined;
119+
});
120+
return this.currentWorkspaceEdit;
121+
});
122+
}
123+
124+
public dispose() {
125+
if (this.isDisposed) {
126+
return;
127+
}
128+
129+
this._onDispose.fire();
130+
super.dispose();
131+
}
132+
133+
private get html() {
134+
const contentRoot = path.join(this._extensionPath, 'customEditorMedia');
135+
const scriptUri = vscode.Uri.file(path.join(contentRoot, 'textEditor.js'));
136+
const nonce = Date.now() + '';
137+
return /* html */`<!DOCTYPE html>
138+
<html lang="en">
139+
<head>
140+
<meta charset="UTF-8">
141+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
142+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${nonce}'; style-src 'unsafe-inline';">
143+
<title>Document</title>
144+
</head>
145+
<body>
146+
<textarea style="width: 300px; height: 300px;"></textarea>
147+
<script nonce=${nonce} src="${this.panel.webview.asWebviewUri(scriptUri)}"></script>
148+
</body>
149+
</html>`;
150+
}
151+
152+
public async update() {
153+
await this.currentWorkspaceEdit;
154+
155+
if (this.isDisposed || this.syncedVersion >= this.document.version) {
156+
return;
157+
}
158+
159+
this.panel.webview.postMessage({
160+
type: 'setValue',
161+
value: this.document.getText(),
162+
});
163+
this.syncedVersion = this.document.version;
164+
}
165+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
8+
export function disposeAll(disposables: vscode.Disposable[]) {
9+
while (disposables.length) {
10+
const item = disposables.pop();
11+
if (item) {
12+
item.dispose();
13+
}
14+
}
15+
}
16+
17+
export abstract class Disposable {
18+
private _isDisposed = false;
19+
20+
protected _disposables: vscode.Disposable[] = [];
21+
22+
public dispose(): any {
23+
if (this._isDisposed) {
24+
return;
25+
}
26+
this._isDisposed = true;
27+
disposeAll(this._disposables);
28+
}
29+
30+
protected _register<T extends vscode.Disposable>(value: T): T {
31+
if (this._isDisposed) {
32+
value.dispose();
33+
} else {
34+
this._disposables.push(value);
35+
}
36+
return value;
37+
}
38+
39+
protected get isDisposed() {
40+
return this._isDisposed;
41+
}
42+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import { AbcTextEditorProvider } from './customTextEditor';
8+
9+
export function activate(context: vscode.ExtensionContext) {
10+
context.subscriptions.push(new AbcTextEditorProvider(context).register());
11+
}

0 commit comments

Comments
 (0)