Skip to content

Commit f56720c

Browse files
committed
use custom editor API, closes llvm#9
1 parent cc9dbf1 commit f56720c

18 files changed

+1603
-609
lines changed

.cspell.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"version": "0.1",
3+
"language": "en",
4+
"words": [
5+
"dvirtz",
6+
"Yitzchaki"
7+
],
8+
"ignorePaths": [
9+
"node_modules/**"
10+
],
11+
"flagWords": [],
12+
"languageSettings": [
13+
{
14+
"languageId": "markdown",
15+
"ignoreRegExpList": [
16+
"/`.*?`/", // inline code
17+
"/(```+)\\s?[\\s\\S]+?\\1/g", // code block
18+
"/'s\\b/", // trailing 's
19+
"<!--.*?-->" // comments
20+
]
21+
}
22+
]
23+
}

.github/workflows/main.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ jobs:
2525
uses: GabrielBB/[email protected]
2626
with:
2727
run: npm test
28+
- name: Spellcheck
29+
run: npm run spellcheck

.vscode/tasks.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,20 @@
1111
"presentation": {
1212
"reveal": "never"
1313
},
14+
"group": "build",
15+
"label": "npm: watch",
16+
"detail": "tsc -watch -p ./"
17+
},
18+
{
19+
"type": "npm",
20+
"script": "test",
1421
"group": {
1522
"kind": "build",
1623
"isDefault": true
17-
}
24+
},
25+
"problemMatcher": [],
26+
"label": "npm: test",
27+
"detail": "node ./out/test/runTest.js"
1828
}
1929
]
20-
}
30+
}

package-lock.json

Lines changed: 1230 additions & 371 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,10 @@
3232
"viewer"
3333
],
3434
"activationEvents": [
35-
"onLanguage:parquet"
35+
"onCustomEditor:parquetViewer.parquetViewer"
3636
],
3737
"main": "./out/extension",
3838
"contributes": {
39-
"languages": [
40-
{
41-
"id": "parquet",
42-
"extensions": [
43-
".parquet"
44-
]
45-
}
46-
],
47-
"commands": [
48-
{
49-
"command": "extension.viewParquetAsJson",
50-
"category": "Parquet",
51-
"title": "View as JSON"
52-
}
53-
],
5439
"configuration": [
5540
{
5641
"title": "parquet-viewer",
@@ -62,6 +47,17 @@
6247
}
6348
}
6449
}
50+
],
51+
"customEditors": [
52+
{
53+
"viewType": "parquetViewer.parquetViewer",
54+
"displayName": "Parquet Viewer",
55+
"selector": [
56+
{
57+
"filenamePattern": "*.parquet"
58+
}
59+
]
60+
}
6561
]
6662
},
6763
"scripts": {
@@ -70,21 +66,26 @@
7066
"watch": "tsc -watch -p ./",
7167
"pretest": "npm run compile",
7268
"test": "node ./out/test/runTest.js",
73-
"deploy": "vsce publish --yarn"
69+
"deploy": "vsce publish --yarn",
70+
"spellcheck": "cspell '**/*.ts' '**/*.md'"
7471
},
7572
"devDependencies": {
76-
"@types/chai": "^4.2.11",
77-
"@types/chai-as-promised": "^7.1.2",
78-
"@types/glob": "^7.1.1",
79-
"@types/mocha": "^2.2.42",
80-
"@types/node": "^8.10.25",
81-
"@types/vscode": "^1.30.0",
73+
"@types/chai": "^4.2.14",
74+
"@types/chai-as-promised": "^7.1.3",
75+
"@types/glob": "^7.1.3",
76+
"@types/mocha": "^8.0.3",
77+
"@types/node": "^14.14.6",
78+
"@types/vscode": "^1.50.0",
8279
"chai": "^4.2.0",
8380
"chai-as-promised": "^7.1.1",
84-
"mocha": "^6.1.4",
85-
"tslint": "^5.8.0",
86-
"typescript": "^3.1.4",
87-
"vsce": "^1.75.0",
88-
"vscode-test": "^1.3.0"
81+
"cspell": "^5.1.3",
82+
"mocha": "^8.2.0",
83+
"tslint": "^6.1.3",
84+
"typescript": "^4.0.5",
85+
"vsce": "^1.81.1",
86+
"vscode-test": "^1.4.1"
87+
},
88+
"dependencies": {
89+
"@async-generators/to-array": "^0.1.0"
8990
}
9091
}

