Repository: stateful/vscode-runme
Pull request #234 (2023-02-24 19:06:57)

Until underlying bug's fixed
Fixes https://github.com/stateful/vscode-runme/issues/233.

sourishkrout: > Tested and GLTM

Cool. The e2e test has surfaced an edge case. Looking into that before merging.

Patch:
diff --git a/examples/fresh/README.md b/examples/fresh/README.md
index d1b877fc..7421d35c 100644
--- a/examples/fresh/README.md
+++ b/examples/fresh/README.md
@@ -11,7 +11,7 @@ To test if you already have Deno installed run:
 If this prints the Deno version to the console the installation was successful.
 
 ```sh { closeTerminalOnSuccess=false interactive=false }
-  deno --version
+deno --version
 ```
 
 To update a previously installed version of Deno, run:
diff --git a/src/extension/executors/deno/deploy.ts b/src/extension/executors/deno/deploy.ts
index aca9cee0..cda67676 100644
--- a/src/extension/executors/deno/deploy.ts
+++ b/src/extension/executors/deno/deploy.ts
@@ -6,6 +6,7 @@ import { ENV_STORE, DENO_ACCESS_TOKEN_KEY, DENO_PROJECT_NAME_KEY } from '../../c
 import { API } from '../../../utils/deno/api'
 import type { Kernel } from '../../kernel'
 import type { CellOutputPayload, ClientMessage } from '../../../types'
+import { replaceOutput } from '../../utils'
 
 export async function deploy (
   this: Kernel,
@@ -26,7 +27,7 @@ export async function deploy (
     /**
      * render deno status at the behinning of the operation
      */
-    exec.replaceOutput(new NotebookCellOutput([
+    replaceOutput(exec, new NotebookCellOutput([
       NotebookCellOutputItem.json(
         <CellOutputPayload<OutputType.deno>>{ type: OutputType.deno }, OutputType.deno)
     ], { deno: { deploy: true } }))
diff --git a/src/extension/executors/runme.ts b/src/extension/executors/runme.ts
index bfe30978..b6cf9c56 100644
--- a/src/extension/executors/runme.ts
+++ b/src/extension/executors/runme.ts
@@ -4,7 +4,7 @@ import { NotebookCellOutput, NotebookCellOutputItem, NotebookCellExecution } fro
 
 import { RunmeTaskProvider } from '../provider/runmeTask'
 import { OutputType } from '../../constants'
-import { getAnnotations } from '../utils'
+import { getAnnotations, replaceOutput } from '../utils'
 import { ExperimentalTerminal } from '../terminal/terminal'
 import type { CellOutputPayload } from '../../types'
 import type { Kernel } from '../kernel'
@@ -43,7 +43,7 @@ export async function runme(
       }, OutputType.outputItems)
     }
 
-    exec.replaceOutput([ new NotebookCellOutput([ item ]) ])
+    replaceOutput(exec, [ new NotebookCellOutput([ item ]) ])
   })
 
   const { execution, promise } = await terminal.execute(t, {
diff --git a/src/extension/executors/runner.ts b/src/extension/executors/runner.ts
index 0d7906c5..ddc376cd 100644
--- a/src/extension/executors/runner.ts
+++ b/src/extension/executors/runner.ts
@@ -17,7 +17,7 @@ import { OutputType } from '../../constants'
 import { CellOutputPayload } from '../../types'
 import { PLATFORM_OS } from '../constants'
 import { IRunner, IRunnerEnvironment } from '../runner'
-import { getAnnotations, getCmdShellSeq } from '../utils'
+import { getAnnotations, getCmdShellSeq, replaceOutput } from '../utils'
 
 import { closeTerminalByEnvID } from './task'
 import { getShellPath } from './utils'
@@ -79,8 +79,7 @@ export async function executeRunner(
         }, OutputType.outputItems)
       }
 
-      exec.clearOutput() // bug? https://github.com/microsoft/vscode/issues/173577
-      exec.replaceOutput([ new NotebookCellOutput([ item ]) ])
+      replaceOutput(exec, [ new NotebookCellOutput([ item ]) ])
     }
 
     program.onStdoutRaw(handleOutput)
diff --git a/src/extension/executors/shell.ts b/src/extension/executors/shell.ts
index 7da2fceb..c30f5f1f 100644
--- a/src/extension/executors/shell.ts
+++ b/src/extension/executors/shell.ts
@@ -5,7 +5,7 @@ import { NotebookCellOutput, NotebookCellOutputItem, NotebookCellExecution } fro
 import { OutputType } from '../../constants'
 import type { CellOutputPayload } from '../../types'
 import type { Kernel } from '../kernel'
-import { getAnnotations } from '../utils'
+import { getAnnotations, replaceOutput } from '../utils'
 
 const MIME_TYPES_WITH_CUSTOM_RENDERERS = ['text/plain']
 
