refactor: split widgets up. fixes #133.
This commit is contained in:
parent
99f9d622e6
commit
a4d56fb819
|
@ -0,0 +1,151 @@
|
||||||
|
/**
|
||||||
|
* helpers.js - helpers for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
var unicode = require('./unicode');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = exports;
|
||||||
|
|
||||||
|
helpers.merge = function(a, b) {
|
||||||
|
Object.keys(b).forEach(function(key) {
|
||||||
|
a[key] = b[key];
|
||||||
|
});
|
||||||
|
return a;
|
||||||
|
};
|
||||||
|
|
||||||
|
helpers.asort = function(obj) {
|
||||||
|
return obj.sort(function(a, b) {
|
||||||
|
a = a.name.toLowerCase();
|
||||||
|
b = b.name.toLowerCase();
|
||||||
|
|
||||||
|
if (a[0] === '.' && b[0] === '.') {
|
||||||
|
a = a[1];
|
||||||
|
b = b[1];
|
||||||
|
} else {
|
||||||
|
a = a[0];
|
||||||
|
b = b[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return a > b ? 1 : (a < b ? -1 : 0);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
helpers.hsort = function(obj) {
|
||||||
|
return obj.sort(function(a, b) {
|
||||||
|
return b.index - a.index;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
helpers.findFile = function(start, target) {
|
||||||
|
return (function read(dir) {
|
||||||
|
var files, file, stat, out;
|
||||||
|
|
||||||
|
if (dir === '/dev' || dir === '/sys'
|
||||||
|
|| dir === '/proc' || dir === '/net') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
files = fs.readdirSync(dir);
|
||||||
|
} catch (e) {
|
||||||
|
files = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < files.length; i++) {
|
||||||
|
file = files[i];
|
||||||
|
|
||||||
|
if (file === target) {
|
||||||
|
return (dir === '/' ? '' : dir) + '/' + file;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
stat = fs.lstatSync((dir === '/' ? '' : dir) + '/' + file);
|
||||||
|
} catch (e) {
|
||||||
|
stat = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stat && stat.isDirectory() && !stat.isSymbolicLink()) {
|
||||||
|
out = read((dir === '/' ? '' : dir) + '/' + file);
|
||||||
|
if (out) return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})(start);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Escape text for tag-enabled elements.
|
||||||
|
helpers.escape = function(text) {
|
||||||
|
return text.replace(/[{}]/g, function(ch) {
|
||||||
|
return ch === '{' ? '{open}' : '{close}';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
helpers.parseTags = function(text) {
|
||||||
|
return Element.prototype._parseTags.call(
|
||||||
|
{ parseTags: true, screen: Screen.global }, text);
|
||||||
|
};
|
||||||
|
|
||||||
|
helpers.generateTags = function(style, text) {
|
||||||
|
var open = ''
|
||||||
|
, close = '';
|
||||||
|
|
||||||
|
Object.keys(style || {}).forEach(function(key) {
|
||||||
|
var val = style[key];
|
||||||
|
if (typeof val === 'string') {
|
||||||
|
val = val.replace(/^light(?!-)/, 'light-');
|
||||||
|
val = val.replace(/^bright(?!-)/, 'bright-');
|
||||||
|
open = '{' + val + '-' + key + '}' + open;
|
||||||
|
close += '{/' + val + '-' + key + '}';
|
||||||
|
} else {
|
||||||
|
if (val === true) {
|
||||||
|
open = '{' + key + '}' + open;
|
||||||
|
close += '{/' + key + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (text != null) {
|
||||||
|
return open + text + close;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
open: open,
|
||||||
|
close: close
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
helpers.attrToBinary = function(style, element) {
|
||||||
|
return Element.prototype.sattr.call(element || {}, style);
|
||||||
|
};
|
||||||
|
|
||||||
|
helpers.stripTags = function(text) {
|
||||||
|
if (!text) return '';
|
||||||
|
return text
|
||||||
|
.replace(/{(\/?)([\w\-,;!#]*)}/g, '')
|
||||||
|
.replace(/\x1b\[[\d;]*m/g, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
helpers.cleanTags = function(text) {
|
||||||
|
return helpers.stripTags(text).trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
helpers.dropUnicode = function(text) {
|
||||||
|
if (!text) return '';
|
||||||
|
return text
|
||||||
|
.replace(unicode.chars.all, '??')
|
||||||
|
.replace(unicode.chars.combining, '')
|
||||||
|
.replace(unicode.chars.surrogate, '?');
|
||||||
|
};
|
9731
lib/widget.js
9731
lib/widget.js
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* box.js - box element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Element = require('./element');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Box
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Box(options) {
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Box(options);
|
||||||
|
}
|
||||||
|
options = options || {};
|
||||||
|
Element.call(this, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
Box.prototype.__proto__ = Element.prototype;
|
||||||
|
|
||||||
|
Box.prototype.type = 'box';
|
||||||
|
|
||||||
|
module.exports = Box;
|
|
@ -0,0 +1,60 @@
|
||||||
|
/**
|
||||||
|
* button.js - button element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Input = require('./input');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Button(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Button(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
if (options.autoFocus == null) {
|
||||||
|
options.autoFocus = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Input.call(this, options);
|
||||||
|
|
||||||
|
this.on('keypress', function(ch, key) {
|
||||||
|
if (key.name === 'enter' || key.name === 'space') {
|
||||||
|
return self.press();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.options.mouse) {
|
||||||
|
this.on('click', function() {
|
||||||
|
return self.press();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button.prototype.__proto__ = Input.prototype;
|
||||||
|
|
||||||
|
Button.prototype.type = 'button';
|
||||||
|
|
||||||
|
Button.prototype.press = function() {
|
||||||
|
this.focus();
|
||||||
|
this.value = true;
|
||||||
|
var result = this.emit('press');
|
||||||
|
delete this.value;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Button;
|
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* checkbox.js - checkbox element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Input = require('./input');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checkbox
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Checkbox(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Checkbox(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
Input.call(this, options);
|
||||||
|
|
||||||
|
this.text = options.content || options.text || '';
|
||||||
|
this.checked = this.value = options.checked || false;
|
||||||
|
|
||||||
|
this.on('keypress', function(ch, key) {
|
||||||
|
if (key.name === 'enter' || key.name === 'space') {
|
||||||
|
self.toggle();
|
||||||
|
self.screen.render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.mouse) {
|
||||||
|
this.on('click', function() {
|
||||||
|
self.toggle();
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.on('focus', function(old) {
|
||||||
|
var lpos = self.lpos;
|
||||||
|
if (!lpos) return;
|
||||||
|
self.screen.program.lsaveCursor('checkbox');
|
||||||
|
self.screen.program.cup(lpos.yi, lpos.xi + 1);
|
||||||
|
self.screen.program.showCursor();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('blur', function() {
|
||||||
|
self.screen.program.lrestoreCursor('checkbox', true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Checkbox.prototype.__proto__ = Input.prototype;
|
||||||
|
|
||||||
|
Checkbox.prototype.type = 'checkbox';
|
||||||
|
|
||||||
|
Checkbox.prototype.render = function() {
|
||||||
|
this.clearPos(true);
|
||||||
|
this.setContent('[' + (this.checked ? 'x' : ' ') + '] ' + this.text, true);
|
||||||
|
return this._render();
|
||||||
|
};
|
||||||
|
|
||||||
|
Checkbox.prototype.check = function() {
|
||||||
|
if (this.checked) return;
|
||||||
|
this.checked = this.value = true;
|
||||||
|
this.emit('check');
|
||||||
|
};
|
||||||
|
|
||||||
|
Checkbox.prototype.uncheck = function() {
|
||||||
|
if (!this.checked) return;
|
||||||
|
this.checked = this.value = false;
|
||||||
|
this.emit('uncheck');
|
||||||
|
};
|
||||||
|
|
||||||
|
Checkbox.prototype.toggle = function() {
|
||||||
|
return this.checked
|
||||||
|
? this.uncheck()
|
||||||
|
: this.check();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Checkbox;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,208 @@
|
||||||
|
/**
|
||||||
|
* filemanager.js - file manager element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var path = require('path')
|
||||||
|
, fs = require('fs');
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var List = require('./list');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FileManager
|
||||||
|
*/
|
||||||
|
|
||||||
|
function FileManager(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new FileManager(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
options.parseTags = true;
|
||||||
|
// options.label = ' {blue-fg}%path{/blue-fg} ';
|
||||||
|
|
||||||
|
List.call(this, options);
|
||||||
|
|
||||||
|
this.cwd = options.cwd || process.cwd();
|
||||||
|
this.file = this.cwd;
|
||||||
|
this.value = this.cwd;
|
||||||
|
|
||||||
|
if (options.label && ~options.label.indexOf('%path')) {
|
||||||
|
this._label.setContent(options.label.replace('%path', this.cwd));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.on('select', function(item) {
|
||||||
|
var value = item.content.replace(/\{[^{}]+\}/g, '').replace(/@$/, '')
|
||||||
|
, file = path.resolve(self.cwd, value);
|
||||||
|
|
||||||
|
return fs.stat(file, function(err, stat) {
|
||||||
|
if (err) {
|
||||||
|
return self.emit('error', err, file);
|
||||||
|
}
|
||||||
|
self.file = file;
|
||||||
|
self.value = file;
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
self.emit('cd', file, self.cwd);
|
||||||
|
self.cwd = file;
|
||||||
|
if (options.label && ~options.label.indexOf('%path')) {
|
||||||
|
self._label.setContent(options.label.replace('%path', file));
|
||||||
|
}
|
||||||
|
self.refresh();
|
||||||
|
} else {
|
||||||
|
self.emit('file', file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
FileManager.prototype.__proto__ = List.prototype;
|
||||||
|
|
||||||
|
FileManager.prototype.type = 'file-manager';
|
||||||
|
|
||||||
|
FileManager.prototype.refresh = function(cwd, callback) {
|
||||||
|
if (!callback) {
|
||||||
|
callback = cwd;
|
||||||
|
cwd = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (cwd) this.cwd = cwd;
|
||||||
|
else cwd = this.cwd;
|
||||||
|
|
||||||
|
return fs.readdir(cwd, function(err, list) {
|
||||||
|
if (err && err.code === 'ENOENT') {
|
||||||
|
self.cwd = cwd !== process.env.HOME
|
||||||
|
? process.env.HOME
|
||||||
|
: '/';
|
||||||
|
return self.refresh(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
if (callback) return callback(err);
|
||||||
|
return self.emit('error', err, cwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirs = []
|
||||||
|
, files = [];
|
||||||
|
|
||||||
|
list.unshift('..');
|
||||||
|
|
||||||
|
list.forEach(function(name) {
|
||||||
|
var f = path.resolve(cwd, name)
|
||||||
|
, stat;
|
||||||
|
|
||||||
|
try {
|
||||||
|
stat = fs.lstatSync(f);
|
||||||
|
} catch (e) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((stat && stat.isDirectory()) || name === '..') {
|
||||||
|
dirs.push({
|
||||||
|
name: name,
|
||||||
|
text: '{light-blue-fg}' + name + '{/light-blue-fg}/',
|
||||||
|
dir: true
|
||||||
|
});
|
||||||
|
} else if (stat && stat.isSymbolicLink()) {
|
||||||
|
files.push({
|
||||||
|
name: name,
|
||||||
|
text: '{light-cyan-fg}' + name + '{/light-cyan-fg}@',
|
||||||
|
dir: false
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
files.push({
|
||||||
|
name: name,
|
||||||
|
text: name,
|
||||||
|
dir: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dirs = helpers.asort(dirs);
|
||||||
|
files = helpers.asort(files);
|
||||||
|
|
||||||
|
list = dirs.concat(files).map(function(data) {
|
||||||
|
return data.text;
|
||||||
|
});
|
||||||
|
|
||||||
|
self.setItems(list);
|
||||||
|
self.select(0);
|
||||||
|
self.screen.render();
|
||||||
|
|
||||||
|
self.emit('refresh');
|
||||||
|
|
||||||
|
if (callback) callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
FileManager.prototype.pick = function(cwd, callback) {
|
||||||
|
if (!callback) {
|
||||||
|
callback = cwd;
|
||||||
|
cwd = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this
|
||||||
|
, focused = this.screen.focused === this
|
||||||
|
, hidden = this.hidden
|
||||||
|
, onfile
|
||||||
|
, oncancel;
|
||||||
|
|
||||||
|
function resume() {
|
||||||
|
self.removeListener('file', onfile);
|
||||||
|
self.removeListener('cancel', oncancel);
|
||||||
|
if (hidden) {
|
||||||
|
self.hide();
|
||||||
|
}
|
||||||
|
if (!focused) {
|
||||||
|
self.screen.restoreFocus();
|
||||||
|
}
|
||||||
|
self.screen.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.on('file', onfile = function(file) {
|
||||||
|
resume();
|
||||||
|
return callback(null, file);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('cancel', oncancel = function() {
|
||||||
|
resume();
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.refresh(cwd, function(err) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
|
||||||
|
if (hidden) {
|
||||||
|
self.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!focused) {
|
||||||
|
self.screen.saveFocus();
|
||||||
|
self.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
FileManager.prototype.reset = function(cwd, callback) {
|
||||||
|
if (!callback) {
|
||||||
|
callback = cwd;
|
||||||
|
cwd = null;
|
||||||
|
}
|
||||||
|
this.cwd = cwd || this.options.cwd;
|
||||||
|
this.refresh(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = FileManager;
|
|
@ -0,0 +1,266 @@
|
||||||
|
/**
|
||||||
|
* form.js - form element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Form(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Form(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
options.ignoreKeys = true;
|
||||||
|
Box.call(this, options);
|
||||||
|
|
||||||
|
if (options.keys) {
|
||||||
|
this.screen._listenKeys(this);
|
||||||
|
this.on('element keypress', function(el, ch, key) {
|
||||||
|
if ((key.name === 'tab' && !key.shift)
|
||||||
|
|| (el.type === 'textbox' && options.autoNext && key.name === 'enter')
|
||||||
|
|| key.name === 'down'
|
||||||
|
|| (options.vi && key.name === 'j')) {
|
||||||
|
if (el.type === 'textbox' || el.type === 'textarea') {
|
||||||
|
if (key.name === 'j') return;
|
||||||
|
if (key.name === 'tab') {
|
||||||
|
// Workaround, since we can't stop the tab from being added.
|
||||||
|
el.emit('keypress', null, { name: 'backspace' });
|
||||||
|
}
|
||||||
|
el.emit('keypress', '\x1b', { name: 'escape' });
|
||||||
|
}
|
||||||
|
self.focusNext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((key.name === 'tab' && key.shift)
|
||||||
|
|| key.name === 'up'
|
||||||
|
|| (options.vi && key.name === 'k')) {
|
||||||
|
if (el.type === 'textbox' || el.type === 'textarea') {
|
||||||
|
if (key.name === 'k') return;
|
||||||
|
el.emit('keypress', '\x1b', { name: 'escape' });
|
||||||
|
}
|
||||||
|
self.focusPrevious();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key.name === 'escape') {
|
||||||
|
self.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Form.prototype.__proto__ = Box.prototype;
|
||||||
|
|
||||||
|
Form.prototype.type = 'form';
|
||||||
|
|
||||||
|
Form.prototype._refresh = function() {
|
||||||
|
// XXX Possibly remove this if statement and refresh on every focus.
|
||||||
|
// Also potentially only include *visible* focusable elements.
|
||||||
|
// This would remove the need to check for _selected.visible in previous()
|
||||||
|
// and next().
|
||||||
|
if (!this._children) {
|
||||||
|
var out = [];
|
||||||
|
|
||||||
|
this.children.forEach(function fn(el) {
|
||||||
|
if (el.keyable) out.push(el);
|
||||||
|
el.children.forEach(fn);
|
||||||
|
});
|
||||||
|
|
||||||
|
this._children = out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Form.prototype._visible = function() {
|
||||||
|
return !!this._children.filter(function(el) {
|
||||||
|
return el.visible;
|
||||||
|
}).length;
|
||||||
|
};
|
||||||
|
|
||||||
|
Form.prototype.next = function() {
|
||||||
|
this._refresh();
|
||||||
|
|
||||||
|
if (!this._visible()) return;
|
||||||
|
|
||||||
|
if (!this._selected) {
|
||||||
|
this._selected = this._children[0];
|
||||||
|
if (!this._selected.visible) return this.next();
|
||||||
|
if (this.screen.focused !== this._selected) return this._selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = this._children.indexOf(this._selected);
|
||||||
|
if (!~i || !this._children[i + 1]) {
|
||||||
|
this._selected = this._children[0];
|
||||||
|
if (!this._selected.visible) return this.next();
|
||||||
|
return this._selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._selected = this._children[i + 1];
|
||||||
|
if (!this._selected.visible) return this.next();
|
||||||
|
return this._selected;
|
||||||
|
};
|
||||||
|
|
||||||
|
Form.prototype.previous = function() {
|
||||||
|
this._refresh();
|
||||||
|
|
||||||
|
if (!this._visible()) return;
|
||||||
|
|
||||||
|
if (!this._selected) {
|
||||||
|
this._selected = this._children[this._children.length - 1];
|
||||||
|
if (!this._selected.visible) return this.previous();
|
||||||
|
if (this.screen.focused !== this._selected) return this._selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = this._children.indexOf(this._selected);
|
||||||
|
if (!~i || !this._children[i - 1]) {
|
||||||
|
this._selected = this._children[this._children.length - 1];
|
||||||
|
if (!this._selected.visible) return this.previous();
|
||||||
|
return this._selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._selected = this._children[i - 1];
|
||||||
|
if (!this._selected.visible) return this.previous();
|
||||||
|
return this._selected;
|
||||||
|
};
|
||||||
|
|
||||||
|
Form.prototype.focusNext = function() {
|
||||||
|
var next = this.next();
|
||||||
|
if (next) next.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
Form.prototype.focusPrevious = function() {
|
||||||
|
var previous = this.previous();
|
||||||
|
if (previous) previous.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
Form.prototype.resetSelected = function() {
|
||||||
|
this._selected = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
Form.prototype.focusFirst = function() {
|
||||||
|
this.resetSelected();
|
||||||
|
this.focusNext();
|
||||||
|
};
|
||||||
|
|
||||||
|
Form.prototype.focusLast = function() {
|
||||||
|
this.resetSelected();
|
||||||
|
this.focusPrevious();
|
||||||
|
};
|
||||||
|
|
||||||
|
Form.prototype.submit = function() {
|
||||||
|
var self = this
|
||||||
|
, out = {};
|
||||||
|
|
||||||
|
this.children.forEach(function fn(el) {
|
||||||
|
if (el.value != null) {
|
||||||
|
var name = el.name || el.type;
|
||||||
|
if (Array.isArray(out[name])) {
|
||||||
|
out[name].push(el.value);
|
||||||
|
} else if (out[name]) {
|
||||||
|
out[name] = [out[name], el.value];
|
||||||
|
} else {
|
||||||
|
out[name] = el.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
el.children.forEach(fn);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.emit('submit', out);
|
||||||
|
|
||||||
|
return this.submission = out;
|
||||||
|
};
|
||||||
|
|
||||||
|
Form.prototype.cancel = function() {
|
||||||
|
this.emit('cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
Form.prototype.reset = function() {
|
||||||
|
this.children.forEach(function fn(el) {
|
||||||
|
switch (el.type) {
|
||||||
|
case 'screen':
|
||||||
|
break;
|
||||||
|
case 'box':
|
||||||
|
break;
|
||||||
|
case 'text':
|
||||||
|
break;
|
||||||
|
case 'line':
|
||||||
|
break;
|
||||||
|
case 'scrollable-box':
|
||||||
|
break;
|
||||||
|
case 'list':
|
||||||
|
el.select(0);
|
||||||
|
return;
|
||||||
|
case 'form':
|
||||||
|
break;
|
||||||
|
case 'input':
|
||||||
|
break;
|
||||||
|
case 'textbox':
|
||||||
|
el.clearInput();
|
||||||
|
return;
|
||||||
|
case 'textarea':
|
||||||
|
el.clearInput();
|
||||||
|
return;
|
||||||
|
case 'button':
|
||||||
|
delete el.value;
|
||||||
|
break;
|
||||||
|
case 'progress-bar':
|
||||||
|
el.setProgress(0);
|
||||||
|
break;
|
||||||
|
case 'file-manager':
|
||||||
|
el.refresh(el.options.cwd);
|
||||||
|
return;
|
||||||
|
case 'checkbox':
|
||||||
|
el.uncheck();
|
||||||
|
return;
|
||||||
|
case 'radio-set':
|
||||||
|
break;
|
||||||
|
case 'radio-button':
|
||||||
|
el.uncheck();
|
||||||
|
return;
|
||||||
|
case 'prompt':
|
||||||
|
break;
|
||||||
|
case 'question':
|
||||||
|
break;
|
||||||
|
case 'message':
|
||||||
|
break;
|
||||||
|
case 'info':
|
||||||
|
break;
|
||||||
|
case 'loading':
|
||||||
|
break;
|
||||||
|
case 'list-bar':
|
||||||
|
//el.select(0);
|
||||||
|
break;
|
||||||
|
case 'dir-manager':
|
||||||
|
el.refresh(el.options.cwd);
|
||||||
|
return;
|
||||||
|
case 'terminal':
|
||||||
|
el.write('');
|
||||||
|
return;
|
||||||
|
case 'image':
|
||||||
|
//el.clearImage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
el.children.forEach(fn);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.emit('reset');
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Form;
|
|
@ -0,0 +1,721 @@
|
||||||
|
/**
|
||||||
|
* image.js - w3m image element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var fs = require('fs')
|
||||||
|
, cp = require('child_process');
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image
|
||||||
|
* Good example of w3mimgdisplay commands:
|
||||||
|
* https://github.com/hut/ranger/blob/master/ranger/ext/img_display.py
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Image(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Image(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
Box.call(this, options);
|
||||||
|
|
||||||
|
if (options.w3m) {
|
||||||
|
Image.w3mdisplay = options.w3m;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Image.hasW3MDisplay == null) {
|
||||||
|
if (fs.existsSync(Image.w3mdisplay)) {
|
||||||
|
Image.hasW3MDisplay = true;
|
||||||
|
} else if (options.search !== false) {
|
||||||
|
var file = helpers.findFile('/usr', 'w3mimgdisplay')
|
||||||
|
|| helpers.findFile('/lib', 'w3mimgdisplay')
|
||||||
|
|| helpers.findFile('/bin', 'w3mimgdisplay');
|
||||||
|
if (file) {
|
||||||
|
Image.hasW3MDisplay = true;
|
||||||
|
Image.w3mdisplay = file;
|
||||||
|
} else {
|
||||||
|
Image.hasW3MDisplay = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.on('hide', function() {
|
||||||
|
self._lastFile = self.file;
|
||||||
|
self.clearImage();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('show', function() {
|
||||||
|
if (!self._lastFile) return;
|
||||||
|
self.setImage(self._lastFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('detach', function() {
|
||||||
|
self._lastFile = self.file;
|
||||||
|
self.clearImage();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('attach', function() {
|
||||||
|
if (!self._lastFile) return;
|
||||||
|
self.setImage(self._lastFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.onScreenEvent('resize', function() {
|
||||||
|
self._needsRatio = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get images to overlap properly. Maybe not worth it:
|
||||||
|
// this.onScreenEvent('render', function() {
|
||||||
|
// self.screen.program.flush();
|
||||||
|
// if (!self._noImage) return;
|
||||||
|
// function display(el, next) {
|
||||||
|
// if (el.type === 'image' && el.file) {
|
||||||
|
// el.setImage(el.file, next);
|
||||||
|
// } else {
|
||||||
|
// next();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// function done(el) {
|
||||||
|
// el.children.forEach(recurse);
|
||||||
|
// }
|
||||||
|
// function recurse(el) {
|
||||||
|
// display(el, function() {
|
||||||
|
// var pending = el.children.length;
|
||||||
|
// el.children.forEach(function(el) {
|
||||||
|
// display(el, function() {
|
||||||
|
// if (!--pending) done(el);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// recurse(self.screen);
|
||||||
|
// });
|
||||||
|
|
||||||
|
this.onScreenEvent('render', function() {
|
||||||
|
self.screen.program.flush();
|
||||||
|
if (!self._noImage) {
|
||||||
|
self.setImage(self.file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.options.file || this.options.img) {
|
||||||
|
this.setImage(this.options.file || this.options.img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image.prototype.__proto__ = Box.prototype;
|
||||||
|
|
||||||
|
Image.prototype.type = 'image';
|
||||||
|
|
||||||
|
Image.w3mdisplay = '/usr/lib/w3m/w3mimgdisplay';
|
||||||
|
|
||||||
|
Image.prototype.spawn = function(file, args, opt, callback) {
|
||||||
|
var screen = this.screen
|
||||||
|
, opt = opt || {}
|
||||||
|
, spawn = require('child_process').spawn
|
||||||
|
, ps;
|
||||||
|
|
||||||
|
ps = spawn(file, args, opt);
|
||||||
|
|
||||||
|
ps.on('error', function(err) {
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
ps.on('exit', function(code) {
|
||||||
|
if (!callback) return;
|
||||||
|
if (code !== 0) return callback(new Error('Exit Code: ' + code));
|
||||||
|
return callback(null, code === 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
return ps;
|
||||||
|
};
|
||||||
|
|
||||||
|
Image.prototype.setImage = function(img, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (this._settingImage) {
|
||||||
|
this._queue = this._queue || [];
|
||||||
|
this._queue.push([img, callback]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._settingImage = true;
|
||||||
|
|
||||||
|
var reset = function(err, success) {
|
||||||
|
self._settingImage = false;
|
||||||
|
self._queue = self._queue || [];
|
||||||
|
var item = self._queue.shift();
|
||||||
|
if (item) {
|
||||||
|
self.setImage(item[0], item[1]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Image.hasW3MDisplay === false) {
|
||||||
|
reset();
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(new Error('W3M Image Display not available.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!img) {
|
||||||
|
reset();
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(new Error('No image.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.file = img;
|
||||||
|
|
||||||
|
return this.getPixelRatio(function(err, ratio) {
|
||||||
|
if (err) {
|
||||||
|
reset();
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.renderImage(img, ratio, function(err, success) {
|
||||||
|
if (err) {
|
||||||
|
reset();
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.shrink || self.options.autofit) {
|
||||||
|
delete self.shrink;
|
||||||
|
delete self.options.shrink;
|
||||||
|
self.options.autofit = true;
|
||||||
|
return self.imageSize(function(err, size) {
|
||||||
|
if (err) {
|
||||||
|
reset();
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self._lastSize
|
||||||
|
&& ratio.tw === self._lastSize.tw
|
||||||
|
&& ratio.th === self._lastSize.th
|
||||||
|
&& size.width === self._lastSize.width
|
||||||
|
&& size.height === self._lastSize.height
|
||||||
|
&& self.aleft === self._lastSize.aleft
|
||||||
|
&& self.atop === self._lastSize.atop) {
|
||||||
|
reset();
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(null, success);
|
||||||
|
}
|
||||||
|
|
||||||
|
self._lastSize = {
|
||||||
|
tw: ratio.tw,
|
||||||
|
th: ratio.th,
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
aleft: self.aleft,
|
||||||
|
atop: self.atop
|
||||||
|
};
|
||||||
|
|
||||||
|
self.position.width = size.width / ratio.tw | 0;
|
||||||
|
self.position.height = size.height / ratio.th | 0;
|
||||||
|
|
||||||
|
self._noImage = true;
|
||||||
|
self.screen.render();
|
||||||
|
self._noImage = false;
|
||||||
|
|
||||||
|
reset();
|
||||||
|
return self.renderImage(img, ratio, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reset();
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(null, success);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Image.prototype.renderImage = function(img, ratio, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (cp.execSync) {
|
||||||
|
callback = callback || function(err, result) { return result; };
|
||||||
|
try {
|
||||||
|
return callback(null, this.renderImageSync(img, ratio));
|
||||||
|
} catch (e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Image.hasW3MDisplay === false) {
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(new Error('W3M Image Display not available.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ratio) {
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(new Error('No ratio.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearImage unsets these:
|
||||||
|
var _file = self.file;
|
||||||
|
var _lastSize = self._lastSize;
|
||||||
|
return self.clearImage(function(err) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
|
||||||
|
self.file = _file;
|
||||||
|
self._lastSize = _lastSize;
|
||||||
|
|
||||||
|
var opt = {
|
||||||
|
stdio: 'pipe',
|
||||||
|
env: process.env,
|
||||||
|
cwd: process.env.HOME
|
||||||
|
};
|
||||||
|
|
||||||
|
var ps = self.spawn(Image.w3mdisplay, [], opt, function(err, success) {
|
||||||
|
if (!callback) return;
|
||||||
|
return err
|
||||||
|
? callback(err)
|
||||||
|
: callback(null, success);
|
||||||
|
});
|
||||||
|
|
||||||
|
var width = self.width * ratio.tw | 0
|
||||||
|
, height = self.height * ratio.th | 0
|
||||||
|
, aleft = self.aleft * ratio.tw | 0
|
||||||
|
, atop = self.atop * ratio.th | 0;
|
||||||
|
|
||||||
|
var input = '0;1;'
|
||||||
|
+ aleft + ';'
|
||||||
|
+ atop + ';'
|
||||||
|
+ width + ';'
|
||||||
|
+ height + ';;;;;'
|
||||||
|
+ img
|
||||||
|
+ '\n4;\n3;\n';
|
||||||
|
|
||||||
|
self._props = {
|
||||||
|
aleft: aleft,
|
||||||
|
atop: atop,
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
|
};
|
||||||
|
|
||||||
|
ps.stdin.write(input);
|
||||||
|
ps.stdin.end();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Image.prototype.clearImage = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (cp.execSync) {
|
||||||
|
callback = callback || function(err, result) { return result; };
|
||||||
|
try {
|
||||||
|
return callback(null, this.clearImageSync());
|
||||||
|
} catch (e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Image.hasW3MDisplay === false) {
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(new Error('W3M Image Display not available.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._props) {
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var opt = {
|
||||||
|
stdio: 'pipe',
|
||||||
|
env: process.env,
|
||||||
|
cwd: process.env.HOME
|
||||||
|
};
|
||||||
|
|
||||||
|
var ps = this.spawn(Image.w3mdisplay, [], opt, function(err, success) {
|
||||||
|
if (!callback) return;
|
||||||
|
return err
|
||||||
|
? callback(err)
|
||||||
|
: callback(null, success);
|
||||||
|
});
|
||||||
|
|
||||||
|
var width = this._props.width + 2
|
||||||
|
, height = this._props.height + 2
|
||||||
|
, aleft = this._props.aleft
|
||||||
|
, atop = this._props.atop;
|
||||||
|
|
||||||
|
if (this._drag) {
|
||||||
|
aleft -= 10;
|
||||||
|
atop -= 10;
|
||||||
|
width += 10;
|
||||||
|
height += 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
var input = '6;'
|
||||||
|
+ aleft + ';'
|
||||||
|
+ atop + ';'
|
||||||
|
+ width + ';'
|
||||||
|
+ height
|
||||||
|
+ '\n4;\n3;\n';
|
||||||
|
|
||||||
|
delete this.file;
|
||||||
|
delete this._props;
|
||||||
|
delete this._lastSize;
|
||||||
|
|
||||||
|
ps.stdin.write(input);
|
||||||
|
ps.stdin.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
Image.prototype.imageSize = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
var img = this.file;
|
||||||
|
|
||||||
|
if (cp.execSync) {
|
||||||
|
callback = callback || function(err, result) { return result; };
|
||||||
|
try {
|
||||||
|
return callback(null, this.imageSizeSync());
|
||||||
|
} catch (e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Image.hasW3MDisplay === false) {
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(new Error('W3M Image Display not available.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!img) {
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(new Error('No image.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
var opt = {
|
||||||
|
stdio: 'pipe',
|
||||||
|
env: process.env,
|
||||||
|
cwd: process.env.HOME
|
||||||
|
};
|
||||||
|
|
||||||
|
var ps = this.spawn(Image.w3mdisplay, [], opt);
|
||||||
|
|
||||||
|
var buf = '';
|
||||||
|
|
||||||
|
ps.stdout.setEncoding('utf8');
|
||||||
|
|
||||||
|
ps.stdout.on('data', function(data) {
|
||||||
|
buf += data;
|
||||||
|
});
|
||||||
|
|
||||||
|
ps.on('error', function(err) {
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
ps.on('exit', function() {
|
||||||
|
if (!callback) return;
|
||||||
|
var size = buf.trim().split(/\s+/);
|
||||||
|
return callback(null, {
|
||||||
|
raw: buf.trim(),
|
||||||
|
width: +size[0],
|
||||||
|
height: +size[1]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var input = '5;' + img + '\n';
|
||||||
|
|
||||||
|
ps.stdin.write(input);
|
||||||
|
ps.stdin.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
Image.prototype.termSize = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (cp.execSync) {
|
||||||
|
callback = callback || function(err, result) { return result; };
|
||||||
|
try {
|
||||||
|
return callback(null, this.termSizeSync());
|
||||||
|
} catch (e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Image.hasW3MDisplay === false) {
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(new Error('W3M Image Display not available.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
var opt = {
|
||||||
|
stdio: 'pipe',
|
||||||
|
env: process.env,
|
||||||
|
cwd: process.env.HOME
|
||||||
|
};
|
||||||
|
|
||||||
|
var ps = this.spawn(Image.w3mdisplay, ['-test'], opt);
|
||||||
|
|
||||||
|
var buf = '';
|
||||||
|
|
||||||
|
ps.stdout.setEncoding('utf8');
|
||||||
|
|
||||||
|
ps.stdout.on('data', function(data) {
|
||||||
|
buf += data;
|
||||||
|
});
|
||||||
|
|
||||||
|
ps.on('error', function(err) {
|
||||||
|
if (!callback) return;
|
||||||
|
return callback(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
ps.on('exit', function() {
|
||||||
|
if (!callback) return;
|
||||||
|
|
||||||
|
if (!buf.trim()) {
|
||||||
|
// Bug: w3mimgdisplay will sometimes
|
||||||
|
// output nothing. Try again:
|
||||||
|
return self.termSize(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = buf.trim().split(/\s+/);
|
||||||
|
|
||||||
|
return callback(null, {
|
||||||
|
raw: buf.trim(),
|
||||||
|
width: +size[0],
|
||||||
|
height: +size[1]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ps.stdin.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
Image.prototype.getPixelRatio = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (cp.execSync) {
|
||||||
|
callback = callback || function(err, result) { return result; };
|
||||||
|
try {
|
||||||
|
return callback(null, this.getPixelRatioSync());
|
||||||
|
} catch (e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX We could cache this, but sometimes it's better
|
||||||
|
// to recalculate to be pixel perfect.
|
||||||
|
if (this._ratio && !this._needsRatio) {
|
||||||
|
return callback(null, this._ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.termSize(function(err, dimensions) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
|
||||||
|
self._ratio = {
|
||||||
|
tw: dimensions.width / self.screen.width,
|
||||||
|
th: dimensions.height / self.screen.height
|
||||||
|
};
|
||||||
|
|
||||||
|
self._needsRatio = false;
|
||||||
|
|
||||||
|
return callback(null, self._ratio);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Image.prototype.renderImageSync = function(img, ratio) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (Image.hasW3MDisplay === false) {
|
||||||
|
throw new Error('W3M Image Display not available.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ratio) {
|
||||||
|
throw new Error('No ratio.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearImage unsets these:
|
||||||
|
var _file = this.file;
|
||||||
|
var _lastSize = this._lastSize;
|
||||||
|
|
||||||
|
this.clearImageSync();
|
||||||
|
|
||||||
|
this.file = _file;
|
||||||
|
this._lastSize = _lastSize;
|
||||||
|
|
||||||
|
var width = this.width * ratio.tw | 0
|
||||||
|
, height = this.height * ratio.th | 0
|
||||||
|
, aleft = this.aleft * ratio.tw | 0
|
||||||
|
, atop = this.atop * ratio.th | 0;
|
||||||
|
|
||||||
|
var input = '0;1;'
|
||||||
|
+ aleft + ';'
|
||||||
|
+ atop + ';'
|
||||||
|
+ width + ';'
|
||||||
|
+ height + ';;;;;'
|
||||||
|
+ img
|
||||||
|
+ '\n4;\n3;\n';
|
||||||
|
|
||||||
|
this._props = {
|
||||||
|
aleft: aleft,
|
||||||
|
atop: atop,
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
cp.execFileSync(Image.w3mdisplay, [], {
|
||||||
|
env: process.env,
|
||||||
|
encoding: 'utf8',
|
||||||
|
input: input,
|
||||||
|
timeout: 1000
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Image.prototype.clearImageSync = function() {
|
||||||
|
if (Image.hasW3MDisplay === false) {
|
||||||
|
throw new Error('W3M Image Display not available.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._props) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var width = this._props.width + 2
|
||||||
|
, height = this._props.height + 2
|
||||||
|
, aleft = this._props.aleft
|
||||||
|
, atop = this._props.atop;
|
||||||
|
|
||||||
|
if (this._drag) {
|
||||||
|
aleft -= 10;
|
||||||
|
atop -= 10;
|
||||||
|
width += 10;
|
||||||
|
height += 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
var input = '6;'
|
||||||
|
+ aleft + ';'
|
||||||
|
+ atop + ';'
|
||||||
|
+ width + ';'
|
||||||
|
+ height
|
||||||
|
+ '\n4;\n3;\n';
|
||||||
|
|
||||||
|
delete this.file;
|
||||||
|
delete this._props;
|
||||||
|
delete this._lastSize;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cp.execFileSync(Image.w3mdisplay, [], {
|
||||||
|
env: process.env,
|
||||||
|
encoding: 'utf8',
|
||||||
|
input: input,
|
||||||
|
timeout: 1000
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Image.prototype.imageSizeSync = function() {
|
||||||
|
var img = this.file;
|
||||||
|
|
||||||
|
if (Image.hasW3MDisplay === false) {
|
||||||
|
throw new Error('W3M Image Display not available.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!img) {
|
||||||
|
throw new Error('No image.');
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf = '';
|
||||||
|
var input = '5;' + img + '\n';
|
||||||
|
|
||||||
|
try {
|
||||||
|
buf = cp.execFileSync(Image.w3mdisplay, [], {
|
||||||
|
env: process.env,
|
||||||
|
encoding: 'utf8',
|
||||||
|
input: input,
|
||||||
|
timeout: 1000
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = buf.trim().split(/\s+/);
|
||||||
|
|
||||||
|
return {
|
||||||
|
raw: buf.trim(),
|
||||||
|
width: +size[0],
|
||||||
|
height: +size[1]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Image.prototype.termSizeSync = function(_, recurse) {
|
||||||
|
if (Image.hasW3MDisplay === false) {
|
||||||
|
throw new Error('W3M Image Display not available.');
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
buf = cp.execFileSync(Image.w3mdisplay, ['-test'], {
|
||||||
|
env: process.env,
|
||||||
|
encoding: 'utf8',
|
||||||
|
timeout: 1000
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!buf.trim()) {
|
||||||
|
// Bug: w3mimgdisplay will sometimes
|
||||||
|
// output nothing. Try again:
|
||||||
|
recurse = recurse || 0;
|
||||||
|
if (++recurse === 5) {
|
||||||
|
throw new Error('Term size not determined.');
|
||||||
|
}
|
||||||
|
return this.termSizeSync(_, recurse);
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = buf.trim().split(/\s+/);
|
||||||
|
|
||||||
|
return {
|
||||||
|
raw: buf.trim(),
|
||||||
|
width: +size[0],
|
||||||
|
height: +size[1]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Image.prototype.getPixelRatioSync = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// XXX We could cache this, but sometimes it's better
|
||||||
|
// to recalculate to be pixel perfect.
|
||||||
|
if (this._ratio && !this._needsRatio) {
|
||||||
|
return this._ratio;
|
||||||
|
}
|
||||||
|
this._needsRatio = false;
|
||||||
|
|
||||||
|
var dimensions = this.termSizeSync();
|
||||||
|
|
||||||
|
this._ratio = {
|
||||||
|
tw: dimensions.width / this.screen.width,
|
||||||
|
th: dimensions.height / this.screen.height
|
||||||
|
};
|
||||||
|
|
||||||
|
return this._ratio;
|
||||||
|
};
|
||||||
|
|
||||||
|
Image.prototype.displayImage = function(callback) {
|
||||||
|
return this.screen.displayImage(this.file, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Image;
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* input.js - abstract input element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Input(options) {
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Input(options);
|
||||||
|
}
|
||||||
|
options = options || {};
|
||||||
|
Box.call(this, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
Input.prototype.__proto__ = Box.prototype;
|
||||||
|
|
||||||
|
Input.prototype.type = 'input';
|
||||||
|
|
||||||
|
module.exports = Input;
|
|
@ -0,0 +1,56 @@
|
||||||
|
/**
|
||||||
|
* line.js - line element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Line
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Line(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Line(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
var orientation = options.orientation || 'vertical';
|
||||||
|
delete options.orientation;
|
||||||
|
|
||||||
|
if (orientation === 'vertical') {
|
||||||
|
options.width = 1;
|
||||||
|
} else {
|
||||||
|
options.height = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Box.call(this, options);
|
||||||
|
|
||||||
|
this.ch = !options.type || options.type === 'line'
|
||||||
|
? orientation === 'horizontal' ? '─' : '│'
|
||||||
|
: options.ch || ' ';
|
||||||
|
|
||||||
|
this.border = {
|
||||||
|
type: 'bg',
|
||||||
|
__proto__: this
|
||||||
|
};
|
||||||
|
|
||||||
|
this.style.border = this.style;
|
||||||
|
}
|
||||||
|
|
||||||
|
Line.prototype.__proto__ = Box.prototype;
|
||||||
|
|
||||||
|
Line.prototype.type = 'line';
|
||||||
|
|
||||||
|
module.exports = Line;
|
|
@ -0,0 +1,514 @@
|
||||||
|
/**
|
||||||
|
* list.js - list element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List
|
||||||
|
*/
|
||||||
|
|
||||||
|
function List(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new List(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
options.ignoreKeys = true;
|
||||||
|
// Possibly put this here: this.items = [];
|
||||||
|
options.scrollable = true;
|
||||||
|
Box.call(this, options);
|
||||||
|
|
||||||
|
this.value = '';
|
||||||
|
this.items = [];
|
||||||
|
this.ritems = [];
|
||||||
|
this.selected = 0;
|
||||||
|
this._isList = true;
|
||||||
|
|
||||||
|
if (!this.style.selected) {
|
||||||
|
this.style.selected = {};
|
||||||
|
this.style.selected.bg = options.selectedBg;
|
||||||
|
this.style.selected.fg = options.selectedFg;
|
||||||
|
this.style.selected.bold = options.selectedBold;
|
||||||
|
this.style.selected.underline = options.selectedUnderline;
|
||||||
|
this.style.selected.blink = options.selectedBlink;
|
||||||
|
this.style.selected.inverse = options.selectedInverse;
|
||||||
|
this.style.selected.invisible = options.selectedInvisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.style.item) {
|
||||||
|
this.style.item = {};
|
||||||
|
this.style.item.bg = options.itemBg;
|
||||||
|
this.style.item.fg = options.itemFg;
|
||||||
|
this.style.item.bold = options.itemBold;
|
||||||
|
this.style.item.underline = options.itemUnderline;
|
||||||
|
this.style.item.blink = options.itemBlink;
|
||||||
|
this.style.item.inverse = options.itemInverse;
|
||||||
|
this.style.item.invisible = options.itemInvisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy: for apps written before the addition of item attributes.
|
||||||
|
['bg', 'fg', 'bold', 'underline',
|
||||||
|
'blink', 'inverse', 'invisible'].forEach(function(name) {
|
||||||
|
if (self.style[name] != null && self.style.item[name] == null) {
|
||||||
|
self.style.item[name] = self.style[name];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.options.itemHoverBg) {
|
||||||
|
this.options.itemHoverEffects = { bg: this.options.itemHoverBg };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.itemHoverEffects) {
|
||||||
|
this.style.item.hover = this.options.itemHoverEffects;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.itemFocusEffects) {
|
||||||
|
this.style.item.focus = this.options.itemFocusEffects;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.interactive = options.interactive !== false;
|
||||||
|
|
||||||
|
this.mouse = options.mouse || false;
|
||||||
|
|
||||||
|
if (options.items) {
|
||||||
|
this.ritems = options.items;
|
||||||
|
options.items.forEach(this.add.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.select(0);
|
||||||
|
|
||||||
|
if (options.mouse) {
|
||||||
|
this.screen._listenMouse(this);
|
||||||
|
this.on('element wheeldown', function(el, data) {
|
||||||
|
self.select(self.selected + 2);
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
this.on('element wheelup', function(el, data) {
|
||||||
|
self.select(self.selected - 2);
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.keys) {
|
||||||
|
this.on('keypress', function(ch, key) {
|
||||||
|
if (key.name === 'up' || (options.vi && key.name === 'k')) {
|
||||||
|
self.up();
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key.name === 'down' || (options.vi && key.name === 'j')) {
|
||||||
|
self.down();
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key.name === 'enter'
|
||||||
|
|| (options.vi && key.name === 'l' && !key.shift)) {
|
||||||
|
self.enterSelected();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key.name === 'escape' || (options.vi && key.name === 'q')) {
|
||||||
|
self.cancelSelected();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.vi && key.name === 'u' && key.ctrl) {
|
||||||
|
self.move(-((self.height - self.iheight) / 2) | 0);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.vi && key.name === 'd' && key.ctrl) {
|
||||||
|
self.move((self.height - self.iheight) / 2 | 0);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.vi && key.name === 'b' && key.ctrl) {
|
||||||
|
self.move(-(self.height - self.iheight));
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.vi && key.name === 'f' && key.ctrl) {
|
||||||
|
self.move(self.height - self.iheight);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.vi && key.name === 'h' && key.shift) {
|
||||||
|
self.move(self.childBase - self.selected);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.vi && key.name === 'm' && key.shift) {
|
||||||
|
// TODO: Maybe use Math.min(this.items.length,
|
||||||
|
// ... for calculating visible items elsewhere.
|
||||||
|
var visible = Math.min(
|
||||||
|
self.height - self.iheight,
|
||||||
|
self.items.length) / 2 | 0;
|
||||||
|
self.move(self.childBase + visible - self.selected);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.vi && key.name === 'l' && key.shift) {
|
||||||
|
// XXX This goes one too far on lists with an odd number of items.
|
||||||
|
self.down(self.childBase
|
||||||
|
+ Math.min(self.height - self.iheight, self.items.length)
|
||||||
|
- self.selected);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.vi && key.name === 'g' && !key.shift) {
|
||||||
|
self.select(0);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.vi && key.name === 'g' && key.shift) {
|
||||||
|
self.select(self.items.length - 1);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.vi && (key.ch === '/' || key.ch === '?')) {
|
||||||
|
if (typeof self.options.search !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return self.options.search(function(err, value) {
|
||||||
|
if (typeof err === 'string' || typeof err === 'function'
|
||||||
|
|| typeof err === 'number' || (err && err.test)) {
|
||||||
|
value = err;
|
||||||
|
err = null;
|
||||||
|
}
|
||||||
|
if (err || !value) return self.screen.render();
|
||||||
|
self.select(self.fuzzyFind(value, key.ch === '?'));
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.on('resize', function() {
|
||||||
|
var visible = self.height - self.iheight;
|
||||||
|
// if (self.selected < visible - 1) {
|
||||||
|
if (visible >= self.selected + 1) {
|
||||||
|
self.childBase = 0;
|
||||||
|
self.childOffset = self.selected;
|
||||||
|
} else {
|
||||||
|
// Is this supposed to be: self.childBase = visible - self.selected + 1; ?
|
||||||
|
self.childBase = self.selected - visible + 1;
|
||||||
|
self.childOffset = visible - 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('adopt', function(el) {
|
||||||
|
if (!~self.items.indexOf(el)) {
|
||||||
|
el.fixed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure children are removed from the
|
||||||
|
// item list if they are items.
|
||||||
|
this.on('remove', function(el) {
|
||||||
|
self.removeItem(el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List.prototype.__proto__ = Box.prototype;
|
||||||
|
|
||||||
|
List.prototype.type = 'list';
|
||||||
|
|
||||||
|
List.prototype.add =
|
||||||
|
List.prototype.addItem =
|
||||||
|
List.prototype.appendItem = function(item) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.ritems.push(item);
|
||||||
|
|
||||||
|
// Note: Could potentially use Button here.
|
||||||
|
var options = {
|
||||||
|
screen: this.screen,
|
||||||
|
content: item,
|
||||||
|
align: this.align || 'left',
|
||||||
|
top: this.items.length,
|
||||||
|
left: 0,
|
||||||
|
right: (this.scrollbar ? 1 : 0),
|
||||||
|
tags: this.parseTags,
|
||||||
|
height: 1,
|
||||||
|
hoverEffects: this.mouse ? this.style.item.hover : null,
|
||||||
|
focusEffects: this.mouse ? this.style.item.focus : null,
|
||||||
|
autoFocus: false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!this.screen.autoPadding) {
|
||||||
|
options.top = this.itop + this.items.length;
|
||||||
|
options.left = this.ileft;
|
||||||
|
options.right = this.iright + (this.scrollbar ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (this.shrink) {
|
||||||
|
// XXX NOTE: Maybe just do this on all shrinkage once autoPadding is default?
|
||||||
|
if (this.shrink && this.options.normalShrink) {
|
||||||
|
delete options.right;
|
||||||
|
options.width = 'shrink';
|
||||||
|
}
|
||||||
|
|
||||||
|
['bg', 'fg', 'bold', 'underline',
|
||||||
|
'blink', 'inverse', 'invisible'].forEach(function(name) {
|
||||||
|
options[name] = function() {
|
||||||
|
var attr = self.items[self.selected] === item && self.interactive
|
||||||
|
? self.style.selected[name]
|
||||||
|
: self.style.item[name];
|
||||||
|
if (typeof attr === 'function') attr = attr(item);
|
||||||
|
return attr;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.style.transparent) {
|
||||||
|
options.transparent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = new Box(options);
|
||||||
|
|
||||||
|
this.items.push(item);
|
||||||
|
this.append(item);
|
||||||
|
|
||||||
|
if (this.items.length === 1) {
|
||||||
|
this.select(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mouse) {
|
||||||
|
item.on('click', function(data) {
|
||||||
|
self.focus();
|
||||||
|
if (self.items[self.selected] === item) {
|
||||||
|
self.emit('action', item, self.selected);
|
||||||
|
self.emit('select', item, self.selected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.select(item);
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('add item');
|
||||||
|
};
|
||||||
|
|
||||||
|
List.prototype.find =
|
||||||
|
List.prototype.fuzzyFind = function(search, back) {
|
||||||
|
var start = this.selected + (back ? -1 : 1);
|
||||||
|
|
||||||
|
if (typeof search === 'number') search += '';
|
||||||
|
|
||||||
|
if (search && search[0] === '/' && search[search.length - 1] === '/') {
|
||||||
|
try {
|
||||||
|
search = new RegExp(search.slice(1, -1));
|
||||||
|
} catch (e) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var test = typeof search === 'string'
|
||||||
|
? function(item) { return !!~item.indexOf(search); }
|
||||||
|
: (search.test ? search.test.bind(search) : search);
|
||||||
|
|
||||||
|
if (typeof test !== 'function') {
|
||||||
|
if (this.screen.options.debug) {
|
||||||
|
throw new Error('fuzzyFind(): `test` is not a function.');
|
||||||
|
}
|
||||||
|
return this.selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!back) {
|
||||||
|
for (var i = start; i < this.ritems.length; i++){
|
||||||
|
if (test(helpers.cleanTags(this.ritems[i]))) return i;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < start; i++){
|
||||||
|
if (test(helpers.cleanTags(this.ritems[i]))) return i;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (var i = start; i >= 0; i--){
|
||||||
|
if (test(helpers.cleanTags(this.ritems[i]))) return i;
|
||||||
|
}
|
||||||
|
for (var i = this.ritems.length - 1; i > start; i--){
|
||||||
|
if (test(helpers.cleanTags(this.ritems[i]))) return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.selected;
|
||||||
|
};
|
||||||
|
|
||||||
|
List.prototype.getItemIndex = function(child) {
|
||||||
|
if (typeof child === 'number') {
|
||||||
|
return child;
|
||||||
|
} else if (typeof child === 'string') {
|
||||||
|
var i = this.ritems.indexOf(child);
|
||||||
|
if (~i) return i;
|
||||||
|
for (i = 0; i < this.ritems.length; i++) {
|
||||||
|
if (helpers.cleanTags(this.ritems[i]) === child) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return this.items.indexOf(child);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
List.prototype.getItem = function(child) {
|
||||||
|
return this.items[this.getItemIndex(child)];
|
||||||
|
};
|
||||||
|
|
||||||
|
List.prototype.removeItem = function(child) {
|
||||||
|
var i = this.getItemIndex(child);
|
||||||
|
if (~i && this.items[i]) {
|
||||||
|
child = this.items.splice(i, 1)[0];
|
||||||
|
this.ritems.splice(i, 1);
|
||||||
|
this.remove(child);
|
||||||
|
if (i === this.selected) {
|
||||||
|
this.select(i - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.emit('remove item');
|
||||||
|
};
|
||||||
|
|
||||||
|
List.prototype.clearItems = function() {
|
||||||
|
return this.setItems([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
List.prototype.setItems = function(items) {
|
||||||
|
var items = items.slice()
|
||||||
|
, original = this.items.slice()
|
||||||
|
, selected = this.selected
|
||||||
|
, sel = this.ritems[this.selected]
|
||||||
|
, i = 0;
|
||||||
|
|
||||||
|
this.select(0);
|
||||||
|
|
||||||
|
for (; i < items.length; i++) {
|
||||||
|
if (this.items[i]) {
|
||||||
|
this.items[i].setContent(items[i]);
|
||||||
|
} else {
|
||||||
|
this.add(items[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; i < original.length; i++) {
|
||||||
|
this.remove(original[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ritems = items;
|
||||||
|
|
||||||
|
// Try to find our old item if it still exists.
|
||||||
|
sel = items.indexOf(sel);
|
||||||
|
if (~sel) {
|
||||||
|
this.select(sel);
|
||||||
|
} else if (items.length === original.length) {
|
||||||
|
this.select(selected);
|
||||||
|
} else {
|
||||||
|
this.select(Math.min(selected, items.length - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('set items');
|
||||||
|
};
|
||||||
|
|
||||||
|
List.prototype.select = function(index) {
|
||||||
|
if (!this.interactive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.items.length) {
|
||||||
|
this.selected = 0;
|
||||||
|
this.value = '';
|
||||||
|
this.scrollTo(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof index === 'object') {
|
||||||
|
index = this.items.indexOf(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
index = 0;
|
||||||
|
} else if (index >= this.items.length) {
|
||||||
|
index = this.items.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selected === index && this._listInitialized) return;
|
||||||
|
this._listInitialized = true;
|
||||||
|
|
||||||
|
this.selected = index;
|
||||||
|
this.value = helpers.cleanTags(this.ritems[this.selected]);
|
||||||
|
if (!this.parent) return;
|
||||||
|
this.scrollTo(this.selected);
|
||||||
|
|
||||||
|
// XXX Move `action` and `select` events here.
|
||||||
|
this.emit('select item', this.items[this.selected], this.selected);
|
||||||
|
};
|
||||||
|
|
||||||
|
List.prototype.move = function(offset) {
|
||||||
|
this.select(this.selected + offset);
|
||||||
|
};
|
||||||
|
|
||||||
|
List.prototype.up = function(offset) {
|
||||||
|
this.move(-(offset || 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
List.prototype.down = function(offset) {
|
||||||
|
this.move(offset || 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
List.prototype.pick = function(label, callback) {
|
||||||
|
if (!callback) {
|
||||||
|
callback = label;
|
||||||
|
label = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.interactive) {
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var focused = this.screen.focused;
|
||||||
|
if (focused && focused._done) focused._done('stop');
|
||||||
|
this.screen.saveFocus();
|
||||||
|
|
||||||
|
// XXX Keep above:
|
||||||
|
// var parent = this.parent;
|
||||||
|
// this.detach();
|
||||||
|
// parent.append(this);
|
||||||
|
|
||||||
|
this.focus();
|
||||||
|
this.show();
|
||||||
|
this.select(0);
|
||||||
|
if (label) this.setLabel(label);
|
||||||
|
this.screen.render();
|
||||||
|
this.once('action', function(el, selected) {
|
||||||
|
if (label) self.removeLabel();
|
||||||
|
self.screen.restoreFocus();
|
||||||
|
self.hide();
|
||||||
|
self.screen.render();
|
||||||
|
if (!el) return callback();
|
||||||
|
return callback(null, helpers.cleanTags(self.ritems[selected]));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
List.prototype.enterSelected = function(i) {
|
||||||
|
if (i != null) this.select(i);
|
||||||
|
this.emit('action', this.items[this.selected], this.selected);
|
||||||
|
this.emit('select', this.items[this.selected], this.selected);
|
||||||
|
};
|
||||||
|
|
||||||
|
List.prototype.cancelSelected = function(i) {
|
||||||
|
if (i != null) this.select(i);
|
||||||
|
this.emit('action');
|
||||||
|
this.emit('cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = List;
|
|
@ -0,0 +1,396 @@
|
||||||
|
/**
|
||||||
|
* listbar.js - listbar element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listbar / HorizontalList
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Listbar(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Listbar(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
this.items = [];
|
||||||
|
this.ritems = [];
|
||||||
|
this.commands = [];
|
||||||
|
|
||||||
|
this.leftBase = 0;
|
||||||
|
this.leftOffset = 0;
|
||||||
|
|
||||||
|
this.mouse = options.mouse || false;
|
||||||
|
|
||||||
|
Box.call(this, options);
|
||||||
|
|
||||||
|
if (!this.style.selected) {
|
||||||
|
this.style.selected = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.style.item) {
|
||||||
|
this.style.item = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.commands || options.items) {
|
||||||
|
this.setItems(options.commands || options.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.keys) {
|
||||||
|
this.on('keypress', function(ch, key) {
|
||||||
|
if (key.name === 'left'
|
||||||
|
|| (options.vi && key.name === 'h')
|
||||||
|
|| (key.shift && key.name === 'tab')) {
|
||||||
|
self.moveLeft();
|
||||||
|
self.screen.render();
|
||||||
|
// Stop propagation if we're in a form.
|
||||||
|
if (key.name === 'tab') return false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key.name === 'right'
|
||||||
|
|| (options.vi && key.name === 'l')
|
||||||
|
|| key.name === 'tab') {
|
||||||
|
self.moveRight();
|
||||||
|
self.screen.render();
|
||||||
|
// Stop propagation if we're in a form.
|
||||||
|
if (key.name === 'tab') return false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key.name === 'enter'
|
||||||
|
|| (options.vi && key.name === 'k' && !key.shift)) {
|
||||||
|
self.emit('action', self.items[self.selected], self.selected);
|
||||||
|
self.emit('select', self.items[self.selected], self.selected);
|
||||||
|
var item = self.items[self.selected];
|
||||||
|
if (item._.cmd.callback) {
|
||||||
|
item._.cmd.callback();
|
||||||
|
}
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key.name === 'escape' || (options.vi && key.name === 'q')) {
|
||||||
|
self.emit('action');
|
||||||
|
self.emit('cancel');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.autoCommandKeys) {
|
||||||
|
this.onScreenEvent('keypress', function(ch, key) {
|
||||||
|
if (/^[0-9]$/.test(ch)) {
|
||||||
|
var i = +ch - 1;
|
||||||
|
if (!~i) i = 9;
|
||||||
|
return self.selectTab(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.on('focus', function() {
|
||||||
|
self.select(self.selected);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Listbar.prototype.__proto__ = Box.prototype;
|
||||||
|
|
||||||
|
Listbar.prototype.type = 'listbar';
|
||||||
|
|
||||||
|
Listbar.prototype.__defineGetter__('selected', function() {
|
||||||
|
return this.leftBase + this.leftOffset;
|
||||||
|
});
|
||||||
|
|
||||||
|
Listbar.prototype.setItems = function(commands) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!Array.isArray(commands)) {
|
||||||
|
commands = Object.keys(commands).reduce(function(obj, key, i) {
|
||||||
|
var cmd = commands[key]
|
||||||
|
, cb;
|
||||||
|
|
||||||
|
if (typeof cmd === 'function') {
|
||||||
|
cb = cmd;
|
||||||
|
cmd = { callback: cb };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.text == null) cmd.text = key;
|
||||||
|
if (cmd.prefix == null) cmd.prefix = ++i + '';
|
||||||
|
|
||||||
|
if (cmd.text == null && cmd.callback) {
|
||||||
|
cmd.text = cmd.callback.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.push(cmd);
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.items.forEach(function(el) {
|
||||||
|
el.detach();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.items = [];
|
||||||
|
this.ritems = [];
|
||||||
|
this.commands = [];
|
||||||
|
|
||||||
|
commands.forEach(function(cmd) {
|
||||||
|
self.add(cmd);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.emit('set items');
|
||||||
|
};
|
||||||
|
|
||||||
|
Listbar.prototype.add =
|
||||||
|
Listbar.prototype.addItem =
|
||||||
|
Listbar.prototype.appendItem = function(item, callback) {
|
||||||
|
var self = this
|
||||||
|
, prev = this.items[this.items.length - 1]
|
||||||
|
, drawn = prev ? prev.aleft + prev.width : 0
|
||||||
|
, cmd
|
||||||
|
, title
|
||||||
|
, len;
|
||||||
|
|
||||||
|
if (!this.screen.autoPadding) {
|
||||||
|
drawn += this.ileft;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof item === 'object') {
|
||||||
|
cmd = item;
|
||||||
|
if (cmd.prefix == null) cmd.prefix = (this.items.length + 1) + '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
cmd = {
|
||||||
|
prefix: (this.items.length + 1) + '',
|
||||||
|
text: item,
|
||||||
|
callback: callback
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof item === 'function') {
|
||||||
|
cmd = {
|
||||||
|
prefix: (this.items.length + 1) + '',
|
||||||
|
text: item.name,
|
||||||
|
callback: item
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.keys && cmd.keys[0]) {
|
||||||
|
cmd.prefix = cmd.keys[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
var t = helpers.generateTags(this.style.prefix || { fg: 'lightblack' });
|
||||||
|
|
||||||
|
title = (cmd.prefix != null ? t.open + cmd.prefix + t.close + ':' : '') + cmd.text;
|
||||||
|
|
||||||
|
len = ((cmd.prefix != null ? cmd.prefix + ':' : '') + cmd.text).length;
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
screen: this.screen,
|
||||||
|
top: 0,
|
||||||
|
left: drawn + 1,
|
||||||
|
height: 1,
|
||||||
|
content: title,
|
||||||
|
width: len + 2,
|
||||||
|
align: 'center',
|
||||||
|
autoFocus: false,
|
||||||
|
tags: true,
|
||||||
|
mouse: true,
|
||||||
|
style: helpers.merge({}, this.style.item),
|
||||||
|
noOverflow: true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!this.screen.autoPadding) {
|
||||||
|
options.top += this.itop;
|
||||||
|
options.left += this.ileft;
|
||||||
|
}
|
||||||
|
|
||||||
|
['bg', 'fg', 'bold', 'underline',
|
||||||
|
'blink', 'inverse', 'invisible'].forEach(function(name) {
|
||||||
|
options.style[name] = function() {
|
||||||
|
var attr = self.items[self.selected] === el
|
||||||
|
? self.style.selected[name]
|
||||||
|
: self.style.item[name];
|
||||||
|
if (typeof attr === 'function') attr = attr(el);
|
||||||
|
return attr;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
var el = new Box(options);
|
||||||
|
|
||||||
|
this._[cmd.text] = el;
|
||||||
|
cmd.element = el;
|
||||||
|
el._.cmd = cmd;
|
||||||
|
|
||||||
|
this.ritems.push(cmd.text);
|
||||||
|
this.items.push(el);
|
||||||
|
this.commands.push(cmd);
|
||||||
|
this.append(el);
|
||||||
|
|
||||||
|
if (cmd.callback) {
|
||||||
|
if (cmd.keys) {
|
||||||
|
this.screen.key(cmd.keys, function(ch, key) {
|
||||||
|
self.emit('action', el, self.selected);
|
||||||
|
self.emit('select', el, self.selected);
|
||||||
|
if (el._.cmd.callback) {
|
||||||
|
el._.cmd.callback();
|
||||||
|
}
|
||||||
|
self.select(el);
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.items.length === 1) {
|
||||||
|
this.select(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mouse) {
|
||||||
|
el.on('click', function(data) {
|
||||||
|
self.emit('action', el, self.selected);
|
||||||
|
self.emit('select', el, self.selected);
|
||||||
|
if (el._.cmd.callback) {
|
||||||
|
el._.cmd.callback();
|
||||||
|
}
|
||||||
|
self.select(el);
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('add item');
|
||||||
|
};
|
||||||
|
|
||||||
|
Listbar.prototype.render = function() {
|
||||||
|
var self = this
|
||||||
|
, drawn = 0;
|
||||||
|
|
||||||
|
if (!this.screen.autoPadding) {
|
||||||
|
drawn += this.ileft;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.items.forEach(function(el, i) {
|
||||||
|
if (i < self.leftBase) {
|
||||||
|
el.hide();
|
||||||
|
} else {
|
||||||
|
el.rleft = drawn + 1;
|
||||||
|
drawn += el.width + 2;
|
||||||
|
el.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._render();
|
||||||
|
};
|
||||||
|
|
||||||
|
Listbar.prototype.select = function(offset) {
|
||||||
|
if (typeof offset !== 'number') {
|
||||||
|
offset = this.items.indexOf(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
var lpos = this._getCoords();
|
||||||
|
if (!lpos) return;
|
||||||
|
|
||||||
|
var self = this
|
||||||
|
, width = (lpos.xl - lpos.xi) - this.iwidth
|
||||||
|
, drawn = 0
|
||||||
|
, visible = 0
|
||||||
|
, el;
|
||||||
|
|
||||||
|
if (offset < 0) {
|
||||||
|
offset = 0;
|
||||||
|
} else if (offset >= this.items.length) {
|
||||||
|
offset = this.items.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
el = this.items[offset];
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
this.items.forEach(function(el, i) {
|
||||||
|
if (i < self.leftBase) return;
|
||||||
|
|
||||||
|
var lpos = el._getCoords();
|
||||||
|
if (!lpos) return;
|
||||||
|
|
||||||
|
if (lpos.xl - lpos.xi <= 0) return;
|
||||||
|
|
||||||
|
drawn += (lpos.xl - lpos.xi) + 2;
|
||||||
|
|
||||||
|
if (drawn <= width) visible++;
|
||||||
|
});
|
||||||
|
|
||||||
|
var diff = offset - (this.leftBase + this.leftOffset);
|
||||||
|
if (offset > this.leftBase + this.leftOffset) {
|
||||||
|
if (offset > this.leftBase + visible - 1) {
|
||||||
|
this.leftOffset = 0;
|
||||||
|
this.leftBase = offset;
|
||||||
|
} else {
|
||||||
|
this.leftOffset += diff;
|
||||||
|
}
|
||||||
|
} else if (offset < this.leftBase + this.leftOffset) {
|
||||||
|
diff = -diff;
|
||||||
|
if (offset < this.leftBase) {
|
||||||
|
this.leftOffset = 0;
|
||||||
|
this.leftBase = offset;
|
||||||
|
} else {
|
||||||
|
this.leftOffset -= diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX Move `action` and `select` events here.
|
||||||
|
this.emit('select item', el, offset);
|
||||||
|
};
|
||||||
|
|
||||||
|
Listbar.prototype.removeItem = function(child) {
|
||||||
|
var i = typeof child !== 'number'
|
||||||
|
? this.items.indexOf(child)
|
||||||
|
: child;
|
||||||
|
|
||||||
|
if (~i && this.items[i]) {
|
||||||
|
child = this.items.splice(i, 1)[0];
|
||||||
|
this.ritems.splice(i, 1);
|
||||||
|
this.commands.splice(i, 1);
|
||||||
|
this.remove(child);
|
||||||
|
if (i === this.selected) {
|
||||||
|
this.select(i - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('remove item');
|
||||||
|
};
|
||||||
|
|
||||||
|
Listbar.prototype.move = function(offset) {
|
||||||
|
this.select(this.selected + offset);
|
||||||
|
};
|
||||||
|
|
||||||
|
Listbar.prototype.moveLeft = function(offset) {
|
||||||
|
this.move(-(offset || 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
Listbar.prototype.moveRight = function(offset) {
|
||||||
|
this.move(offset || 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
Listbar.prototype.selectTab = function(index) {
|
||||||
|
var item = this.items[index];
|
||||||
|
if (item) {
|
||||||
|
if (item._.cmd.callback) {
|
||||||
|
item._.cmd.callback();
|
||||||
|
}
|
||||||
|
this.select(index);
|
||||||
|
this.screen.render();
|
||||||
|
}
|
||||||
|
this.emit('select tab', item, index);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Listbar;
|
|
@ -0,0 +1,238 @@
|
||||||
|
/**
|
||||||
|
* listtable.js - list table element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
var List = require('./list');
|
||||||
|
var Table = require('./table');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ListTable
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ListTable(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new ListTable(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
options.shrink = true;
|
||||||
|
options.normalShrink = true;
|
||||||
|
options.style = options.style || {};
|
||||||
|
options.style.border = options.style.border || {};
|
||||||
|
options.style.header = options.style.header || {};
|
||||||
|
options.style.cell = options.style.cell || {};
|
||||||
|
this.__align = options.align || 'center';
|
||||||
|
delete options.align;
|
||||||
|
|
||||||
|
options.style.selected = options.style.cell.selected;
|
||||||
|
options.style.item = options.style.cell;
|
||||||
|
|
||||||
|
List.call(this, options);
|
||||||
|
|
||||||
|
this._header = new Box({
|
||||||
|
parent: this,
|
||||||
|
left: this.screen.autoPadding ? 0 : this.ileft,
|
||||||
|
top: 0,
|
||||||
|
width: 'shrink',
|
||||||
|
height: 1,
|
||||||
|
style: options.style.header,
|
||||||
|
tags: options.parseTags || options.tags
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('scroll', function() {
|
||||||
|
self._header.setFront();
|
||||||
|
var visible = self.height - self.iheight;
|
||||||
|
self._header.rtop = 1 + self.childBase - (self.border ? 1 : 0);
|
||||||
|
if (!self.screen.autoPadding) {
|
||||||
|
self._header.rtop = 1 + self.childBase;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.pad = options.pad != null
|
||||||
|
? options.pad
|
||||||
|
: 2;
|
||||||
|
|
||||||
|
this.setData(options.rows || options.data);
|
||||||
|
|
||||||
|
this.on('resize', function() {
|
||||||
|
var selected = self.selected;
|
||||||
|
self.setData(self.rows);
|
||||||
|
self.select(selected);
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ListTable.prototype.__proto__ = List.prototype;
|
||||||
|
|
||||||
|
ListTable.prototype.type = 'list-table';
|
||||||
|
|
||||||
|
ListTable.prototype._calculateMaxes = Table.prototype._calculateMaxes;
|
||||||
|
|
||||||
|
ListTable.prototype.setRows =
|
||||||
|
ListTable.prototype.setData = function(rows) {
|
||||||
|
var self = this
|
||||||
|
, align = this.__align;
|
||||||
|
|
||||||
|
this.clearItems();
|
||||||
|
|
||||||
|
this.rows = rows || [];
|
||||||
|
|
||||||
|
this._calculateMaxes();
|
||||||
|
|
||||||
|
this.addItem('');
|
||||||
|
|
||||||
|
this.rows.forEach(function(row, i) {
|
||||||
|
var isHeader = i === 0;
|
||||||
|
var isFooter = i === self.rows.length - 1;
|
||||||
|
var text = '';
|
||||||
|
row.forEach(function(cell, i) {
|
||||||
|
var width = self._maxes[i];
|
||||||
|
var clen = self.strWidth(cell);
|
||||||
|
|
||||||
|
if (i !== 0) {
|
||||||
|
text += ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
while (clen < width) {
|
||||||
|
if (align === 'center') {
|
||||||
|
cell = ' ' + cell + ' ';
|
||||||
|
clen += 2;
|
||||||
|
} else if (align === 'left') {
|
||||||
|
cell = cell + ' ';
|
||||||
|
clen += 1;
|
||||||
|
} else if (align === 'right') {
|
||||||
|
cell = ' ' + cell;
|
||||||
|
clen += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clen > width) {
|
||||||
|
if (align === 'center') {
|
||||||
|
cell = cell.substring(1);
|
||||||
|
clen--;
|
||||||
|
} else if (align === 'left') {
|
||||||
|
cell = cell.slice(0, -1);
|
||||||
|
clen--;
|
||||||
|
} else if (align === 'right') {
|
||||||
|
cell = cell.substring(1);
|
||||||
|
clen--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text += cell;
|
||||||
|
});
|
||||||
|
if (isHeader) {
|
||||||
|
self._header.setContent(text);
|
||||||
|
} else {
|
||||||
|
self.addItem(text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._header.setFront();
|
||||||
|
|
||||||
|
this.select(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
ListTable.prototype._select = ListTable.prototype.select;
|
||||||
|
ListTable.prototype.select = function(i) {
|
||||||
|
if (i === 0) {
|
||||||
|
i = 1;
|
||||||
|
}
|
||||||
|
if (i <= this.childBase) {
|
||||||
|
this.setScroll(this.childBase - 1);
|
||||||
|
}
|
||||||
|
return this._select(i);
|
||||||
|
};
|
||||||
|
|
||||||
|
ListTable.prototype.render = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var coords = this._render();
|
||||||
|
if (!coords) return;
|
||||||
|
|
||||||
|
this._calculateMaxes();
|
||||||
|
|
||||||
|
if (!this._maxes) return coords;
|
||||||
|
|
||||||
|
var lines = this.screen.lines
|
||||||
|
, xi = coords.xi
|
||||||
|
, xl = coords.xl
|
||||||
|
, yi = coords.yi
|
||||||
|
, yl = coords.yl
|
||||||
|
, rx
|
||||||
|
, ry
|
||||||
|
, i;
|
||||||
|
|
||||||
|
var battr = this.sattr(this.style.border);
|
||||||
|
|
||||||
|
var width = coords.xl - coords.xi - this.iright
|
||||||
|
, height = coords.yl - coords.yi - this.ibottom;
|
||||||
|
|
||||||
|
if (!this.border || this.options.noCellBorders) return coords;
|
||||||
|
|
||||||
|
// Draw border with correct angles.
|
||||||
|
ry = 0;
|
||||||
|
for (i = 0; i < height + 1; i++) {
|
||||||
|
if (!lines[yi + ry]) break;
|
||||||
|
rx = 0;
|
||||||
|
self._maxes.slice(0, -1).forEach(function(max, i) {
|
||||||
|
rx += max;
|
||||||
|
if (!lines[yi + ry][xi + rx + 1]) return;
|
||||||
|
// center
|
||||||
|
if (ry === 0) {
|
||||||
|
// top
|
||||||
|
lines[yi + ry][xi + ++rx][0] = battr;
|
||||||
|
lines[yi + ry][xi + rx][1] = '\u252c'; // '┬'
|
||||||
|
// XXX If we alter iheight and itop for no borders - nothing should be written here
|
||||||
|
if (!self.border.top) {
|
||||||
|
lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
|
||||||
|
}
|
||||||
|
} else if (ry === height) {
|
||||||
|
// bottom
|
||||||
|
lines[yi + ry][xi + ++rx][0] = battr;
|
||||||
|
lines[yi + ry][xi + rx][1] = '\u2534'; // '┴'
|
||||||
|
// XXX If we alter iheight and ibottom for no borders - nothing should be written here
|
||||||
|
if (!self.border.bottom) {
|
||||||
|
lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// middle
|
||||||
|
++rx;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ry += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw internal borders.
|
||||||
|
for (ry = 1; ry < height; ry++) {
|
||||||
|
if (!lines[yi + ry]) break;
|
||||||
|
rx = 0;
|
||||||
|
self._maxes.slice(0, -1).forEach(function(max, i) {
|
||||||
|
rx += max;
|
||||||
|
if (!lines[yi + ry][xi + rx + 1]) return;
|
||||||
|
if (self.options.fillCellBorders !== false) {
|
||||||
|
var lbg = lines[yi + ry][xi + rx][0] & 0x1ff;
|
||||||
|
lines[yi + ry][xi + ++rx][0] = (battr & ~0x1ff) | lbg;
|
||||||
|
} else {
|
||||||
|
lines[yi + ry][xi + ++rx][0] = battr;
|
||||||
|
}
|
||||||
|
lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return coords;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = ListTable;
|
|
@ -0,0 +1,88 @@
|
||||||
|
/**
|
||||||
|
* loading.js - loading element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
var Text = require('./text');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loading
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Loading(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Loading(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
Box.call(this, options);
|
||||||
|
|
||||||
|
this._.icon = new Text({
|
||||||
|
parent: this,
|
||||||
|
align: 'center',
|
||||||
|
top: 2,
|
||||||
|
left: 1,
|
||||||
|
right: 1,
|
||||||
|
height: 1,
|
||||||
|
content: '|'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading.prototype.__proto__ = Box.prototype;
|
||||||
|
|
||||||
|
Loading.prototype.type = 'loading';
|
||||||
|
|
||||||
|
Loading.prototype.load = function(text) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// XXX Keep above:
|
||||||
|
// var parent = this.parent;
|
||||||
|
// this.detach();
|
||||||
|
// parent.append(this);
|
||||||
|
|
||||||
|
this.show();
|
||||||
|
this.setContent(text);
|
||||||
|
|
||||||
|
if (this._.timer) {
|
||||||
|
this.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.screen.lockKeys = true;
|
||||||
|
|
||||||
|
this._.timer = setInterval(function() {
|
||||||
|
if (self._.icon.content === '|') {
|
||||||
|
self._.icon.setContent('/');
|
||||||
|
} else if (self._.icon.content === '/') {
|
||||||
|
self._.icon.setContent('-');
|
||||||
|
} else if (self._.icon.content === '-') {
|
||||||
|
self._.icon.setContent('\\');
|
||||||
|
} else if (self._.icon.content === '\\') {
|
||||||
|
self._.icon.setContent('|');
|
||||||
|
}
|
||||||
|
self.screen.render();
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
Loading.prototype.stop = function() {
|
||||||
|
this.screen.lockKeys = false;
|
||||||
|
this.hide();
|
||||||
|
if (this._.timer) {
|
||||||
|
clearInterval(this._.timer);
|
||||||
|
delete this._.timer;
|
||||||
|
}
|
||||||
|
this.screen.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Loading;
|
|
@ -0,0 +1,81 @@
|
||||||
|
/**
|
||||||
|
* log.js - log element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var util = require('util');
|
||||||
|
|
||||||
|
var nextTick = global.setImmediate || process.nextTick.bind(process);
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var ScrollableText = require('./scrollabletext');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Log(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Log(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
ScrollableText.call(this, options);
|
||||||
|
|
||||||
|
this.scrollback = options.scrollback != null
|
||||||
|
? options.scrollback
|
||||||
|
: Infinity;
|
||||||
|
this.scrollOnInput = options.scrollOnInput;
|
||||||
|
|
||||||
|
this.on('set content', function() {
|
||||||
|
if (!self._userScrolled || self.scrollOnInput) {
|
||||||
|
nextTick(function() {
|
||||||
|
self.setScrollPerc(100);
|
||||||
|
self._userScrolled = false;
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.prototype.__proto__ = ScrollableText.prototype;
|
||||||
|
|
||||||
|
Log.prototype.type = 'log';
|
||||||
|
|
||||||
|
Log.prototype.log =
|
||||||
|
Log.prototype.add = function() {
|
||||||
|
var args = Array.prototype.slice.call(arguments);
|
||||||
|
if (typeof args[0] === 'object') {
|
||||||
|
args[0] = util.inspect(args[0], true, 20, true);
|
||||||
|
}
|
||||||
|
var text = util.format.apply(util, args);
|
||||||
|
this.emit('log', text);
|
||||||
|
var ret = this.pushLine(text);
|
||||||
|
if (this._clines.fake.length > this.scrollback) {
|
||||||
|
this.shiftLine(0, (this.scrollback / 3) | 0);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
Log.prototype._scroll = Log.prototype.scroll;
|
||||||
|
Log.prototype.scroll = function(offset, always) {
|
||||||
|
if (offset === 0) return this._scroll(offset, always);
|
||||||
|
this._userScrolled = true;
|
||||||
|
var ret = this._scroll(offset, always);
|
||||||
|
if (this.getScrollPerc() === 100) {
|
||||||
|
this._userScrolled = false;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Log;
|
|
@ -0,0 +1,122 @@
|
||||||
|
/**
|
||||||
|
* message.js - message element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message / Error
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Message(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Message(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
options.tags = true;
|
||||||
|
|
||||||
|
Box.call(this, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
Message.prototype.__proto__ = Box.prototype;
|
||||||
|
|
||||||
|
Message.prototype.type = 'message';
|
||||||
|
|
||||||
|
Message.prototype.log =
|
||||||
|
Message.prototype.display = function(text, time, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (typeof time === 'function') {
|
||||||
|
callback = time;
|
||||||
|
time = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time == null) time = 3;
|
||||||
|
|
||||||
|
// Keep above:
|
||||||
|
// var parent = this.parent;
|
||||||
|
// this.detach();
|
||||||
|
// parent.append(this);
|
||||||
|
|
||||||
|
if (this.scrollable) {
|
||||||
|
this.screen.saveFocus();
|
||||||
|
this.focus();
|
||||||
|
this.scrollTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.show();
|
||||||
|
this.setContent(text);
|
||||||
|
this.screen.render();
|
||||||
|
|
||||||
|
if (time === Infinity || time === -1 || time === 0) {
|
||||||
|
var end = function() {
|
||||||
|
if (end.done) return;
|
||||||
|
end.done = true;
|
||||||
|
if (self.scrollable) {
|
||||||
|
try {
|
||||||
|
self.screen.restoreFocus();
|
||||||
|
} catch (e) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.hide();
|
||||||
|
self.screen.render();
|
||||||
|
if (callback) callback();
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
self.onScreenEvent('keypress', function fn(ch, key) {
|
||||||
|
if (key.name === 'mouse') return;
|
||||||
|
if (self.scrollable) {
|
||||||
|
if ((key.name === 'up' || (self.options.vi && key.name === 'k'))
|
||||||
|
|| (key.name === 'down' || (self.options.vi && key.name === 'j'))
|
||||||
|
|| (self.options.vi && key.name === 'u' && key.ctrl)
|
||||||
|
|| (self.options.vi && key.name === 'd' && key.ctrl)
|
||||||
|
|| (self.options.vi && key.name === 'b' && key.ctrl)
|
||||||
|
|| (self.options.vi && key.name === 'f' && key.ctrl)
|
||||||
|
|| (self.options.vi && key.name === 'g' && !key.shift)
|
||||||
|
|| (self.options.vi && key.name === 'g' && key.shift)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (self.options.ignoreKeys && ~self.options.ignoreKeys.indexOf(key.name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.removeScreenEvent('keypress', fn);
|
||||||
|
end();
|
||||||
|
});
|
||||||
|
if (!self.options.mouse) return;
|
||||||
|
self.onScreenEvent('mouse', function fn(data) {
|
||||||
|
if (data.action === 'mousemove') return;
|
||||||
|
self.removeScreenEvent('mouse', fn);
|
||||||
|
end();
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
self.hide();
|
||||||
|
self.screen.render();
|
||||||
|
if (callback) callback();
|
||||||
|
}, time * 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
Message.prototype.error = function(text, time, callback) {
|
||||||
|
return this.display('{red-fg}Error: ' + text + '{/red-fg}', time, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Message;
|
|
@ -0,0 +1,231 @@
|
||||||
|
/**
|
||||||
|
* node.js - base abstract node for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var EventEmitter = require('../events').EventEmitter;
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Node(options) {
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Node(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
EventEmitter.call(this);
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
this.options = options;
|
||||||
|
this.screen = this.screen
|
||||||
|
|| options.screen
|
||||||
|
|| require('./screen').global
|
||||||
|
|| (function(){throw new Error('No active screen.')})();
|
||||||
|
this.parent = options.parent || null;
|
||||||
|
this.children = [];
|
||||||
|
this.$ = this._ = this.data = {};
|
||||||
|
this.uid = Node.uid++;
|
||||||
|
this.index = -1;
|
||||||
|
|
||||||
|
if (this.type !== 'screen') {
|
||||||
|
this.detached = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.parent) {
|
||||||
|
this.parent.append(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
(options.children || []).forEach(this.append.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
Node.uid = 0;
|
||||||
|
|
||||||
|
Node.prototype.__proto__ = EventEmitter.prototype;
|
||||||
|
|
||||||
|
Node.prototype.type = 'node';
|
||||||
|
|
||||||
|
Node.prototype.insert = function(element, i) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
element.detach();
|
||||||
|
element.parent = this;
|
||||||
|
|
||||||
|
if (i === 0) {
|
||||||
|
this.children.unshift(element);
|
||||||
|
} else if (i === this.children.length) {
|
||||||
|
this.children.push(element);
|
||||||
|
} else {
|
||||||
|
this.children.splice(i, 0, element);
|
||||||
|
}
|
||||||
|
|
||||||
|
element.emit('reparent', this);
|
||||||
|
this.emit('adopt', element);
|
||||||
|
|
||||||
|
(function emit(el) {
|
||||||
|
var n = el.detached !== self.detached;
|
||||||
|
el.detached = self.detached;
|
||||||
|
if (n) el.emit('attach');
|
||||||
|
el.children.forEach(emit);
|
||||||
|
})(element);
|
||||||
|
|
||||||
|
if (!this.screen.focused) {
|
||||||
|
this.screen.focused = element;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.prepend = function(element) {
|
||||||
|
this.insert(element, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.append = function(element) {
|
||||||
|
this.insert(element, this.children.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.insertBefore = function(element, other) {
|
||||||
|
var i = this.children.indexOf(other);
|
||||||
|
if (~i) this.insert(element, i);
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.insertAfter = function(element, other) {
|
||||||
|
var i = this.children.indexOf(other);
|
||||||
|
if (~i) this.insert(element, i + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.remove = function(element) {
|
||||||
|
if (element.parent !== this) return;
|
||||||
|
|
||||||
|
var i = this.children.indexOf(element);
|
||||||
|
if (!~i) return;
|
||||||
|
|
||||||
|
element.clearPos();
|
||||||
|
|
||||||
|
element.parent = null;
|
||||||
|
|
||||||
|
this.children.splice(i, 1);
|
||||||
|
|
||||||
|
i = this.screen.clickable.indexOf(element);
|
||||||
|
if (~i) this.screen.clickable.splice(i, 1);
|
||||||
|
i = this.screen.keyable.indexOf(element);
|
||||||
|
if (~i) this.screen.keyable.splice(i, 1);
|
||||||
|
|
||||||
|
element.emit('reparent', null);
|
||||||
|
this.emit('remove', element);
|
||||||
|
|
||||||
|
(function emit(el) {
|
||||||
|
var n = el.detached !== true;
|
||||||
|
el.detached = true;
|
||||||
|
if (n) el.emit('detach');
|
||||||
|
el.children.forEach(emit);
|
||||||
|
})(element);
|
||||||
|
|
||||||
|
if (this.screen.focused === element) {
|
||||||
|
this.screen.rewindFocus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.detach = function() {
|
||||||
|
if (this.parent) this.parent.remove(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.forDescendants = function(iter, s) {
|
||||||
|
if (s) iter(this);
|
||||||
|
this.children.forEach(function emit(el) {
|
||||||
|
iter(el);
|
||||||
|
el.children.forEach(emit);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.forAncestors = function(iter, s) {
|
||||||
|
var el = this;
|
||||||
|
if (s) iter(this);
|
||||||
|
while (el = el.parent) {
|
||||||
|
iter(el);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.collectDescendants = function(s) {
|
||||||
|
var out = [];
|
||||||
|
this.forDescendants(function(el) {
|
||||||
|
out.push(el);
|
||||||
|
}, s);
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.collectAncestors = function(s) {
|
||||||
|
var out = [];
|
||||||
|
this.forAncestors(function(el) {
|
||||||
|
out.push(el);
|
||||||
|
}, s);
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.emitDescendants = function() {
|
||||||
|
var args = Array.prototype.slice(arguments)
|
||||||
|
, iter;
|
||||||
|
|
||||||
|
if (typeof args[args.length - 1] === 'function') {
|
||||||
|
iter = args.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.forDescendants(function(el) {
|
||||||
|
if (iter) iter(el);
|
||||||
|
el.emit.apply(el, args);
|
||||||
|
}, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.emitAncestors = function() {
|
||||||
|
var args = Array.prototype.slice(arguments)
|
||||||
|
, iter;
|
||||||
|
|
||||||
|
if (typeof args[args.length - 1] === 'function') {
|
||||||
|
iter = args.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.forAncestors(function(el) {
|
||||||
|
if (iter) iter(el);
|
||||||
|
el.emit.apply(el, args);
|
||||||
|
}, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.hasDescendant = function(target) {
|
||||||
|
return (function find(el) {
|
||||||
|
for (var i = 0; i < el.children.length; i++) {
|
||||||
|
if (el.children[i] === target) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (find(el.children[i]) === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.hasAncestor = function(target) {
|
||||||
|
var el = this;
|
||||||
|
while (el = el.parent) {
|
||||||
|
if (el === target) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.get = function(name, value) {
|
||||||
|
if (this.data.hasOwnProperty(name)) {
|
||||||
|
return this.data[name];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.set = function(name, value) {
|
||||||
|
return this.data[name] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Node;
|
|
@ -0,0 +1,155 @@
|
||||||
|
/**
|
||||||
|
* progressbar.js - progress bar element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Input = require('./input');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ProgressBar
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ProgressBar(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new ProgressBar(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
Input.call(this, options);
|
||||||
|
|
||||||
|
this.filled = options.filled || 0;
|
||||||
|
if (typeof this.filled === 'string') {
|
||||||
|
this.filled = +this.filled.slice(0, -1);
|
||||||
|
}
|
||||||
|
this.value = this.filled;
|
||||||
|
|
||||||
|
this.pch = options.pch || ' ';
|
||||||
|
|
||||||
|
// XXX Workaround that predates the usage of `el.ch`.
|
||||||
|
if (options.ch) {
|
||||||
|
this.pch = options.ch;
|
||||||
|
this.ch = ' ';
|
||||||
|
}
|
||||||
|
if (options.bch) {
|
||||||
|
this.ch = options.bch;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.style.bar) {
|
||||||
|
this.style.bar = {};
|
||||||
|
this.style.bar.fg = options.barFg;
|
||||||
|
this.style.bar.bg = options.barBg;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.orientation = options.orientation || 'horizontal';
|
||||||
|
|
||||||
|
if (options.keys) {
|
||||||
|
this.on('keypress', function(ch, key) {
|
||||||
|
var back, forward;
|
||||||
|
if (self.orientation === 'horizontal') {
|
||||||
|
back = ['left', 'h'];
|
||||||
|
forward = ['right', 'l'];
|
||||||
|
} else if (self.orientation === 'vertical') {
|
||||||
|
back = ['down', 'j'];
|
||||||
|
forward = ['up', 'k'];
|
||||||
|
}
|
||||||
|
if (key.name === back[0] || (options.vi && key.name === back[1])) {
|
||||||
|
self.progress(-5);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key.name === forward[0] || (options.vi && key.name === forward[1])) {
|
||||||
|
self.progress(5);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.mouse) {
|
||||||
|
this.on('click', function(data) {
|
||||||
|
var x, y, m, p;
|
||||||
|
if (!self.lpos) return;
|
||||||
|
if (self.orientation === 'horizontal') {
|
||||||
|
x = data.x - self.lpos.xi;
|
||||||
|
m = (self.lpos.xl - self.lpos.xi) - self.iwidth;
|
||||||
|
p = x / m * 100 | 0;
|
||||||
|
} else if (self.orientation === 'vertical') {
|
||||||
|
y = data.y - self.lpos.yi;
|
||||||
|
m = (self.lpos.yl - self.lpos.yi) - self.iheight;
|
||||||
|
p = y / m * 100 | 0;
|
||||||
|
}
|
||||||
|
self.setProgress(p);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressBar.prototype.__proto__ = Input.prototype;
|
||||||
|
|
||||||
|
ProgressBar.prototype.type = 'progress-bar';
|
||||||
|
|
||||||
|
ProgressBar.prototype.render = function() {
|
||||||
|
var ret = this._render();
|
||||||
|
if (!ret) return;
|
||||||
|
|
||||||
|
var xi = ret.xi
|
||||||
|
, xl = ret.xl
|
||||||
|
, yi = ret.yi
|
||||||
|
, yl = ret.yl
|
||||||
|
, dattr;
|
||||||
|
|
||||||
|
if (this.border) xi++, yi++, xl--, yl--;
|
||||||
|
|
||||||
|
if (this.orientation === 'horizontal') {
|
||||||
|
xl = xi + ((xl - xi) * (this.filled / 100)) | 0;
|
||||||
|
} else if (this.orientation === 'vertical') {
|
||||||
|
yi = yi + ((yl - yi) - (((yl - yi) * (this.filled / 100)) | 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
dattr = this.sattr(this.style.bar);
|
||||||
|
|
||||||
|
this.screen.fillRegion(dattr, this.pch, xi, xl, yi, yl);
|
||||||
|
|
||||||
|
if (this.content) {
|
||||||
|
var line = this.screen.lines[yi];
|
||||||
|
for (var i = 0; i < this.content.length; i++) {
|
||||||
|
line[xi + i][1] = this.content[i];
|
||||||
|
}
|
||||||
|
line.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
ProgressBar.prototype.progress = function(filled) {
|
||||||
|
this.filled += filled;
|
||||||
|
if (this.filled < 0) this.filled = 0;
|
||||||
|
else if (this.filled > 100) this.filled = 100;
|
||||||
|
if (this.filled === 100) {
|
||||||
|
this.emit('complete');
|
||||||
|
}
|
||||||
|
this.value = this.filled;
|
||||||
|
};
|
||||||
|
|
||||||
|
ProgressBar.prototype.setProgress = function(filled) {
|
||||||
|
this.filled = 0;
|
||||||
|
this.progress(filled);
|
||||||
|
};
|
||||||
|
|
||||||
|
ProgressBar.prototype.reset = function() {
|
||||||
|
this.emit('reset');
|
||||||
|
this.filled = 0;
|
||||||
|
this.value = this.filled;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = ProgressBar;
|
|
@ -0,0 +1,120 @@
|
||||||
|
/**
|
||||||
|
* prompt.js - prompt element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
var Button = require('./button');
|
||||||
|
var Textbox = require('./textbox');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Prompt(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Prompt(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
options.hidden = true;
|
||||||
|
|
||||||
|
Box.call(this, options);
|
||||||
|
|
||||||
|
this._.input = new Textbox({
|
||||||
|
parent: this,
|
||||||
|
top: 3,
|
||||||
|
height: 1,
|
||||||
|
left: 2,
|
||||||
|
right: 2,
|
||||||
|
bg: 'black'
|
||||||
|
});
|
||||||
|
|
||||||
|
this._.okay = new Button({
|
||||||
|
parent: this,
|
||||||
|
top: 5,
|
||||||
|
height: 1,
|
||||||
|
left: 2,
|
||||||
|
width: 6,
|
||||||
|
content: 'Okay',
|
||||||
|
align: 'center',
|
||||||
|
bg: 'black',
|
||||||
|
hoverBg: 'blue',
|
||||||
|
autoFocus: false,
|
||||||
|
mouse: true
|
||||||
|
});
|
||||||
|
|
||||||
|
this._.cancel = new Button({
|
||||||
|
parent: this,
|
||||||
|
top: 5,
|
||||||
|
height: 1,
|
||||||
|
shrink: true,
|
||||||
|
left: 10,
|
||||||
|
width: 8,
|
||||||
|
content: 'Cancel',
|
||||||
|
align: 'center',
|
||||||
|
bg: 'black',
|
||||||
|
hoverBg: 'blue',
|
||||||
|
autoFocus: false,
|
||||||
|
mouse: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Prompt.prototype.__proto__ = Box.prototype;
|
||||||
|
|
||||||
|
Prompt.prototype.type = 'prompt';
|
||||||
|
|
||||||
|
Prompt.prototype.input =
|
||||||
|
Prompt.prototype.setInput =
|
||||||
|
Prompt.prototype.readInput = function(text, value, callback) {
|
||||||
|
var self = this;
|
||||||
|
var okay, cancel;
|
||||||
|
|
||||||
|
if (!callback) {
|
||||||
|
callback = value;
|
||||||
|
value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep above:
|
||||||
|
// var parent = this.parent;
|
||||||
|
// this.detach();
|
||||||
|
// parent.append(this);
|
||||||
|
|
||||||
|
this.show();
|
||||||
|
this.setContent(' ' + text);
|
||||||
|
|
||||||
|
this._.input.value = value;
|
||||||
|
|
||||||
|
this.screen.saveFocus();
|
||||||
|
|
||||||
|
this._.okay.on('press', okay = function() {
|
||||||
|
self._.input.submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._.cancel.on('press', cancel = function() {
|
||||||
|
self._.input.cancel();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._.input.readInput(function(err, data) {
|
||||||
|
self.hide();
|
||||||
|
self.screen.restoreFocus();
|
||||||
|
self._.okay.removeListener('press', okay);
|
||||||
|
self._.cancel.removeListener('press', cancel);
|
||||||
|
return callback(err, data);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.screen.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Prompt;
|
|
@ -0,0 +1,116 @@
|
||||||
|
/**
|
||||||
|
* question.js - question element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
var Button = require('./button');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Question
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Question(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Question(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
options.hidden = true;
|
||||||
|
|
||||||
|
Box.call(this, options);
|
||||||
|
|
||||||
|
this._.okay = new Button({
|
||||||
|
screen: this.screen,
|
||||||
|
parent: this,
|
||||||
|
top: 2,
|
||||||
|
height: 1,
|
||||||
|
left: 2,
|
||||||
|
width: 6,
|
||||||
|
content: 'Okay',
|
||||||
|
align: 'center',
|
||||||
|
bg: 'black',
|
||||||
|
hoverBg: 'blue',
|
||||||
|
autoFocus: false,
|
||||||
|
mouse: true
|
||||||
|
});
|
||||||
|
|
||||||
|
this._.cancel = new Button({
|
||||||
|
screen: this.screen,
|
||||||
|
parent: this,
|
||||||
|
top: 2,
|
||||||
|
height: 1,
|
||||||
|
shrink: true,
|
||||||
|
left: 10,
|
||||||
|
width: 8,
|
||||||
|
content: 'Cancel',
|
||||||
|
align: 'center',
|
||||||
|
bg: 'black',
|
||||||
|
hoverBg: 'blue',
|
||||||
|
autoFocus: false,
|
||||||
|
mouse: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Question.prototype.__proto__ = Box.prototype;
|
||||||
|
|
||||||
|
Question.prototype.type = 'question';
|
||||||
|
|
||||||
|
Question.prototype.ask = function(text, callback) {
|
||||||
|
var self = this;
|
||||||
|
var press, okay, cancel;
|
||||||
|
|
||||||
|
// Keep above:
|
||||||
|
// var parent = this.parent;
|
||||||
|
// this.detach();
|
||||||
|
// parent.append(this);
|
||||||
|
|
||||||
|
this.show();
|
||||||
|
this.setContent(' ' + text);
|
||||||
|
|
||||||
|
this.onScreenEvent('keypress', press = function(ch, key) {
|
||||||
|
if (key.name === 'mouse') return;
|
||||||
|
if (key.name !== 'enter'
|
||||||
|
&& key.name !== 'escape'
|
||||||
|
&& key.name !== 'q'
|
||||||
|
&& key.name !== 'y'
|
||||||
|
&& key.name !== 'n') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
done(null, key.name === 'enter' || key.name === 'y');
|
||||||
|
});
|
||||||
|
|
||||||
|
this._.okay.on('press', okay = function() {
|
||||||
|
done(null, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
this._.cancel.on('press', cancel = function() {
|
||||||
|
done(null, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.screen.saveFocus();
|
||||||
|
this.focus();
|
||||||
|
|
||||||
|
function done(err, data) {
|
||||||
|
self.hide();
|
||||||
|
self.screen.restoreFocus();
|
||||||
|
self.removeScreenEvent('keypress', press);
|
||||||
|
self._.okay.removeListener('press', okay);
|
||||||
|
self._.cancel.removeListener('press', cancel);
|
||||||
|
return callback(err, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.screen.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Question;
|
|
@ -0,0 +1,59 @@
|
||||||
|
/**
|
||||||
|
* radiobutton.js - radio button element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Checkbox = require('./checkbox');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RadioButton
|
||||||
|
*/
|
||||||
|
|
||||||
|
function RadioButton(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new RadioButton(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
Checkbox.call(this, options);
|
||||||
|
|
||||||
|
this.on('check', function() {
|
||||||
|
var el = self;
|
||||||
|
while (el = el.parent) {
|
||||||
|
if (el.type === 'radio-set'
|
||||||
|
|| el.type === 'form') break;
|
||||||
|
}
|
||||||
|
el = el || self.parent;
|
||||||
|
el.forDescendants(function(el) {
|
||||||
|
if (el.type !== 'radio-button' || el === self) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
el.uncheck();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
RadioButton.prototype.__proto__ = Checkbox.prototype;
|
||||||
|
|
||||||
|
RadioButton.prototype.type = 'radio-button';
|
||||||
|
|
||||||
|
RadioButton.prototype.render = function() {
|
||||||
|
this.clearPos(true);
|
||||||
|
this.setContent('(' + (this.checked ? '*' : ' ') + ') ' + this.text, true);
|
||||||
|
return this._render();
|
||||||
|
};
|
||||||
|
|
||||||
|
RadioButton.prototype.toggle = RadioButton.prototype.check;
|
||||||
|
|
||||||
|
module.exports = RadioButton;
|
|
@ -0,0 +1,34 @@
|
||||||
|
/**
|
||||||
|
* radioset.js - radio set element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RadioSet
|
||||||
|
*/
|
||||||
|
|
||||||
|
function RadioSet(options) {
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new RadioSet(options);
|
||||||
|
}
|
||||||
|
options = options || {};
|
||||||
|
// Possibly inherit parent's style.
|
||||||
|
// options.style = this.parent.style;
|
||||||
|
Box.call(this, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
RadioSet.prototype.__proto__ = Box.prototype;
|
||||||
|
|
||||||
|
RadioSet.prototype.type = 'radio-set';
|
||||||
|
|
||||||
|
module.exports = RadioSet;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,387 @@
|
||||||
|
/**
|
||||||
|
* scrollablebox.js - scrollable box element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ScrollableBox
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ScrollableBox(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new ScrollableBox(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
Box.call(this, options);
|
||||||
|
|
||||||
|
if (options.scrollable === false) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollable = true;
|
||||||
|
this.childOffset = 0;
|
||||||
|
this.childBase = 0;
|
||||||
|
this.baseLimit = options.baseLimit || Infinity;
|
||||||
|
this.alwaysScroll = options.alwaysScroll;
|
||||||
|
|
||||||
|
this.scrollbar = options.scrollbar;
|
||||||
|
if (this.scrollbar) {
|
||||||
|
this.scrollbar.ch = this.scrollbar.ch || ' ';
|
||||||
|
this.style.scrollbar = this.style.scrollbar || this.scrollbar.style;
|
||||||
|
if (!this.style.scrollbar) {
|
||||||
|
this.style.scrollbar = {};
|
||||||
|
this.style.scrollbar.fg = this.scrollbar.fg;
|
||||||
|
this.style.scrollbar.bg = this.scrollbar.bg;
|
||||||
|
this.style.scrollbar.bold = this.scrollbar.bold;
|
||||||
|
this.style.scrollbar.underline = this.scrollbar.underline;
|
||||||
|
this.style.scrollbar.inverse = this.scrollbar.inverse;
|
||||||
|
this.style.scrollbar.invisible = this.scrollbar.invisible;
|
||||||
|
}
|
||||||
|
//this.scrollbar.style = this.style.scrollbar;
|
||||||
|
if (this.track || this.scrollbar.track) {
|
||||||
|
this.track = this.scrollbar.track || this.track;
|
||||||
|
this.style.track = this.style.scrollbar.track || this.style.track;
|
||||||
|
this.track.ch = this.track.ch || ' ';
|
||||||
|
this.style.track = this.style.track || this.track.style;
|
||||||
|
if (!this.style.track) {
|
||||||
|
this.style.track = {};
|
||||||
|
this.style.track.fg = this.track.fg;
|
||||||
|
this.style.track.bg = this.track.bg;
|
||||||
|
this.style.track.bold = this.track.bold;
|
||||||
|
this.style.track.underline = this.track.underline;
|
||||||
|
this.style.track.inverse = this.track.inverse;
|
||||||
|
this.style.track.invisible = this.track.invisible;
|
||||||
|
}
|
||||||
|
this.track.style = this.style.track;
|
||||||
|
}
|
||||||
|
// Allow controlling of the scrollbar via the mouse:
|
||||||
|
if (options.mouse) {
|
||||||
|
this.on('mousedown', function(data) {
|
||||||
|
if (self._scrollingBar) {
|
||||||
|
// Do not allow dragging on the scrollbar:
|
||||||
|
delete self.screen._dragging;
|
||||||
|
delete self._drag;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var x = data.x - self.aleft;
|
||||||
|
var y = data.y - self.atop;
|
||||||
|
if (x === self.width - self.iright - 1) {
|
||||||
|
// Do not allow dragging on the scrollbar:
|
||||||
|
delete self.screen._dragging;
|
||||||
|
delete self._drag;
|
||||||
|
var perc = (y - self.itop) / (self.height - self.iheight);
|
||||||
|
self.setScrollPerc(perc * 100 | 0);
|
||||||
|
self.screen.render();
|
||||||
|
var smd, smu;
|
||||||
|
self._scrollingBar = true;
|
||||||
|
self.onScreenEvent('mousedown', smd = function(data) {
|
||||||
|
var y = data.y - self.atop;
|
||||||
|
var perc = y / self.height;
|
||||||
|
self.setScrollPerc(perc * 100 | 0);
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
// If mouseup occurs out of the window, no mouseup event fires, and
|
||||||
|
// scrollbar will drag again on mousedown until another mouseup
|
||||||
|
// occurs.
|
||||||
|
self.onScreenEvent('mouseup', smu = function(data) {
|
||||||
|
self._scrollingBar = false;
|
||||||
|
self.removeScreenEvent('mousedown', smd);
|
||||||
|
self.removeScreenEvent('mouseup', smu);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.mouse) {
|
||||||
|
this.on('wheeldown', function(el, data) {
|
||||||
|
self.scroll(self.height / 2 | 0 || 1);
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
this.on('wheelup', function(el, data) {
|
||||||
|
self.scroll(-(self.height / 2 | 0) || -1);
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.keys && !options.ignoreKeys) {
|
||||||
|
this.on('keypress', function(ch, key) {
|
||||||
|
if (key.name === 'up' || (options.vi && key.name === 'k')) {
|
||||||
|
self.scroll(-1);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key.name === 'down' || (options.vi && key.name === 'j')) {
|
||||||
|
self.scroll(1);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.vi && key.name === 'u' && key.ctrl) {
|
||||||
|
self.scroll(-(self.height / 2 | 0) || -1);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.vi && key.name === 'd' && key.ctrl) {
|
||||||
|
self.scroll(self.height / 2 | 0 || 1);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.vi && key.name === 'b' && key.ctrl) {
|
||||||
|
self.scroll(-self.height || -1);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.vi && key.name === 'f' && key.ctrl) {
|
||||||
|
self.scroll(self.height || 1);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.vi && key.name === 'g' && !key.shift) {
|
||||||
|
self.scrollTo(0);
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.vi && key.name === 'g' && key.shift) {
|
||||||
|
self.scrollTo(self.getScrollHeight());
|
||||||
|
self.screen.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.on('parsed content', function() {
|
||||||
|
self._recalculateIndex();
|
||||||
|
});
|
||||||
|
|
||||||
|
self._recalculateIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollableBox.prototype.__proto__ = Box.prototype;
|
||||||
|
|
||||||
|
ScrollableBox.prototype.type = 'scrollable-box';
|
||||||
|
|
||||||
|
// XXX Potentially use this in place of scrollable checks elsewhere.
|
||||||
|
ScrollableBox.prototype.__defineGetter__('reallyScrollable', function() {
|
||||||
|
if (this.shrink) return this.scrollable;
|
||||||
|
return this.getScrollHeight() > this.height;
|
||||||
|
});
|
||||||
|
|
||||||
|
ScrollableBox.prototype._scrollBottom = function() {
|
||||||
|
if (!this.scrollable) return 0;
|
||||||
|
|
||||||
|
// We could just calculate the children, but we can
|
||||||
|
// optimize for lists by just returning the items.length.
|
||||||
|
if (this._isList) {
|
||||||
|
return this.items ? this.items.length : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.lpos && this.lpos._scrollBottom) {
|
||||||
|
return this.lpos._scrollBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bottom = this.children.reduce(function(current, el) {
|
||||||
|
// el.height alone does not calculate the shrunken height, we need to use
|
||||||
|
// getCoords. A shrunken box inside a scrollable element will not grow any
|
||||||
|
// larger than the scrollable element's context regardless of how much
|
||||||
|
// content is in the shrunken box, unless we do this (call getCoords
|
||||||
|
// without the scrollable calculation):
|
||||||
|
// See: $ node test/widget-shrink-fail-2.js
|
||||||
|
if (!el.detached) {
|
||||||
|
var lpos = el._getCoords(false, true);
|
||||||
|
if (lpos) {
|
||||||
|
return Math.max(current, el.rtop + (lpos.yl - lpos.yi));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Math.max(current, el.rtop + el.height);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// XXX Use this? Makes .getScrollHeight() useless!
|
||||||
|
// if (bottom < this._clines.length) bottom = this._clines.length;
|
||||||
|
|
||||||
|
if (this.lpos) this.lpos._scrollBottom = bottom;
|
||||||
|
|
||||||
|
return bottom;
|
||||||
|
};
|
||||||
|
|
||||||
|
ScrollableBox.prototype.setScroll =
|
||||||
|
ScrollableBox.prototype.scrollTo = function(offset, always) {
|
||||||
|
// XXX
|
||||||
|
// At first, this appeared to account for the first new calculation of childBase:
|
||||||
|
this.scroll(0);
|
||||||
|
return this.scroll(offset - (this.childBase + this.childOffset), always);
|
||||||
|
};
|
||||||
|
|
||||||
|
ScrollableBox.prototype.getScroll = function() {
|
||||||
|
return this.childBase + this.childOffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
ScrollableBox.prototype.scroll = function(offset, always) {
|
||||||
|
if (!this.scrollable) return;
|
||||||
|
|
||||||
|
if (this.detached) return;
|
||||||
|
|
||||||
|
// Handle scrolling.
|
||||||
|
var visible = this.height - this.iheight
|
||||||
|
, base = this.childBase
|
||||||
|
, d
|
||||||
|
, p
|
||||||
|
, t
|
||||||
|
, b
|
||||||
|
, max
|
||||||
|
, emax;
|
||||||
|
|
||||||
|
if (this.alwaysScroll || always) {
|
||||||
|
// Semi-workaround
|
||||||
|
this.childOffset = offset > 0
|
||||||
|
? visible - 1 + offset
|
||||||
|
: offset;
|
||||||
|
} else {
|
||||||
|
this.childOffset += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.childOffset > visible - 1) {
|
||||||
|
d = this.childOffset - (visible - 1);
|
||||||
|
this.childOffset -= d;
|
||||||
|
this.childBase += d;
|
||||||
|
} else if (this.childOffset < 0) {
|
||||||
|
d = this.childOffset;
|
||||||
|
this.childOffset += -d;
|
||||||
|
this.childBase += d;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.childBase < 0) {
|
||||||
|
this.childBase = 0;
|
||||||
|
} else if (this.childBase > this.baseLimit) {
|
||||||
|
this.childBase = this.baseLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find max "bottom" value for
|
||||||
|
// content and descendant elements.
|
||||||
|
// Scroll the content if necessary.
|
||||||
|
if (this.childBase === base) {
|
||||||
|
return this.emit('scroll');
|
||||||
|
}
|
||||||
|
|
||||||
|
// When scrolling text, we want to be able to handle SGR codes as well as line
|
||||||
|
// feeds. This allows us to take preformatted text output from other programs
|
||||||
|
// and put it in a scrollable text box.
|
||||||
|
this.parseContent();
|
||||||
|
|
||||||
|
// XXX
|
||||||
|
// max = this.getScrollHeight() - (this.height - this.iheight);
|
||||||
|
|
||||||
|
max = this._clines.length - (this.height - this.iheight);
|
||||||
|
if (max < 0) max = 0;
|
||||||
|
emax = this._scrollBottom() - (this.height - this.iheight);
|
||||||
|
if (emax < 0) emax = 0;
|
||||||
|
|
||||||
|
this.childBase = Math.min(this.childBase, Math.max(emax, max));
|
||||||
|
|
||||||
|
if (this.childBase < 0) {
|
||||||
|
this.childBase = 0;
|
||||||
|
} else if (this.childBase > this.baseLimit) {
|
||||||
|
this.childBase = this.baseLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimize scrolling with CSR + IL/DL.
|
||||||
|
p = this.lpos;
|
||||||
|
// Only really need _getCoords() if we want
|
||||||
|
// to allow nestable scrolling elements...
|
||||||
|
// or if we **really** want shrinkable
|
||||||
|
// scrolling elements.
|
||||||
|
// p = this._getCoords();
|
||||||
|
if (p && this.childBase !== base && this.screen.cleanSides(this)) {
|
||||||
|
t = p.yi + this.itop;
|
||||||
|
b = p.yl - this.ibottom - 1;
|
||||||
|
d = this.childBase - base;
|
||||||
|
|
||||||
|
if (d > 0 && d < visible) {
|
||||||
|
// scrolled down
|
||||||
|
this.screen.deleteLine(d, t, t, b);
|
||||||
|
} else if (d < 0 && -d < visible) {
|
||||||
|
// scrolled up
|
||||||
|
d = -d;
|
||||||
|
this.screen.insertLine(d, t, t, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.emit('scroll');
|
||||||
|
};
|
||||||
|
|
||||||
|
ScrollableBox.prototype._recalculateIndex = function() {
|
||||||
|
var max, emax;
|
||||||
|
|
||||||
|
if (this.detached || !this.scrollable) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX
|
||||||
|
// max = this.getScrollHeight() - (this.height - this.iheight);
|
||||||
|
|
||||||
|
max = this._clines.length - (this.height - this.iheight);
|
||||||
|
if (max < 0) max = 0;
|
||||||
|
emax = this._scrollBottom() - (this.height - this.iheight);
|
||||||
|
if (emax < 0) emax = 0;
|
||||||
|
|
||||||
|
this.childBase = Math.min(this.childBase, Math.max(emax, max));
|
||||||
|
|
||||||
|
if (this.childBase < 0) {
|
||||||
|
this.childBase = 0;
|
||||||
|
} else if (this.childBase > this.baseLimit) {
|
||||||
|
this.childBase = this.baseLimit;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ScrollableBox.prototype.resetScroll = function() {
|
||||||
|
if (!this.scrollable) return;
|
||||||
|
this.childOffset = 0;
|
||||||
|
this.childBase = 0;
|
||||||
|
return this.emit('scroll');
|
||||||
|
};
|
||||||
|
|
||||||
|
ScrollableBox.prototype.getScrollHeight = function() {
|
||||||
|
return Math.max(this._clines.length, this._scrollBottom());
|
||||||
|
};
|
||||||
|
|
||||||
|
ScrollableBox.prototype.getScrollPerc = function(s) {
|
||||||
|
var pos = this.lpos || this._getCoords();
|
||||||
|
if (!pos) return s ? -1 : 0;
|
||||||
|
|
||||||
|
var height = (pos.yl - pos.yi) - this.iheight
|
||||||
|
, i = this.getScrollHeight()
|
||||||
|
, p;
|
||||||
|
|
||||||
|
if (height < i) {
|
||||||
|
if (this.alwaysScroll) {
|
||||||
|
p = this.childBase / (i - height);
|
||||||
|
} else {
|
||||||
|
p = (this.childBase + this.childOffset) / (i - 1);
|
||||||
|
}
|
||||||
|
return p * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return s ? -1 : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
ScrollableBox.prototype.setScrollPerc = function(i) {
|
||||||
|
// XXX
|
||||||
|
// var m = this.getScrollHeight();
|
||||||
|
var m = Math.max(this._clines.length, this._scrollBottom());
|
||||||
|
return this.scrollTo((i / 100) * m | 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = ScrollableBox;
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* scrollabletext.js - scrollable text element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var ScrollableBox = require('./scrollablebox');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ScrollableText
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ScrollableText(options) {
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new ScrollableText(options);
|
||||||
|
}
|
||||||
|
options = options || {};
|
||||||
|
options.alwaysScroll = true;
|
||||||
|
ScrollableBox.call(this, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollableText.prototype.__proto__ = ScrollableBox.prototype;
|
||||||
|
|
||||||
|
ScrollableText.prototype.type = 'scrollable-text';
|
||||||
|
|
||||||
|
module.exports = ScrollableText;
|
|
@ -0,0 +1,330 @@
|
||||||
|
/**
|
||||||
|
* table.js - table element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Table(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Table(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
options.shrink = true;
|
||||||
|
options.style = options.style || {};
|
||||||
|
options.style.border = options.style.border || {};
|
||||||
|
options.style.header = options.style.header || {};
|
||||||
|
options.style.cell = options.style.cell || {};
|
||||||
|
options.align = options.align || 'center';
|
||||||
|
|
||||||
|
// Regular tables do not get custom height (this would
|
||||||
|
// require extra padding). Maybe add in the future.
|
||||||
|
delete options.height;
|
||||||
|
|
||||||
|
Box.call(this, options);
|
||||||
|
|
||||||
|
this.pad = options.pad != null
|
||||||
|
? options.pad
|
||||||
|
: 2;
|
||||||
|
|
||||||
|
this.setData(options.rows || options.data);
|
||||||
|
|
||||||
|
this.on('resize', function() {
|
||||||
|
self.setContent('');
|
||||||
|
self.setData(self.rows);
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Table.prototype.__proto__ = Box.prototype;
|
||||||
|
|
||||||
|
Table.prototype.type = 'table';
|
||||||
|
|
||||||
|
Table.prototype._calculateMaxes = function() {
|
||||||
|
var self = this;
|
||||||
|
var maxes = [];
|
||||||
|
|
||||||
|
this.rows.forEach(function(row) {
|
||||||
|
row.forEach(function(cell, i) {
|
||||||
|
var clen = self.strWidth(cell);
|
||||||
|
if (!maxes[i] || maxes[i] < clen) {
|
||||||
|
maxes[i] = clen;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var total = maxes.reduce(function(total, max) {
|
||||||
|
return total + max;
|
||||||
|
}, 0);
|
||||||
|
total += maxes.length + 1;
|
||||||
|
|
||||||
|
// XXX There might be an issue with resizing where on the first resize event
|
||||||
|
// width appears to be less than total if it's a percentage or left/right
|
||||||
|
// combination.
|
||||||
|
if (this.width < total) {
|
||||||
|
delete this.position.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.position.width != null) {
|
||||||
|
var missing = this.width - total;
|
||||||
|
var w = missing / maxes.length | 0;
|
||||||
|
var wr = missing % maxes.length;
|
||||||
|
maxes = maxes.map(function(max, i) {
|
||||||
|
if (i === maxes.length - 1) {
|
||||||
|
return max + w + wr;
|
||||||
|
}
|
||||||
|
return max + w;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
maxes = maxes.map(function(max) {
|
||||||
|
return max + self.pad;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._maxes = maxes;
|
||||||
|
};
|
||||||
|
|
||||||
|
Table.prototype.setRows =
|
||||||
|
Table.prototype.setData = function(rows) {
|
||||||
|
var self = this
|
||||||
|
, text = ''
|
||||||
|
, line = ''
|
||||||
|
, align = this.align;
|
||||||
|
|
||||||
|
this.rows = rows || [];
|
||||||
|
|
||||||
|
this._calculateMaxes();
|
||||||
|
|
||||||
|
this.rows.forEach(function(row, i) {
|
||||||
|
var isHeader = i === 0;
|
||||||
|
var isFooter = i === self.rows.length - 1;
|
||||||
|
row.forEach(function(cell, i) {
|
||||||
|
var width = self._maxes[i];
|
||||||
|
var clen = self.strWidth(cell);
|
||||||
|
|
||||||
|
if (i !== 0) {
|
||||||
|
text += ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
while (clen < width) {
|
||||||
|
if (align === 'center') {
|
||||||
|
cell = ' ' + cell + ' ';
|
||||||
|
clen += 2;
|
||||||
|
} else if (align === 'left') {
|
||||||
|
cell = cell + ' ';
|
||||||
|
clen += 1;
|
||||||
|
} else if (align === 'right') {
|
||||||
|
cell = ' ' + cell;
|
||||||
|
clen += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clen > width) {
|
||||||
|
if (align === 'center') {
|
||||||
|
cell = cell.substring(1);
|
||||||
|
clen--;
|
||||||
|
} else if (align === 'left') {
|
||||||
|
cell = cell.slice(0, -1);
|
||||||
|
clen--;
|
||||||
|
} else if (align === 'right') {
|
||||||
|
cell = cell.substring(1);
|
||||||
|
clen--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text += cell;
|
||||||
|
});
|
||||||
|
if (!isFooter) {
|
||||||
|
text += '\n\n';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
delete this.align;
|
||||||
|
this.setContent(text);
|
||||||
|
this.align = align;
|
||||||
|
};
|
||||||
|
|
||||||
|
Table.prototype.render = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var coords = this._render();
|
||||||
|
if (!coords) return;
|
||||||
|
|
||||||
|
this._calculateMaxes();
|
||||||
|
|
||||||
|
if (!this._maxes) return coords;
|
||||||
|
|
||||||
|
var lines = this.screen.lines
|
||||||
|
, xi = coords.xi
|
||||||
|
, xl = coords.xl
|
||||||
|
, yi = coords.yi
|
||||||
|
, yl = coords.yl
|
||||||
|
, rx
|
||||||
|
, ry
|
||||||
|
, i;
|
||||||
|
|
||||||
|
var dattr = this.sattr(this.style)
|
||||||
|
, hattr = this.sattr(this.style.header)
|
||||||
|
, cattr = this.sattr(this.style.cell)
|
||||||
|
, battr = this.sattr(this.style.border);
|
||||||
|
|
||||||
|
var width = coords.xl - coords.xi - this.iright
|
||||||
|
, height = coords.yl - coords.yi - this.ibottom;
|
||||||
|
|
||||||
|
// Apply attributes to header cells and cells.
|
||||||
|
for (var y = this.itop; y < height; y++) {
|
||||||
|
if (!lines[yi + y]) break;
|
||||||
|
for (var x = this.ileft; x < width; x++) {
|
||||||
|
if (!lines[yi + y][xi + x]) break;
|
||||||
|
// Check to see if it's not the default attr. Allows for tags:
|
||||||
|
if (lines[yi + y][xi + x][0] !== dattr) continue;
|
||||||
|
if (y === this.itop) {
|
||||||
|
lines[yi + y][xi + x][0] = hattr;
|
||||||
|
} else {
|
||||||
|
lines[yi + y][xi + x][0] = cattr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.border || this.options.noCellBorders) return coords;
|
||||||
|
|
||||||
|
// Draw border with correct angles.
|
||||||
|
ry = 0;
|
||||||
|
for (i = 0; i < self.rows.length + 1; i++) {
|
||||||
|
if (!lines[yi + ry]) break;
|
||||||
|
rx = 0;
|
||||||
|
self._maxes.forEach(function(max, i) {
|
||||||
|
rx += max;
|
||||||
|
if (i === 0) {
|
||||||
|
if (!lines[yi + ry][xi + 0]) return;
|
||||||
|
// left side
|
||||||
|
if (ry === 0) {
|
||||||
|
// top
|
||||||
|
lines[yi + ry][xi + 0][0] = battr;
|
||||||
|
// lines[yi + ry][xi + 0][1] = '\u250c'; // '┌'
|
||||||
|
} else if (ry / 2 === self.rows.length) {
|
||||||
|
// bottom
|
||||||
|
lines[yi + ry][xi + 0][0] = battr;
|
||||||
|
// lines[yi + ry][xi + 0][1] = '\u2514'; // '└'
|
||||||
|
} else {
|
||||||
|
// middle
|
||||||
|
lines[yi + ry][xi + 0][0] = battr;
|
||||||
|
lines[yi + ry][xi + 0][1] = '\u251c'; // '├'
|
||||||
|
// XXX If we alter iwidth and ileft for no borders - nothing should be written here
|
||||||
|
if (!self.border.left) {
|
||||||
|
lines[yi + ry][xi + 0][1] = '\u2500'; // '─'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (i === self._maxes.length - 1) {
|
||||||
|
if (!lines[yi + ry][xi + rx + 1]) return;
|
||||||
|
// right side
|
||||||
|
if (ry === 0) {
|
||||||
|
// top
|
||||||
|
lines[yi + ry][xi + ++rx][0] = battr;
|
||||||
|
// lines[yi + ry][xi + rx][1] = '\u2510'; // '┐'
|
||||||
|
} else if (ry / 2 === self.rows.length) {
|
||||||
|
// bottom
|
||||||
|
lines[yi + ry][xi + ++rx][0] = battr;
|
||||||
|
// lines[yi + ry][xi + rx][1] = '\u2518'; // '┘'
|
||||||
|
} else {
|
||||||
|
// middle
|
||||||
|
lines[yi + ry][xi + ++rx][0] = battr;
|
||||||
|
lines[yi + ry][xi + rx][1] = '\u2524'; // '┤'
|
||||||
|
// XXX If we alter iwidth and iright for no borders - nothing should be written here
|
||||||
|
if (!self.border.right) {
|
||||||
|
lines[yi + ry][xi + rx][1] = '\u2500'; // '─'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!lines[yi + ry][xi + rx + 1]) return;
|
||||||
|
// center
|
||||||
|
if (ry === 0) {
|
||||||
|
// top
|
||||||
|
lines[yi + ry][xi + ++rx][0] = battr;
|
||||||
|
lines[yi + ry][xi + rx][1] = '\u252c'; // '┬'
|
||||||
|
// XXX If we alter iheight and itop for no borders - nothing should be written here
|
||||||
|
if (!self.border.top) {
|
||||||
|
lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
|
||||||
|
}
|
||||||
|
} else if (ry / 2 === self.rows.length) {
|
||||||
|
// bottom
|
||||||
|
lines[yi + ry][xi + ++rx][0] = battr;
|
||||||
|
lines[yi + ry][xi + rx][1] = '\u2534'; // '┴'
|
||||||
|
// XXX If we alter iheight and ibottom for no borders - nothing should be written here
|
||||||
|
if (!self.border.bottom) {
|
||||||
|
lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// middle
|
||||||
|
if (self.options.fillCellBorders) {
|
||||||
|
var lbg = (ry <= 2 ? hattr : cattr) & 0x1ff;
|
||||||
|
lines[yi + ry][xi + ++rx][0] = (battr & ~0x1ff) | lbg;
|
||||||
|
} else {
|
||||||
|
lines[yi + ry][xi + ++rx][0] = battr;
|
||||||
|
}
|
||||||
|
lines[yi + ry][xi + rx][1] = '\u253c'; // '┼'
|
||||||
|
// ++rx;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ry += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw internal borders.
|
||||||
|
for (ry = 1; ry < self.rows.length * 2; ry++) {
|
||||||
|
if (!lines[yi + ry]) break;
|
||||||
|
rx = 0;
|
||||||
|
self._maxes.slice(0, -1).forEach(function(max, i) {
|
||||||
|
rx += max;
|
||||||
|
if (!lines[yi + ry][xi + rx + 1]) return;
|
||||||
|
if (ry % 2 !== 0) {
|
||||||
|
if (self.options.fillCellBorders) {
|
||||||
|
var lbg = (ry <= 2 ? hattr : cattr) & 0x1ff;
|
||||||
|
lines[yi + ry][xi + ++rx][0] = (battr & ~0x1ff) | lbg;
|
||||||
|
} else {
|
||||||
|
lines[yi + ry][xi + ++rx][0] = battr;
|
||||||
|
}
|
||||||
|
lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
|
||||||
|
} else {
|
||||||
|
rx++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rx = 1;
|
||||||
|
self._maxes.forEach(function(max, i) {
|
||||||
|
while (max--) {
|
||||||
|
if (ry % 2 === 0) {
|
||||||
|
if (!lines[yi + ry]) break;
|
||||||
|
if (!lines[yi + ry][xi + rx + 1]) break;
|
||||||
|
if (self.options.fillCellBorders) {
|
||||||
|
var lbg = (ry <= 2 ? hattr : cattr) & 0x1ff;
|
||||||
|
lines[yi + ry][xi + rx][0] = (battr & ~0x1ff) | lbg;
|
||||||
|
} else {
|
||||||
|
lines[yi + ry][xi + rx][0] = battr;
|
||||||
|
}
|
||||||
|
lines[yi + ry][xi + rx][1] = '\u2500'; // '─'
|
||||||
|
}
|
||||||
|
rx++;
|
||||||
|
}
|
||||||
|
rx++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return coords;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Table;
|
|
@ -0,0 +1,380 @@
|
||||||
|
/**
|
||||||
|
* terminal.js - term.js terminal element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var nextTick = global.setImmediate || process.nextTick.bind(process);
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminal
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Terminal(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Terminal(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
options.scrollable = false;
|
||||||
|
|
||||||
|
Box.call(this, options);
|
||||||
|
|
||||||
|
this.handler = options.handler;
|
||||||
|
this.shell = options.shell || process.env.SHELL || 'sh';
|
||||||
|
this.args = options.args || [];
|
||||||
|
|
||||||
|
this.cursor = this.options.cursor;
|
||||||
|
this.cursorBlink = this.options.cursorBlink;
|
||||||
|
this.screenKeys = this.options.screenKeys;
|
||||||
|
|
||||||
|
this.style = this.style || {};
|
||||||
|
this.style.bg = this.style.bg || 'default';
|
||||||
|
this.style.fg = this.style.fg || 'default';
|
||||||
|
|
||||||
|
this.bootstrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Terminal.prototype.__proto__ = Box.prototype;
|
||||||
|
|
||||||
|
Terminal.prototype.type = 'terminal';
|
||||||
|
|
||||||
|
Terminal.prototype.bootstrap = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var element = {
|
||||||
|
// window
|
||||||
|
get document() { return element; },
|
||||||
|
navigator: { userAgent: 'node.js' },
|
||||||
|
|
||||||
|
// document
|
||||||
|
get defaultView() { return element; },
|
||||||
|
get documentElement() { return element; },
|
||||||
|
createElement: function() { return element; },
|
||||||
|
|
||||||
|
// element
|
||||||
|
get ownerDocument() { return element; },
|
||||||
|
addEventListener: function() {},
|
||||||
|
removeEventListener: function() {},
|
||||||
|
getElementsByTagName: function(name) { return [element]; },
|
||||||
|
getElementById: function() { return element; },
|
||||||
|
parentNode: null,
|
||||||
|
offsetParent: null,
|
||||||
|
appendChild: function() {},
|
||||||
|
removeChild: function() {},
|
||||||
|
setAttribute: function() {},
|
||||||
|
getAttribute: function() {},
|
||||||
|
style: {},
|
||||||
|
focus: function() {},
|
||||||
|
blur: function() {},
|
||||||
|
console: console
|
||||||
|
};
|
||||||
|
|
||||||
|
element.parentNode = element;
|
||||||
|
element.offsetParent = element;
|
||||||
|
|
||||||
|
this.term = require('term.js')({
|
||||||
|
termName: 'xterm',
|
||||||
|
cols: this.width - this.iwidth,
|
||||||
|
rows: this.height - this.iheight,
|
||||||
|
context: element,
|
||||||
|
document: element,
|
||||||
|
body: element,
|
||||||
|
parent: element,
|
||||||
|
cursorBlink: this.cursorBlink,
|
||||||
|
screenKeys: this.screenKeys
|
||||||
|
});
|
||||||
|
|
||||||
|
this.term.refresh = function() {
|
||||||
|
self.screen.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.term.keyDown = function() {};
|
||||||
|
this.term.keyPress = function() {};
|
||||||
|
|
||||||
|
this.term.open(element);
|
||||||
|
|
||||||
|
// Emits key sequences in html-land.
|
||||||
|
// Technically not necessary here.
|
||||||
|
// In reality if we wanted to be neat, we would overwrite the keyDown and
|
||||||
|
// keyPress methods with our own node.js-keys->terminal-keys methods, but
|
||||||
|
// since all the keys are already coming in as escape sequences, we can just
|
||||||
|
// send the input directly to the handler/socket (see below).
|
||||||
|
// this.term.on('data', function(data) {
|
||||||
|
// self.handler(data);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Incoming keys and mouse inputs.
|
||||||
|
// NOTE: Cannot pass mouse events - coordinates will be off!
|
||||||
|
this.screen.program.input.on('data', this._onData = function(data) {
|
||||||
|
if (self.screen.focused === self && !self._isMouse(data)) {
|
||||||
|
self.handler(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.onScreenEvent('mouse', function(data) {
|
||||||
|
if (self.screen.focused !== self) return;
|
||||||
|
|
||||||
|
if (data.x < self.aleft + self.ileft) return;
|
||||||
|
if (data.y < self.atop + self.itop) return;
|
||||||
|
if (data.x > self.aleft - self.ileft + self.width) return;
|
||||||
|
if (data.y > self.atop - self.itop + self.height) return;
|
||||||
|
|
||||||
|
if (self.term.x10Mouse
|
||||||
|
|| self.term.vt200Mouse
|
||||||
|
|| self.term.normalMouse
|
||||||
|
|| self.term.mouseEvents
|
||||||
|
|| self.term.utfMouse
|
||||||
|
|| self.term.sgrMouse
|
||||||
|
|| self.term.urxvtMouse) {
|
||||||
|
;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var b = data.raw[0]
|
||||||
|
, x = data.x - self.aleft
|
||||||
|
, y = data.y - self.atop
|
||||||
|
, s;
|
||||||
|
|
||||||
|
if (self.term.urxvtMouse) {
|
||||||
|
if (self.screen.program.sgrMouse) {
|
||||||
|
b += 32;
|
||||||
|
}
|
||||||
|
s = '\x1b[' + b + ';' + (x + 32) + ';' + (y + 32) + 'M';
|
||||||
|
} else if (self.term.sgrMouse) {
|
||||||
|
if (!self.screen.program.sgrMouse) {
|
||||||
|
b -= 32;
|
||||||
|
}
|
||||||
|
s = '\x1b[<' + b + ';' + x + ';' + y
|
||||||
|
+ (data.action === 'mousedown' ? 'M' : 'm');
|
||||||
|
} else {
|
||||||
|
if (self.screen.program.sgrMouse) {
|
||||||
|
b += 32;
|
||||||
|
}
|
||||||
|
s = '\x1b[M'
|
||||||
|
+ String.fromCharCode(b)
|
||||||
|
+ String.fromCharCode(x + 32)
|
||||||
|
+ String.fromCharCode(y + 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.handler(s);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('focus', function() {
|
||||||
|
self.term.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('blur', function() {
|
||||||
|
self.term.blur();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.term.on('title', function(title) {
|
||||||
|
self.title = title;
|
||||||
|
self.emit('title', title);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('resize', function() {
|
||||||
|
nextTick(function() {
|
||||||
|
self.term.resize(self.width - self.iwidth, self.height - self.iheight);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.once('render', function() {
|
||||||
|
self.term.resize(self.width - self.iwidth, self.height - self.iheight);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.handler) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pty = require('pty.js').fork(this.shell, this.args, {
|
||||||
|
name: 'xterm',
|
||||||
|
cols: this.width - this.iwidth,
|
||||||
|
rows: this.height - this.iheight,
|
||||||
|
cwd: process.env.HOME,
|
||||||
|
env: process.env
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('resize', function() {
|
||||||
|
nextTick(function() {
|
||||||
|
self.pty.resize(self.width - self.iwidth, self.height - self.iheight);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.handler = function(data) {
|
||||||
|
self.pty.write(data);
|
||||||
|
self.screen.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pty.on('data', function(data) {
|
||||||
|
self.write(data);
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.pty.on('exit', function(code) {
|
||||||
|
self.emit('exit', code || null);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.onScreenEvent('keypress', function() {
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.screen._listenKeys(this);
|
||||||
|
|
||||||
|
this.on('destroy', function() {
|
||||||
|
self.screen.program.removeListener('data', self._onData);
|
||||||
|
self.pty.destroy();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Terminal.prototype.write = function(data) {
|
||||||
|
return this.term.write(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
Terminal.prototype.render = function() {
|
||||||
|
var ret = this._render();
|
||||||
|
if (!ret) return;
|
||||||
|
|
||||||
|
this.dattr = this.sattr(this.style);
|
||||||
|
|
||||||
|
var xi = ret.xi + this.ileft
|
||||||
|
, xl = ret.xl - this.iright
|
||||||
|
, yi = ret.yi + this.itop
|
||||||
|
, yl = ret.yl - this.ibottom
|
||||||
|
, cursor;
|
||||||
|
|
||||||
|
var scrollback = this.term.lines.length - (yl - yi);
|
||||||
|
|
||||||
|
for (var y = Math.max(yi, 0); y < yl; y++) {
|
||||||
|
var line = this.screen.lines[y];
|
||||||
|
if (!line || !this.term.lines[scrollback + y - yi]) break;
|
||||||
|
|
||||||
|
if (y === yi + this.term.y
|
||||||
|
&& this.term.cursorState
|
||||||
|
&& this.screen.focused === this
|
||||||
|
&& (this.term.ydisp === this.term.ybase || this.term.selectMode)
|
||||||
|
&& !this.term.cursorHidden) {
|
||||||
|
cursor = xi + this.term.x;
|
||||||
|
} else {
|
||||||
|
cursor = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var x = Math.max(xi, 0); x < xl; x++) {
|
||||||
|
if (!line[x] || !this.term.lines[scrollback + y - yi][x - xi]) break;
|
||||||
|
|
||||||
|
line[x][0] = this.term.lines[scrollback + y - yi][x - xi][0];
|
||||||
|
|
||||||
|
if (x === cursor) {
|
||||||
|
if (this.cursor === 'line') {
|
||||||
|
line[x][0] = this.dattr;
|
||||||
|
line[x][1] = '\u2502';
|
||||||
|
continue;
|
||||||
|
} else if (this.cursor === 'underline') {
|
||||||
|
line[x][0] = this.dattr | (2 << 18);
|
||||||
|
} else if (this.cursor === 'block' || !this.cursor) {
|
||||||
|
line[x][0] = this.dattr | (8 << 18);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
line[x][1] = this.term.lines[scrollback + y - yi][x - xi][1];
|
||||||
|
|
||||||
|
// default foreground = 257
|
||||||
|
if (((line[x][0] >> 9) & 0x1ff) === 257) {
|
||||||
|
line[x][0] &= ~(0x1ff << 9);
|
||||||
|
line[x][0] |= ((this.dattr >> 9) & 0x1ff) << 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
// default background = 256
|
||||||
|
if ((line[x][0] & 0x1ff) === 256) {
|
||||||
|
line[x][0] &= ~0x1ff;
|
||||||
|
line[x][0] |= this.dattr & 0x1ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
line.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
Terminal.prototype._isMouse = function(buf) {
|
||||||
|
var s = buf;
|
||||||
|
if (Buffer.isBuffer(s)) {
|
||||||
|
if (s[0] > 127 && s[1] === undefined) {
|
||||||
|
s[0] -= 128;
|
||||||
|
s = '\x1b' + s.toString('utf-8');
|
||||||
|
} else {
|
||||||
|
s = s.toString('utf-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (buf[0] === 0x1b && buf[1] === 0x5b && buf[2] === 0x4d)
|
||||||
|
|| /^\x1b\[M([\x00\u0020-\uffff]{3})/.test(s)
|
||||||
|
|| /^\x1b\[(\d+;\d+;\d+)M/.test(s)
|
||||||
|
|| /^\x1b\[<(\d+;\d+;\d+)([mM])/.test(s)
|
||||||
|
|| /^\x1b\[<(\d+;\d+;\d+;\d+)&w/.test(s)
|
||||||
|
|| /^\x1b\[24([0135])~\[(\d+),(\d+)\]\r/.test(s)
|
||||||
|
|| /^\x1b\[(O|I)/.test(s);
|
||||||
|
};
|
||||||
|
|
||||||
|
Terminal.prototype.setScroll =
|
||||||
|
Terminal.prototype.scrollTo = function(offset, always) {
|
||||||
|
this.term.ydisp = offset;
|
||||||
|
return this.emit('scroll');
|
||||||
|
};
|
||||||
|
|
||||||
|
Terminal.prototype.getScroll = function() {
|
||||||
|
return this.term.ydisp;
|
||||||
|
};
|
||||||
|
|
||||||
|
Terminal.prototype.scroll = function(offset, always) {
|
||||||
|
this.term.scrollDisp(offset);
|
||||||
|
return this.emit('scroll');
|
||||||
|
};
|
||||||
|
|
||||||
|
Terminal.prototype.resetScroll = function() {
|
||||||
|
this.term.ydisp = 0;
|
||||||
|
this.term.ybase = 0;
|
||||||
|
return this.emit('scroll');
|
||||||
|
};
|
||||||
|
|
||||||
|
Terminal.prototype.getScrollHeight = function() {
|
||||||
|
return this.term.rows - 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
Terminal.prototype.getScrollPerc = function(s) {
|
||||||
|
return (this.term.ydisp / this.term.ybase) * 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
Terminal.prototype.setScrollPerc = function(i) {
|
||||||
|
return this.setScroll((i / 100) * this.term.ybase | 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
Terminal.prototype.screenshot = function(xi, xl, yi, yl) {
|
||||||
|
xi = 0 + (xi || 0);
|
||||||
|
if (xl != null) {
|
||||||
|
xl = 0 + (xl || 0);
|
||||||
|
} else {
|
||||||
|
xl = this.term.lines[0].length;
|
||||||
|
}
|
||||||
|
yi = 0 + (yi || 0);
|
||||||
|
if (yl != null) {
|
||||||
|
yl = 0 + (yl || 0);
|
||||||
|
} else {
|
||||||
|
yl = this.term.lines.length;
|
||||||
|
}
|
||||||
|
return this.screen.screenshot(xi, xl, yi, yl, this.term);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Terminal;
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* text.js - text element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Element = require('./element');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Text(options) {
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Text(options);
|
||||||
|
}
|
||||||
|
options = options || {};
|
||||||
|
options.shrink = true;
|
||||||
|
Element.call(this, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
Text.prototype.__proto__ = Element.prototype;
|
||||||
|
|
||||||
|
Text.prototype.type = 'text';
|
||||||
|
|
||||||
|
module.exports = Text;
|
|
@ -0,0 +1,340 @@
|
||||||
|
/**
|
||||||
|
* textarea.js - textarea element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var unicode = require('../unicode');
|
||||||
|
|
||||||
|
var nextTick = global.setImmediate || process.nextTick.bind(process);
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Input = require('./input');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Textarea
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Textarea(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Textarea(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
options.scrollable = options.scrollable !== false;
|
||||||
|
|
||||||
|
Input.call(this, options);
|
||||||
|
|
||||||
|
this.screen._listenKeys(this);
|
||||||
|
|
||||||
|
this.value = options.value || '';
|
||||||
|
|
||||||
|
this.__updateCursor = this._updateCursor.bind(this);
|
||||||
|
this.on('resize', this.__updateCursor);
|
||||||
|
this.on('move', this.__updateCursor);
|
||||||
|
|
||||||
|
if (options.inputOnFocus) {
|
||||||
|
this.on('focus', this.readInput.bind(this, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.inputOnFocus && options.keys) {
|
||||||
|
this.on('keypress', function(ch, key) {
|
||||||
|
if (self._reading) return;
|
||||||
|
if (key.name === 'enter' || (options.vi && key.name === 'i')) {
|
||||||
|
return self.readInput();
|
||||||
|
}
|
||||||
|
if (key.name === 'e') {
|
||||||
|
return self.readEditor();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.mouse) {
|
||||||
|
this.on('click', function(data) {
|
||||||
|
if (self._reading) return;
|
||||||
|
if (data.button !== 'right') return;
|
||||||
|
self.readEditor();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Textarea.prototype.__proto__ = Input.prototype;
|
||||||
|
|
||||||
|
Textarea.prototype.type = 'textarea';
|
||||||
|
|
||||||
|
Textarea.prototype._updateCursor = function(get) {
|
||||||
|
if (this.screen.focused !== this) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lpos = get ? this.lpos : this._getCoords();
|
||||||
|
if (!lpos) return;
|
||||||
|
|
||||||
|
var last = this._clines[this._clines.length - 1]
|
||||||
|
, program = this.screen.program
|
||||||
|
, line
|
||||||
|
, cx
|
||||||
|
, cy;
|
||||||
|
|
||||||
|
// Stop a situation where the textarea begins scrolling
|
||||||
|
// and the last cline appears to always be empty from the
|
||||||
|
// _typeScroll `+ '\n'` thing.
|
||||||
|
// Maybe not necessary anymore?
|
||||||
|
if (last === '' && this.value[this.value.length - 1] !== '\n') {
|
||||||
|
last = this._clines[this._clines.length - 2] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
line = Math.min(
|
||||||
|
this._clines.length - 1 - (this.childBase || 0),
|
||||||
|
(lpos.yl - lpos.yi) - this.iheight - 1);
|
||||||
|
|
||||||
|
// When calling clearValue() on a full textarea with a border, the first
|
||||||
|
// argument in the above Math.min call ends up being -2. Make sure we stay
|
||||||
|
// positive.
|
||||||
|
line = Math.max(0, line);
|
||||||
|
|
||||||
|
cy = lpos.yi + this.itop + line;
|
||||||
|
cx = lpos.xi + this.ileft + this.strWidth(last);
|
||||||
|
|
||||||
|
// XXX Not sure, but this may still sometimes
|
||||||
|
// cause problems when leaving editor.
|
||||||
|
if (cy === program.y && cx === program.x) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cy === program.y) {
|
||||||
|
if (cx > program.x) {
|
||||||
|
program.cuf(cx - program.x);
|
||||||
|
} else if (cx < program.x) {
|
||||||
|
program.cub(program.x - cx);
|
||||||
|
}
|
||||||
|
} else if (cx === program.x) {
|
||||||
|
if (cy > program.y) {
|
||||||
|
program.cud(cy - program.y);
|
||||||
|
} else if (cy < program.y) {
|
||||||
|
program.cuu(program.y - cy);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
program.cup(cy, cx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Textarea.prototype.input =
|
||||||
|
Textarea.prototype.setInput =
|
||||||
|
Textarea.prototype.readInput = function(callback) {
|
||||||
|
var self = this
|
||||||
|
, focused = this.screen.focused === this;
|
||||||
|
|
||||||
|
if (this._reading) return;
|
||||||
|
this._reading = true;
|
||||||
|
|
||||||
|
this._callback = callback;
|
||||||
|
|
||||||
|
if (!focused) {
|
||||||
|
this.screen.saveFocus();
|
||||||
|
this.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.screen.grabKeys = true;
|
||||||
|
|
||||||
|
this._updateCursor();
|
||||||
|
this.screen.program.showCursor();
|
||||||
|
//this.screen.program.sgr('normal');
|
||||||
|
|
||||||
|
this._done = function fn(err, value) {
|
||||||
|
if (!self._reading) return;
|
||||||
|
|
||||||
|
if (fn.done) return;
|
||||||
|
fn.done = true;
|
||||||
|
|
||||||
|
self._reading = false;
|
||||||
|
|
||||||
|
delete self._callback;
|
||||||
|
delete self._done;
|
||||||
|
|
||||||
|
self.removeListener('keypress', self.__listener);
|
||||||
|
delete self.__listener;
|
||||||
|
|
||||||
|
self.removeListener('blur', self.__done);
|
||||||
|
delete self.__done;
|
||||||
|
|
||||||
|
self.screen.program.hideCursor();
|
||||||
|
self.screen.grabKeys = false;
|
||||||
|
|
||||||
|
if (!focused) {
|
||||||
|
self.screen.restoreFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.options.inputOnFocus) {
|
||||||
|
self.screen.rewindFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ugly
|
||||||
|
if (err === 'stop') return;
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
self.emit('error', err);
|
||||||
|
} else if (value != null) {
|
||||||
|
self.emit('submit', value);
|
||||||
|
} else {
|
||||||
|
self.emit('cancel', value);
|
||||||
|
}
|
||||||
|
self.emit('action', value);
|
||||||
|
|
||||||
|
if (!callback) return;
|
||||||
|
|
||||||
|
return err
|
||||||
|
? callback(err)
|
||||||
|
: callback(null, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Put this in a nextTick so the current
|
||||||
|
// key event doesn't trigger any keys input.
|
||||||
|
nextTick(function() {
|
||||||
|
self.__listener = self._listener.bind(self);
|
||||||
|
self.on('keypress', self.__listener);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.__done = this._done.bind(this, null, null);
|
||||||
|
this.on('blur', this.__done);
|
||||||
|
};
|
||||||
|
|
||||||
|
Textarea.prototype._listener = function(ch, key) {
|
||||||
|
var done = this._done
|
||||||
|
, value = this.value;
|
||||||
|
|
||||||
|
if (key.name === 'return') return;
|
||||||
|
if (key.name === 'enter') {
|
||||||
|
ch = '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle directional keys.
|
||||||
|
if (key.name === 'left' || key.name === 'right'
|
||||||
|
|| key.name === 'up' || key.name === 'down') {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.keys && key.ctrl && key.name === 'e') {
|
||||||
|
return this.readEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Optimize typing by writing directly
|
||||||
|
// to the screen and screen buffer here.
|
||||||
|
if (key.name === 'escape') {
|
||||||
|
done(null, null);
|
||||||
|
} else if (key.name === 'backspace') {
|
||||||
|
if (this.value.length) {
|
||||||
|
if (this.screen.fullUnicode) {
|
||||||
|
if (unicode.isSurrogate(this.value, this.value.length - 2)) {
|
||||||
|
// || unicode.isCombining(this.value, this.value.length - 1)) {
|
||||||
|
this.value = this.value.slice(0, -2);
|
||||||
|
} else {
|
||||||
|
this.value = this.value.slice(0, -1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.value = this.value.slice(0, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (ch) {
|
||||||
|
if (!/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(ch)) {
|
||||||
|
this.value += ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.value !== value) {
|
||||||
|
this.screen.render();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Textarea.prototype._typeScroll = function() {
|
||||||
|
// XXX Workaround
|
||||||
|
var height = this.height - this.iheight;
|
||||||
|
if (this._clines.length - this.childBase > height) {
|
||||||
|
this.scroll(this._clines.length);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Textarea.prototype.getValue = function() {
|
||||||
|
return this.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
Textarea.prototype.setValue = function(value) {
|
||||||
|
if (value == null) {
|
||||||
|
value = this.value;
|
||||||
|
}
|
||||||
|
if (this._value !== value) {
|
||||||
|
this.value = value;
|
||||||
|
this._value = value;
|
||||||
|
this.setContent(this.value);
|
||||||
|
this._typeScroll();
|
||||||
|
this._updateCursor();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Textarea.prototype.clearInput =
|
||||||
|
Textarea.prototype.clearValue = function() {
|
||||||
|
return this.setValue('');
|
||||||
|
};
|
||||||
|
|
||||||
|
Textarea.prototype.submit = function() {
|
||||||
|
if (!this.__listener) return;
|
||||||
|
return this.__listener('\x1b', { name: 'escape' });
|
||||||
|
};
|
||||||
|
|
||||||
|
Textarea.prototype.cancel = function() {
|
||||||
|
if (!this.__listener) return;
|
||||||
|
return this.__listener('\x1b', { name: 'escape' });
|
||||||
|
};
|
||||||
|
|
||||||
|
Textarea.prototype.render = function() {
|
||||||
|
this.setValue();
|
||||||
|
return this._render();
|
||||||
|
};
|
||||||
|
|
||||||
|
Textarea.prototype.editor =
|
||||||
|
Textarea.prototype.setEditor =
|
||||||
|
Textarea.prototype.readEditor = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (this._reading) {
|
||||||
|
var _cb = this._callback
|
||||||
|
, cb = callback;
|
||||||
|
|
||||||
|
this._done('stop');
|
||||||
|
|
||||||
|
callback = function(err, value) {
|
||||||
|
if (_cb) _cb(err, value);
|
||||||
|
if (cb) cb(err, value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!callback) {
|
||||||
|
callback = function() {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.screen.readEditor({ value: this.value }, function(err, value) {
|
||||||
|
if (err) {
|
||||||
|
if (err.message === 'Unsuccessful.') {
|
||||||
|
self.screen.render();
|
||||||
|
return self.readInput(callback);
|
||||||
|
}
|
||||||
|
self.screen.render();
|
||||||
|
self.readInput(callback);
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
self.setValue(value);
|
||||||
|
self.screen.render();
|
||||||
|
return self.readInput(callback);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Textarea;
|
|
@ -0,0 +1,77 @@
|
||||||
|
/**
|
||||||
|
* textbox.js - textbox element for blessed
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Textarea = require('./textarea');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Textbox
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Textbox(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new Textbox(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
options.scrollable = false;
|
||||||
|
|
||||||
|
Textarea.call(this, options);
|
||||||
|
|
||||||
|
this.secret = options.secret;
|
||||||
|
this.censor = options.censor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Textbox.prototype.__proto__ = Textarea.prototype;
|
||||||
|
|
||||||
|
Textbox.prototype.type = 'textbox';
|
||||||
|
|
||||||
|
Textbox.prototype.__olistener = Textbox.prototype._listener;
|
||||||
|
Textbox.prototype._listener = function(ch, key) {
|
||||||
|
if (key.name === 'enter') {
|
||||||
|
this._done(null, this.value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return this.__olistener(ch, key);
|
||||||
|
};
|
||||||
|
|
||||||
|
Textbox.prototype.setValue = function(value) {
|
||||||
|
var visible, val;
|
||||||
|
if (value == null) {
|
||||||
|
value = this.value;
|
||||||
|
}
|
||||||
|
if (this._value !== value) {
|
||||||
|
value = value.replace(/\n/g, '');
|
||||||
|
this.value = value;
|
||||||
|
this._value = value;
|
||||||
|
if (this.secret) {
|
||||||
|
this.setContent('');
|
||||||
|
} else if (this.censor) {
|
||||||
|
this.setContent(Array(this.value.length + 1).join('*'));
|
||||||
|
} else {
|
||||||
|
visible = -(this.width - this.iwidth - 1);
|
||||||
|
val = this.value.replace(/\t/g, this.screen.tabc);
|
||||||
|
this.setContent(val.slice(visible));
|
||||||
|
}
|
||||||
|
this._updateCursor();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Textbox.prototype.submit = function() {
|
||||||
|
if (!this.__listener) return;
|
||||||
|
return this.__listener('\r', { name: 'enter' });
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Textbox;
|
Loading…
Reference in New Issue