blob: f4d57927ff1711e69a493c5e59b4d22ee942a084 [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* Class used to convert lib.replaceVars-like strings into actual terminal
* escape.
*
* This is roughly analogous to Linux's termcap library.
*
* Instances of this class are able to translate both outgoing strings and
* incoming key sequences.
*/
lib.hterm.Termcap = function() {};
/**
* Replace %<function>(VAR,...) and %(VAR) patterns in the given string, using
* the set of output functions and variables.
*
* Use this for string you intend to write to the terminal. For example,
* the default prompt for wash: '%set-attr(FG_BOLD, FG_CYAN)wash$ %set-attr()'.
*
* See the outputVars and outputFunctions below for the list of valid stuff.
*/
lib.hterm.Termcap.prototype.output = function(str, opt_vars) {
var vars;
if (opt_vars) {
opt_vars.__proto__ = this.outputVars;
vars = opt_vars;
} else {
vars = this.outputVars;
}
return lib.hterm.Termcap.replaceVars_(str, vars, this.outputFunctions);
};
/**
* Replace %<function>(VAR,...) and %(VAR) patterns in the given string, using
* the set of output functions and variables.
*
* Use this to convert mnemonic keystrokes into their byte sequences. For
* example, some default keybindings from lib_wa_readline.js:
*
* '%ctrl("_")': 'undo',
* '%ctrl("/")': 'undo',
*
* '%ctrl(LEFT)': 'backward-word',
* '%ctrl(RIGHT)': 'forward-word',
*
* '%meta(BACKSPACE)': 'backward-kill-word',
* '%meta(DELETE)': 'kill-word',
*
* See the inputVars and inputFunctions below for the list of valid stuff.
*/
lib.hterm.Termcap.prototype.input = function(str, opt_vars) {
var vars;
if (opt_vars) {
opt_vars.__proto__ = this.inputVars;
vars = opt_vars;
} else {
vars = this.inputVars;
}
return lib.hterm.Termcap.replaceVars_(str, vars, this.inputFunctions);
};
/**
* The valid variables for lib.hterm.Termcap..output()
*/
lib.hterm.Termcap.prototype.outputVars = {
'FG_BOLD': '1',
'FG_BLACK': '30',
'FG_RED': '31',
'FG_GREEN': '32',
'FG_YELLOW': '33',
'FG_BLUE': '34',
'FG_MAGENTA': '35',
'FG_CYAN': '36',
'FG_WHITE': '37',
'FG_DEFAULT': '39',
'BG_BLACK': '40',
'BG_RED': '41',
'BG_GREEN': '42',
'BG_YELLOW': '43',
'BG_BLUE': '44',
'BG_MAGENTA': '45',
'BG_CYAN': '46',
'BG_WHITE': '47',
'BG_DEFAULT': '49',
};
/**
* The valid functions for lib.hterm.Termcap..output()
*/
lib.hterm.Termcap.prototype.outputFunctions = {
'crlf': function(str) {
return str.replace(/\n/g, '\r\n');
},
'set-attr': function(/* ... */) {
var args = ['0'];
args.push.apply(args, arguments);
return '\x1b[' + args.join(';') + 'm';
},
'add-attr': function(/* ... */) {
var args = [];
args.push.apply(args, arguments);
return '\x1b[' + args.join(';') + 'm';
},
'insert-blank': function(opt_count) {
return ('\x1b[' + (opt_count || '') + '@');
},
'erase-chars': function(opt_count) {
return ('\x1b[' + (opt_count || '') + 'X');
},
'erase-right': function() {
return ('\x1b[K');
},
'set-row-column': function(row, column) {
return '\x1b[' + row + ';' + column + 'H';
},
'cursor-left': function(opt_count) {
return ('\x1b[' + (opt_count || '') + 'D');
},
'cursor-right': function(opt_count) {
return ('\x1b[' + (opt_count || '') + 'C');
},
'bell': function() {
return ('\x07');
},
'insert-lines': function(opt_count) {
return ('\x1b[' + (opt_count || '') + 'L');
},
'get-row-column': function() {
return ('\x1b[6n');
}
};
/**
* The valid variables for lib.hterm.Termcap..input()
*/
lib.hterm.Termcap.prototype.inputVars = {
'BACKSPACE': '\x7f',
'DELETE': '\x1b[3~',
'DOWN': '\x1b[B',
'END': '\x1b[F',
'ENTER': '\r',
'HOME': '\x1b[H',
'INSERT': '\x1b[2~',
'LEFT': '\x1b[D',
'META': '\x1b',
'PGDN': '\x1b[6~',
'PGUP': '\x1b[5~',
'RIGHT': '\x1b[C',
'UP': '\x1b[A',
};
/**
* The valid functions for lib.hterm.Termcap..input()
*/
lib.hterm.Termcap.prototype.inputFunctions = {
'shift': function(seq) {
if (/\x1b\[/.test(seq))
return lib.hterm.Termcap.modcsi(';2', seq);
if (seq.length == 1)
return seq.toUpperCase();
throw new Error('Invalid ctrl sequence: ' + seq);
},
'meta': function(seq) {
if (/\x1b\[/.test(seq))
return lib.hterm.Termcap.modcsi(';3', seq);
return '\x1b' + seq;
},
'shift-meta': function(seq) {
if (/\x1b\[/.test(seq))
return lib.hterm.Termcap.modcsi(';4', seq);
return '\x1b' + seq.toUpperCase();
},
'ctrl': function(seq) {
if (/\x1b\[/.test(seq))
return lib.hterm.Termcap.modcsi(';5', seq);
if (seq.length == 1)
return String.fromCharCode(seq.toUpperCase().charCodeAt(0) - 64);
throw new Error('Invalid ctrl sequence: ' + seq);
},
'shift-ctrl': function(ch) {
if (/\x1b\[/.test(seq))
return lib.hterm.Termcap.modcsi(';6', seq);
if (seq.length == 1)
return String.fromCharCode(seq.toUpperCase().charCodeAt(0) - 64);
throw new Error('Invalid shift-ctrl sequence: ' + seq);
},
'ctrl-meta': function(seq) {
if (/\x1b\[/.test(seq))
return lib.hterm.Termcap.modcsi(';7', seq);
if (seq.length == 1) {
return '\x1b' + String.fromCharCode(
seq.toUpperCase().charCodeAt(0) - 64);;
}
throw new Error('Invalid ctrl-meta sequence: ' + seq);
},
'shift-ctrl-meta': function(seq) {
if (/\x1b\[/.test(seq))
return lib.hterm.Termcap.modcsi(';8', seq);
if (seq.length == 1) {
return '\x1b' + String.fromCharCode(
seq.toUpperCase().charCodeAt(0) - 64);;
}
throw new Error('Invalid shift-ctrl-meta sequence: ' + seq);
},
};
/**
* Similar to lib.f.replaceVars, but allows for multiple-parameter functions
* and string and integer literals.
*
* TODO(rginda): String literals are brittle. We only check that they start
* and end with double-quotes. Comma-splitting is also brittle, and strings
* containing commas will cause trouble.
*/
lib.hterm.Termcap.replaceVars_ = function(str, vars, functions) {
var resolve = function(param, source) {
if ((/^-?\d+$/.test(param)))
return param;
if ((/^\".*\"$/.test(param))) {
return param.slice(1, -1);
}
if (typeof vars[param] == 'undefined') {
throw new Error('Unknown variable: ' + source + ': ' + param);
}
return vars[param];
}
var doReplace = function(match, fn, paramstr) {
if (!fn && !paramstr)
return '%()';
if (paramstr) {
var ary = paramstr.split(/\s*,\s*/);
for (var i = 0; i < ary.length; ++i) {
ary[i] = resolve(ary[i], '%' + fn + '(' + paramstr + ')');
}
}
if (fn) {
if (!(fn in functions))
throw new Error('Unknown escape function: ' + fn);
return functions[fn].apply(null, ary);
}
if (ary.length != 1)
throw new Error('Expected single argument, got: ' + paramstr);
return ary[0];
};
return str.replace(/%([a-z0-9+\-_]*)\(([^\)]*)\)/gi, doReplace);
};
lib.hterm.Termcap.modcsi = function(mod, seq) {
if (seq.length == 3) {
// Some of the CSI sequences have zero parameters unless modified.
return '\x1b[1' + mod + seq.substr(2, 1);
}
// Others always have at least one parameter.
return seq = seq.substr(0, seq.length - 1) + mod + seq.substr(seq.length - 1);
};