Skip to content

Commit

Permalink
improve editing experience w/ content assistance asyncapi#132 asyncap…
Browse files Browse the repository at this point in the history
  • Loading branch information
ivangsa committed Oct 18, 2022
1 parent 4b097b8 commit b597a6b
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 116 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 25 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,17 @@
"light": "resources/icons/open-preview_black.svg",
"dark": "resources/icons/open-preview_white.svg"
}
},
{
"command": "asyncapi.paste",
"title": "AsyncAPI: Paste as Schema"
}
],
"snippets": [
{
"language": "yaml",
"path": "./snippets/snippets.json"
}
{
"language": "yaml",
"path": "./snippets/snippets.json"
}
],
"menus": {
"editor/title": [
Expand All @@ -69,6 +73,22 @@
{
"command": "asyncapi.preview"
}
],
"editor/context": [
{
"when": "resourceLangId == json || resourceLangId == yaml && asyncapi.isAsyncAPI",
"command": "asyncapi.paste",
"group": "9_cutcopypaste@5"
}
],
"keybindings": [
{
"command": "asyncapi.paste",
"key": "Ctrl+V",
"linux": "Ctrl+Shift+V",
"mac": "Cmd+V",
"when": "editorTextFocus"
}
]
}
},
Expand Down Expand Up @@ -99,6 +119,7 @@
"conventional-changelog-conventionalcommits": "^4.2.3",
"copy-webpack-plugin": "^10.2.4",
"eslint": "^8.14.0",
"genson-js": "0.0.8",
"glob": "^8.0.1",
"mocha": "^9.2.2",
"semantic-release": "19.0.3",
Expand Down
116 changes: 116 additions & 0 deletions src/PreviewWebPanel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import * as vscode from 'vscode';
import * as path from 'path';

export function previewAsyncAPI(context) {
return async (uri: vscode.Uri) => {
uri = uri || (await promptForAsyncapiFile()) as vscode.Uri;
if (uri) {
console.log('Opening asyncapi file', uri.fsPath);
openAsyncAPI(context, uri);
}
};
}

export const openAsyncapiFiles: { [id: string]: vscode.WebviewPanel } = {}; // vscode.Uri.fsPath => vscode.WebviewPanel

export function isAsyncAPIFile(document?: vscode.TextDocument) {
if (!document) {
return false;
}
if (document.languageId === 'json') {
try {
const json = JSON.parse(document.getText());
return json.asyncapi;
} catch (e) {
return false;
}
}
if (document.languageId === 'yml' || document.languageId === 'yaml') {
return document.getText().match('^asyncapi:') !== null;
}
return false;
}

export function openAsyncAPI(context: vscode.ExtensionContext, uri: vscode.Uri) {
const localResourceRoots = [
vscode.Uri.file(path.dirname(uri.fsPath)),
vscode.Uri.joinPath(context.extensionUri, 'dist/node_modules/@asyncapi/react-component/browser/standalone'),
vscode.Uri.joinPath(context.extensionUri, 'dist/node_modules/@asyncapi/react-component/styles'),
];
if (vscode.workspace.workspaceFolders) {
vscode.workspace.workspaceFolders.forEach(folder => {
localResourceRoots.push(folder.uri);
});
}
const panel: vscode.WebviewPanel =
openAsyncapiFiles[uri.fsPath] ||
vscode.window.createWebviewPanel('asyncapi-preview', '', vscode.ViewColumn.Two, {
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots,
});
panel.title = path.basename(uri.fsPath);
panel.webview.html = getWebviewContent(context, panel.webview, uri);

panel.onDidDispose(() => {
delete openAsyncapiFiles[uri.fsPath];
});
openAsyncapiFiles[uri.fsPath] = panel;
}

