Fix Shift+Enter in terminal input modes (#999)
* Fix Shift+Enter terminal input modes * Advertise terminal input capabilities
This commit is contained in:
@@ -681,7 +681,13 @@ export class TerminalSessionController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.terminalManager?.getTerminal(activeStream.terminalId)) {
|
||||
const terminalManager = this.terminalManager;
|
||||
if (!terminalManager) {
|
||||
this.detachStream(activeStream.terminalId, { emitExit: true });
|
||||
return;
|
||||
}
|
||||
const terminal = terminalManager.getTerminal(activeStream.terminalId);
|
||||
if (!terminal) {
|
||||
this.detachStream(activeStream.terminalId, { emitExit: true });
|
||||
return;
|
||||
}
|
||||
@@ -689,7 +695,7 @@ export class TerminalSessionController {
|
||||
activeStream.outputCoalescer.flush();
|
||||
activeStream.snapshotInFlight = true;
|
||||
try {
|
||||
const snapshot = await this.terminalManager.getTerminalState(activeStream.terminalId);
|
||||
const snapshot = await terminalManager.getTerminalState(activeStream.terminalId);
|
||||
if (this.activeStreams.get(activeStream.slot) !== activeStream) {
|
||||
return;
|
||||
}
|
||||
@@ -706,6 +712,11 @@ export class TerminalSessionController {
|
||||
}),
|
||||
);
|
||||
|
||||
const replayPreamble = terminal.getReplayPreamble();
|
||||
if (replayPreamble.length > 0) {
|
||||
activeStream.outputCoalescer.handle(replayPreamble);
|
||||
}
|
||||
|
||||
const bufferedOutputs = activeStream.bufferedOutputs.splice(
|
||||
0,
|
||||
activeStream.bufferedOutputs.length,
|
||||
|
||||
@@ -234,6 +234,7 @@ describe.skipIf(isPlatform("win32"))("terminal POSIX-only", () => {
|
||||
});
|
||||
|
||||
expect(resolvedEnv.TERM).toBe("xterm-256color");
|
||||
expect(resolvedEnv.TERM_PROGRAM).toBe("kitty");
|
||||
expect(resolvedEnv.PASEO_ZSH_ZDOTDIR).toBe("/tmp/paseo-zdotdir");
|
||||
expect(resolvedEnv.ZDOTDIR).not.toBe("/tmp/paseo-zdotdir");
|
||||
expect(existsSync(join(resolvedEnv.ZDOTDIR, ".zshenv"))).toBe(true);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { fileURLToPath } from "node:url";
|
||||
import { createExternalProcessEnv } from "../server/paseo-env.js";
|
||||
import { writePrivateFileAtomicSync } from "../server/private-files.js";
|
||||
import type { TerminalCell, TerminalState } from "../shared/messages.js";
|
||||
import { TerminalInputModeTracker } from "../shared/terminal-input-mode.js";
|
||||
|
||||
const { Terminal } = xterm;
|
||||
const require = createRequire(import.meta.url);
|
||||
@@ -54,6 +55,7 @@ export interface TerminalSession {
|
||||
getSize(): { rows: number; cols: number };
|
||||
getState(): TerminalState;
|
||||
getStateSnapshot(): TerminalStateSnapshot;
|
||||
getReplayPreamble(): string;
|
||||
getTitle(): string | undefined;
|
||||
getExitInfo(): TerminalExitInfo | null;
|
||||
kill(): void;
|
||||
@@ -219,6 +221,7 @@ export function buildTerminalEnvironment(
|
||||
): Record<string, string> {
|
||||
const baseEnv: Record<string, string> = createExternalProcessEnv(process.env, input.env, {
|
||||
TERM: "xterm-256color",
|
||||
TERM_PROGRAM: "kitty",
|
||||
});
|
||||
|
||||
if (basename(input.shell) !== "zsh") {
|
||||
@@ -551,6 +554,7 @@ export async function createTerminal(options: CreateTerminalOptions): Promise<Te
|
||||
let pendingInput = "";
|
||||
let inputFlushImmediate: ReturnType<typeof setImmediate> | null = null;
|
||||
let stateRevision = 0;
|
||||
const inputModeTracker = new TerminalInputModeTracker();
|
||||
|
||||
// Create xterm.js headless terminal
|
||||
const terminal = new Terminal({
|
||||
@@ -703,6 +707,7 @@ export async function createTerminal(options: CreateTerminalOptions): Promise<Te
|
||||
}
|
||||
disposed = true;
|
||||
pendingInput = "";
|
||||
inputModeTracker.reset();
|
||||
if (inputFlushImmediate) {
|
||||
clearImmediate(inputFlushImmediate);
|
||||
inputFlushImmediate = null;
|
||||
@@ -735,6 +740,10 @@ export async function createTerminal(options: CreateTerminalOptions): Promise<Te
|
||||
// Pipe PTY output to terminal emulator
|
||||
ptyProcess.onData((data) => {
|
||||
if (killed) return;
|
||||
const inputModeUpdate = inputModeTracker.feed(data);
|
||||
for (const response of inputModeUpdate.responses) {
|
||||
ptyProcess.write(response);
|
||||
}
|
||||
recentOutputText = `${recentOutputText}${data}`;
|
||||
if (recentOutputText.length > TERMINAL_EXIT_OUTPUT_CHAR_LIMIT) {
|
||||
recentOutputText = recentOutputText.slice(-TERMINAL_EXIT_OUTPUT_CHAR_LIMIT);
|
||||
@@ -803,6 +812,10 @@ export async function createTerminal(options: CreateTerminalOptions): Promise<Te
|
||||
};
|
||||
}
|
||||
|
||||
function getReplayPreamble(): string {
|
||||
return inputModeTracker.getPreamble();
|
||||
}
|
||||
|
||||
function writeInputToPty(data: string): void {
|
||||
ptyProcess.write(data);
|
||||
}
|
||||
@@ -1034,6 +1047,7 @@ export async function createTerminal(options: CreateTerminalOptions): Promise<Te
|
||||
getSize,
|
||||
getState,
|
||||
getStateSnapshot,
|
||||
getReplayPreamble,
|
||||
getTitle,
|
||||
getExitInfo,
|
||||
kill,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { fork } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { TerminalState } from "../shared/messages.js";
|
||||
import { TerminalInputModeTracker } from "../shared/terminal-input-mode.js";
|
||||
import type {
|
||||
ClientMessage,
|
||||
ServerMessage,
|
||||
@@ -43,6 +44,7 @@ interface PendingRequest {
|
||||
interface WorkerTerminalRecord {
|
||||
info: WorkerTerminalInfo;
|
||||
state: TerminalState;
|
||||
inputModeTracker: TerminalInputModeTracker;
|
||||
exitInfo: TerminalExitInfo | null;
|
||||
messageListeners: Set<(msg: ServerMessage) => void>;
|
||||
exitListeners: Set<(info: TerminalExitInfo) => void>;
|
||||
@@ -169,6 +171,7 @@ export function createWorkerTerminalManager(
|
||||
const record: WorkerTerminalRecord = {
|
||||
info: cloneTerminalInfo(input.info),
|
||||
state: input.state,
|
||||
inputModeTracker: new TerminalInputModeTracker(),
|
||||
exitInfo: null,
|
||||
messageListeners: new Set(),
|
||||
exitListeners: new Set(),
|
||||
@@ -247,6 +250,9 @@ export function createWorkerTerminalManager(
|
||||
revision: 0,
|
||||
};
|
||||
},
|
||||
getReplayPreamble(): string {
|
||||
return record.inputModeTracker.getPreamble();
|
||||
},
|
||||
getTitle(): string | undefined {
|
||||
return record.info.title;
|
||||
},
|
||||
@@ -302,6 +308,9 @@ export function createWorkerTerminalManager(
|
||||
if (message.message.type === "snapshot") {
|
||||
record.state = message.message.state;
|
||||
}
|
||||
if (message.message.type === "output") {
|
||||
record.inputModeTracker.feed(message.message.data);
|
||||
}
|
||||
for (const listener of Array.from(record.messageListeners)) {
|
||||
listener(message.message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user