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
34 changes: 34 additions & 0 deletions lib/ghostty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,40 @@ export class GhosttyTerminal {
return null; // TODO: Add hyperlink support
}

/**
* Check if there are pending responses from the terminal.
* Responses are generated by escape sequences like DSR (Device Status Report).
*/
hasResponse(): boolean {
return this.exports.ghostty_terminal_has_response(this.handle);
}

/**
* Read pending responses from the terminal.
* Returns the response string, or null if no responses pending.
*
* Responses are generated by escape sequences that require replies:
* - DSR 6 (cursor position): Returns \x1b[row;colR
* - DSR 5 (operating status): Returns \x1b[0n
*/
readResponse(): string | null {
if (!this.hasResponse()) return null;

const bufSize = 256; // Most responses are small
const bufPtr = this.exports.ghostty_wasm_alloc_u8_array(bufSize);

try {
const bytesRead = this.exports.ghostty_terminal_read_response(this.handle, bufPtr, bufSize);

if (bytesRead <= 0) return null;

const bytes = new Uint8Array(this.memory.buffer, bufPtr, bytesRead);
return new TextDecoder().decode(bytes.slice());
} finally {
this.exports.ghostty_wasm_free_u8_array(bufPtr, bufSize);
}
}

/**
* Query arbitrary terminal mode by number
* @param mode Mode number (e.g., 25 for cursor visibility, 2004 for bracketed paste)
Expand Down
27 changes: 27 additions & 0 deletions lib/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,10 @@ export class Terminal implements ITerminalCore {
// Write directly to WASM terminal (handles VT parsing internally)
this.wasmTerm!.write(data);

// Process any responses generated by the terminal (e.g., DSR cursor position)
// These need to be sent back to the PTY via onData
this.processTerminalResponses();

// Check for bell character (BEL, \x07)
// WASM doesn't expose bell events, so we detect it in the data stream
if (typeof data === 'string' && data.includes('\x07')) {
Expand Down Expand Up @@ -1684,6 +1688,29 @@ export class Terminal implements ITerminalCore {
animate();
}

/**
* Process any pending terminal responses and emit them via onData.
*
* This handles escape sequences that require the terminal to send a response
* back to the PTY, such as:
* - DSR 6 (cursor position): Shell sends \x1b[6n, terminal responds with \x1b[row;colR
* - DSR 5 (operating status): Shell sends \x1b[5n, terminal responds with \x1b[0n
*
* Without this, shells like nushell that rely on cursor position queries
* will hang waiting for a response that never comes.
*/
private processTerminalResponses(): void {
if (!this.wasmTerm) return;

// Read any pending responses from the WASM terminal
const response = this.wasmTerm.readResponse();
if (response) {
// Send response back to the PTY via onData
// This is the same path as user keyboard input
this.dataEmitter.fire(response);
}
}

/**
* Check for title changes in written data (OSC sequences)
* Simplified implementation - looks for OSC 0, 1, 2
Expand Down
4 changes: 4 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,10 @@ export interface GhosttyWasmExports extends WebAssembly.Exports {
bufLen: number
): number; // Returns cells written or -1 on error
ghostty_terminal_is_row_wrapped(terminal: TerminalHandle, row: number): number;

// Response API (for DSR and other terminal queries)
ghostty_terminal_has_response(terminal: TerminalHandle): boolean;
ghostty_terminal_read_response(terminal: TerminalHandle, bufPtr: number, bufLen: number): number; // Returns bytes written, 0 if no response, -1 on error
}

// ============================================================================
Expand Down
Loading