Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions packages/nuxt-cli/src/dev/tui/terminal-replies.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Buffer } from 'node:buffer'
import { Buffer } from 'node:buffer'

/** Introduces an OSC, DCS, APC or PM string, which runs until its terminator. */
// eslint-disable-next-line no-control-regex
Expand Down Expand Up @@ -55,12 +55,26 @@ export function filterTerminalReplies(stdin: NodeJS.ReadableStream): ReplyFilter
timer = undefined
}

/** This chunk's keys have not been emitted yet; the next chunk is typing. */
function endAfterThisChunk(): void {
/**
* Finish a reply that ends `length` characters into what has been buffered.
*
* The whole chunk is suppressed, since `readline` has not turned it into keys
* yet and there is no way to drop only part of it. Anything the reply did not
* account for was typed, so it is put back to be read as keys of its own.
*/
function endReply(buffered: string, length: number): void {
const typed = buffered.slice(length)
clearTimeout(timer)
timer = undefined
pending = ''
setImmediate(stopReplying)
// After this chunk, so the reply is still suppressed while `readline` reads
// it, and what was typed is read back with the filter already clear.
setImmediate(() => {
stopReplying()
if (typed) {
stdin.unshift(Buffer.from(typed, 'latin1'))
}
})
}

/** Wait for the rest of a reply, without waiting on it forever. */
Expand All @@ -79,13 +93,17 @@ export function filterTerminalReplies(stdin: NodeJS.ReadableStream): ReplyFilter
// matched together with what follows it.
const buffered = pending + chunk.toString('latin1')

if (CSI_REPLY_RE.test(buffered)) {
const csi = CSI_REPLY_RE.exec(buffered)
if (csi) {
replying = true
return endAfterThisChunk()
return endReply(buffered, csi[0].length)
}
if (STRING_REPLY_RE.test(buffered)) {
replying = true
return STRING_TERMINATOR_RE.test(buffered) ? endAfterThisChunk() : awaitRest(buffered)
const terminator = STRING_TERMINATOR_RE.exec(buffered)
return terminator
? endReply(buffered, terminator.index + terminator[0].length)
: awaitRest(buffered)
}
if (PARTIAL_CSI_RE.test(buffered)) {
replying = true
Expand Down
18 changes: 17 additions & 1 deletion packages/nuxt-cli/test/unit/dev-keys.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,28 @@ describe('panel keys', () => {
const { keys, type } = attach()

await type('\u001B]11;rgb:1e1e/1e1e/1e1e\u001B')
await type('\\\\')
await type('\\')
await type('o')

expect(keys).toEqual(['o'])
})

it('should read a key that arrives in the same chunk as a csi report', async () => {
const { keys, type } = attach()

await type('\u001B[12;34Ro')

expect(keys).toEqual(['o'])
})

it('should read a key that arrives in the same chunk as a string reply', async () => {
const { keys, type } = attach()

await type('\u001B]11;rgb:1e1e/1e1e/1e1e\u0007o')

expect(keys).toEqual(['o'])
})

it('should give the keyboard back when a reply is never terminated', async () => {
const { keys, type } = attach()

Expand Down
Loading