272 lines
8.2 KiB
JavaScript
272 lines
8.2 KiB
JavaScript
import * as Terminal from '../build/xterm';
|
|
import * as attach from '../build/addons/attach/attach';
|
|
import * as fit from '../build/addons/fit/fit';
|
|
import * as fullscreen from '../build/addons/fullscreen/fullscreen';
|
|
import * as search from '../build/addons/search/search';
|
|
import * as webLinks from '../build/addons/webLinks/webLinks';
|
|
import * as winptyCompat from '../build/addons/winptyCompat/winptyCompat';
|
|
|
|
|
|
Terminal.applyAddon(attach);
|
|
Terminal.applyAddon(fit);
|
|
Terminal.applyAddon(fullscreen);
|
|
Terminal.applyAddon(search);
|
|
Terminal.applyAddon(webLinks);
|
|
Terminal.applyAddon(winptyCompat);
|
|
|
|
|
|
var term,
|
|
protocol,
|
|
socketURL,
|
|
socket,
|
|
pid;
|
|
|
|
var terminalContainer = document.getElementById('terminal-container'),
|
|
actionElements = {
|
|
findNext: document.querySelector('#find-next'),
|
|
findPrevious: document.querySelector('#find-previous')
|
|
},
|
|
paddingElement = document.getElementById('padding');
|
|
|
|
function setPadding() {
|
|
term.element.style.padding = parseInt(paddingElement.value, 10).toString() + 'px';
|
|
term.fit();
|
|
}
|
|
|
|
createTerminal();
|
|
|
|
const disposeRecreateButtonHandler = () => {
|
|
// If the terminal exists dispose of it, otherwise recreate it
|
|
if (term) {
|
|
term.dispose();
|
|
term = null;
|
|
window.term = null;
|
|
socket = null;
|
|
document.getElementById('dispose').innerHTML = 'Recreate Terminal';
|
|
}
|
|
else {
|
|
createTerminal();
|
|
document.getElementById('dispose').innerHTML = 'Dispose terminal';
|
|
}
|
|
};
|
|
|
|
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
|
|
|
|
function createTerminal() {
|
|
// Clean terminal
|
|
while (terminalContainer.children.length) {
|
|
terminalContainer.removeChild(terminalContainer.children[0]);
|
|
}
|
|
term = new Terminal({});
|
|
window.term = term; // Expose `term` to window for debugging purposes
|
|
term.on('resize', function (size) {
|
|
if (!pid) {
|
|
return;
|
|
}
|
|
var cols = size.cols,
|
|
rows = size.rows,
|
|
url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows;
|
|
|
|
fetch(url, {method: 'POST'});
|
|
});
|
|
protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
|
|
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
|
|
|
|
term.open(terminalContainer);
|
|
term.winptyCompatInit();
|
|
term.webLinksInit();
|
|
term.fit();
|
|
term.focus();
|
|
|
|
addDomListener(paddingElement, 'change', setPadding);
|
|
|
|
addDomListener(actionElements.findNext, 'keypress', function (e) {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault();
|
|
term.findNext(actionElements.findNext.value);
|
|
}
|
|
});
|
|
addDomListener(actionElements.findPrevious, 'keypress', function (e) {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault();
|
|
term.findPrevious(actionElements.findPrevious.value);
|
|
}
|
|
});
|
|
|
|
// fit is called within a setTimeout, cols and rows need this.
|
|
setTimeout(function () {
|
|
initOptions(term);
|
|
document.getElementById(`opt-cols`).value = term.cols;
|
|
document.getElementById(`opt-rows`).value = term.rows;
|
|
paddingElement.value = 0;
|
|
|
|
// Set terminal size again to set the specific dimensions on the demo
|
|
updateTerminalSize();
|
|
|
|
fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then(function (res) {
|
|
|
|
res.text().then(function (processId) {
|
|
pid = processId;
|
|
socketURL += processId;
|
|
socket = new WebSocket(socketURL);
|
|
socket.onopen = runRealTerminal;
|
|
socket.onclose = runFakeTerminal;
|
|
socket.onerror = runFakeTerminal;
|
|
});
|
|
});
|
|
}, 0);
|
|
}
|
|
|
|
function runRealTerminal() {
|
|
term.attach(socket);
|
|
term._initialized = true;
|
|
}
|
|
|
|
function runFakeTerminal() {
|
|
if (term._initialized) {
|
|
return;
|
|
}
|
|
|
|
term._initialized = true;
|
|
|
|
var shellprompt = '$ ';
|
|
|
|
term.prompt = function () {
|
|
term.write('\r\n' + shellprompt);
|
|
};
|
|
|
|
term.writeln('Welcome to xterm.js');
|
|
term.writeln('This is a local terminal emulation, without a real terminal in the back-end.');
|
|
term.writeln('Type some keys and commands to play around.');
|
|
term.writeln('');
|
|
term.prompt();
|
|
|
|
term._core.register(term.addDisposableListener('key', function (key, ev) {
|
|
var printable = (
|
|
!ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey
|
|
);
|
|
|
|
if (ev.keyCode == 13) {
|
|
term.prompt();
|
|
} else if (ev.keyCode == 8) {
|
|
// Do not delete the prompt
|
|
if (term.x > 2) {
|
|
term.write('\b \b');
|
|
}
|
|
} else if (printable) {
|
|
term.write(key);
|
|
}
|
|
}));
|
|
|
|
term._core.register(term.addDisposableListener('paste', function (data, ev) {
|
|
term.write(data);
|
|
}));
|
|
}
|
|
|
|
function initOptions(term) {
|
|
var blacklistedOptions = [
|
|
// Internal only options
|
|
'cancelEvents',
|
|
'convertEol',
|
|
'debug',
|
|
'handler',
|
|
'screenKeys',
|
|
'termName',
|
|
'useFlowControl',
|
|
// Complex option
|
|
'theme',
|
|
// Only in constructor
|
|
'rendererType'
|
|
];
|
|
var stringOptions = {
|
|
bellSound: null,
|
|
bellStyle: ['none', 'sound'],
|
|
cursorStyle: ['block', 'underline', 'bar'],
|
|
experimentalCharAtlas: ['none', 'static', 'dynamic'],
|
|
fontFamily: null,
|
|
fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
|
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900']
|
|
};
|
|
var options = Object.keys(term._core.options);
|
|
var booleanOptions = [];
|
|
var numberOptions = [];
|
|
options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => {
|
|
switch (typeof term.getOption(o)) {
|
|
case 'boolean':
|
|
booleanOptions.push(o);
|
|
break;
|
|
case 'number':
|
|
numberOptions.push(o);
|
|
break;
|
|
default:
|
|
if (Object.keys(stringOptions).indexOf(o) === -1) {
|
|
console.warn(`Unrecognized option: "${o}"`);
|
|
}
|
|
}
|
|
});
|
|
|
|
var html = '';
|
|
html += '<div class="option-group">';
|
|
booleanOptions.forEach(o => {
|
|
html += `<div class="option"><label><input id="opt-${o}" type="checkbox" ${term.getOption(o) ? 'checked' : ''}/> ${o}</label></div>`;
|
|
});
|
|
html += '</div><div class="option-group">';
|
|
numberOptions.forEach(o => {
|
|
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}"/></label></div>`;
|
|
});
|
|
html += '</div><div class="option-group">';
|
|
Object.keys(stringOptions).forEach(o => {
|
|
if (stringOptions[o]) {
|
|
html += `<div class="option"><label>${o} <select id="opt-${o}">${stringOptions[o].map(v => `<option ${term.getOption(o) === v ? 'selected' : ''}>${v}</option>`).join('')}</select></label></div>`;
|
|
} else {
|
|
html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${term.getOption(o)}"/></label></div>`
|
|
}
|
|
});
|
|
html += '</div>';
|
|
|
|
var container = document.getElementById('options-container');
|
|
container.innerHTML = html;
|
|
|
|
// Attach listeners
|
|
booleanOptions.forEach(o => {
|
|
var input = document.getElementById(`opt-${o}`);
|
|
addDomListener(input, 'change', () => {
|
|
console.log('change', o, input.checked);
|
|
term.setOption(o, input.checked);
|
|
});
|
|
});
|
|
numberOptions.forEach(o => {
|
|
var input = document.getElementById(`opt-${o}`);
|
|
addDomListener(input, 'change', () => {
|
|
console.log('change', o, input.value);
|
|
if (o === 'cols' || o === 'rows') {
|
|
updateTerminalSize();
|
|
} else {
|
|
term.setOption(o, parseInt(input.value, 10));
|
|
}
|
|
});
|
|
});
|
|
Object.keys(stringOptions).forEach(o => {
|
|
var input = document.getElementById(`opt-${o}`);
|
|
addDomListener(input, 'change', () => {
|
|
console.log('change', o, input.value);
|
|
term.setOption(o, input.value);
|
|
});
|
|
});
|
|
}
|
|
|
|
function addDomListener(element, type, handler) {
|
|
element.addEventListener(type, handler);
|
|
term._core.register({ dispose: () => element.removeEventListener(type, handler) });
|
|
}
|
|
|
|
function updateTerminalSize() {
|
|
var cols = parseInt(document.getElementById(`opt-cols`).value, 10);
|
|
var rows = parseInt(document.getElementById(`opt-rows`).value, 10);
|
|
var width = (cols * term._core.renderer.dimensions.actualCellWidth + term._core.viewport.scrollBarWidth).toString() + 'px';
|
|
var height = (rows * term._core.renderer.dimensions.actualCellHeight).toString() + 'px';
|
|
terminalContainer.style.width = width;
|
|
terminalContainer.style.height = height;
|
|
term.fit();
|
|
}
|