diff --git a/examples/README.md b/examples/README.md
index 0379801eb..a4af62d0d 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -128,13 +128,30 @@ You can copy also results from the inline executed shell:
openssl rand -base64 32
```
-## Non-Supported Languages
+## Non-Shell Languages
-These are shown as simple markdowns, e.g:
+These are sometimes executable by default, like for python:
-```py { readonly=true }
-def hello():
- print("Hello World")
+```py
+print("Hello World")
+```
+
+Otherwise, execution can be set with the `interpreter` annotation, like so:
+
+```yaml { interpreter=cat }
+config:
+ nested:
+ para: true
+```
+
+Non-shell scripts can also access environment variables, and are run from the current working directory:
+
+```sh
+export YOUR_NAME=enter your name
+```
+
+```javascript { name=echo-hello-js }
+console.log(`Hello, ${process.env.YOUR_NAME}, from ${__dirname}!`)
```
## Curl an image
@@ -159,11 +176,3 @@ With [`antonmedv/fx`](https://github.com/antonmedv/fx) you can inspect JSON file
```sh { terminalRows=20 }
curl -s "https://api.marquee.activecove.com/getWeather?lat=52&lon=10" | fx
```
-
-## YAML
-
-```yaml
-config:
- netsed:
- para: true
-```
diff --git a/package.json b/package.json
index 170917164..07ab7359a 100644
--- a/package.json
+++ b/package.json
@@ -623,7 +623,6 @@
"test:format:fix": "prettier --write .",
"test:unit": "vitest -c ./vitest.conf.ts",
"test:e2e": "wdio run ./tests/e2e/wdio.conf.ts",
- "pretest:e2e": "npm run download:binary",
"watch": "npm run build:dev -- --watch",
"vscode:prepublish": "npm run prepare-binary",
"download:binary": "npm run prepare-binary",
diff --git a/src/client/components/configuration/annotations.ts b/src/client/components/configuration/annotations.ts
index 3659fd402..34aa19b17 100644
--- a/src/client/components/configuration/annotations.ts
+++ b/src/client/components/configuration/annotations.ts
@@ -145,6 +145,11 @@ export class Annotations extends LitElement {
description: "Cell's canonical name for easy referencing in the CLI.",
docs: 'https://docs.runme.dev/configuration#cell-options',
},
+ interpreter: {
+ description: 'Script shebang line',
+ // FIXME: update docs link
+ docs: '',
+ },
category: {
description: 'Execute this code cell within a category.',
docs: 'https://docs.runme.dev/configuration#run-all-cells-by-category',
@@ -446,6 +451,7 @@ export class Annotations extends LitElement {
${this.renderTextFieldTabEntry('mimeType')}
${this.renderCategoryTabEntry('category')}
${this.renderTextFieldTabEntry('terminalRows')}
+ ${this.renderTextFieldTabEntry('interpreter')}
diff --git a/src/extension/executors/runner.ts b/src/extension/executors/runner.ts
index 1db290d4e..23d754aaa 100644
--- a/src/extension/executors/runner.ts
+++ b/src/extension/executors/runner.ts
@@ -35,7 +35,7 @@ import { toggleTerminal } from '../commands'
import { NotebookCellOutputManager } from '../cell'
import { closeTerminalByEnvID } from './task'
-import { getCellShellPath, parseCommandSeq, getCellCwd } from './utils'
+import { parseCommandSeq, getCellCwd, getCellProgram } from './utils'
import { handleVercelDeployOutput, isVercelDeployScript } from './vercel'
import type { IEnvironmentManager } from '.'
@@ -124,8 +124,10 @@ export async function executeRunner(
}
}
+ const { programName, commandMode } = getCellProgram(exec.cell, exec.cell.notebook, execKey)
+
const program = await runner.createProgramSession({
- programName: getCellShellPath(exec.cell, exec.cell.notebook, execKey) ?? execKey,
+ programName,
environment,
exec: execution,
envs: Object.entries(envs).map(([k, v]) => `${k}=${v}`),
@@ -134,6 +136,8 @@ export async function executeRunner(
tty: interactive,
convertEol: !mimeType || mimeType === 'text/plain',
storeLastOutput: true,
+ languageId: exec.cell.document.languageId,
+ commandMode,
})
context.subscriptions.push(program)
diff --git a/src/extension/executors/utils.ts b/src/extension/executors/utils.ts
index cf11f4ae0..e3b14775a 100644
--- a/src/extension/executors/utils.ts
+++ b/src/extension/executors/utils.ts
@@ -16,9 +16,10 @@ import {
import { ENV_STORE } from '../constants'
import { DEFAULT_PROMPT_ENV, OutputType } from '../../constants'
-import type { CellOutputPayload, Serializer } from '../../types'
+import type { CellOutputPayload, Serializer, ShellType } from '../../types'
import { NotebookCellOutputManager } from '../cell'
import { getAnnotations, getWorkspaceFolder } from '../utils'
+import { CommandMode } from '../grpc/runnerTypes'
const ENV_VAR_REGEXP = /(\$\w+)/g
/**
@@ -206,8 +207,8 @@ export async function retrieveShellCommand(
* @param execKey Used as fallback in case `$SHELL` is not present
*/
export function getSystemShellPath(): string | undefined
-export function getSystemShellPath(execKey: string | undefined): string | undefined
export function getSystemShellPath(execKey: string): string
+export function getSystemShellPath(execKey?: string): string | undefined
export function getSystemShellPath(execKey?: string): string | undefined {
return process.env.SHELL ?? execKey
}
@@ -236,6 +237,62 @@ export function getCellShellPath(
return getSystemShellPath(execKey)
}
+export function isShellLanguage(languageId: string): ShellType | undefined {
+ switch (languageId.toLowerCase()) {
+ case 'sh':
+ case 'bash':
+ case 'zsh':
+ case 'ksh':
+ case 'shell':
+ case 'shellscript':
+ return 'sh'
+
+ case 'bat':
+ case 'cmd':
+ return 'cmd'
+
+ case 'powershell':
+ case 'pwsh':
+ return 'powershell'
+
+ case 'fish':
+ return 'fish'
+
+ default:
+ return undefined
+ }
+}
+
+export function getCellProgram(
+ cell: NotebookCell | NotebookCellData | Serializer.Cell,
+ notebook: NotebookData | Serializer.Notebook | NotebookDocument,
+ execKey: string
+): { programName: string; commandMode: CommandMode } {
+ let result: { programName: string; commandMode: CommandMode }
+ const { interpreter } = getAnnotations(cell.metadata)
+
+ if (isShellLanguage(execKey)) {
+ const shellPath = getCellShellPath(cell, notebook, execKey) ?? execKey
+
+ result = {
+ programName: shellPath,
+ commandMode: CommandMode.INLINE_SHELL,
+ }
+ } else {
+ // TODO(mxs): make this configurable!!
+ result = {
+ programName: '',
+ commandMode: CommandMode.TEMP_FILE,
+ }
+ }
+
+ if (interpreter) {
+ result.programName = interpreter
+ }
+
+ return result
+}
+
export async function getCellCwd(
cell: NotebookCell | NotebookCellData | Serializer.Cell,
notebook?: NotebookData | NotebookDocument | Serializer.Notebook,
diff --git a/src/extension/kernel.ts b/src/extension/kernel.ts
index d00703e04..bfe75ec67 100644
--- a/src/extension/kernel.ts
+++ b/src/extension/kernel.ts
@@ -39,9 +39,9 @@ import {
processEnviron,
isWindows,
setNotebookCategories,
- isShellLanguage,
getTerminalRunmeId,
} from './utils'
+import { isShellLanguage } from './executors/utils'
import './wasm/wasm_exec.js'
import { IRunner, IRunnerEnvironment } from './runner'
import { executeRunner } from './executors/runner'
@@ -494,7 +494,7 @@ export class Kernel implements Disposable {
// TODO(mxs): support windows shells
!isWindows()
) {
- const runScript = (execKey: string = 'sh') =>
+ const runScript = (key: string = execKey) =>
executeRunner(
this,
this.context,
@@ -503,7 +503,7 @@ export class Kernel implements Disposable {
runningCell,
this.messaging,
uuid,
- execKey,
+ key,
outputs,
this.environment,
environmentManager
@@ -513,9 +513,9 @@ export class Kernel implements Disposable {
return false
})
- if (isShellLanguage(execKey)) {
+ if (isShellLanguage(execKey) || !(execKey in executor)) {
successfulCellExecution = await runScript(execKey)
- } else if (execKey in executor) {
+ } else {
successfulCellExecution = await executor[execKey as keyof typeof executor].call(
this,
exec,
@@ -524,22 +524,23 @@ export class Kernel implements Disposable {
runScript,
environmentManager
)
- } else {
- this.#shebangComingSoon.open()
-
- successfulCellExecution = false
}
- } else {
+ } else if (execKey in executor) {
/**
* check if user is running experiment to execute shell via runme cli
*/
- successfulCellExecution = await executor[execKey as keyof typeof executor]?.call(
+ successfulCellExecution = await executor[execKey as keyof typeof executor].call(
this,
exec,
runningCell,
outputs
)
+ } else {
+ window.showErrorMessage('Cell language is not executable')
+
+ successfulCellExecution = false
}
+
TelemetryReporter.sendTelemetryEvent('cell.endExecute', {
'cell.success': successfulCellExecution?.toString(),
})
diff --git a/src/extension/provider/runmeTask.ts b/src/extension/provider/runmeTask.ts
index 64655de8c..7f6daee38 100644
--- a/src/extension/provider/runmeTask.ts
+++ b/src/extension/provider/runmeTask.ts
@@ -24,7 +24,7 @@ import { getAnnotations, getWorkspaceEnvs, prepareCmdSeq } from '../utils'
import { Serializer, RunmeTaskDefinition } from '../../types'
import { SerializerBase } from '../serializer'
import type { IRunner, IRunnerEnvironment, RunProgramOptions } from '../runner'
-import { getCellCwd, getCellShellPath, parseCommandSeq } from '../executors/utils'
+import { getCellCwd, getCellProgram, parseCommandSeq } from '../executors/utils'
import { Kernel } from '../kernel'
type TaskOptions = Pick
@@ -118,6 +118,12 @@ export class RunmeTaskProvider implements TaskProvider {
const isBackground = options.isBackground || background
+ const { programName, commandMode } = getCellProgram(
+ cell,
+ notebook,
+ ('languageId' in cell && cell.languageId) || 'sh'
+ )
+
const name = `${command}`
const task = new Task(
@@ -145,7 +151,7 @@ export class RunmeTaskProvider implements TaskProvider {
}
const runOpts: RunProgramOptions = {
- programName: getCellShellPath(cell, notebook) ?? 'sh',
+ programName,
exec: {
type: 'commands',
commands: commands ?? [''],
@@ -156,6 +162,7 @@ export class RunmeTaskProvider implements TaskProvider {
convertEol: true,
envs: Object.entries(envs).map(([k, v]) => `${k}=${v}`),
storeLastOutput: true,
+ commandMode,
}
const program = await runner.createProgramSession(runOpts)
diff --git a/src/extension/runner.ts b/src/extension/runner.ts
index b78214408..86c52714e 100644
--- a/src/extension/runner.ts
+++ b/src/extension/runner.ts
@@ -6,12 +6,14 @@ import {
type Disposable,
type TerminalDimensions,
EventEmitter,
+ window,
} from 'vscode'
import { RpcError } from '@protobuf-ts/runtime-rpc'
import type { DisposableAsync } from '../types'
import {
+ CommandMode,
CreateSessionRequest,
ExecuteRequest,
ExecuteResponse,
@@ -51,6 +53,9 @@ export interface RunProgramOptions {
background?: boolean
convertEol?: boolean
storeLastOutput?: boolean
+ languageId?: string
+ fileExtension?: string
+ commandMode?: CommandMode
}
export interface IRunner extends Disposable {
@@ -407,6 +412,17 @@ export class GrpcRunnerProgramSession implements IRunnerProgramSession {
this.session.responses.onError((error) => {
if (error instanceof RpcError) {
+ if (error.message.includes('invalid LanguageId')) {
+ window.showErrorMessage(
+ // eslint-disable-next-line max-len
+ 'Unable to automatically execute cell. To execute this cell, set the "interpreter" field in the configuration foldout!'
+ )
+ }
+
+ if (error.message.includes('invalid ProgramName')) {
+ window.showErrorMessage(`Unable to locate interpreter "${this.opts.programName}"`)
+ }
+
console.error(
'RpcError occurred!',
{
@@ -712,6 +728,9 @@ export class GrpcRunnerProgramSession implements IRunnerProgramSession {
terminalDimensions,
background,
storeLastOutput,
+ fileExtension,
+ languageId,
+ commandMode,
}: RunProgramOptions): ExecuteRequest {
if (environment && !(environment instanceof GrpcRunnerEnvironment)) {
throw new Error('Expected gRPC environment!')
@@ -729,6 +748,9 @@ export class GrpcRunnerProgramSession implements IRunnerProgramSession {
...(exec?.type === 'script' && { script: exec.script }),
...(terminalDimensions && { winsize: terminalDimensionsToWinsize(terminalDimensions) }),
storeLastOutput,
+ fileExtension,
+ languageId,
+ commandMode,
})
}
diff --git a/src/extension/utils.ts b/src/extension/utils.ts
index 452a8e3a9..1a924e227 100644
--- a/src/extension/utils.ts
+++ b/src/extension/utils.ts
@@ -21,13 +21,7 @@ import { v5 as uuidv5 } from 'uuid'
import getPort from 'get-port'
import dotenv from 'dotenv'
-import {
- CellAnnotations,
- CellAnnotationsErrorResult,
- RunmeTerminal,
- Serializer,
- ShellType,
-} from '../types'
+import { CellAnnotations, CellAnnotationsErrorResult, RunmeTerminal, Serializer } from '../types'
import { SafeCellAnnotationsSchema, CellAnnotationsSchema } from '../schema'
import {
AuthenticationProviders,
@@ -167,31 +161,6 @@ export function getKey(runningCell: vscode.TextDocument): string {
return languageId
}
-export function isShellLanguage(languageId: string): ShellType | undefined {
- switch (languageId.toLowerCase()) {
- case 'sh':
- case 'bash':
- case 'zsh':
- case 'ksh':
- case 'shell':
- return 'sh'
-
- case 'bat':
- case 'cmd':
- return 'cmd'
-
- case 'powershell':
- case 'pwsh':
- return 'powershell'
-
- case 'fish':
- return 'fish'
-
- default:
- return undefined
- }
-}
-
/**
* treat cells like a series of individual commands
* which need to be executed in sequence
diff --git a/src/schema.ts b/src/schema.ts
index 37eeff6ec..85eda5819 100644
--- a/src/schema.ts
+++ b/src/schema.ts
@@ -74,6 +74,7 @@ export const AnnotationSchema = {
return true
}, 'mime type specification invalid format')
.default('text/plain'),
+ interpreter: z.string().optional().default(''),
cwd: z.string().nonempty().optional(),
category: z.preprocess(
(value) => (typeof value === 'string' ? cleanAnnotation(value, ',') : value),
diff --git a/tests/e2e/specs/basic.e2e.ts b/tests/e2e/specs/basic.e2e.ts
index b7818154f..d84195ed2 100644
--- a/tests/e2e/specs/basic.e2e.ts
+++ b/tests/e2e/specs/basic.e2e.ts
@@ -83,6 +83,28 @@ describe('Runme VS Code Extension', async () => {
])
})
+ it('basic hello world python execution', async () => {
+ const cell = await notebook.getCell('print("Hello World")')
+
+ await cell.run()
+
+ expect(await cell.getCellOutput(OutputType.TerminalView)).toStrictEqual([
+ 'Hello World\n'
+ ])
+ })
+
+ it('basic yaml example', async () => {
+ const cell = await notebook.getCell('config:\n nested:\n para: true')
+
+ await cell.run()
+
+ await tryExecuteCommand(await browser.getWorkbench(), 'focus active cell output')
+
+ expect(await cell.getCellOutput(OutputType.TerminalView)).toStrictEqual([
+ 'config:\nnested:\npara: true\n'
+ ])
+ })
+
it('more shell example', async () => {
const cell = await notebook.getCell('echo "Foo 👀"\nsleep 2\necho "Bar 🕺"\nsleep 2\necho "Loo 🚀"')
await cell.run()
diff --git a/tests/extension/commands/index.test.ts b/tests/extension/commands/index.test.ts
index daaefdf3b..8ed9d0a21 100644
--- a/tests/extension/commands/index.test.ts
+++ b/tests/extension/commands/index.test.ts
@@ -67,6 +67,8 @@ vi.mock('../../../src/extension/runner', () => ({
GrpcRunnerEnvironment: class { }
}))
+vi.mock('../../../src/extension/grpc/runnerTypes', () => ({}))
+
beforeEach(() => {
vi.mocked(window.showWarningMessage).mockClear()
vi.mocked(window.showInformationMessage).mockClear()
diff --git a/tests/extension/executors/utils.test.ts b/tests/extension/executors/utils.test.ts
index 0a83d7e9f..3d68f6ec6 100644
--- a/tests/extension/executors/utils.test.ts
+++ b/tests/extension/executors/utils.test.ts
@@ -11,9 +11,11 @@ import {
parseCommandSeq,
getCellShellPath,
getSystemShellPath,
- getCellCwd
+ getCellCwd,
+ isShellLanguage
} from '../../../src/extension/executors/utils'
import { getCmdSeq, getWorkspaceFolder, getAnnotations } from '../../../src/extension/utils'
+import { getCellProgram } from '../../../src/extension/executors/utils'
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
@@ -35,6 +37,16 @@ vi.mock('node:fs/promises', () => ({
}
}))
+const COMMAND_MODE_INLINE_SHELL = 1
+const COMMAND_MODE_TEMP_FILE = 2
+
+vi.mock('../../../src/extension/grpc/runnerTypes', () => ({
+ CommandMode: {
+ INLINE_SHELL: 1,
+ TEMP_FILE: 2,
+ }
+}))
+
beforeEach(() => {
vi.mocked(window.showInputBox).mockClear()
vi.mocked(window.showErrorMessage).mockClear()
@@ -346,6 +358,54 @@ suite('getCellShellPath', () => {
})
})
+suite('isShellLanguage', () => {
+ test('usual suspects', () => {
+ for (const shell of ['bash', 'sh', 'fish', 'ksh', 'zsh', 'shell', 'bat', 'cmd', 'powershell', 'pwsh']) {
+ expect(isShellLanguage(shell)).toBeTruthy()
+ }
+ })
+})
+
+suite('getCellProgram', () => {
+ test('is inline shell for shell types', async () => {
+ for (const shell of ['bash', 'sh', 'fish', 'ksh', 'zsh', 'shell', 'bat', 'cmd', 'powershell', 'pwsh']) {
+ vi.mocked(getAnnotations).mockReturnValueOnce({} as any)
+
+ expect(getCellProgram({ metadata: { } } as any, {} as any, shell)).toStrictEqual({
+ commandMode: COMMAND_MODE_INLINE_SHELL,
+ programName: getSystemShellPath()
+ })
+ }
+ })
+
+ test('is temp file for non-shell types', async () => {
+ vi.mocked(getAnnotations).mockReturnValueOnce({} as any)
+
+ expect(getCellProgram({ metadata: { } } as any, {} as any, 'python')).toStrictEqual({
+ commandMode: COMMAND_MODE_TEMP_FILE,
+ programName: ''
+ })
+ })
+
+ test('respects custom interpreter in shell mode', async () => {
+ vi.mocked(getAnnotations).mockImplementationOnce(((x: any) => ({ interpreter: x.interpreter })) as any)
+
+ expect(getCellProgram({ metadata: { interpreter: 'fish' } } as any, {} as any, 'sh')).toStrictEqual({
+ commandMode: COMMAND_MODE_INLINE_SHELL,
+ programName: 'fish'
+ })
+ })
+
+ test('respects custom interpreter in temp file mode', async () => {
+ vi.mocked(getAnnotations).mockImplementationOnce(((x: any) => ({ interpreter: x.interpreter })) as any)
+
+ expect(getCellProgram({ metadata: { interpreter: 'bun' } } as any, {} as any, 'javascript')).toStrictEqual({
+ commandMode: COMMAND_MODE_TEMP_FILE,
+ programName: 'bun'
+ })
+ })
+})
+
suite('getCellCwd', () => {
const projectRoot = '/project'
const mdFilePath = '/project/folder/DOC.md'
diff --git a/tests/extension/kernel.test.ts b/tests/extension/kernel.test.ts
index e0449fa66..7437f1964 100644
--- a/tests/extension/kernel.test.ts
+++ b/tests/extension/kernel.test.ts
@@ -24,6 +24,8 @@ vi.mock('../../src/extension/executors/index.js', () => ({
}))
vi.mock('../../src/extension/runner', () => ({}))
+vi.mock('../../src/extension/grpc/runnerTypes', () => ({}))
+
const getCells = (cnt: number, metadata: Record = {}) => ([...new Array(cnt)]).map((_, i) => ({
document: { getText: vi.fn().mockReturnValue(`Cell #${i}`) },
notebook: {
@@ -208,7 +210,7 @@ suite('_doExecuteCell', () => {
test('shows error window if language is not supported', async () => {
const k = new Kernel({} as any)
- k['runner'] = {} as any
+ k['runner'] = undefined
k.createCellExecution = vi.fn().mockResolvedValue({
start: vi.fn(),
@@ -221,10 +223,14 @@ suite('_doExecuteCell', () => {
languageId: 'barfoo'
} as any)
- await k['_doExecuteCell']({
- document: { uri: { fsPath: '/foo/bar' } },
- metadata: { 'runme.dev/uuid': '849448b2-3c41-4323-920e-3098e71302ce' }
- } as any)
+ try {
+ await k['_doExecuteCell']({
+ document: { uri: { fsPath: '/foo/bar' } },
+ metadata: { 'runme.dev/uuid': '849448b2-3c41-4323-920e-3098e71302ce' }
+ } as any)
+ } catch(e) {
+
+ }
expect(TelemetryReporter.sendTelemetryEvent).toHaveBeenCalledWith(
'cell.startExecute'
diff --git a/tests/extension/messages/cellOutput.test.ts b/tests/extension/messages/cellOutput.test.ts
index a2c676c86..b788ef6cb 100644
--- a/tests/extension/messages/cellOutput.test.ts
+++ b/tests/extension/messages/cellOutput.test.ts
@@ -9,6 +9,8 @@ vi.mock('vscode')
vi.mock('vscode-telemetry')
vi.mock('../../../src/extension/runner', () => ({}))
+vi.mock('../../../src/extension/grpc/runnerTypes', () => ({}))
+
suite('Handle CellOutput messages', () => {
const mockOutput = (type: OutputType) => {
@@ -78,4 +80,4 @@ suite('Handle CellOutput messages', () => {
test('Vercel cell output', async () => {
return expectOutput(OutputType.vercel)
})
-})
\ No newline at end of file
+})
diff --git a/tests/extension/provider/annotations.test.ts b/tests/extension/provider/annotations.test.ts
index bf6bd7637..204fe66f5 100644
--- a/tests/extension/provider/annotations.test.ts
+++ b/tests/extension/provider/annotations.test.ts
@@ -32,6 +32,7 @@ vi.mock('../../../src/extension/utils', () => ({
}))
vi.mock('../../../src/extension/runner', () => ({ }))
+vi.mock('../../../src/extension/grpc/runnerTypes', () => ({}))
describe('Runme Annotations', () => {
const kernel = new Kernel({} as any)
diff --git a/tests/extension/runner.test.ts b/tests/extension/runner.test.ts
index ec245a25c..26337fd45 100644
--- a/tests/extension/runner.test.ts
+++ b/tests/extension/runner.test.ts
@@ -2,7 +2,8 @@ import path from 'node:path'
import { vi, suite, test, expect, beforeEach } from 'vitest'
import { type GrpcTransport } from '@protobuf-ts/grpc-transport'
-import { EventEmitter, NotebookCellKind, Position, tasks, commands, EndOfLine } from 'vscode'
+import { EventEmitter, NotebookCellKind, Position, tasks, commands, EndOfLine, window } from 'vscode'
+import { RpcError } from '@protobuf-ts/runtime-rpc'
import {
GrpcRunner,
@@ -199,6 +200,10 @@ suite('grpc Runner', () => {
})
suite('grpc program session', () => {
+ beforeEach(() => {
+ vi.mocked(window.showErrorMessage).mockClear()
+ })
+
test('session dispose is called on runner dispose', async () => {
const { runner, session, duplex } = await createNewSession()
@@ -559,6 +564,18 @@ suite('grpc Runner', () => {
expect(writeListener).toBeCalledTimes(1)
expect(writeListener).toBeCalledWith('test\n')
})
+
+ test('grpc program failure gives info message', async () => {
+ const { duplex } = await createNewSession()
+
+ duplex._onError.fire(new RpcError('invalid LanguageId'))
+ expect(window.showErrorMessage).toBeCalledTimes(1)
+
+ vi.mocked(window.showErrorMessage).mockClear()
+
+ duplex._onError.fire(new RpcError('invalid ProgramName'))
+ expect(window.showErrorMessage).toBeCalledTimes(1)
+ })
})
})
diff --git a/tests/extension/serializer.test.ts b/tests/extension/serializer.test.ts
index e7ce42270..5f90487cf 100644
--- a/tests/extension/serializer.test.ts
+++ b/tests/extension/serializer.test.ts
@@ -40,6 +40,7 @@ vi.mock('../../src/extension/languages', () => ({
default: {
fromContext: vi.fn(),
},
+ NotebookData: class {}
}))
vi.mock('../../src/extension/utils', () => ({
diff --git a/tests/extension/utils.test.ts b/tests/extension/utils.test.ts
index 826926430..9e91e3f68 100644
--- a/tests/extension/utils.test.ts
+++ b/tests/extension/utils.test.ts
@@ -24,7 +24,6 @@ import {
getNotebookCategories,
getNamespacedMid,
bootFile,
- isShellLanguage,
fileOrDirectoryExists,
isMultiRootWorkspace,
convertEnvList,
@@ -136,12 +135,6 @@ test('getKey', () => {
} as any)).toBe('sh')
})
-test('isShellLanguage', () => {
- for (const shell of ['bash', 'sh', 'fish', 'ksh', 'zsh', 'shell', 'bat', 'cmd', 'powershell', 'pwsh']) {
- expect(isShellLanguage(shell)).toBeTruthy()
- }
-})
-
suite('getCmdShellSeq', () => {
test('one command', () => {
const cellText = 'deno task start'
@@ -285,7 +278,8 @@ suite('#getAnnotations', () => {
category: '',
excludeFromRunAll: false,
promptEnv: true,
- 'runme.dev/uuid': '48d86c43-84a4-469d-8c78-963513b0f9d0'
+ 'runme.dev/uuid': '48d86c43-84a4-469d-8c78-963513b0f9d0',
+ interpreter: '',
}
)
})
@@ -306,7 +300,8 @@ suite('#getAnnotations', () => {
name: 'echo-hello',
promptEnv: true,
excludeFromRunAll: false,
- category: ''
+ category: '',
+ interpreter: '',
})
})
})