@@ -74,8 +74,7 @@ async function shellExecutor(
       }, OutputType.outputItems)
     }
 
-    exec.clearOutput() // bug? https://github.com/microsoft/vscode/issues/173577
-    exec.replaceOutput([ new NotebookCellOutput([ item ]) ])
+    replaceOutput(exec, [ new NotebookCellOutput([ item ]) ])
   }
 
   child.stdout.on('data', handleOutput)
diff --git a/src/extension/executors/utils.ts b/src/extension/executors/utils.ts
index 02a839ff..10c88790 100644
--- a/src/extension/executors/utils.ts
+++ b/src/extension/executors/utils.ts
@@ -6,6 +6,7 @@ import { NotebookCellOutput, NotebookCellExecution, NotebookCellOutputItem, wind
 import { ENV_STORE } from '../constants'
 import { OutputType } from '../../constants'
 import type { CellOutputPayload } from '../../types'
+import { replaceOutput } from '../utils'
 
 const ENV_VAR_REGEXP = /(\$\w+)/g
 /**
@@ -14,7 +15,7 @@ const ENV_VAR_REGEXP = /(\$\w+)/g
 const EXPORT_EXTRACT_REGEX = /(\n*)export \w+=(("[^"]*")|('[^']*')|(.+(?=(\n|;))))/gim
 
 export function renderError (exec: NotebookCellExecution, output: string) {
-  return exec.replaceOutput(new NotebookCellOutput([
+  return replaceOutput(exec, new NotebookCellOutput([
     NotebookCellOutputItem.json(
       <CellOutputPayload<OutputType.error>>{
         type: OutputType.error,
diff --git a/src/extension/executors/vercel/auth.ts b/src/extension/executors/vercel/auth.ts
index 7294351c..f8565737 100644
--- a/src/extension/executors/vercel/auth.ts
+++ b/src/extension/executors/vercel/auth.ts
@@ -8,6 +8,7 @@ import { NotebookCellExecution, NotebookCellOutputItem, NotebookCellOutput, wind
 import type { Argv } from 'yargs'
 
 import { renderError } from '../utils'
+import { replaceOutput } from '../../utils'
 
 import { getAuthToken, getConfigFilePath } from './utils'
 
@@ -91,14 +92,14 @@ export async function login (
     await fs.writeFile(configFilePath, JSON.stringify({ token: verifyResponse.token }))
     server.close()
   } catch (err: any) {
-    exec.replaceOutput(new NotebookCellOutput([
+    replaceOutput(exec, new NotebookCellOutput([
       NotebookCellOutputItem.text(err.message)
     ]))
     return false
   }
 
   const { username } = await authPromise as any
-  exec.replaceOutput(new NotebookCellOutput([
+  replaceOutput(exec, new NotebookCellOutput([
     NotebookCellOutputItem.text(`Logged in as ${username}`)
   ]))
   return true
@@ -108,7 +109,7 @@ export async function logout (exec: NotebookCellExecution): Promise<boolean> {
   let token = await getAuthToken()
 
   if (!token) {
-    exec.replaceOutput(new NotebookCellOutput([
+    replaceOutput(exec, new NotebookCellOutput([
       NotebookCellOutputItem.text('Not currently logged in, so logout did nothing')
     ]))
     return true
@@ -143,7 +144,7 @@ export async function logout (exec: NotebookCellExecution): Promise<boolean> {
     return false
   }
 
-  exec.replaceOutput(new NotebookCellOutput([
+  replaceOutput(exec, new NotebookCellOutput([
     NotebookCellOutputItem.text('Logged out!')
   ]))
   return true
diff --git a/src/extension/executors/vercel/deploy.ts b/src/extension/executors/vercel/deploy.ts
index 670f702d..d373f121 100644
--- a/src/extension/executors/vercel/deploy.ts
+++ b/src/extension/executors/vercel/deploy.ts
@@ -10,6 +10,7 @@ import { renderError } from '../utils'
 import { OutputType } from '../../../constants'
 import type { Kernel } from '../../kernel'
 import type { CellOutputPayload } from '../../../types'
+import { replaceOutput } from '../../utils'
 
 import { listTeams, getUser, getProject, getProjects, createProject, cancelDeployment, VercelProject } from './api'
 import { getAuthToken, quickPick, updateGitIgnore, createVercelFile } from './utils'
@@ -147,7 +148,7 @@ export async function deploy (
       }
 
       deploymentId = event.payload.id
-      exec.replaceOutput(new NotebookCellOutput([
+      replaceOutput(exec, new NotebookCellOutput([
         NotebookCellOutputItem.json(<CellOutputPayload<OutputType.vercel>>{
           type: OutputType.vercel,
           output: event
@@ -159,7 +160,7 @@ export async function deploy (
       }
     }
   } catch (err: any) {
-    exec.replaceOutput(new NotebookCellOutput([
+    replaceOutput(exec, new NotebookCellOutput([
       NotebookCellOutputItem.json(<CellOutputPayload<OutputType.error>>{
         type: OutputType.error,
         output: err.message
diff --git a/src/extension/provider/annotations.ts b/src/extension/provider/annotations.ts
index 4d1db631..d4385533 100644
--- a/src/extension/provider/annotations.ts
+++ b/src/extension/provider/annotations.ts
@@ -13,7 +13,7 @@ import { OutputType } from '../../constants'
 import { CellOutputPayload } from '../../types'
 import { RunmeExtension } from '../extension'
 import { Kernel } from '../kernel'
-import { getAnnotations, validateAnnotations } from '../utils'
+import { getAnnotations, replaceOutput, validateAnnotations } from '../utils'
 
 export class AnnotationsProvider implements NotebookCellStatusBarItemProvider {
   constructor(private readonly kernel: Kernel) {
@@ -45,7 +45,7 @@ export class AnnotationsProvider implements NotebookCellStatusBarItemProvider {
           validationErrors: validateAnnotations(cell)
         },
       }
-      await exec.replaceOutput([
+      await replaceOutput(exec, [
         new NotebookCellOutput([
           NotebookCellOutputItem.json(json, OutputType.annotations),
           NotebookCellOutputItem.json(json),
diff --git a/src/extension/utils.ts b/src/extension/utils.ts
index 2910e6da..b6fcf341 100644
--- a/src/extension/utils.ts
+++ b/src/extension/utils.ts
@@ -2,7 +2,15 @@ import path from 'node:path'
 import util from 'node:util'
 import cp from 'node:child_process'
 
-import vscode, { FileType, Uri, workspace, NotebookDocument, NotebookCell } from 'vscode'
+import vscode, {
+  FileType,
+  Uri,
+  workspace,
+  NotebookDocument,
+  NotebookCell,
+  NotebookCellExecution,
+  NotebookCellOutput,
+} from 'vscode'
 import { v5 as uuidv5 } from 'uuid'
 
 import { CellAnnotations, CellAnnotationsErrorResult, Serializer } from '../types'
@@ -267,4 +275,16 @@ export function hashDocumentUri(uri: string): string {
   const salt = vscode.env.machineId
   const namespace = uuidv5(salt, uuidv5.URL)
   return uuidv5(uri, namespace).toString()
-}
\ No newline at end of file
+}
+
+/**
+ * Helper to workaround this bug: https://github.com/microsoft/vscode/issues/173577
+ */
+export function replaceOutput(
+  exec: NotebookCellExecution,
+  out: NotebookCellOutput | readonly NotebookCellOutput[],
+  cell?: NotebookCell
+): Thenable<void> {
+  exec.clearOutput()
+  return exec.replaceOutput(out, cell)
+}


Test patch:
diff --git a/tests/extension/executors/utils.test.ts b/tests/extension/executors/utils.test.ts
index 60ba4bc4..15f54c37 100644
--- a/tests/extension/executors/utils.test.ts
+++ b/tests/extension/executors/utils.test.ts
@@ -18,6 +18,10 @@ vi.mock('vscode', () => ({
   }
 }))
 
