diff --git a/README.md b/README.md index 71d29a5..99c4800 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,6 @@ Open _Command Palette_, choose `Live Code: Open Preview to the Side`, or simply ```json { - "liveCode.defaultPlatform": { - "type": "string", - "default": "browser", - "description": "Set the execution environment of the preview", - "enum": ["browser", "node"] - }, "liveCode.renderJSX": { "type": "boolean", "default": true, diff --git a/images/browser-plain.svg b/images/browser-plain.svg new file mode 100644 index 0000000..4dbf06b --- /dev/null +++ b/images/browser-plain.svg @@ -0,0 +1 @@ + diff --git a/images/browser.svg b/images/browser.svg new file mode 100644 index 0000000..2f20b8b --- /dev/null +++ b/images/browser.svg @@ -0,0 +1 @@ + diff --git a/images/electron.svg b/images/electron.svg deleted file mode 100644 index 9492963..0000000 --- a/images/electron.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/images/logo-plain.svg b/images/logo-plain.svg new file mode 100644 index 0000000..f00adc9 --- /dev/null +++ b/images/logo-plain.svg @@ -0,0 +1,4 @@ + + + + diff --git a/images/node-plain.svg b/images/node-plain.svg new file mode 100644 index 0000000..0b8d359 --- /dev/null +++ b/images/node-plain.svg @@ -0,0 +1 @@ + diff --git a/package.json b/package.json index 78ff854..bfe5753 100644 --- a/package.json +++ b/package.json @@ -27,34 +27,61 @@ "contributes": { "commands": [ { - "command": "liveCode.openPreviewToSide", - "title": "Open Preview to the Side", + "command": "liveCode.choosePreviewToTheSide", + "title": "Choose A Runtime Preview to the Side", "category": "Live Code", - "icon": "images/logo.svg" + "icon": "images/logo-plain.svg" }, { - "command": "liveCode.changePlatform", - "title": "Change Platform", + "command": "liveCode.openBrowserPreviewToSide", + "title": "Open Browser Preview to the Side", "category": "Live Code", - "icon": "$(notebook-kernel-select)" + "icon": "images/browser-plain.svg" + }, + { + "command": "liveCode.openNodePreviewToSide", + "title": "Open Node.js Preview to the Side", + "category": "Live Code", + "icon": "images/node-plain.svg" + }, + { + "command": "liveCode.changeCurrentRuntimeOfPreview", + "title": "Change Current Runtime of Preview", + "category": "Live Code", + "icon": "images/logo-plain.svg" } ], "menus": { "editor/context": [ { - "command": "liveCode.openPreviewToSide", + "command": "liveCode.openBrowserPreviewToSide", + "when": "editorLangId in liveCode.supportedLanguageIds", + "group": "liveCode" + }, + { + "command": "liveCode.openNodePreviewToSide", "when": "editorLangId in liveCode.supportedLanguageIds", "group": "liveCode" } ], "editor/title": [ { - "command": "liveCode.openPreviewToSide", + "command": "liveCode.openBrowserPreviewToSide", "when": "editorLangId in liveCode.supportedLanguageIds", "group": "navigation" }, { - "command": "liveCode.changePlatform", + "command": "liveCode.openNodePreviewToSide", + "when": "editorLangId in liveCode.supportedLanguageIds", + "group": "navigation" + }, + { + "command": "liveCode.choosePreviewToTheSide", + "when": "editorLangId in liveCode.supportedLanguageIds", + "group": "navigation" + }, + { + "command": "liveCode.changeCurrentRuntimeOfPreview", "when": "liveCode.isPreviewFocus", "group": "navigation" } @@ -62,7 +89,7 @@ }, "keybindings": [ { - "command": "liveCode.openPreviewToSide", + "command": "liveCode.choosePreviewToTheSide", "key": "ctrl+k l", "mac": "cmd+k l", "when": "editorLangId in liveCode.supportedLanguageIds && !liveCode.isPreviewFocus" @@ -72,19 +99,6 @@ "type": "object", "title": "Live Code", "properties": { - "liveCode.defaultPlatform": { - "type": "string", - "default": "browser", - "description": "Set the execution environment for the preview", - "enum": [ - "browser", - "node" - ], - "enumDescriptions": [ - "Execute code in web browser", - "Execute code in Node.js" - ] - }, "liveCode.renderJSX": { "type": "boolean", "default": true, diff --git a/src/extension.ts b/src/extension.ts index d69081f..5d7320a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,7 +2,7 @@ import path from 'path' import * as prettyFormat from 'pretty-format' import * as vscode from 'vscode' -import type {Platform, AnyFunction} from './types' +import type {Runtime, AnyFunction, BuildPlatform} from './types' import bundle from './extension/bundle' import {install} from './extension/install' import getWebviewContent from './extension/getWebviewContent' @@ -37,7 +37,7 @@ const jsLanguageIds = [ 'typescriptreact', ] -const platformTitleMap: Record = { +const runtimeTitleMap: Record = { node: 'Node.js', browser: 'browser', } @@ -49,7 +49,7 @@ const documentPanelMap = new Map() const panelConfigMap = new WeakMap< vscode.WebviewPanel, { - currentPlatform: Platform + currentRuntime: Runtime } >() // instance data @@ -99,42 +99,10 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push( // TODO: unregister if no panel is showing - vscode.commands.registerCommand('liveCode.changePlatform', async () => { - const entry = [...documentPanelMap].find(([, x]) => x.active) - if (!entry) { - return - } - const [document, panel] = entry - const {currentPlatform} = panelConfigMap.get(panel)! - const items: vscode.QuickPickItem[] = [ - { - label: 'node', - description: 'Node.js', - }, - { - label: 'browser', - description: 'Web browser (VS Code built in)', - }, - ] - const result = await vscode.window.showQuickPick( - items.map((x) => ({ - ...x, - description: `${x.description as string}${ - x.label === currentPlatform ? ' - Current' : '' - }`, - })), - { - placeHolder: 'Change platform in current preivew of Live Code', - } - ) - if (result) { - panelConfigMap.set(panel, { - ...panelConfigMap.get(panel), - currentPlatform: result.label as Platform, - }) - void processDocument(document) - } - }), + vscode.commands.registerCommand( + 'liveCode.changeCurrentRuntimeOfPreview', + choosePreviewToTheSide + ), vscode.commands.registerCommand('liveCode.reloadPreview', () => { log('existingPanel reload') const doc = vscode.window.activeTextEditor?.document @@ -147,65 +115,123 @@ export function activate(context: vscode.ExtensionContext) { return } }), - vscode.commands.registerCommand('liveCode.openPreviewToSide', () => { - const doc = vscode.window.activeTextEditor?.document - if (!doc) { - return - } - const existingPanel = documentPanelMap.get(doc) - if (existingPanel) { - existingPanel.reveal() - return - } - const panel = vscode.window.createWebviewPanel( - 'liveCode.preview', - '', - vscode.ViewColumn.Beside, - { - enableScripts: true, - } - ) - documentPanelMap.set(doc, panel) - panelConfigMap.set(panel, {currentPlatform: getDefaultPlatform()}) - panelDataMap.set(panel, {workerRef: {current: null}}) - setPanelTitleAndIcon(panel, doc) - setWebviewContent(panel.webview) - panel.webview.onDidReceiveMessage((e: {type: string; data: unknown}) => { - if (e.type !== 'timeMark') { - log('onDidReceiveMessage', e) - } - if (e.type === 'ready') { - void processDocument(doc) - } else if (e.type === 'revealLine') { - revealSourceLine(e.data) - } else if (e.type === 'requestReload') { - void vscode.commands.executeCommand('liveCode.reloadPreview') - } else if (e.type === 'timeMark') { - log(e.data) - } - }) - const setIsPreviewFocus = (value: boolean) => { - void vscode.commands.executeCommand( - 'setContext', - 'liveCode.isPreviewFocus', - value - ) - } - setIsPreviewFocus(true) - panel.onDidChangeViewState((e) => { - setIsPreviewFocus(e.webviewPanel.active) - }) - panel.onDidDispose(() => { - setIsPreviewFocus(false) - documentPanelMap.delete(doc) - panelConfigMap.delete(panel) - panelDataMap.get(panel)?.workerRef?.current?.terminate() - panelDataMap.delete(panel) - }) + vscode.commands.registerCommand( + 'liveCode.choosePreviewToTheSide', + choosePreviewToTheSide + ), + vscode.commands.registerCommand('liveCode.openBrowserPreviewToSide', () => { + openPreviewToSide('browser') + }), + vscode.commands.registerCommand('liveCode.openNodePreviewToSide', () => { + openPreviewToSide('node') }) ) } +async function showRuntimePick(currentRuntime?: Runtime) { + const items: vscode.QuickPickItem[] = [ + { + label: 'node', + description: 'Node.js', + }, + { + label: 'browser', + description: 'Web browser (VS Code built in)', + }, + ] + const result = await vscode.window.showQuickPick( + items.map((x) => ({ + ...x, + description: `${x.description as string}${ + x.label === currentRuntime ? ' - Current' : '' + }`, + })), + { + placeHolder: `Choose a runtime for Live Code`, + } + ) + + return result ? (result.label as Runtime) : null +} + +async function choosePreviewToTheSide() { + const entry = [...documentPanelMap].find(([, x]) => x.active) + if (!entry) { + const runtime = await showRuntimePick() + if (runtime) { + openPreviewToSide(runtime) + } + return + } + const [document, panel] = entry + const {currentRuntime} = panelConfigMap.get(panel)! + const runtime = await showRuntimePick(currentRuntime) + if (runtime) { + panelConfigMap.set(panel, { + ...panelConfigMap.get(panel), + currentRuntime: runtime, + }) + void processDocument(document) + } +} + +function openPreviewToSide(runtime: Runtime) { + const doc = vscode.window.activeTextEditor?.document + if (!doc) { + return + } + const existingPanel = documentPanelMap.get(doc) + if (existingPanel) { + existingPanel.reveal() + return + } + const panel = vscode.window.createWebviewPanel( + 'liveCode.preview', + '', + vscode.ViewColumn.Beside, + { + enableScripts: true, + } + ) + documentPanelMap.set(doc, panel) + panelConfigMap.set(panel, {currentRuntime: runtime}) + panelDataMap.set(panel, {workerRef: {current: null}}) + setPanelTitleAndIcon(panel, doc) + setWebviewContent(panel.webview) + panel.webview.onDidReceiveMessage((e: {type: string; data: unknown}) => { + if (e.type !== 'timeMark') { + log('onDidReceiveMessage', e) + } + if (e.type === 'ready') { + void processDocument(doc) + } else if (e.type === 'revealLine') { + revealSourceLine(e.data) + } else if (e.type === 'requestReload') { + void vscode.commands.executeCommand('liveCode.reloadPreview') + } else if (e.type === 'timeMark') { + log(e.data) + } + }) + const setIsPreviewFocus = (value: boolean) => { + void vscode.commands.executeCommand( + 'setContext', + 'liveCode.isPreviewFocus', + value + ) + } + setIsPreviewFocus(true) + panel.onDidChangeViewState((e) => { + setIsPreviewFocus(e.webviewPanel.active) + }) + panel.onDidDispose(() => { + setIsPreviewFocus(false) + documentPanelMap.delete(doc) + panelConfigMap.delete(panel) + panelDataMap.get(panel)?.workerRef?.current?.terminate() + panelDataMap.delete(panel) + }) +} + export function deactivate() { itsContext = null documentPanelMap.clear() @@ -231,8 +257,8 @@ async function processDocument( const workerRef = panelDataMap.get(panel)?.workerRef await workerRef?.current?.terminate() - const {currentPlatform} = panelConfigMap.get(panel)! - const isBrowser = currentPlatform === 'browser' + const {currentRuntime} = panelConfigMap.get(panel)! + const isBrowser = currentRuntime === 'browser' setPanelTitleAndIcon(panel, document) let error: unknown let code: string | void @@ -242,7 +268,7 @@ async function processDocument( const timer = timeMark<'bundle' | 'nodeVM' | 'postMessage'>() timer.start('bundle') ;[error, code] = await of( - bundleDocument(document, currentPlatform).then((r) => { + bundleDocument(document, currentRuntime).then((r) => { if (isBrowser) { css = r?.css } @@ -251,7 +277,7 @@ async function processDocument( }) ) timer.end('bundle') - if (code && currentPlatform === 'node') { + if (code && currentRuntime === 'node') { timer.start('nodeVM') ;[error, {result, logs} = {}] = await of( nodeVM.runInNewContext(code, { @@ -276,7 +302,7 @@ async function processDocument( type: shouldReload ? 'codeReload' : 'code', // data should be serialized data: { - platform: currentPlatform, + platform: currentRuntime, config: {...vscode.workspace.getConfiguration('liveCode')}, result, logs, @@ -300,8 +326,9 @@ function debounce>(fn: T, wait: number) { } // https://icon-sets.iconify.design/devicon/ -const iconMap: Record = { - browser: 'images/electron.svg', +// https://icon-sets.iconify.design/devicon-plain/ +const iconMap: Record = { + browser: 'images/browser.svg', node: 'images/node.svg', } @@ -309,14 +336,14 @@ function setPanelTitleAndIcon( panel: vscode.WebviewPanel, document: vscode.TextDocument ) { - const {currentPlatform} = panelConfigMap.get(panel)! + const {currentRuntime} = panelConfigMap.get(panel)! panel.title = `Preview` + (document ? ' ' + path.basename(document.uri.fsPath) : '') + - ` in ${platformTitleMap[currentPlatform]}` + ` in ${runtimeTitleMap[currentRuntime]}` panel.iconPath = vscode.Uri.joinPath( itsContext!.extensionUri, - iconMap[currentPlatform] + iconMap[currentRuntime] ) } @@ -339,12 +366,15 @@ function setWebviewContent(webview: vscode.Webview) { }) } -function getDefaultPlatform() { - const config = vscode.workspace.getConfiguration('liveCode') - return (config.get('defaultPlatform') ?? 'browser') as Platform -} +// function getDefaultRuntime() { +// const config = vscode.workspace.getConfiguration('liveCode') +// return (config.get('defaultRuntime') ?? 'browser') as Runtime +// } -function bundleDocument(document: vscode.TextDocument, platform: Platform) { +function bundleDocument( + document: vscode.TextDocument, + platform: BuildPlatform +) { return bundle(transform(document.getText()), { platform, filename: document.isUntitled ? undefined : document.uri.fsPath, diff --git a/src/preview.tsx b/src/preview.tsx index 757b08f..069e43a 100644 --- a/src/preview.tsx +++ b/src/preview.tsx @@ -13,7 +13,7 @@ import {IsDarkModeProvider, useIsDarkMode} from './preview/darkMode' import ErrorBoundary from './preview/ErrorBoundary' import Inspector from './preview/Inspector' import {StyledConsole, Hook, Unhook, Message} from './preview/console' -import {AppConfig, AnyFunction, Platform} from './types' +import {AppConfig, AnyFunction, Runtime} from './types' import timeMark from './utils/timeMark' import {of} from './utils/promise' @@ -97,14 +97,14 @@ declare global { } type Data = { - platform: Platform + platform: Runtime error?: unknown code?: string css?: string result?: [string, ExpContext][] logs: Message[] config: { - defaultPlatform: Platform + // defaultRuntime: Runtime renderJSX: boolean showLineNumbers: boolean } diff --git a/src/types.ts b/src/types.ts index 8935a4e..23d439d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,13 @@ -export type Platform = 'browser' | 'node' +import type {Platform} from 'esbuild' + +/** + * The runtime supported by extension + */ +export type Runtime = 'browser' | 'node' +/** + * The platform supported by ESBuild + */ +export type BuildPlatform = Platform export type AppConfig = { timestamp?: number