src/dispose.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import * as vscode from 'vscode';
2+
3+
export function disposeAll(disposables: vscode.Disposable[]): void {
4+
while (disposables.length) {
5+
const item = disposables.pop();
6+
if (item) {
7+
item.dispose();
8+
}
9+
}
10+
}
11+
12+
export abstract class Disposable {
13+
private _isDisposed = false;
14+
15+
protected _disposables: vscode.Disposable[] = [];
16+
17+
public dispose(): any {
18+
if (this._isDisposed) {
19+
return;
20+
}
21+
this._isDisposed = true;
22+
disposeAll(this._disposables);
23+
}
24+
25+
protected _register<T extends vscode.Disposable>(value: T): T {
26+
if (this._isDisposed) {
27+
value.dispose();
28+
} else {
29+
this._disposables.push(value);
30+
}
31+
return value;
32+
}
33+
34+
protected get isDisposed(): boolean {
35+
return this._isDisposed;
36+
}
37+
}

src/extension.ts

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,17 @@
22
// The module 'vscode' contains the VS Code extensibility API
33
// Import the module and reference it with the alias vscode in your code below
44
import * as vscode from 'vscode';
5-
import { ParquetContentProvider } from './parquet_content_provider';
5+
import { ParquetEditorProvider } from './parquet-editor-provider';
6+
import { ParquetToolsRunner } from './parquet-tools-runner';
67

