readme, tput, mouse, types, misc.

This commit is contained in:
Christopher Jeffrey 2013-06-25 06:34:10 -05:00
parent 07334af6fc
commit 77593efd05
4 changed files with 285 additions and 32 deletions

View File

@ -572,6 +572,41 @@ Elements are rendered with the lower elements in the children array being
painted first. In terms of the painter's algorithm, the lowest indicies in the
array are the furthest away, just like in the DOM.
### Optimization and CSR
You may notice a lot of terminal apps (e.g. mutt, irssi, vim, ncmpcpp) don't
have sidebars, and only have "elements" that take up the entire width of the
screen. The reason for this is speed (and general cleanliness). VT-like
terminals have something called a CSR (change_scroll_region) code, as well as
IL (insert_line), and DL (delete_code) codes. Using these three codes, it is
possible to create a very efficient rendering by avoiding redrawing the entire
screen when a line is inserted or removed. Since blessed is extremely dynamic,
it is hard to do this optimization automatically (blessed assumes you may
create any element of any width in any position). So, there is a solution:
``` js
var box = new blessed.Box(...);
box.setContent('line 1\nline 2');
box.insertBottom('line 3');
box.insertBottom('line 4');
box.insertTop('line 0');
```
If your element has the same width as the screen, the line insertion will be
optimized by using a combination CSR/IL/DL codes. These methods may be made
smarter in the future to detect whether any elements are being overlapped to
the sides.
Outputting:
```
| line 0 |
| line 1 |
| line 2 |
| line 3 |
| line 4 |
```
### Testing
- For an interactive test, see `test/widget.js`.

View File

