Improve SSH terminal UI and input handling
This commit is contained in:
+53
@@ -0,0 +1,53 @@
|
||||
import { color, crlf } from './terminal.js';
|
||||
|
||||
export function configureTerminal(stream, title) {
|
||||
stream.write('\x1b[?2004h\x1b[?7h\x1b[?25h\x1b[?1h\x1b=');
|
||||
stream.write(`\x1b]2;${String(title).replace(/[\x00-\x1f\x7f]/g, '')}\x07`);
|
||||
}
|
||||
|
||||
export function createTuiSession({ channel, ctx, router, renderHome, prompt, onClose }) {
|
||||
const state = { input: '', cursor: 0, history: [], historyIndex: 0, closed: false };
|
||||
const redrawInput = () => {
|
||||
if (state.closed) return;
|
||||
channel.write(`\r\x1b[2K${prompt(ctx)}${state.input}`);
|
||||
if (state.cursor < state.input.length) channel.write(`\x1b[${state.input.length - state.cursor}D`);
|
||||
};
|
||||
const insert = text => {
|
||||
const clean = text.replace(/\x1b\[20[01]~/g, '').replace(/[\r\n]+/g, ' ').replace(/[\x00-\x1f\x7f-\x9f]/g, '');
|
||||
if (!clean) return; state.input = state.input.slice(0, state.cursor) + clean + state.input.slice(state.cursor); state.cursor += clean.length; redrawInput();
|
||||
};
|
||||
const showHome = () => { if (!state.closed) channel.write(renderHome(ctx) + prompt(ctx) + state.input); };
|
||||
const notify = text => { if (!state.closed) { channel.write(`\r\x1b[2K${crlf(text)}\r\n`); redrawInput(); } };
|
||||
const updateHeader = text => { if (!state.closed) channel.write(`\x1b7\x1b[1;1H\x1b[2K${text}\x1b8`); };
|
||||
const submit = () => {
|
||||
const input = state.input.trim(); channel.write('\r\n');
|
||||
if (input) { if (state.history.at(-1) !== input) state.history.push(input); if (state.history.length > 100) state.history.shift(); }
|
||||
state.historyIndex = state.history.length; state.input = ''; state.cursor = 0;
|
||||
const name = input.split(/\s+/)[0]?.toLowerCase() || 'home';
|
||||
if (name === 'home' || name === 'clear') { router.execute(ctx, input); showHome(); return; }
|
||||
const output = router.execute(ctx, input); if (state.closed || channel.destroyed) return;
|
||||
if (output) channel.write(`${crlf(output)}\r\n`); channel.write(prompt(ctx));
|
||||
};
|
||||
const history = direction => {
|
||||
if (!state.history.length) return;
|
||||
state.historyIndex = Math.max(0, Math.min(state.history.length, state.historyIndex + direction));
|
||||
state.input = state.historyIndex === state.history.length ? '' : state.history[state.historyIndex]; state.cursor = state.input.length; redrawInput();
|
||||
};
|
||||
const move = delta => { const next = Math.max(0, Math.min(state.input.length, state.cursor + delta)); if (next !== state.cursor) { channel.write(delta < 0 ? `\x1b[${state.cursor - next}D` : `\x1b[${next - state.cursor}C`); state.cursor = next; } };
|
||||
channel.on('data', data => {
|
||||
let key = data.toString();
|
||||
if (key.includes('\x1b[200~')) { insert(key); return; }
|
||||
while (key.length) {
|
||||
const sequence = ['\x1b[A', '\x1bOA', '\x1b[B', '\x1bOB', '\x1b[C', '\x1bOC', '\x1b[D', '\x1bOD'].find(item => key.startsWith(item));
|
||||
if (sequence) { if (sequence.endsWith('A')) history(-1); else if (sequence.endsWith('B')) history(1); else if (sequence.endsWith('C')) move(1); else move(-1); key = key.slice(sequence.length); continue; }
|
||||
const char = key[0]; key = key.slice(1);
|
||||
if (char === '\r' || char === '\n') { if (char === '\r' && key[0] === '\n') key = key.slice(1); submit(); }
|
||||
else if (char === '\x03' || (char === '\x04' && !state.input)) channel.end();
|
||||
else if (char === '\x7f' || char === '\b') { if (state.cursor > 0) { state.input = state.input.slice(0, state.cursor - 1) + state.input.slice(state.cursor); state.cursor--; redrawInput(); } }
|
||||
else if (char === '\x01') move(-state.cursor); else if (char === '\x05') move(state.input.length - state.cursor);
|
||||
else if (char >= ' ') insert(char);
|
||||
}
|
||||
});
|
||||
channel.on('close', () => { state.closed = true; onClose?.(); });
|
||||
return { showHome, notify, redrawInput, updateHeader, state };
|
||||
}
|
||||
Reference in New Issue
Block a user