130 lines
3.6 KiB
JavaScript
130 lines
3.6 KiB
JavaScript
const term = new Terminal({
|
|
cursorBlink: true,
|
|
convertEol: true,
|
|
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
|
fontSize: 14,
|
|
allowProposedApi: true,
|
|
theme: {
|
|
background: '#1a1b26',
|
|
foreground: '#a9b1d6',
|
|
cursor: '#a9b1d6',
|
|
selection: 'rgba(169, 177, 214, 0.3)'
|
|
}
|
|
});
|
|
|
|
// Load addons
|
|
const fitAddon = new FitAddon.FitAddon();
|
|
const webglAddon = new WebglAddon.WebglAddon();
|
|
const webLinksAddon = new WebLinksAddon.WebLinksAddon();
|
|
const unicode11Addon = new Unicode11Addon.Unicode11Addon();
|
|
|
|
term.loadAddon(fitAddon);
|
|
term.loadAddon(webLinksAddon);
|
|
term.loadAddon(unicode11Addon);
|
|
term.unicode.activeVersion = '11';
|
|
|
|
term.open(document.getElementById('terminal'));
|
|
|
|
// WebGL addon must load after open()
|
|
try {
|
|
term.loadAddon(webglAddon);
|
|
webglAddon.onContextLoss(() => {
|
|
webglAddon.dispose();
|
|
});
|
|
} catch (e) {
|
|
console.warn('WebGL addon failed, using canvas renderer:', e);
|
|
}
|
|
|
|
fitAddon.fit();
|
|
term.focus();
|
|
|
|
let inputBuffer = '';
|
|
let inputResolver = null;
|
|
|
|
term.onData(data => {
|
|
if (inputResolver) {
|
|
if (data === '\r') {
|
|
term.write('\r\n');
|
|
const result = inputBuffer;
|
|
inputBuffer = '';
|
|
const resolver = inputResolver;
|
|
inputResolver = null;
|
|
resolver(result);
|
|
} else if (data === '\x7f' || data === '\x08') {
|
|
if (inputBuffer.length > 0) {
|
|
inputBuffer = inputBuffer.slice(0, -1);
|
|
term.write('\b \b');
|
|
}
|
|
} else if (data === '\x03') {
|
|
term.write('^C\r\n');
|
|
inputBuffer = '';
|
|
if (inputResolver) {
|
|
inputResolver('');
|
|
inputResolver = null;
|
|
}
|
|
} else if (data >= ' ' && data <= '~') {
|
|
inputBuffer += data;
|
|
term.write(data);
|
|
}
|
|
}
|
|
});
|
|
|
|
const encoder = new TextEncoder();
|
|
const decoder = new TextDecoder();
|
|
|
|
if (!globalThis.fs) {
|
|
globalThis.fs = {};
|
|
}
|
|
|
|
const originalWrite = globalThis.fs.write;
|
|
const originalRead = globalThis.fs.read;
|
|
|
|
globalThis.fs.write = function(fd, buf, offset, length, position, callback) {
|
|
if (fd === 1 || fd === 2) {
|
|
const text = decoder.decode(buf.slice(offset, offset + length));
|
|
term.write(text);
|
|
callback(null, length);
|
|
} else if (originalWrite) {
|
|
originalWrite.call(this, fd, buf, offset, length, position, callback);
|
|
} else {
|
|
callback(new Error('Invalid fd'));
|
|
}
|
|
};
|
|
|
|
globalThis.fs.read = function(fd, buf, offset, length, position, callback) {
|
|
if (fd === 0) {
|
|
const promise = new Promise(resolve => {
|
|
inputResolver = resolve;
|
|
});
|
|
|
|
promise.then(line => {
|
|
const input = encoder.encode(line + '\n');
|
|
const n = Math.min(length, input.length);
|
|
buf.set(input.slice(0, n), offset);
|
|
callback(null, n);
|
|
});
|
|
} else if (originalRead) {
|
|
originalRead.call(this, fd, buf, offset, length, position, callback);
|
|
} else {
|
|
callback(new Error('Invalid fd'));
|
|
}
|
|
};
|
|
|
|
const go = new Go();
|
|
|
|
WebAssembly.instantiateStreaming(fetch('chess-client.wasm'), go.importObject)
|
|
.then(result => {
|
|
go.run(result.instance);
|
|
})
|
|
.catch(err => {
|
|
term.writeln('\r\n\x1b[31mError loading WASM: ' + err + '\x1b[0m');
|
|
console.error('WASM load error:', err);
|
|
});
|
|
|
|
// Resize handling with debounce for performance
|
|
let resizeTimeout;
|
|
const resizeObserver = new ResizeObserver(() => {
|
|
clearTimeout(resizeTimeout);
|
|
resizeTimeout = setTimeout(() => fitAddon.fit(), 16);
|
|
});
|
|
resizeObserver.observe(document.getElementById('terminal')); |