Files
NetMesh/components/terminal/runtime/cursorPreference.ts
zhaolei 3c72efcb7f
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
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

119 lines
3.5 KiB
TypeScript

import type { IDisposable, Terminal as XTerm } from "@xterm/xterm";
import type { RefObject } from "react";
import type { TerminalSettings } from "../../../types";
type CursorPreferenceSettings = Pick<TerminalSettings, "cursorShape" | "cursorBlink">;
type MutableCursorOptions = {
cursorStyle?: "block" | "bar" | "underline";
cursorBlink?: boolean;
};
type TerminalLike = {
options: MutableCursorOptions;
parser?: {
registerCsiHandler?: (
id: { prefix?: string; intermediates?: string; final: string },
callback: (params: readonly (number | number[])[]) => boolean,
) => IDisposable;
};
_core?: {
coreService?: {
decPrivateModes?: {
cursorStyle?: "block" | "bar" | "underline";
cursorBlink?: boolean;
};
};
};
};
const scheduleAfterDefaultHandler = (callback: () => void): void => {
if (typeof queueMicrotask === "function") {
queueMicrotask(callback);
return;
}
setTimeout(callback, 0);
};
const hasCursorBlinkPrivateModeParam = (params: readonly (number | number[])[]): boolean => (
params.some((param) => (
Array.isArray(param)
? param.includes(12)
: param === 12
))
);
export const resolveUserCursorPreference = (
settings: Partial<CursorPreferenceSettings> | undefined,
): Required<CursorPreferenceSettings> => ({
cursorShape: settings?.cursorShape ?? "block",
cursorBlink: settings?.cursorBlink ?? true,
});
export const applyUserCursorPreference = (
term: TerminalLike,
settings: Partial<CursorPreferenceSettings> | undefined,
): void => {
const preference = resolveUserCursorPreference(settings);
const privateModes = term._core?.coreService?.decPrivateModes;
if (privateModes) {
privateModes.cursorStyle = undefined;
privateModes.cursorBlink = undefined;
}
term.options.cursorStyle = preference.cursorShape;
term.options.cursorBlink = preference.cursorBlink;
};
export const applyUserCursorBlinkPreference = (
term: TerminalLike,
settings: Partial<CursorPreferenceSettings> | undefined,
): void => {
const preference = resolveUserCursorPreference(settings);
const privateModes = term._core?.coreService?.decPrivateModes;
if (privateModes) {
privateModes.cursorBlink = undefined;
}
term.options.cursorBlink = preference.cursorBlink;
};
export const installUserCursorPreferenceGuard = (
term: XTerm | TerminalLike,
terminalSettingsRef: RefObject<TerminalSettings | undefined>,
): IDisposable | null => {
const terminal = term as TerminalLike;
const parser = terminal.parser;
if (!parser?.registerCsiHandler) return null;
const registerCsiHandler = parser.registerCsiHandler.bind(parser);
const applyBlinkPreference = () => applyUserCursorBlinkPreference(terminal, terminalSettingsRef.current);
const cursorStyleDisposable = registerCsiHandler({ intermediates: " ", final: "q" }, () => {
scheduleAfterDefaultHandler(applyBlinkPreference);
return false;
});
const cursorBlinkSetDisposable = registerCsiHandler({ prefix: "?", final: "h" }, (params) => {
if (hasCursorBlinkPrivateModeParam(params)) {
scheduleAfterDefaultHandler(applyBlinkPreference);
}
return false;
});
const cursorBlinkResetDisposable = registerCsiHandler({ prefix: "?", final: "l" }, (params) => {
if (hasCursorBlinkPrivateModeParam(params)) {
scheduleAfterDefaultHandler(applyBlinkPreference);
}
return false;
});
return {
dispose: () => {
cursorStyleDisposable.dispose();
cursorBlinkSetDisposable.dispose();
cursorBlinkResetDisposable.dispose();
},
};
};