Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
114 lines
2.9 KiB
TypeScript
114 lines
2.9 KiB
TypeScript
import type { TerminalSettings } from "../../../domain/models";
|
|
|
|
export const DEFAULT_SHIFT_ENTER_TEXT = "\\n";
|
|
|
|
/** Kitty CSI-u encoding for Shift+Enter (keycode 13, modifier shift → 2). */
|
|
export const SHIFT_ENTER_CSI_U_SEQUENCE = "\u001b[13;2u";
|
|
|
|
type ShiftEnterEvent = Pick<
|
|
KeyboardEvent,
|
|
"altKey" | "ctrlKey" | "key" | "metaKey" | "shiftKey" | "type"
|
|
> & {
|
|
isComposing?: boolean;
|
|
};
|
|
|
|
export function decodeTerminalTextEscapes(text: string): string {
|
|
let decoded = "";
|
|
|
|
for (let index = 0; index < text.length; index += 1) {
|
|
const char = text[index];
|
|
if (char !== "\\" || index >= text.length - 1) {
|
|
decoded += char;
|
|
continue;
|
|
}
|
|
|
|
const next = text[index + 1];
|
|
switch (next) {
|
|
case "n":
|
|
decoded += "\n";
|
|
index += 1;
|
|
break;
|
|
case "r":
|
|
decoded += "\r";
|
|
index += 1;
|
|
break;
|
|
case "t":
|
|
decoded += "\t";
|
|
index += 1;
|
|
break;
|
|
case "\\":
|
|
decoded += "\\";
|
|
index += 1;
|
|
break;
|
|
default:
|
|
decoded += char;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return decoded;
|
|
}
|
|
|
|
export function shouldSendShiftEnterText(
|
|
event: ShiftEnterEvent,
|
|
settings?: Pick<TerminalSettings, "shiftEnterNewlineEnabled">,
|
|
): boolean {
|
|
return (
|
|
settings?.shiftEnterNewlineEnabled !== false &&
|
|
event.type === "keydown" &&
|
|
event.key === "Enter" &&
|
|
event.shiftKey &&
|
|
!event.altKey &&
|
|
!event.ctrlKey &&
|
|
!event.metaKey &&
|
|
!event.isComposing
|
|
);
|
|
}
|
|
|
|
export function resolveShiftEnterText(
|
|
settings?: Pick<TerminalSettings, "shiftEnterNewlineText">,
|
|
): string {
|
|
const configured = settings?.shiftEnterNewlineText;
|
|
return decodeTerminalTextEscapes(
|
|
typeof configured === "string" ? configured : DEFAULT_SHIFT_ENTER_TEXT,
|
|
);
|
|
}
|
|
|
|
export function isBareShiftEnterLineEnding(text: string): boolean {
|
|
return text === "\n" || text === "\r" || text === "\r\n";
|
|
}
|
|
|
|
/**
|
|
* True when Kitty encoding already keeps Shift+Enter distinct from plain Enter.
|
|
* Non-preserving flag sets (e.g. alternate-key or associated-text alone) still
|
|
* encode Shift+Enter as a bare CR/LF, so the alternate-screen remap must run.
|
|
*/
|
|
export function doesKittyEncodingPreserveShiftEnter(
|
|
encoded: string | null | undefined,
|
|
): boolean {
|
|
return typeof encoded === "string"
|
|
&& encoded.length > 0
|
|
&& !isBareShiftEnterLineEnding(encoded);
|
|
}
|
|
|
|
export function isShiftEnterLineContinuationText(text: string): boolean {
|
|
return /\\(?:\r\n|\r|\n)$/.test(text);
|
|
}
|
|
|
|
export type ShiftEnterSubmittedInput = {
|
|
text: string;
|
|
lineEnding: "\r\n" | "\r" | "\n";
|
|
};
|
|
|
|
export function getShiftEnterSubmittedInput(
|
|
text: string,
|
|
): ShiftEnterSubmittedInput | null {
|
|
if (isShiftEnterLineContinuationText(text)) return null;
|
|
const match = text.match(/^([^\r\n]*)(\r\n|\r|\n)$/);
|
|
if (!match) return null;
|
|
return {
|
|
text: match[1],
|
|
lineEnding: match[2] as ShiftEnterSubmittedInput["lineEnding"],
|
|
};
|
|
}
|