diff --git a/package-lock.json b/package-lock.json index 30b7ad1..1c43ef8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2494,6 +2494,12 @@ "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", "dev": true }, + "genson-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/genson-js/-/genson-js-0.0.8.tgz", + "integrity": "sha512-4NUusDTwF+lzYh72uKV+Uvpky9iPO+YDIMpGImA5pbHfLV9HwgRCA4hYjGu78V4J4Cx2IZRTFfRERn9aUs74mw==", + "dev": true + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", diff --git a/package.json b/package.json index 32b370c..1d799dd 100644 --- a/package.json +++ b/package.json @@ -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": [ @@ -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" + } ] } }, @@ -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", diff --git a/src/PreviewWebPanel.ts b/src/PreviewWebPanel.ts new file mode 100644 index 0000000..8a601da --- /dev/null +++ b/src/PreviewWebPanel.ts @@ -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 = ` + + +
+ + + + + + + + + + + + `; + return html; +} diff --git a/src/SmartPasteCommand.ts b/src/SmartPasteCommand.ts new file mode 100644 index 0000000..f5aa060 --- /dev/null +++ b/src/SmartPasteCommand.ts @@ -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)); + } +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index ca93ad6..3aab485 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -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!'); @@ -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 = ` - - - - - - - - - - + context.subscriptions.push(vscode.commands.registerCommand('asyncapi.preview', previewAsyncAPI(context))); - - - `; - return html; + context.subscriptions.push(vscode.commands.registerCommand("asyncapi.paste", asyncapiSmartPaste)); } export function deactivate() {} diff --git a/tsconfig.json b/tsconfig.json index 965a7b4..74378c4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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. */