async function promptForAsyncapiFile() {
if (isAsyncAPIFile(vscode.window.activeTextEditor?.document)) {
return vscode.window.activeTextEditor?.document.uri;
}
return await vscode.window.showOpenDialog({
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
openLabel: 'Open AsyncAPI file',
filters: {
AsyncAPI: ['yml', 'yaml', 'json'],
},
});
}

function getWebviewContent(context: vscode.ExtensionContext, webview: vscode.Webview, asyncapiFile: vscode.Uri) {
const asyncapiComponentJs = webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, 'dist/node_modules/@asyncapi/react-component/browser/standalone/index.js')
);
const asyncapiComponentCss = webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, 'dist/node_modules/@asyncapi/react-component/styles/default.min.css')
);
const asyncapiWebviewUri = webview.asWebviewUri(asyncapiFile);
const asyncapiBasePath = asyncapiWebviewUri.toString().replace('%2B', '+'); // this is loaded by a different library so it requires unescaping the + character
const html = `
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="${asyncapiComponentCss}">
</head>
<body x-timestamp="${Date.now()}">
<div id="asyncapi"></div>
<script src="${asyncapiComponentJs}"></script>
<script>
AsyncApiStandalone.render({
schema: {
url: '${asyncapiWebviewUri}',
options: { method: "GET", mode: "cors" },
},
config: {
show: {
sidebar: true,
errors: true,
},
parserOptions: { path: '${asyncapiBasePath}' }
},
}, document.getElementById('asyncapi'));
</script>
</body>
</html>
`;
return html;
}
59 changes: 59 additions & 0 deletions src/SmartPasteCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as vscode from 'vscode';
import * as yml from 'js-yaml';
import { createSchema } from './lib/genson-js/schema-builder';

export async function asyncapiSmartPaste() {
let editor = vscode.window.activeTextEditor;
if(!editor) {
return;
}
let start = editor.selection.start;
let end = editor.selection.end;
let selectedText = editor.document.getText(editor.selection);
let currentLineText = editor.document.lineAt(start.line).text;
let clipboad = await vscode.env.clipboard.readText();

console.log("Smart Pasting", clipboad);
const json = parse(clipboad);
if(typeof json === 'object') {
const schema = { PastedSchema: createSchema(json) };
replace(editor, stringify(schema, editor.document.languageId, 4), editor.selection);
return;
}


return vscode.commands.executeCommand('editor.action.clipboardPasteAction');
}

function insertNewLine(editor: vscode.TextEditor, text: string, line: number) {
editor.edit((editBuilder: vscode.TextEditorEdit) => {
editBuilder.insert(new vscode.Position(line + 1, 0), text + '\n');
});
}

function replace(editor: vscode.TextEditor, text: string, selection: vscode.Selection) {
editor.edit((editBuilder: vscode.TextEditorEdit) => {
editBuilder.replace(selection, text);
editor.revealRange(new vscode.Range(selection.start, selection.start));
});
}

function parse(text: string) {
try {
return JSON.parse(text);
} catch (e) {
try {
return yml.load(text);
} catch (e) {
return text;
}
}
}

function stringify(schema, languageId: string, indentYml: number) {
if(languageId === 'json') {
return JSON.stringify(schema, null, 2);
} else {
return yml.dump(schema).replace(/^(?!\s*$)/gm, ' '.repeat(indentYml));
}
}
115 changes: 4 additions & 111 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as vscode from 'vscode';
import * as path from 'path';
import { isAsyncAPIFile, openAsyncAPI, openAsyncapiFiles, previewAsyncAPI } from './PreviewWebPanel';
import { asyncapiSmartPaste } from './SmartPasteCommand';

const openAsyncapiFiles: { [id: string]: vscode.WebviewPanel } = {}; // vscode.Uri.fsPath => vscode.WebviewPanel

export function activate(context: vscode.ExtensionContext) {
console.log('Congratulations, your extension "asyncapi-preview" is now active!');
Expand Down Expand Up @@ -30,117 +30,10 @@ export function activate(context: vscode.ExtensionContext) {
}
});

