feat(terminal): capture cursor presentation modes (style, blink, hidden)
This commit is contained in:
@@ -20,7 +20,13 @@ type SnapshotState = {
|
|||||||
cols: number;
|
cols: number;
|
||||||
grid: SnapshotCell[][];
|
grid: SnapshotCell[][];
|
||||||
scrollback: SnapshotCell[][];
|
scrollback: SnapshotCell[][];
|
||||||
cursor: { row: number; col: number };
|
cursor: {
|
||||||
|
row: number;
|
||||||
|
col: number;
|
||||||
|
hidden?: boolean;
|
||||||
|
style?: "block" | "underline" | "bar";
|
||||||
|
blink?: boolean;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
async function writeToTerminal(
|
async function writeToTerminal(
|
||||||
@@ -50,10 +56,32 @@ function extractState(terminal: ClientTerminal | HeadlessTerminal): SnapshotStat
|
|||||||
cols: terminal.cols,
|
cols: terminal.cols,
|
||||||
grid,
|
grid,
|
||||||
scrollback,
|
scrollback,
|
||||||
cursor: {
|
cursor: extractCursorState(terminal),
|
||||||
row: buffer.cursorY,
|
};
|
||||||
col: buffer.cursorX,
|
}
|
||||||
},
|
|
||||||
|
function extractCursorState(
|
||||||
|
terminal: ClientTerminal | HeadlessTerminal,
|
||||||
|
): SnapshotState["cursor"] {
|
||||||
|
const buffer = terminal.buffer.active;
|
||||||
|
const coreService = (terminal as any)._core?.coreService;
|
||||||
|
const cursorStyle = coreService?.decPrivateModes?.cursorStyle;
|
||||||
|
const normalizedCursorStyle =
|
||||||
|
cursorStyle === "block" || cursorStyle === "underline" || cursorStyle === "bar"
|
||||||
|
? cursorStyle
|
||||||
|
: undefined;
|
||||||
|
const cursorBlink =
|
||||||
|
typeof coreService?.decPrivateModes?.cursorBlink === "boolean"
|
||||||
|
? coreService.decPrivateModes.cursorBlink
|
||||||
|
: undefined;
|
||||||
|
const hidden = Boolean(coreService?.isCursorHidden);
|
||||||
|
|
||||||
|
return {
|
||||||
|
row: buffer.cursorY,
|
||||||
|
col: buffer.cursorX,
|
||||||
|
...(hidden ? { hidden: true } : {}),
|
||||||
|
...(normalizedCursorStyle ? { style: normalizedCursorStyle } : {}),
|
||||||
|
...(typeof cursorBlink === "boolean" ? { blink: cursorBlink } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +115,7 @@ function extractRow(terminal: ClientTerminal | HeadlessTerminal, row: number): S
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("terminal-snapshot", () => {
|
describe("terminal-snapshot", () => {
|
||||||
it("replays extracted terminal state into a client xterm with matching grid, scrollback, and cursor", async () => {
|
it("replays extracted terminal state into a client xterm with matching grid, scrollback, and cursor presentation", async () => {
|
||||||
const source = new HeadlessTerminal({
|
const source = new HeadlessTerminal({
|
||||||
rows: 4,
|
rows: 4,
|
||||||
cols: 12,
|
cols: 12,
|
||||||
@@ -100,6 +128,8 @@ describe("terminal-snapshot", () => {
|
|||||||
await writeToTerminal(source, "\u001b[1mbold\u001b[0m\r\n");
|
await writeToTerminal(source, "\u001b[1mbold\u001b[0m\r\n");
|
||||||
await writeToTerminal(source, "cursor");
|
await writeToTerminal(source, "cursor");
|
||||||
await writeToTerminal(source, "\u001b[2D");
|
await writeToTerminal(source, "\u001b[2D");
|
||||||
|
await writeToTerminal(source, "\u001b[2 q");
|
||||||
|
await writeToTerminal(source, "\u001b[?25l");
|
||||||
|
|
||||||
const snapshot = extractState(source);
|
const snapshot = extractState(source);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ type TerminalStyle = {
|
|||||||
bold: boolean;
|
bold: boolean;
|
||||||
italic: boolean;
|
italic: boolean;
|
||||||
underline: boolean;
|
underline: boolean;
|
||||||
|
dim: boolean;
|
||||||
|
inverse: boolean;
|
||||||
|
strikethrough: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_STYLE: TerminalStyle = {
|
const DEFAULT_STYLE: TerminalStyle = {
|
||||||
@@ -18,6 +21,9 @@ const DEFAULT_STYLE: TerminalStyle = {
|
|||||||
bold: false,
|
bold: false,
|
||||||
italic: false,
|
italic: false,
|
||||||
underline: false,
|
underline: false,
|
||||||
|
dim: false,
|
||||||
|
inverse: false,
|
||||||
|
strikethrough: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function renderTerminalSnapshotToAnsi(state: TerminalState): string {
|
export function renderTerminalSnapshotToAnsi(state: TerminalState): string {
|
||||||
@@ -33,11 +39,34 @@ export function renderTerminalSnapshotToAnsi(state: TerminalState): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lines.push("\u001b[0m");
|
lines.push("\u001b[0m");
|
||||||
|
const cursorPresentationAnsi = renderCursorPresentationToAnsi(state.cursor);
|
||||||
|
if (cursorPresentationAnsi) {
|
||||||
|
lines.push(cursorPresentationAnsi);
|
||||||
|
}
|
||||||
lines.push(`\u001b[${state.cursor.row + 1};${state.cursor.col + 1}H`);
|
lines.push(`\u001b[${state.cursor.row + 1};${state.cursor.col + 1}H`);
|
||||||
|
lines.push(state.cursor.hidden ? "\u001b[?25l" : "\u001b[?25h");
|
||||||
lines.push("\u001b[?7h");
|
lines.push("\u001b[?7h");
|
||||||
return lines.join("");
|
return lines.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderCursorPresentationToAnsi(cursor: TerminalState["cursor"]): string | null {
|
||||||
|
if (!cursor.style) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cursorStyleCode = (() => {
|
||||||
|
if (cursor.style === "block") {
|
||||||
|
return cursor.blink === false ? 2 : 1;
|
||||||
|
}
|
||||||
|
if (cursor.style === "underline") {
|
||||||
|
return cursor.blink === false ? 4 : 3;
|
||||||
|
}
|
||||||
|
return cursor.blink === false ? 6 : 5;
|
||||||
|
})();
|
||||||
|
|
||||||
|
return `\u001b[${cursorStyleCode} q`;
|
||||||
|
}
|
||||||
|
|
||||||
function renderTerminalRow(row: TerminalCell[]): string {
|
function renderTerminalRow(row: TerminalCell[]): string {
|
||||||
const output: string[] = [];
|
const output: string[] = [];
|
||||||
const length = getTerminalRowLength(row);
|
const length = getTerminalRowLength(row);
|
||||||
@@ -76,7 +105,10 @@ function getTerminalRowLength(row: TerminalCell[]): number {
|
|||||||
cell.bgMode !== undefined ||
|
cell.bgMode !== undefined ||
|
||||||
cell.bold ||
|
cell.bold ||
|
||||||
cell.italic ||
|
cell.italic ||
|
||||||
cell.underline
|
cell.underline ||
|
||||||
|
cell.dim ||
|
||||||
|
cell.inverse ||
|
||||||
|
cell.strikethrough
|
||||||
) {
|
) {
|
||||||
return index + 1;
|
return index + 1;
|
||||||
}
|
}
|
||||||
@@ -93,6 +125,9 @@ function getTerminalStyle(cell: TerminalCell): TerminalStyle {
|
|||||||
bold: Boolean(cell.bold),
|
bold: Boolean(cell.bold),
|
||||||
italic: Boolean(cell.italic),
|
italic: Boolean(cell.italic),
|
||||||
underline: Boolean(cell.underline),
|
underline: Boolean(cell.underline),
|
||||||
|
dim: Boolean(cell.dim),
|
||||||
|
inverse: Boolean(cell.inverse),
|
||||||
|
strikethrough: Boolean(cell.strikethrough),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +139,10 @@ function terminalStylesEqual(left: TerminalStyle, right: TerminalStyle): boolean
|
|||||||
left.bgMode === right.bgMode &&
|
left.bgMode === right.bgMode &&
|
||||||
left.bold === right.bold &&
|
left.bold === right.bold &&
|
||||||
left.italic === right.italic &&
|
left.italic === right.italic &&
|
||||||
left.underline === right.underline
|
left.underline === right.underline &&
|
||||||
|
left.dim === right.dim &&
|
||||||
|
left.inverse === right.inverse &&
|
||||||
|
left.strikethrough === right.strikethrough
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,12 +152,21 @@ function styleToAnsi(style: TerminalStyle): string {
|
|||||||
if (style.bold) {
|
if (style.bold) {
|
||||||
codes.push("1");
|
codes.push("1");
|
||||||
}
|
}
|
||||||
|
if (style.dim) {
|
||||||
|
codes.push("2");
|
||||||
|
}
|
||||||
if (style.italic) {
|
if (style.italic) {
|
||||||
codes.push("3");
|
codes.push("3");
|
||||||
}
|
}
|
||||||
if (style.underline) {
|
if (style.underline) {
|
||||||
codes.push("4");
|
codes.push("4");
|
||||||
}
|
}
|
||||||
|
if (style.inverse) {
|
||||||
|
codes.push("7");
|
||||||
|
}
|
||||||
|
if (style.strikethrough) {
|
||||||
|
codes.push("9");
|
||||||
|
}
|
||||||
|
|
||||||
if (style.fg !== undefined && style.fgMode !== undefined) {
|
if (style.fg !== undefined && style.fgMode !== undefined) {
|
||||||
codes.push(...colorToSgr(style.fgMode, style.fg, false));
|
codes.push(...colorToSgr(style.fgMode, style.fg, false));
|
||||||
|
|||||||
@@ -2104,6 +2104,19 @@ export const TerminalCellSchema = z.object({
|
|||||||
bold: z.boolean().optional(),
|
bold: z.boolean().optional(),
|
||||||
italic: z.boolean().optional(),
|
italic: z.boolean().optional(),
|
||||||
underline: z.boolean().optional(),
|
underline: z.boolean().optional(),
|
||||||
|
dim: z.boolean().optional(),
|
||||||
|
inverse: z.boolean().optional(),
|
||||||
|
strikethrough: z.boolean().optional(),
|
||||||
|
}).strict();
|
||||||
|
|
||||||
|
export const TerminalCursorStyleSchema = z.enum(["block", "underline", "bar"]);
|
||||||
|
|
||||||
|
export const TerminalCursorSchema = z.object({
|
||||||
|
row: z.number(),
|
||||||
|
col: z.number(),
|
||||||
|
hidden: z.boolean().optional(),
|
||||||
|
style: TerminalCursorStyleSchema.optional(),
|
||||||
|
blink: z.boolean().optional(),
|
||||||
}).strict();
|
}).strict();
|
||||||
|
|
||||||
export const TerminalStateSchema = z.object({
|
export const TerminalStateSchema = z.object({
|
||||||
@@ -2111,7 +2124,7 @@ export const TerminalStateSchema = z.object({
|
|||||||
cols: z.number(),
|
cols: z.number(),
|
||||||
grid: z.array(z.array(TerminalCellSchema)),
|
grid: z.array(z.array(TerminalCellSchema)),
|
||||||
scrollback: z.array(z.array(TerminalCellSchema)),
|
scrollback: z.array(z.array(TerminalCellSchema)),
|
||||||
cursor: z.object({ row: z.number(), col: z.number() }).strict(),
|
cursor: TerminalCursorSchema,
|
||||||
}).strict();
|
}).strict();
|
||||||
|
|
||||||
export const ListTerminalsResponseSchema = z.object({
|
export const ListTerminalsResponseSchema = z.object({
|
||||||
@@ -2363,6 +2376,8 @@ export type SubscribeTerminalResponse = z.infer<typeof SubscribeTerminalResponse
|
|||||||
export type UnsubscribeTerminalRequest = z.infer<typeof UnsubscribeTerminalRequestSchema>;
|
export type UnsubscribeTerminalRequest = z.infer<typeof UnsubscribeTerminalRequestSchema>;
|
||||||
export type TerminalInput = z.infer<typeof TerminalInputSchema>;
|
export type TerminalInput = z.infer<typeof TerminalInputSchema>;
|
||||||
export type TerminalCell = z.infer<typeof TerminalCellSchema>;
|
export type TerminalCell = z.infer<typeof TerminalCellSchema>;
|
||||||
|
export type TerminalCursorStyle = z.infer<typeof TerminalCursorStyleSchema>;
|
||||||
|
export type TerminalCursor = z.infer<typeof TerminalCursorSchema>;
|
||||||
export type TerminalState = z.infer<typeof TerminalStateSchema>;
|
export type TerminalState = z.infer<typeof TerminalStateSchema>;
|
||||||
export type KillTerminalRequest = z.infer<typeof KillTerminalRequestSchema>;
|
export type KillTerminalRequest = z.infer<typeof KillTerminalRequestSchema>;
|
||||||
export type KillTerminalResponse = z.infer<typeof KillTerminalResponseSchema>;
|
export type KillTerminalResponse = z.infer<typeof KillTerminalResponseSchema>;
|
||||||
|
|||||||
@@ -53,6 +53,23 @@ async function waitForLines(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function waitForState(
|
||||||
|
session: TerminalSession,
|
||||||
|
predicate: (state: ReturnType<TerminalSession["getState"]>) => boolean,
|
||||||
|
timeoutMs = 5000,
|
||||||
|
): Promise<ReturnType<TerminalSession["getState"]>> {
|
||||||
|
const start = Date.now();
|
||||||
|
while (Date.now() - start < timeoutMs) {
|
||||||
|
const state = session.getState();
|
||||||
|
if (predicate(state)) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Timeout waiting for terminal state predicate to match");
|
||||||
|
}
|
||||||
|
|
||||||
describe("Terminal", () => {
|
describe("Terminal", () => {
|
||||||
const sessions: TerminalSession[] = [];
|
const sessions: TerminalSession[] = [];
|
||||||
const temporaryDirs: string[] = [];
|
const temporaryDirs: string[] = [];
|
||||||
@@ -587,6 +604,35 @@ describe("Terminal", () => {
|
|||||||
expect(typeof state.cursor.col).toBe("number");
|
expect(typeof state.cursor.col).toBe("number");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("captures cursor presentation modes emitted by terminal apps", async () => {
|
||||||
|
const session = trackSession(
|
||||||
|
await createTerminal({
|
||||||
|
cwd: "/tmp",
|
||||||
|
shell: "/bin/sh",
|
||||||
|
env: { PS1: "$ " },
|
||||||
|
rows: 24,
|
||||||
|
cols: 80,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitForLines(session, ["$"]);
|
||||||
|
session.send({ type: "input", data: "printf '\\033[2 q\\033[?25l'\r" });
|
||||||
|
|
||||||
|
const state = await waitForState(
|
||||||
|
session,
|
||||||
|
(current) =>
|
||||||
|
current.cursor.style === "block" &&
|
||||||
|
current.cursor.blink === false &&
|
||||||
|
current.cursor.hidden === true,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(state.cursor).toMatchObject({
|
||||||
|
style: "block",
|
||||||
|
blink: false,
|
||||||
|
hidden: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("grid cells have char and color attributes", async () => {
|
it("grid cells have char and color attributes", async () => {
|
||||||
const session = trackSession(
|
const session = trackSession(
|
||||||
await createTerminal({
|
await createTerminal({
|
||||||
|
|||||||
@@ -4,36 +4,13 @@ import { randomUUID } from "crypto";
|
|||||||
import { chmodSync, existsSync, statSync } from "node:fs";
|
import { chmodSync, existsSync, statSync } from "node:fs";
|
||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
import { createRequire } from "node:module";
|
import { createRequire } from "node:module";
|
||||||
|
import type { TerminalCell, TerminalState } from "../shared/messages.js";
|
||||||
|
|
||||||
const { Terminal } = xterm;
|
const { Terminal } = xterm;
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
let nodePtySpawnHelperChecked = false;
|
let nodePtySpawnHelperChecked = false;
|
||||||
const STATE_BROADCAST_INTERVAL_MS = 33;
|
const STATE_BROADCAST_INTERVAL_MS = 33;
|
||||||
|
|
||||||
export interface Cell {
|
|
||||||
char: string;
|
|
||||||
fg: number | undefined;
|
|
||||||
bg: number | undefined;
|
|
||||||
fgMode?: number; // 0=default, 1=16 ANSI, 2=256, 3=RGB
|
|
||||||
bgMode?: number;
|
|
||||||
bold?: boolean;
|
|
||||||
italic?: boolean;
|
|
||||||
underline?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Pos {
|
|
||||||
row: number;
|
|
||||||
col: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TerminalState {
|
|
||||||
rows: number;
|
|
||||||
cols: number;
|
|
||||||
grid: Cell[][];
|
|
||||||
scrollback: Cell[][];
|
|
||||||
cursor: Pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ClientMessage =
|
export type ClientMessage =
|
||||||
| { type: "input"; data: string }
|
| { type: "input"; data: string }
|
||||||
| { type: "resize"; rows: number; cols: number }
|
| { type: "resize"; rows: number; cols: number }
|
||||||
@@ -130,7 +107,7 @@ export function ensureNodePtySpawnHelperExecutableForCurrentPlatform(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractCell(terminal: TerminalType, row: number, col: number): Cell {
|
function extractCell(terminal: TerminalType, row: number, col: number): TerminalCell {
|
||||||
const buffer = terminal.buffer.active;
|
const buffer = terminal.buffer.active;
|
||||||
const line = buffer.getLine(row);
|
const line = buffer.getLine(row);
|
||||||
if (!line) {
|
if (!line) {
|
||||||
@@ -162,17 +139,20 @@ function extractCell(terminal: TerminalType, row: number, col: number): Cell {
|
|||||||
bold: cell.isBold() !== 0,
|
bold: cell.isBold() !== 0,
|
||||||
italic: cell.isItalic() !== 0,
|
italic: cell.isItalic() !== 0,
|
||||||
underline: cell.isUnderline() !== 0,
|
underline: cell.isUnderline() !== 0,
|
||||||
|
dim: cell.isDim() !== 0,
|
||||||
|
inverse: cell.isInverse() !== 0,
|
||||||
|
strikethrough: cell.isStrikethrough() !== 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractGrid(terminal: TerminalType): Cell[][] {
|
function extractGrid(terminal: TerminalType): TerminalCell[][] {
|
||||||
const grid: Cell[][] = [];
|
const grid: TerminalCell[][] = [];
|
||||||
const buffer = terminal.buffer.active;
|
const buffer = terminal.buffer.active;
|
||||||
// Visible viewport starts at baseY
|
// Visible viewport starts at baseY
|
||||||
const baseY = buffer.baseY;
|
const baseY = buffer.baseY;
|
||||||
|
|
||||||
for (let row = 0; row < terminal.rows; row++) {
|
for (let row = 0; row < terminal.rows; row++) {
|
||||||
const rowCells: Cell[] = [];
|
const rowCells: TerminalCell[] = [];
|
||||||
for (let col = 0; col < terminal.cols; col++) {
|
for (let col = 0; col < terminal.cols; col++) {
|
||||||
rowCells.push(extractCell(terminal, baseY + row, col));
|
rowCells.push(extractCell(terminal, baseY + row, col));
|
||||||
}
|
}
|
||||||
@@ -182,15 +162,15 @@ function extractGrid(terminal: TerminalType): Cell[][] {
|
|||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractScrollback(terminal: TerminalType): Cell[][] {
|
function extractScrollback(terminal: TerminalType): TerminalCell[][] {
|
||||||
const scrollback: Cell[][] = [];
|
const scrollback: TerminalCell[][] = [];
|
||||||
const buffer = terminal.buffer.active;
|
const buffer = terminal.buffer.active;
|
||||||
// baseY is the first row of the visible viewport (0-indexed)
|
// baseY is the first row of the visible viewport (0-indexed)
|
||||||
// Lines 0 to baseY-1 are in scrollback, lines baseY onwards are visible
|
// Lines 0 to baseY-1 are in scrollback, lines baseY onwards are visible
|
||||||
const scrollbackLines = buffer.baseY;
|
const scrollbackLines = buffer.baseY;
|
||||||
|
|
||||||
for (let row = 0; row < scrollbackLines; row++) {
|
for (let row = 0; row < scrollbackLines; row++) {
|
||||||
const rowCells: Cell[] = [];
|
const rowCells: TerminalCell[] = [];
|
||||||
const line = buffer.getLine(row);
|
const line = buffer.getLine(row);
|
||||||
for (let col = 0; col < terminal.cols; col++) {
|
for (let col = 0; col < terminal.cols; col++) {
|
||||||
if (line) {
|
if (line) {
|
||||||
@@ -211,6 +191,9 @@ function extractScrollback(terminal: TerminalType): Cell[][] {
|
|||||||
bold: cell.isBold() !== 0,
|
bold: cell.isBold() !== 0,
|
||||||
italic: cell.isItalic() !== 0,
|
italic: cell.isItalic() !== 0,
|
||||||
underline: cell.isUnderline() !== 0,
|
underline: cell.isUnderline() !== 0,
|
||||||
|
dim: cell.isDim() !== 0,
|
||||||
|
inverse: cell.isInverse() !== 0,
|
||||||
|
strikethrough: cell.isStrikethrough() !== 0,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
rowCells.push({ char: " ", fg: undefined, bg: undefined });
|
rowCells.push({ char: " ", fg: undefined, bg: undefined });
|
||||||
@@ -225,6 +208,28 @@ function extractScrollback(terminal: TerminalType): Cell[][] {
|
|||||||
return scrollback;
|
return scrollback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractCursorState(terminal: TerminalType): TerminalState["cursor"] {
|
||||||
|
const coreService = (terminal as any)._core?.coreService;
|
||||||
|
const cursorStyle = coreService?.decPrivateModes?.cursorStyle;
|
||||||
|
const normalizedCursorStyle =
|
||||||
|
cursorStyle === "block" || cursorStyle === "underline" || cursorStyle === "bar"
|
||||||
|
? cursorStyle
|
||||||
|
: undefined;
|
||||||
|
const cursorBlink =
|
||||||
|
typeof coreService?.decPrivateModes?.cursorBlink === "boolean"
|
||||||
|
? coreService.decPrivateModes.cursorBlink
|
||||||
|
: undefined;
|
||||||
|
const hidden = Boolean(coreService?.isCursorHidden);
|
||||||
|
|
||||||
|
return {
|
||||||
|
row: terminal.buffer.active.cursorY,
|
||||||
|
col: terminal.buffer.active.cursorX,
|
||||||
|
...(hidden ? { hidden: true } : {}),
|
||||||
|
...(normalizedCursorStyle ? { style: normalizedCursorStyle } : {}),
|
||||||
|
...(typeof cursorBlink === "boolean" ? { blink: cursorBlink } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function createTerminal(options: CreateTerminalOptions): Promise<TerminalSession> {
|
export async function createTerminal(options: CreateTerminalOptions): Promise<TerminalSession> {
|
||||||
const {
|
const {
|
||||||
cwd,
|
cwd,
|
||||||
@@ -345,13 +350,9 @@ export async function createTerminal(options: CreateTerminalOptions): Promise<Te
|
|||||||
cols: terminal.cols,
|
cols: terminal.cols,
|
||||||
grid: extractGrid(terminal),
|
grid: extractGrid(terminal),
|
||||||
scrollback: extractScrollback(terminal),
|
scrollback: extractScrollback(terminal),
|
||||||
cursor: {
|
cursor: extractCursorState(terminal),
|
||||||
row: terminal.buffer.active.cursorY,
|
|
||||||
col: terminal.buffer.active.cursorX,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function send(msg: ClientMessage): void {
|
function send(msg: ClientMessage): void {
|
||||||
if (killed) return;
|
if (killed) return;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user