@ -834,7 +834,7 @@ Program.prototype.nextLine = function() {
// ESC c Full Reset (RIS).
Program.prototype.reset = function() {
//this.x = this.y = 1;
if (this.tput) return this.put.ris();
if (this.tput) return this.put.rs1 ? this.put.rs1() : this.put.ris();
return this.write('\x1bc');
};
@ -848,7 +848,7 @@ Program.prototype.tabSet = function() {
Program.prototype.saveCursor = function() {
this.savedX = this.x || 1;
this.savedY = this.y || 1;
if (this.tput) return this.put.sc(); // not correct
if (this.tput) return this.put.sc();
return this.esc('7');
};
@ -856,7 +856,7 @@ Program.prototype.saveCursor = function() {
Program.prototype.restoreCursor = function() {
this.x = this.savedX || 1;
this.y = this.savedY || 1;
if (this.tput) return this.put.rc(); // not correct
if (this.tput) return this.put.rc();
return this.esc('8');
};
@ -869,6 +869,13 @@ Program.prototype.lineHeight = function() {
Program.prototype.charset = function(val, level) {
level = level || 0;
// See also:
// acs_chars / acsc / ac
// enter_alt_charset_mode / smacs / as
// exit_alt_charset_mode / rmacs / ae
// enter_pc_charset_mode / smpch / S2
// exit_pc_charset_mode / rmpch / S3
// tput: TODO
// if (this.tput) return this.put('s' + level, val);
@ -889,12 +896,16 @@ Program.prototype.charset = function(val, level) {
switch (val) {
case 'SCLD': // DEC Special Character and Line Drawing Set.
if (this.tput) return this.put.smacs();
val = '0';
break;
case 'UK': // UK
val = 'A';
break;
case 'US': // United States (USASCII).
case 'USASCII':
case 'ASCII':
if (this.tput) return this.put.rmacs();
val = 'B';
break;
case 'Dutch': // Dutch
@ -934,6 +945,7 @@ Program.prototype.charset = function(val, level) {
val = '/A';
break;
default: // Default
if (this.tput) return this.put.rmacs();
val = 'B';
break;
}
@ -941,6 +953,18 @@ Program.prototype.charset = function(val, level) {
return this.write('\x1b(' + val);
};
Program.prototype.enter_alt_charset_mode =
Program.prototype.as =
Program.prototype.smacs = function() {
return this.charset('SCLD');
};
Program.prototype.exit_alt_charset_mode =
Program.prototype.ae =
Program.prototype.rmacs = function() {
return this.charset('US');
};
// ESC N
// Single Shift Select of G2 Character Set
// ( SS2 is 0x8e). This affects next character only.
@ -1157,6 +1181,7 @@ Program.prototype.clear = function() {
Program.prototype.el =
Program.prototype.eraseInLine = function(param) {
if (this.tput) {
//if (this.tput.back_color_erase) ...
switch (param) {
case 'left':
param = 1;
@ -1824,12 +1849,24 @@ Program.prototype.decset = function() {
};
Program.prototype.dectcem =
Program.prototype.cnorm =
Program.prototype.cvvis =
Program.prototype.showCursor = function() {
this.cursorHidden = false;
// NOTE: In xterm terminfo:
// cnorm stops blinking cursor
// cvvis starts blinking cursor
if (this.tput) return this.put.cnorm();
//if (this.tput) return this.put.cvvis();
// return this.write('\x1b[?12l\x1b[?25h'); // cursor_normal
// return this.write('\x1b[?12;25h'); // cursor_visible
return this.setMode('?25');
};
Program.prototype.alternate =
Program.prototype.smcup =
Program.prototype.alternateBuffer = function() {
if (this.tput) return this.put.smcup();
if (this.term('vt') || this.term('linux')) return;
//return this.setMode('?47');
//return this.setMode('?1047');
@ -1932,19 +1969,26 @@ Program.prototype.decrst = function() {
};
Program.prototype.dectcemh =
Program.prototype.cursor_invisible =
Program.prototype.vi =
Program.prototype.civis =
Program.prototype.hideCursor = function() {
this.cursorHidden = true;
if (this.tput) return this.put.civis();
return this.resetMode('?25');
};
Program.prototype.rmcup =
Program.prototype.normalBuffer = function() {
//return this.resetMode('?47');
//return this.resetMode('?1047');
if (this.tput) return this.put.rmcup();
return this.resetMode('?1049');
};
Program.prototype.enableMouse = function() {
if (this.term('rxvt')) {
return this.setMouse({ urxvtMouse: true });
if (this.term('rxvt-unicode')) {
return this.setMouse({ urxvtMouse: true }, true);
}
if (this.term('xterm') || this.term('screen')) {
@ -1952,35 +1996,51 @@ Program.prototype.enableMouse = function() {
allMotion: true,
utfMouse: true,
sendFocus: true
});
}, true);
}
if (this.term('vt')) {
return this.setMouse({ vt200Mouse: true });
return this.setMouse({ vt200Mouse: true }, true);
}
};
Program.prototype.disableMouse = function() {
return this.setMouse({
x10Mouse: false,
vt200Mouse: false,
hiliteTracking: false,
cellMotion: false,
allMotion: false,
sendFocus: false,
utfMouse: false,
sgrMouse: false,
urxvtMouse: false
//return this.setMouse({
// x10Mouse: false,
// vt200Mouse: false,
// hiliteTracking: false,
// cellMotion: false,
// allMotion: false,
// sendFocus: false,
// utfMouse: false,
// sgrMouse: false,
// urxvtMouse: false
//}, false);
if (!this._currentMouse) return;
var obj = {};
Object.keys(this._currentMouse).forEach(function(key) {
obj[key] = false;
});
return this.setMouse(obj, false);
};
// Set Mouse
Program.prototype.setMouse = function(opt) {
Program.prototype.setMouse = function(opt, enable) {
if (opt.normalMouse != null) {
opt.cellMotion = opt.normalMouse;
opt.allMotion = opt.normalMouse;
}
if (enable) {
this._currentMouse = opt;
} else {
delete this._currentMouse;
}
// Make sure we're not a vtNNN
if (this.term('vt')) return;
@ -2191,7 +2251,6 @@ Program.prototype.tabClear = function(param) {
// Ps = 1 1 -> Print all pages.
Program.prototype.mc =
Program.prototype.mediaCopy = function() {
// tput: TODO. See: mc0, mc5p, mc4, mc5
//if (dec) {
// this.write('\x1b[?' + Array.prototype.slice.call(arguments).join(';') + 'i');
// return;
@ -2199,6 +2258,34 @@ Program.prototype.mediaCopy = function() {
return this.write('\x1b[' + Array.prototype.slice.call(arguments).join(';') + 'i');
};
Program.prototype.print_screen =
Program.prototype.ps =
Program.prototype.mc0 = function() {
if (this.tput) return this.put.mc0();
return this.mc('0');
};
Program.prototype.prtr_on =
Program.prototype.po =
Program.prototype.mc5 = function() {
if (this.tput) return this.put.mc5();
return this.mc('5');
};
Program.prototype.prtr_off =
Program.prototype.pf =
Program.prototype.mc4 = function() {
if (this.tput) return this.put.mc4();
return this.mc('4');
};
Program.prototype.prtr_non =
Program.prototype.pO =
Program.prototype.mc5p = function() {
if (this.tput) return this.put.mc5p();
return this.mc('?5');
};
// CSI > Ps; Ps m
// Set or reset resource-values used by xterm to decide whether
// to construct escape sequences holding information about the
@ -2247,8 +2334,14 @@ Program.prototype.setPointerMode = function(param) {
// CSI ! p Soft terminal reset (DECSTR).
// http://vt100.net/docs/vt220-rm/table4-10.html
Program.prototype.decstr =
Program.prototype.rs2 =
Program.prototype.softReset = function() {
return this.write('\x1b[!p');
//if (this.tput) return this.put.init_2string();
//if (this.tput) return this.put.reset_2string();
if (this.tput) return this.put.rs2();
//return this.write('\x1b[!p');
//return this.write('\x1b[!p\x1b[?3;4l\x1b[4l\x1b>'); // init
return this.write('\x1b[!p\x1b[?3;4l\x1b[4l\x1b>'); // reset
};
// CSI Ps$ p
@ -2643,9 +2736,17 @@ Program.prototype.selectiveEraseRectangle = function(params) {
// The ``page'' parameter is not used by xterm, and will be omit-
// ted.
Program.prototype.decrqlp =
Program.prototype.req_mouse_pos =
Program.prototype.reqmp =
Program.prototype.requestLocatorPosition = function(params, callback) {
// Correct for tput?
if (this.tput) return this.put.req_mouse_pos.apply(this.put, arguments);
if (this.tput && this.tput.req_mouse_pos) {
// See also:
// get_mouse / getm / Gm
// mouse_info / minfo / Mi
// Correct for tput?
var code = this.tput.req_mouse_pos.apply(this.tput, params);
return this.receive(code, callback);
}
return this.receive('\x1b[' + (param || '') + '\'|', callback);
};

View File

@ -524,6 +524,13 @@ Tput.prototype.inject = function(info) {
return methods[key].call(self, args);
};
});
this.info = info;
this.all = info.all;
this.methods = info.methods;
this.bools = info.bools;
this.numbers = info.numbers;
this.strings = info.strings;
};
Tput.prototype._compile = function(val) {

View File

@ -46,9 +46,12 @@ function Node(options) {
Node.prototype.__proto__ = EventEmitter.prototype;
Node.prototype.type = 'node';
Node.prototype.prepend = function(element) {
var old = element.parent;
element.detach();
element.parent = this;
if (this._isScreen && !this.focused) {
@ -77,6 +80,7 @@ Node.prototype.prepend = function(element) {
Node.prototype.append = function(element) {
var old = element.parent;
element.detach();
element.parent = this;
if (this._isScreen && !this.focused) {
@ -260,6 +264,8 @@ Screen.global = null;
Screen.prototype.__proto__ = Node.prototype;
Screen.prototype.type = 'screen';
// TODO: Bubble events.
Screen.prototype._listenMouse = function(el) {
var self = this;
@ -834,6 +840,8 @@ function Element(options) {
Element.prototype.__proto__ = Node.prototype;
Element.prototype.type = 'element';
/*
Element._emit = Element.prototype.emit;
Element.prototype.emit = function(type) {
@ -1268,6 +1276,8 @@ function Box(options) {
Box.prototype.__proto__ = Element.prototype;
Box.prototype.type = 'box';
// TODO: Optimize. Move elsewhere.
Box.prototype._getShrinkSize = function(content) {
return {
@ -1315,7 +1325,8 @@ Box.prototype.render = function(stop) {
yl = yi_ + this.height;
}
if (this.parent.childBase != null && ~this.parent.items.indexOf(this)) {
// Check to make sure we're visible and inside of the visible scroll area.
if (this.parent.childBase != null && (!this.parent.items || ~this.parent.items.indexOf(this))) {
var rtop = this.rtop - (this.parent.border ? 1 : 0)
, visible = this.parent.height - (this.parent.border ? 2 : 0);
@ -1473,9 +1484,25 @@ outer:
}
if (this.border) {
// var alt;
// if (this.screen.tput
// && this.screen.tput.strings.enter_alt_charset_mode
// && this.border.type === 'ascii') {
// //this.screen.program.put.smacs();
// battr |= 32 << 18;
// alt = true;
// }
yi = yi_;
for (xi = xi_; xi < xl; xi++) {
if (!lines[yi]) break;
// if (alt) {
// if (xi === xi_) ch = 'l';
// else if (xi === xl - 1) ch = 'k';
// else ch = 'q';
// } else
if (this.border.type === 'ascii') {
if (xi === xi_) ch = '┌';
else if (xi === xl - 1) ch = '┐';
@ -1494,6 +1521,11 @@ outer:
yi = yi_ + 1;
for (; yi < yl; yi++) {
if (!lines[yi]) break;
// if (alt) {
// ch = 'x';
// } else
if (this.border.type === 'ascii') {
ch = '│';
} else if (this.border.type === 'bg') {
@ -1517,6 +1549,13 @@ outer:
yi = yl - 1;
for (xi = xi_; xi < xl; xi++) {
if (!lines[yi]) break;
// if (alt) {
// if (xi === xi_) ch = 'm';
// else if (xi === xl - 1) ch = 'j';
// else ch = 'q';
// } else
if (this.border.type === 'ascii') {
if (xi === xi_) ch = '└';
else if (xi === xl - 1) ch = '┘';
@ -1532,6 +1571,11 @@ outer:
lines[yi].dirty = true;
}
}
// if (alt) {
// //this.screen.program.put.rmacs();
// battr &= ~(32 << 18);
// }
}
this.children.forEach(function(el) {
@ -1543,20 +1587,27 @@ outer:
// Create a much more efficient rendering by using insert-line,
// delete-line, and change screen region codes when possible.
// NOTE: If someone does:
// box.left = box.right = 0;
// screen.render();
// box.left++;
// box.insertTop('foobar');
// Things will break because we're using _lastPos instead of render(true).
// Maybe _lastPos could be updated on .left, .right, etc setters?
Box.prototype.insertTop = function(line) {
if (!this._lastPos) return;
if (this._lastPos.xi === 0 && this._lastPos.xl === this.screen.width) {
if (this._lastPos && this._lastPos.xi === 0 && this._lastPos.xl === this.screen.width) {
this.screen.insertTop(this._lastPos.yi, this._lastPos.yl - 1);
}
this.setContent(line + '\n' + this.content, true);
// this.screen.render();
};
Box.prototype.insertBottom = function(line) {
if (!this._lastPos) return;
if (this._lastPos.xi === 0 && this._lastPos.xl === this.screen.width) {
if (this._lastPos && this._lastPos.xi === 0 && this._lastPos.xl === this.screen.width) {
this.screen.insertBottom(this._lastPos.yi, this._lastPos.yl - 1);
}
this.setContent(this.content + '\n' + line, true);
// this.screen.render();
};
/**
@ -1606,6 +1657,8 @@ function Line(options) {
Line.prototype.__proto__ = Box.prototype;
Line.prototype.type = 'line';
/**
* ScrollableBox
*/
@ -1624,6 +1677,8 @@ function ScrollableBox(options) {
ScrollableBox.prototype.__proto__ = Box.prototype;
ScrollableBox.prototype.type = 'scrollable-box';
ScrollableBox.prototype.scroll = function(offset) {
var visible = this.height - (this.border ? 2 : 0);
// Maybe do for lists:
@ -1664,6 +1719,7 @@ function List(options) {
ScrollableBox.call(this, options);
this.items = [];
this.ritems = [];
this.selected = 0;
this.selectedBg = convert(options.selectedBg);
@ -1677,6 +1733,7 @@ function List(options) {
this.mouse = options.mouse || false;
if (options.items) {
this.ritems = options.items;
options.items.forEach(this.add.bind(this));
}
@ -1783,6 +1840,8 @@ function List(options) {
List.prototype.__proto__ = ScrollableBox.prototype;
List.prototype.type = 'list';
List.prototype.add = function(item) {
var self = this;
@ -1821,7 +1880,11 @@ List.prototype.remove = function(child) {
List.prototype.setItems = function(items) {
var i = 0
, original = this.items.slice();
, original = this.items.slice()
//, selected = this.selected
, sel = this.ritems[this.selected];
this.ritems = items;
this.select(0);
@ -1836,6 +1899,11 @@ List.prototype.setItems = function(items) {
for (; i < original.length; i++) {
this.remove(original[i]);
}
// Try to find our old item if it still exists.
sel = items.indexOf(sel);
if (~sel) this.select(sel);
//this.select(~sel ? sel : selected);
};
List.prototype.select = function(index) {
@ -1947,6 +2015,8 @@ function ScrollableText(options) {
ScrollableText.prototype.__proto__ = ScrollableBox.prototype;
ScrollableText.prototype.type = 'scrollable-text';
ScrollableText.prototype._scroll = ScrollableText.prototype.scroll;
ScrollableText.prototype.scroll = function(offset) {
var base = this.childBase
@ -2013,6 +2083,8 @@ function Input(options) {
Input.prototype.__proto__ = Box.prototype;
Input.prototype.type = 'input';
/**
* Textbox
*/
@ -2025,10 +2097,27 @@ function Textbox(options) {
this.screen._listenKeys(this);
this.value = options.value || '';
this.secret = options.secret;
this.censor = options.censor;
var self = this;
this.on('resize', updateCursor);
this.on('move', updateCursor);
function updateCursor() {
//if (!self.visible) return;
if (self.screen.focused !== self) return;
self.screen.program.cup(
self.top + 1 + (self.border ? 1 : 0),
self.left + 1 + (self.border ? 1 : 0)
+ self.value.length);
}
}
Textbox.prototype.__proto__ = Input.prototype;
Textbox.prototype.type = 'textbox';
Textbox.prototype.setInput = function(callback) {
var self = this;
@ -2122,7 +2211,10 @@ Textbox.prototype.render = function(stop) {
// Could technically optimize this.
if (this.secret) {
this.setContent('');
//this.setContent(Array(this.value.length + 1).join('*'));
return this._render(stop);
}
if (this.censor) {
this.setContent(Array(this.value.length + 1).join('*'));
return this._render(stop);
}
this.setContent(this.value.slice(-(this.width - (this.border ? 2 : 0) - 1)));
@ -2136,19 +2228,31 @@ Textbox.prototype.setEditor = function(callback) {
self.screen.program.normalBuffer();
self.screen.program.showCursor();
var _listenedMouse = self.screen._listenedMouse;
if (self.screen._listenedMouse) {
self.screen.program.disableMouse();
}
return readEditor(function(err, value) {
self.screen.program.alternateBuffer();
self.screen.program.hideCursor();
if (_listenedMouse) {
self.screen.program.enableMouse();
}
self.screen.alloc();
self.screen.render();
if (err) return callback(err);
value = value.replace(/[\r\n]/g, '');
self.value = value;
if (!self.secret) {
self.setContent(value);
}
////if (self.censor) {
//// self.setContent(Array(self.value.length + 1).join('*'));
////} else
//if (!self.secret) {
// self.setContent(value);
//}
//return callback(null, value);
return self.setInput(callback);
});
};
@ -2166,6 +2270,8 @@ function Textarea(options) {
Textarea.prototype.__proto__ = Input.prototype;
Textarea.prototype.type = 'textarea';
/**
* Button
*/
@ -2202,6 +2308,8 @@ function Button(options) {
Button.prototype.__proto__ = Input.prototype;
Button.prototype.type = 'button';
Button.prototype.press = function() {
var self = this;
this.emit('press');
@ -2237,6 +2345,8 @@ function ProgressBar(options) {
ProgressBar.prototype.__proto__ = Input.prototype;
ProgressBar.prototype.type = 'progress-bar';
ProgressBar.prototype._render = ProgressBar.prototype.render;
ProgressBar.prototype.render = function(stop) {
// NOTE: Maybe move this `hidden` check down below `stop` check and return `ret`.