Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit 016b3b0

Browse files
myan9starpit
authored andcommitted
fix(packages/core): repl exec should invoke commands separated by semicolon
Fixes #5260
1 parent 1e1037f commit 016b3b0

File tree

5 files changed

+71
-78
lines changed

5 files changed

+71
-78
lines changed

packages/core/src/core/repl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@ export { encodeComponent }
2525
import { split, _split, Split } from '../repl/split'
2626
export { split, _split, Split }
2727

28-
export { exec, click, semicolonInvoke, qexec, pexec, rexec, getImpl, setEvaluatorImpl } from '../repl/exec'
28+
export { exec, click, qexec, pexec, rexec, getImpl, setEvaluatorImpl } from '../repl/exec'
2929

3030
export { ReplEval, DirectReplEval } from '../repl/types'

packages/core/src/models/repl.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
*/
1616

1717
import { Tab } from '../webapp/tab'
18-
import { MixedResponse, RawContent, RawResponse } from './entity'
19-
import { EvaluatorArgs, KResponse } from './command'
18+
import { RawContent, RawResponse } from './entity'
19+
import { KResponse } from './command'
2020
import { ExecOptions } from './execOptions'
2121

2222
export default interface REPL {
@@ -64,13 +64,6 @@ export default interface REPL {
6464
*/
6565
update(tab: Tab, command: string, execOptions?: ExecOptions): Promise<void>
6666

67-
/**
68-
* If the command is semicolon-separated, invoke each element of the
69-
* split separately
70-
*
71-
*/
72-
semicolonInvoke(opts: EvaluatorArgs): Promise<MixedResponse>
73-
7467
/**
7568
* Prepare a string to be part of a `command` argument to the *exec
7669
* functions, quoting and escaping as necessary.

packages/core/src/repl/exec.ts

Lines changed: 65 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import {
4040
CommandHandlerWithEvents,
4141
EvaluatorArgs as Arguments,
4242
ExecType,
43-
EvaluatorArgs,
4443
KResponse,
4544
ParsedOptions,
4645
YargsParserFlags
@@ -310,7 +309,7 @@ class InProcessExecutor implements Executor {
310309
private async execUnsafe<T extends KResponse, O extends ParsedOptions>(
311310
commandUntrimmed: string,
312311
execOptions = emptyExecOptions()
313-
): Promise<T | CodedError<number> | HTMLElement | CommandEvaluationError> {
312+
): Promise<T | CodedError<number> | HTMLElement | MixedResponse | CommandEvaluationError> {
314313
//
315314
const tab = execOptions.tab || getCurrentTab()
316315

@@ -407,48 +406,54 @@ class InProcessExecutor implements Executor {
407406
createOutputStream: execOptions.createOutputStream || (() => this.makeStream(getTabId(tab), execUUID))
408407
}
409408

410-
let response: T | Promise<T>
411-
try {
412-
response = await Promise.resolve(
413-
currentEvaluatorImpl.apply<T, O>(commandUntrimmed, execOptions, evaluator, args)
414-
).then(response => {
415-
// indicate that the command was successfuly completed
416-
evaluator.success({
417-
tab,
418-
type: (execOptions && execOptions.type) || ExecType.TopLevel,
419-
isDrilldown: execOptions.isDrilldown,
420-
command,
421-
parsedOptions
422-
})
409+
let response: T | Promise<T> | MixedResponse
423410

424-
return response
425-
})
426-
} catch (err) {
427-
evaluator.error(command, tab, execType, err)
428-
if (execType === ExecType.Nested) {
429-
throw err
411+
const commands = command.split(/\s*;\s*/)
412+
if (commands.length > 1) {
413+
response = await semicolonInvoke(commands, execOptions)
414+
} else {
415+
try {
416+
response = await Promise.resolve(
417+
currentEvaluatorImpl.apply<T, O>(commandUntrimmed, execOptions, evaluator, args)
418+
).then(response => {
419+
// indicate that the command was successfuly completed
420+
evaluator.success({
421+
tab,
422+
type: (execOptions && execOptions.type) || ExecType.TopLevel,
423+
isDrilldown: execOptions.isDrilldown,
424+
command,
425+
parsedOptions
426+
})
427+
428+
return response
429+
})
430+
} catch (err) {
431+
evaluator.error(command, tab, execType, err)
432+
if (execType === ExecType.Nested) {
433+
throw err
434+
}
435+
response = err
430436
}
431-
response = err
432-
}
433437

434-
if (evaluator.options.viewTransformer && execType !== ExecType.Nested) {
435-
response = await Promise.resolve(response)
436-
.then(async _ => {
437-
const maybeAView = await evaluator.options.viewTransformer(args, _)
438-
return maybeAView || _
439-
})
440-
.catch(err => {
441-
// view transformer failed; treat this as the response to the user
442-
return err
443-
})
444-
}
438+
if (evaluator.options.viewTransformer && execType !== ExecType.Nested) {
439+
response = await Promise.resolve(response)
440+
.then(async _ => {
441+
const maybeAView = await evaluator.options.viewTransformer(args, _)
442+
return maybeAView || _
443+
})
444+
.catch(err => {
445+
// view transformer failed; treat this as the response to the user
446+
return err
447+
})
448+
}
445449

446-
// the || true part is a safeguard for cases where typescript
447-
// didn't catch a command handler returning nothing; it
448-
// shouldn't happen, but probably isn't a sign of a dire
449-
// problem. issue a debug warning, in any case
450-
if (!response) {
451-
debug('warning: command handler returned nothing', commandUntrimmed)
450+
// the || true part is a safeguard for cases where typescript
451+
// didn't catch a command handler returning nothing; it
452+
// shouldn't happen, but probably isn't a sign of a dire
453+
// problem. issue a debug warning, in any case
454+
if (!response) {
455+
debug('warning: command handler returned nothing', commandUntrimmed)
456+
}
452457
}
453458

454459
this.emitCompletionEvent(
@@ -480,7 +485,7 @@ class InProcessExecutor implements Executor {
480485
public async exec<T extends KResponse, O extends ParsedOptions>(
481486
commandUntrimmed: string,
482487
execOptions = emptyExecOptions()
483-
): Promise<T | CodedError<number> | HTMLElement | CommandEvaluationError> {
488+
): Promise<T | CodedError<number> | HTMLElement | MixedResponse | CommandEvaluationError> {
484489
try {
485490
return await this.execUnsafe(commandUntrimmed, execOptions)
486491
} catch (err) {
@@ -623,39 +628,36 @@ export const setExecutorImpl = (impl: Executor): void => {
623628
* split separately
624629
*
625630
*/
626-
export async function semicolonInvoke(opts: EvaluatorArgs): Promise<MixedResponse> {
627-
const commands = opts.command.split(/\s*;\s*/)
628-
if (commands.length > 1) {
629-
debug('semicolonInvoke', commands)
631+
async function semicolonInvoke(commands: string[], execOptions: ExecOptions): Promise<MixedResponse> {
632+
debug('semicolonInvoke', commands)
630633

631-
const nonEmptyCommands = commands.filter(_ => _)
634+
const nonEmptyCommands = commands.filter(_ => _)
632635

633-
const result: MixedResponse = await promiseEach(nonEmptyCommands, async command => {
634-
const entity = await qexec<MixedResponsePart | true>(
635-
command,
636-
undefined,
637-
undefined,
638-
Object.assign({}, opts.execOptions, { quiet: false, /* block, */ execUUID: opts.execOptions.execUUID })
639-
)
636+
const result: MixedResponse = await promiseEach(nonEmptyCommands, async command => {
637+
const entity = await qexec<MixedResponsePart | true>(
638+
command,
639+
undefined,
640+
undefined,
641+
Object.assign({}, execOptions, { quiet: false, /* block, */ execUUID: execOptions.execUUID })
642+
)
640643

641-
if (entity === true) {
642-
// pty output
643-
return ''
644-
} else {
645-
return entity
646-
}
647-
})
644+
if (entity === true) {
645+
// pty output
646+
return ''
647+
} else {
648+
return entity
649+
}
650+
})
648651

649-
return result
650-
}
652+
return result
651653
}
652654

653655
/**
654656
* @return an instance that obeys the REPL interface
655657
*
656658
*/
657659
export function getImpl(tab: Tab): REPL {
658-
const impl = { qexec, rexec, pexec, click, semicolonInvoke, encodeComponent, split } as REPL
660+
const impl = { qexec, rexec, pexec, click, encodeComponent, split } as REPL
659661
tab.REPL = impl
660662
return impl
661663
}

packages/core/src/repl/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import { CodedError } from '../models/errors'
1818
import { ExecOptions } from '../models/execOptions'
1919
import { Evaluator, EvaluatorArgs, KResponse, ParsedOptions } from '../models/command'
20+
import { MixedResponse } from '../models/entity'
2021

2122
/**
2223
* repl.exec, and the family repl.qexec, repl.pexec, etc. are all
@@ -28,7 +29,7 @@ export interface Executor {
2829
exec<T extends KResponse, O extends ParsedOptions>(
2930
commandUntrimmed: string,
3031
execOptions: ExecOptions
31-
): Promise<T | CodedError<number> | HTMLElement>
32+
): Promise<T | CodedError<number> | HTMLElement | MixedResponse>
3233
}
3334

3435
/**

plugins/plugin-bash-like/fs/src/lib/ls.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,7 @@ function toTable(entries: GlobStats[], args: Arguments<LsOptions>): HTMLElement
237237
*
238238
*/
239239
const doLs = (cmd: string) => async (opts: Arguments<LsOptions>): Promise<MixedResponse | HTMLElement | Table> => {
240-
const semi = await opts.REPL.semicolonInvoke(opts)
241-
if (semi) {
242-
return semi
243-
} else if (/\|/.test(opts.command)) {
240+
if (/\|/.test(opts.command)) {
244241
// conservatively send possibly piped output to the PTY
245242
return opts.REPL.qexec(`sendtopty ${opts.command}`, opts.block)
246243
}

0 commit comments

Comments
 (0)