+vi.mock('../../../src/extension/utils', () => ({
+  replaceOutput: vi.fn(),
+}))
+
 beforeEach(() => {
   vi.mocked(window.showInputBox).mockClear()
   vi.mocked(window.showErrorMessage).mockClear()
diff --git a/tests/extension/provider/annotations.test.ts b/tests/extension/provider/annotations.test.ts
index b8bf8442..3432cf23 100644
--- a/tests/extension/provider/annotations.test.ts
+++ b/tests/extension/provider/annotations.test.ts
@@ -4,6 +4,7 @@ import { commands, NotebookCellKind } from 'vscode'
 import { AnnotationsProvider } from '../../../src/extension/provider/annotations'
 import { Kernel } from '../../../src/extension/kernel'
 import { OutputType } from '../../../src/constants'
+import { replaceOutput } from '../../../src/extension/utils'
 
 vi.mock('vscode')
 vi.mock('vscode-telemetry')
@@ -27,7 +28,8 @@ vi.mock('../../../src/extension/utils', () => ({
       }
     }
   }),
-  validateAnnotations: vi.fn()
+  validateAnnotations: vi.fn(),
+  replaceOutput: vi.fn(),
 }))
 
 vi.mock('../../../src/extension/runner', () => ({ }))
@@ -130,12 +132,10 @@ describe('Runme Annotations', () => {
         outputs: []
       }
 
-      const replaceOutput = vi.fn()
       const end = vi.fn()
       kernel.createCellExecution = vi.fn().mockResolvedValue({
         start: vi.fn(),
         end,
-        replaceOutput,
       })
       await annotationsProvider.toggleCellAnnotations(cell as any)
       expect(kernel.createCellExecution).toBeCalledTimes(1)