From a186930ab0514d930078f94c7ff4b184b0707aeb Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Mon, 17 Oct 2022 16:12:04 -0700 Subject: [PATCH] allow to copy non interactive shell output --- examples/README.md | 8 +++ package.json | 3 +- src/client/components/deno.ts | 18 +++--- src/client/components/index.ts | 1 + src/client/components/outputItems.ts | 76 ++++++++++++++++++++++++++ src/client/index.ts | 5 ++ src/constants.ts | 5 +- src/extension/executors/deno/deploy.ts | 8 +-- src/extension/executors/shell.ts | 16 +++++- src/extension/kernel.ts | 20 ++++--- src/extension/provider/copy.ts | 2 +- src/types.ts | 17 +++--- 12 files changed, 147 insertions(+), 32 deletions(-) create mode 100644 src/client/components/outputItems.ts diff --git a/examples/README.md b/examples/README.md index 9ee352916..f5608d241 100644 --- a/examples/README.md +++ b/examples/README.md @@ -84,3 +84,11 @@ Supports changes to `$PATH`: export PATH="/some/path:$PATH" echo $PATH ``` + +## Copy From Result Cell + +You can copy also results from the inline executed shell: + +```sh { interactive=false } +openssl rand -base64 32 +``` diff --git a/package.json b/package.json index 1574aa06d..2b2f8a618 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,8 @@ "stateful.runme/shell-stdout", "stateful.runme/vercel-stdout", "stateful.runme/deno-stdout", - "stateful.runme/html-stdout" + "stateful.runme/html-stdout", + "stateful.runme/output-items" ], "requiresMessaging": "optional" } diff --git a/src/client/components/deno.ts b/src/client/components/deno.ts index 3f57a5787..42976cf5c 100644 --- a/src/client/components/deno.ts +++ b/src/client/components/deno.ts @@ -5,8 +5,8 @@ import '@vscode/webview-ui-toolkit/dist/button/index' import { getContext } from '../utils' import { Deployment } from '../../utils/deno/api_types' -import { DenoMessages } from '../../constants' -import type { DenoMessage } from '../../types' +import { ClientMessages } from '../../constants' +import type { ClientMessage } from '../../types' import './spinner' @@ -112,8 +112,8 @@ export class DenoOutput extends LitElement { this.#isPromoting= true this.requestUpdate() - ctx.postMessage(>{ - type: DenoMessages.promote, + ctx.postMessage(>{ + type: ClientMessages.promote, output: { id: deployment.projectId, productionDeployment: deployment.id @@ -129,19 +129,19 @@ export class DenoOutput extends LitElement { return } - ctx.onDidReceiveMessage((e: DenoMessage) => { + ctx.onDidReceiveMessage((e: ClientMessage) => { if (!e.type.startsWith('deno:')) { return } switch (e.type) { - case DenoMessages.deployed: { - const payload = e.output as DenoMessage['output'] + case ClientMessages.deployed: { + const payload = e.output as ClientMessage['output'] this.#promoted = payload break } - case DenoMessages.update: { - const payload = e.output as DenoMessage['output'] + case ClientMessages.update: { + const payload = e.output as ClientMessage['output'] this.deployed = Boolean(payload.deployed) this.project = payload.project this.deployments = payload.deployments diff --git a/src/client/components/index.ts b/src/client/components/index.ts index f3f77d0b1..860842a76 100644 --- a/src/client/components/index.ts +++ b/src/client/components/index.ts @@ -5,3 +5,4 @@ export * from './vercel' export * from './deno' export * from './vite' export * from './svelte' +export * from './outputItems' diff --git a/src/client/components/outputItems.ts b/src/client/components/outputItems.ts new file mode 100644 index 000000000..27f5769cd --- /dev/null +++ b/src/client/components/outputItems.ts @@ -0,0 +1,76 @@ +import { LitElement, css, html } from 'lit' +import { customElement, property } from 'lit/decorators.js' + +import '@vscode/webview-ui-toolkit/dist/button/index' + +import { getContext } from '../utils' +import { ClientMessage } from '../../types' +import { ClientMessages } from '../../constants' + +@customElement('shell-output-items') +export class ShellOutputItems extends LitElement { + // Define scoped styles right with your component, in plain CSS + static styles = css` + .output-items { + display: flex; + width: 101%; + justify-content: flex-end; + } + vscode-button { + background: transparent; + color: #ccc; + transform: scale(.9); + } + vscode-button:hover { + background: var(--button-secondary-background); + } + vscode-button:focus { + outline: #007fd4 1px solid; + } + .icon { + width: 13px; + margin: 0 5px 0 -5px; + padding: 0; + } + ` + + @property({ type: String }) + content = '' + + // Render the UI as a function of component state + render() { + return html`
+ + + + + + Copy + + ` + } + + #copy () { + const ctx = getContext() + + if (!ctx.postMessage) { + return + } + + return navigator.clipboard.writeText(this.content).then( + () => ctx.postMessage!(>{ + type: ClientMessages.infoMessage, + output: 'Copied result content to clipboard!' + }), + (err) => ctx.postMessage!(>{ + type: ClientMessages.errorMessage, + output: `'Failed to copy to clipboard: ${err.message}!'` + }) + ) + } +} diff --git a/src/client/index.ts b/src/client/index.ts index 1b75b02ba..73181035d 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -54,6 +54,11 @@ export const activate: ActivationFunction = (context: RendererContext) => iframe.setAttribute('style', 'width: 100%; border: 0; height: 400px;') element.appendChild(iframe) break + case OutputType.outputItems: + const outputItemElem = document.createElement('shell-output-items') + outputItemElem.setAttribute('content', payload.output as string) + element.appendChild(outputItemElem) + break case OutputType.error: element.innerHTML = `⚠️ ${payload.output}` break diff --git a/src/constants.ts b/src/constants.ts index 1976fbc7f..f0de32212 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -9,10 +9,13 @@ export enum OutputType { deno = 'stateful.runme/deno-stdout', html = 'stateful.runme/html-stdout', script = 'stateful.runme/script-stdout', + outputItems = 'stateful.runme/output-items', error = 'error' } -export enum DenoMessages { +export enum ClientMessages { + infoMessage = 'common:infoMessage', + errorMessage = 'common:errorMessage', update = 'deno:deploymentUpdate', deployed = 'deno:finishedDeployment', promote = 'deno:promoteDeployment' diff --git a/src/extension/executors/deno/deploy.ts b/src/extension/executors/deno/deploy.ts index e42df8775..5c04f0f4c 100644 --- a/src/extension/executors/deno/deploy.ts +++ b/src/extension/executors/deno/deploy.ts @@ -1,11 +1,11 @@ import { NotebookCellOutput, NotebookCellOutputItem, NotebookCellExecution } from 'vscode' import { renderError } from '../utils' -import { OutputType, DenoMessages } from '../../../constants' +import { OutputType, ClientMessages } from '../../../constants' import { ENV_STORE, DENO_ACCESS_TOKEN_KEY } from '../../constants' import { API } from '../../../utils/deno/api' import type { Kernel } from '../../kernel' -import type { CellOutput, DenoMessage } from '../../../types' +import type { CellOutput, ClientMessage } from '../../../types' export async function deploy ( this: Kernel, @@ -47,8 +47,8 @@ export async function deploy ( } deployed = created > start - this.messaging.postMessage(>{ - type: DenoMessages.update, + this.messaging.postMessage(>{ + type: ClientMessages.update, output: { deployed, deployments, diff --git a/src/extension/executors/shell.ts b/src/extension/executors/shell.ts index 9a75e2b50..499a43a2c 100644 --- a/src/extension/executors/shell.ts +++ b/src/extension/executors/shell.ts @@ -16,8 +16,11 @@ async function shellExecutor( const outputItems: string[] = [] const child = spawn(script, { cwd, shell: true, env }) console.log(`[Runme] Started process on pid ${child.pid}`) - // this needs more work / specification + /** + * this needs more work / specification + */ const contentType = exec.cell.metadata.attributes?.['output'] + /** * handle output for stdout and stderr */ @@ -33,7 +36,16 @@ async function shellExecutor( }, contentType) } - exec.replaceOutput(new NotebookCellOutput([ item ])) + console.log('OITPUT TWIC') + exec.replaceOutput([ + new NotebookCellOutput([ item ]), + new NotebookCellOutput([ + NotebookCellOutputItem.json(>{ + type: OutputType.outputItems, + output: outputItems.join('\n') + }, OutputType.outputItems) + ]) + ]) } child.stdout.on('data', handleOutput) diff --git a/src/extension/kernel.ts b/src/extension/kernel.ts index d690f6590..7eace9e08 100644 --- a/src/extension/kernel.ts +++ b/src/extension/kernel.ts @@ -1,7 +1,7 @@ import vscode, { ExtensionContext } from 'vscode' -import type { DenoMessage } from '../types' -import { DenoMessages } from '../constants' +import type { ClientMessage } from '../types' +import { ClientMessages } from '../constants' import { API } from '../utils/deno/api' import executor from './executors' @@ -36,9 +36,9 @@ export class Kernel implements vscode.Disposable { this.#disposables.forEach((d) => d.dispose()) } - async #handleRendererMessage ({ message }: { message: DenoMessage }) { - if (message.type === DenoMessages.promote) { - const payload = message as DenoMessage + async #handleRendererMessage ({ message }: { message: ClientMessage }) { + if (message.type === ClientMessages.promote) { + const payload = message as ClientMessage const token = ENV_STORE.get(DENO_ACCESS_TOKEN_KEY) if (!token) { return @@ -46,11 +46,17 @@ export class Kernel implements vscode.Disposable { const api = API.fromToken(token) const deployed = await api.promoteDeployment(payload.output.id, payload.output.productionDeployment) - this.messaging.postMessage(>{ - type: DenoMessages.deployed, + this.messaging.postMessage(>{ + type: ClientMessages.deployed, output: deployed }) + } else if (message.type === ClientMessages.infoMessage) { + return vscode.window.showInformationMessage(message.output as string) + } else if (message.type === ClientMessages.errorMessage) { + return vscode.window.showInformationMessage(message.output as string) } + + console.log(`Unknown event type: ${message.type}`) } private async _executeAll(cells: vscode.NotebookCell[]) { diff --git a/src/extension/provider/copy.ts b/src/extension/provider/copy.ts index ee471011a..e004f899b 100644 --- a/src/extension/provider/copy.ts +++ b/src/extension/provider/copy.ts @@ -3,7 +3,7 @@ import vscode from 'vscode' export class CopyProvider implements vscode.NotebookCellStatusBarItemProvider { async provideCellStatusBarItems(): Promise { const item = new vscode.NotebookCellStatusBarItem( - '$(clippy) Copy', + '$(copy) Copy', vscode.NotebookCellStatusBarAlignment.Right ) item.command = 'runme.copyCellToClipboard' diff --git a/src/types.ts b/src/types.ts index bbbd83423..9511cae89 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -import { OutputType, DenoMessages } from './constants' +import { OutputType, ClientMessages } from './constants' export interface ParsedReadmeEntry { name?: string @@ -46,17 +46,20 @@ interface Payload { type: string payload: any } + [OutputType.outputItems]: string } -export interface DenoMessage { +export interface ClientMessage { type: T - output: DenoMessagePayload[T] + output: ClientMessagePayload[T] } -export interface DenoMessagePayload { - [DenoMessages.deployed]: boolean - [DenoMessages.update]: DenoPayload - [DenoMessages.promote]: { +export interface ClientMessagePayload { + [ClientMessages.deployed]: boolean + [ClientMessages.update]: DenoPayload + [ClientMessages.promote]: { id: string productionDeployment: string } + [ClientMessages.infoMessage]: string + [ClientMessages.errorMessage]: string }