Enhance CLI terminal integration and output handling in InkApp. Introduce terminalService for managing PTY backend, including resizing and updating terminal output. Implement ANSI stripping and tab expansion for accurate line rendering. Improve state management for terminal, logs, and LLM output, ensuring responsive UI updates and error handling during input submission.
This commit is contained in:
123
src/terminalService.js
Normal file
123
src/terminalService.js
Normal file
@@ -0,0 +1,123 @@
|
||||
import EventEmitter from 'events';
|
||||
import pty from 'node-pty';
|
||||
|
||||
class TerminalService extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.lines = [];
|
||||
this.partial = '';
|
||||
this.ptyProcess = null;
|
||||
this.started = false;
|
||||
this.maxLines = 1000;
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.started) return;
|
||||
const shell = process.platform === 'win32' ? 'powershell.exe' : 'bash';
|
||||
const cols = process.stdout && process.stdout.columns ? process.stdout.columns : 120;
|
||||
const rows = process.stdout && process.stdout.rows ? process.stdout.rows : 30;
|
||||
|
||||
const isWindows = process.platform === 'win32';
|
||||
const userShell = process.env.SHELL && !isWindows ? process.env.SHELL : null;
|
||||
const shellPath = userShell || (isWindows ? 'powershell.exe' : '/bin/bash');
|
||||
const args = ['--rcfile','rc'];
|
||||
|
||||
this.ptyProcess = pty.spawn(shellPath, args, {
|
||||
name: 'xterm-256color',
|
||||
cols,
|
||||
rows,
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
TERM: 'xterm-256color',
|
||||
PS1: '> '
|
||||
},
|
||||
});
|
||||
|
||||
this.ptyProcess.onData((data) => {
|
||||
// Normalize line endings
|
||||
const normalized = String(data).replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
||||
const parts = normalized.split('\n');
|
||||
|
||||
// First part joins the existing partial
|
||||
if (parts.length > 0) {
|
||||
this.partial += parts[0];
|
||||
}
|
||||
|
||||
// For each subsequent part before the last, we have completed lines
|
||||
for (let i = 1; i < parts.length; i += 1) {
|
||||
this.lines.push(this.partial);
|
||||
this.partial = parts[i];
|
||||
}
|
||||
|
||||
// Enforce max lines buffer
|
||||
if (this.lines.length > this.maxLines) {
|
||||
this.lines.splice(0, this.lines.length - this.maxLines);
|
||||
}
|
||||
|
||||
// Emit lines including current partial to ensure prompts (no trailing newline) are visible
|
||||
const display = this.partial ? [...this.lines, this.partial] : this.lines.slice();
|
||||
this.emit('update', display);
|
||||
});
|
||||
|
||||
// Resize with the host TTY
|
||||
const onResize = () => {
|
||||
try {
|
||||
const newCols = process.stdout.columns || cols;
|
||||
const newRows = process.stdout.rows || rows;
|
||||
this.ptyProcess.resize(newCols, newRows);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
if (process.stdout && process.stdout.on) {
|
||||
process.stdout.on('resize', onResize);
|
||||
}
|
||||
|
||||
this.ptyProcess.onExit(({ exitCode, signal }) => {
|
||||
this.emit('exit', { exitCode, signal });
|
||||
});
|
||||
|
||||
this.started = true;
|
||||
}
|
||||
|
||||
getLines() {
|
||||
return this.lines.slice();
|
||||
}
|
||||
|
||||
write(input) {
|
||||
if (!this.ptyProcess) return;
|
||||
this.ptyProcess.write(input);
|
||||
}
|
||||
|
||||
resize(columns, rows) {
|
||||
if (!this.ptyProcess) return;
|
||||
try {
|
||||
const cols = Math.max(1, Number(columns) || 1);
|
||||
const r = rows ? Math.max(1, Number(rows) || 1) : undefined;
|
||||
if (r !== undefined) {
|
||||
this.ptyProcess.resize(cols, r);
|
||||
} else {
|
||||
this.ptyProcess.resize(cols, this.ptyProcess.rows || 24);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
try {
|
||||
if (this.ptyProcess) {
|
||||
this.ptyProcess.kill();
|
||||
this.ptyProcess = null;
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const terminalService = new TerminalService();
|
||||
export default terminalService;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user