let disposable = vscode.commands.registerCommand('asyncapi.preview', async (uri: vscode.Uri) => {
uri = uri || (await promptForAsyncapiFile());
if (uri) {
console.log('Opening asyncapi file', uri.fsPath);
openAsyncAPI(context, uri);
}
});

context.subscriptions.push(disposable);
}

function isAsyncAPIFile(document?: vscode.TextDocument) {
if (!document) {
return false;
}
if (document.languageId === 'json') {
try {
const json = JSON.parse(document.getText());
return json.asyncapi;
} catch (e) {
return false;
}
}
if(document.languageId === 'yml' || document.languageId === 'yaml') {
return document.getText().match('^asyncapi:') !== null;
}
return false;
}

function openAsyncAPI(context: vscode.ExtensionContext, uri: vscode.Uri) {
const localResourceRoots = [
vscode.Uri.file(path.dirname(uri.fsPath)),
vscode.Uri.joinPath(context.extensionUri, 'dist/node_modules/@asyncapi/react-component/browser/standalone'),
vscode.Uri.joinPath(context.extensionUri, 'dist/node_modules/@asyncapi/react-component/styles'),
];
if (vscode.workspace.workspaceFolders) {
vscode.workspace.workspaceFolders.forEach(folder => {
localResourceRoots.push(folder.uri);
});
}
const panel: vscode.WebviewPanel =
openAsyncapiFiles[uri.fsPath] ||
vscode.window.createWebviewPanel('asyncapi-preview', '', vscode.ViewColumn.Two, {
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots,
});
panel.title = path.basename(uri.fsPath);
panel.webview.html = getWebviewContent(context, panel.webview, uri);

panel.onDidDispose(() => {
delete openAsyncapiFiles[uri.fsPath];
});
openAsyncapiFiles[uri.fsPath] = panel;
}

async function promptForAsyncapiFile() {
if (isAsyncAPIFile(vscode.window.activeTextEditor?.document)) {
return vscode.window.activeTextEditor?.document.uri;
}
return await vscode.window.showOpenDialog({
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
openLabel: 'Open AsyncAPI file',
filters: {
AsyncAPI: ['yml', 'yaml', 'json'],
},
});
}

function getWebviewContent(context: vscode.ExtensionContext, webview: vscode.Webview, asyncapiFile: vscode.Uri) {
const asyncapiComponentJs = webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, 'dist/node_modules/@asyncapi/react-component/browser/standalone/index.js')
);
const asyncapiComponentCss = webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, 'dist/node_modules/@asyncapi/react-component/styles/default.min.css')
);
const asyncapiWebviewUri = webview.asWebviewUri(asyncapiFile);
const asyncapiBasePath = asyncapiWebviewUri.toString().replace('%2B', '+'); // this is loaded by a different library so it requires unescaping the + character
const html = `
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="${asyncapiComponentCss}">
</head>
<body x-timestamp="${Date.now()}">
<div id="asyncapi"></div>

<script src="${asyncapiComponentJs}"></script>
<script>
AsyncApiStandalone.render({
schema: {
url: '${asyncapiWebviewUri}',
options: { method: "GET", mode: "cors" },
},
config: {
show: {
sidebar: true,
errors: true,
},
parserOptions: { path: '${asyncapiBasePath}' }
},
}, document.getElementById('asyncapi'));
</script>
context.subscriptions.push(vscode.commands.registerCommand('asyncapi.preview', previewAsyncAPI(context)));

</body>
</html>
`;
return html;
context.subscriptions.push(vscode.commands.registerCommand("asyncapi.paste", asyncapiSmartPaste));
}

export function deactivate() {}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
],
"sourceMap": true,
"rootDir": "src",
"strict": true /* enable all strict type-checking options */
"strict": false /* enable all strict type-checking options */
/* Additional Checks */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
Expand Down

0 comments on commit b597a6b

Please sign in to comment.