Repository: stateful/vscode-runme
Pull request #381 (2023-04-05 20:53:48)

Fix notebook terminal cursor color
If there is no `--vscode-terminalCursor-foreground` variable, this will revert to the foreground color instead.

This fixes the terminal cursor not appearing in the `Github Light (Colorblind)` theme.


Patch:
diff --git a/package-lock.json b/package-lock.json
index 0ae5ccfa..09374942 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "runme",
-  "version": "1.0.3",
+  "version": "1.0.5",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "runme",
-      "version": "1.0.3",
+      "version": "1.0.5",
       "hasInstallScript": true,
       "license": "Apache-2.0",
       "dependencies": {
diff --git a/package.json b/package.json
index 89735173..86686e5a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "runme",
-  "version": "1.0.3",
+  "version": "1.0.5",
   "publisher": "stateful",
   "displayName": "Runme",
   "description": "Run your README.md as an interactive notebook",
diff --git a/src/client/components/terminal.ts b/src/client/components/terminal.ts
index d9ef57c0..b087d9bf 100644
--- a/src/client/components/terminal.ts
+++ b/src/client/components/terminal.ts
@@ -293,8 +293,10 @@ export class TerminalView extends LitElement {
       this.terminal?.write(this.initialContent)
     }
 
-    this.fitAddon = new FitAddon()
-    this.fitAddon.activate(this.terminal!)
+    // TODO(mxs): bring back, see #383
+    // this.fitAddon = new FitAddon()
+    // this.fitAddon.activate(this.terminal!)
+
     this.serializer = new SerializeAddon()
     this.terminal.loadAddon(this.serializer)
     this.terminal.loadAddon(new Unicode11Addon())
@@ -347,7 +349,8 @@ export class TerminalView extends LitElement {
 
     this.terminal!.open(terminalContainer)
     this.terminal!.focus()
-    this.fitAddon?.fit()
+    // TODO(mxs): bring back, see #383
+    // this.fitAddon?.fit()
     this.#updateTerminalTheme()
 
     if (this.lastLine) {
@@ -360,9 +363,11 @@ export class TerminalView extends LitElement {
   }
 
   #updateTerminalTheme(): void {
+    const foregroundColor = this.#getThemeHexColor(terminalCSS('foreground'))
+
     const terminalTheme: ITheme = {
-      foreground: this.#getThemeHexColor(terminalCSS('foreground')),
-      cursor: this.#getThemeHexColor(vscodeCSS('terminalCursor', 'foreground')),
+      foreground: foregroundColor,
+      cursor: this.#getThemeHexColor(vscodeCSS('terminalCursor', 'foreground')) || foregroundColor,
       cursorAccent: this.#getThemeHexColor(vscodeCSS('terminalCursor', 'background')),
       selectionForeground: this.#getThemeHexColor(terminalCSS('selectionForeground')),
       selectionBackground: this.#getThemeHexColor(terminalCSS('selectionBackground')),
@@ -381,7 +386,8 @@ export class TerminalView extends LitElement {
   }
 
   #onResizeWindow(): void {
-    this.fitAddon?.fit()
+    // TODO(mxs): bring back, see #383
+    // this.fitAddon?.fit()
   }
 
   #onWebLinkClick(event: MouseEvent, uri: string): void {
diff --git a/src/extension/executors/runner.ts b/src/extension/executors/runner.ts
index 9c2a3701..7ce4813e 100644
--- a/src/extension/executors/runner.ts
+++ b/src/extension/executors/runner.ts
@@ -57,8 +57,8 @@ export async function executeRunner(
     RUNME_ID
   }
 
-  const commands = await parseCommandSeq(cellText, (s) => s.split('\n'))
-  if(!commands) { return false }
+  const commands = await parseCommandSeq(cellText)
+  if (!commands) { return false }
 
   if (commands.length === 0) {
     commands.push('')
diff --git a/src/extension/executors/utils.ts b/src/extension/executors/utils.ts
index d9da2d14..bc1f291d 100644
--- a/src/extension/executors/utils.ts
+++ b/src/extension/executors/utils.ts
@@ -6,7 +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'
+import { replaceOutput, getCmdSeq } from '../utils'
 
 const ENV_VAR_REGEXP = /(\$\w+)/g
 /**
@@ -59,8 +59,7 @@ export async function promptUserForVariable(
 }
 
 export function getCommandExportExtractMatches(
-  rawText: string,
-  supportsDirect = true
+  rawText: string
 ): CommandExportExtractMatch[] {
   const test = new RegExp(EXPORT_EXTRACT_REGEX)
 
@@ -85,10 +84,8 @@ export function getCommandExportExtractMatches(
       value = placeHolder.slice(2, -1)
     } else if (!placeHolder.includes('\n')) {
       matchType = 'prompt'
-    } else if (supportsDirect) {
-      matchType = 'direct'
     } else {
-      continue
+      matchType = 'direct'
     }
 
     result.push({
@@ -193,12 +190,9 @@ export function getShellPath(execKey?: string): string|undefined {
  * Returns `undefined` when a user cancels on prompt
  */
 export async function parseCommandSeq(
-  cellText: string,
-  parseBlock?: (block: string) => string[]
+  cellText: string
 ): Promise<string[]|undefined> {
-  parseBlock ??= (s) => s ? s.split('\n') : []
-
-  const exportMatches = getCommandExportExtractMatches(cellText, false)
+  const exportMatches = getCommandExportExtractMatches(cellText)
 
   type CommandBlock =
     |{
@@ -248,5 +242,5 @@ export async function parseCommandSeq(
   parsedCommandBlocks.push({ type: 'block', content: cellText.slice(offset) })
 
   return parsedCommandBlocks
-    .flatMap(({ type, content }) => (type === 'block' && parseBlock?.(content)) || (content ? [content] : []))
+    .flatMap(({ type, content }) => type === 'block' ? getCmdSeq(content) : [content])
 }
diff --git a/src/extension/terminal/terminalState.ts b/src/extension/terminal/terminalState.ts
index 93a84456..844f0c78 100644
--- a/src/extension/terminal/terminalState.ts
+++ b/src/extension/terminal/terminalState.ts
@@ -32,7 +32,11 @@ export class XTermState implements ITerminalState {
   }
 
   input(data: string, wasUserInput: boolean): void {
-    // @ts-expect-error unexposed method
-    this.xterm['coreService']['triggerDataEvent'](data, wasUserInput)
+    try {
+      // @ts-expect-error unexposed method
+      this.xterm['_core']['coreService']['triggerDataEvent'](data, wasUserInput)
+    } catch (e) {
+      console.error(e)
+    }
   }
 }


Test patch:
diff --git a/tests/extension/executors/utils.test.ts b/tests/extension/executors/utils.test.ts
index 3752f473..189744d7 100644
--- a/tests/extension/executors/utils.test.ts
+++ b/tests/extension/executors/utils.test.ts
@@ -5,7 +5,6 @@ import { expect, vi, test, suite, beforeEach } from 'vitest'
 
 import { ENV_STORE } from '../../../src/extension/constants'
 import { retrieveShellCommand, parseCommandSeq } from '../../../src/extension/executors/utils'
-import { getCmdSeq } from '../../../src/extension/utils'
 
 const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
 
@@ -209,71 +208,25 @@ suite('parseCommandSeq', () => {
     expect(res?.[0]).toBe('export TEST="test value"')
   })
 
-  test('single line export with cancelled prompt', async () => {
-    vi.mocked(window.showInputBox).mockImplementationOnce(async () => undefined)
-
-    const res = await parseCommandSeq([
-      'export TEST="<placeholder>"'
-    ].join('\n'))
-
-    expect(res).toBe(undefined)
-  })
-
   test('multiline export', async () => {
-    const exportLines = [
+    const exportLine = [
       'export TEST="I',
       'am',
       'doing',
       'well!"'
-    ]
+    ].join('\n')
 
-    const res = await parseCommandSeq(exportLines.join('\n'))
+    const res = await parseCommandSeq(exportLine)
 
     expect(res).toBeTruthy
-    expect(res).toHaveLength(4)
-    expect(res).toStrictEqual(exportLines)
+    expect(res).toHaveLength(1)
+    expect(res?.[0]).toBe(exportLine)
   })
 
   test('exports between normal command sequences', async () => {
     vi.mocked(window.showInputBox).mockImplementationOnce(async () => 'test value')
 
-    const cmdLines = [
-      'echo "Hello!"',
-      'echo "Hi!"',
-      'export TEST="<placeholder>"',
-      'echo $TEST',
-      'export TEST_MULTILINE="This',
-      'is',
-      'a',
-      'multiline',
-      'env!"',
-      'echo $TEST_MULTILINE'
-    ]
-
-    const res = await parseCommandSeq(cmdLines.join('\n'))
-
-    expect(res).toBeTruthy()
-    expect(res).toStrictEqual([
-      'echo "Hello!"',
-      'echo "Hi!"',
-      'export TEST="test value"',
-      '',
-      'echo $TEST',
-      ...[
-        'export TEST_MULTILINE="This',
-        'is',
-        'a',
-        'multiline',
-        'env!"',
-      ],
-      'echo $TEST_MULTILINE'
-    ])
-  })
-
-  test('exports between normal command sequences with getCmdSeq', async () => {
-    vi.mocked(window.showInputBox).mockImplementationOnce(async () => 'test value')
-
-    const cmdLines = [
+    const cmd = [
       'echo "Hello!"',
       'echo "Hi!"',
       'export TEST="<placeholder>"',
@@ -284,9 +237,9 @@ suite('parseCommandSeq', () => {
       'multiline',
       'env!"',
       'echo $TEST_MULTILINE'
-    ]
+    ].join('\n')
 
-    const res = await parseCommandSeq(cmdLines.join('\n'), getCmdSeq)
+    const res = await parseCommandSeq(cmd)
 
     expect(res).toBeTruthy()
     expect(res).toStrictEqual([
@@ -294,13 +247,13 @@ suite('parseCommandSeq', () => {
       'echo "Hi!"',
       'export TEST="test value"',
       'echo $TEST',
-      ...[
+      [
         'export TEST_MULTILINE="This',
         'is',
         'a',
         'multiline',
         'env!"',
-      ],
+      ].join('\n'),
       'echo $TEST_MULTILINE'
     ])
   })