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