78
// this method is called when your extension is activated
89
// your extension is activated the very first time the command is executed
910
export function activate(context: vscode.ExtensionContext) {
1011
console.log('parquet-viewer activated');
1112

12-
// ParquetContentProvider.spawnParquetTools(['-h']).then(process => process.on('error', (err: string) => {
13-
// vscode.window.showErrorMessage('parquet-tools not in PATH (' + err + ')');
14-
// }));
15-
16-
const scheme = 'parquet';
17-
const provider = new ParquetContentProvider();
18-
19-
context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(scheme, provider));
20-
21-
let onFile = function (document: vscode.TextDocument) {
22-
if (document.fileName.endsWith('parquet') && document.uri.scheme !== scheme) {
23-
let uri = vscode.Uri.parse(scheme + '://' + document.uri.path + ".as.json");
24-
vscode.window.showTextDocument(uri);
25-
}
26-
};
27-
28-
context.subscriptions.push(vscode.commands.registerTextEditorCommand('extension.viewParquetAsJson', (textEditor: { document: any; }) => {
29-
let document = textEditor.document;
30-
if (!document.fileName.endsWith('parquet')) {
31-
vscode.window.showErrorMessage("Please open a parquet file");
32-
return; // no editor
33-
}
34-
onFile(document);
13+
ParquetToolsRunner.spawnParquetTools(['-h']).then(process => process.on('error', (err: string) => {
14+
vscode.window.showErrorMessage('parquet-tools not in PATH (' + err + ')');
3515
}));
3616

37-
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(onFile));
38-
context.subscriptions.push(vscode.workspace.onDidChangeTextDocument((e: { document: any; }) => {
39-
onFile(e.document);
40-
}));
41-
42-
if (vscode.window.activeTextEditor) {
43-
onFile(vscode.window.activeTextEditor.document);
44-
}
17+
context.subscriptions.push(ParquetEditorProvider.register(context));
4518
}
46-
47-
// this method is called when your extension is deactivated
48-
export function deactivate() {
49-
}

src/parquet-document-provider.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as vscode from "vscode";
2+
import * as assert from 'assert';
3+
4+
export class ParquetTextDocumentContentProvider implements vscode.TextDocumentContentProvider {
5+
private static jsonMap: Map<string, string> = new Map();
6+
7+
public static has(path: string): boolean {
8+
return ParquetTextDocumentContentProvider.jsonMap.has(path);
9+
}
10+
11+
public static add(path:string, content: string) {
12+
ParquetTextDocumentContentProvider.jsonMap.set(path, content);
13+
}
14+
15+
onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>();
16+
onDidChange = this.onDidChangeEmitter.event;
17+
18+
async provideTextDocumentContent(uri: vscode.Uri): Promise<string | undefined> {
19+
const parquetPath = uri.fsPath.replace(/\.as\.json$/, '');
20+
assert(ParquetTextDocumentContentProvider.has(parquetPath));
21+
return ParquetTextDocumentContentProvider.jsonMap.get(parquetPath);
22+
}
23+
}

src/parquet-editor-provider.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import * as vscode from "vscode";
2+
import * as path from 'path';
3+
import { getNonce } from './util';
4+
import { Disposable } from "./dispose";
5+
import { ParquetTextDocumentContentProvider } from "./parquet-document-provider";
6+
import { ParquetToolsRunner } from "./parquet-tools-runner";
7+
import toArray from '@async-generators/to-array';
8+
9+
10+
class DummyDocument extends Disposable implements vscode.CustomDocument {
11+
uri: vscode.Uri;
12+
path: string;
13+
14+
constructor(uri: vscode.Uri) {
15+
super();
16+
this.uri = uri;
17+
this.path = uri.fsPath;
18+
}
19+
20+
private open() {
21+
vscode.window.showTextDocument(
22+
this.uri.with({ scheme: 'parquet', path: this.path + '.as.json' })
23+
);
24+
}
25+
26+
public async show() {
27+
if (ParquetTextDocumentContentProvider.has(this.path)) {
28+
this.open();
29+
return;
30+
}
31+
32+
return await vscode.window.withProgress({
33+
location: vscode.ProgressLocation.Notification,
34+
title: `opening ${path.basename(this.path)}`,
35+
cancellable: true
36+
},
37+
async (progress, token) => {
38+
try {
39+
const json = await toArray(ParquetToolsRunner.toJson(this.path, token));
40+
ParquetTextDocumentContentProvider.add(this.path, json.join(''));
41+
this.open();
42+
} catch (err) {
43+
if (!token.isCancellationRequested) {
44+
vscode.window.showErrorMessage(err);
45+
}
46+
}
47+
});
48+
}
49+
50+
dispose(): void {
51+
super.dispose();
52+
}
53+
}
54+
55+
export class ParquetEditorProvider implements vscode.CustomReadonlyEditorProvider<DummyDocument> {
56+
57+
private static readonly viewType = 'parquetViewer.parquetViewer';
58+
59+
public static register(context: vscode.ExtensionContext): vscode.Disposable {
60+
const provider = new ParquetEditorProvider;
61+
const providerRegistration = vscode.window.registerCustomEditorProvider(ParquetEditorProvider.viewType, provider);
62+
63+
context.subscriptions.push(
64+
vscode.workspace.registerTextDocumentContentProvider('parquet', new ParquetTextDocumentContentProvider)
65+
);
66+
67+
return providerRegistration;
68+
}
69+
70+
async openCustomDocument(uri: vscode.Uri): Promise<DummyDocument> {
71+
return new DummyDocument(uri);
72+
}
73+
74+
async resolveCustomEditor(
75+
document: DummyDocument,
76+
webviewPanel: vscode.WebviewPanel,
77+
_token: vscode.CancellationToken
78+
): Promise<void> {
79+
// Setup initial content for the webview
80+
webviewPanel.webview.options = {
81+
enableScripts: true,
82+
};
83+
84+
webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview, document);
85+
86+
webviewPanel.webview.onDidReceiveMessage(_ => document.show());
87+
88+
document.show();
89+
}
90+
91+
private getHtmlForWebview(webview: vscode.Webview, document: DummyDocument): string {
92+
// Use a nonce to whitelist which scripts can be run
93+
const nonce = getNonce();
94+
95+
const res = /* html */`
96+
<!DOCTYPE html>
97+
<html>
98+
<head>
99+
<title>browser-amd-editor</title>
100+
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
101+
<!-- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src ${webview.cspSource} 'nonce-${nonce}'; img-src ${webview.cspSource}; style-src 'unsafe-inline' ${webview.cspSource};"> -->
102+
</head>
103+
<body>
104+
<p>Click <a href="${path.basename(document.uri.fsPath)}.as.json" id="here">here</a> to open JSON</p>
105+
<script nonce="${nonce}">
106+
//# sourceURL=to-json.js
107+
const vscode = acquireVsCodeApi();
108+
document.getElementById('here').addEventListener('click', _ => {
109+
vscode.postMessage();
110+
});
111+
</script>
112+
</body>
113+
</html>`;
114+
return res;
115+
}
116+
117+
118+
}

0 commit comments

Comments
 (0)