refactor artificial cursors and other features.

This commit is contained in:
Christopher Jeffrey 2015-03-19 23:49:26 -07:00
parent c3257e29dc
commit 9070e7f78f
5 changed files with 261 additions and 124 deletions

View File

@ -307,7 +307,7 @@ The screen on which every other node renders.
true if successful. true if successful.
- **cursorColor(color)** - attempt to change cursor color. returns true if - **cursorColor(color)** - attempt to change cursor color. returns true if
successful. successful.
- **resetCursor()** - attempt to reset cursor. returns true if successful. - **cursorReset()** - attempt to reset cursor. returns true if successful.
#### Element (from Node) #### Element (from Node)
@ -989,15 +989,34 @@ allowing you to have a custom cursor that you control.
``` js ``` js
var screen = blessed.screen({ var screen = blessed.screen({
artificialCursor: true, cursor: {
cursorShape: 'line', artificial: true,
cursorBlink: true, shape: 'line',
cursorColor: null // null for default blink: true,
color: null // null for default
}
}); });
``` ```
That's it. It's controlled the same way as the regular cursor. That's it. It's controlled the same way as the regular cursor.
To create a custom cursor:
``` js
var screen = blessed.screen({
cursor: {
artificial: true,
shape: {
bg: 'red',
fg: 'white',
bold: true,
ch: '#'
},
blink: true
}
});
```
### Positioning ### Positioning

View File

@ -374,13 +374,17 @@ exports.ccolors = {
] ]
}; };
exports.ncolors = [];
Object.keys(exports.ccolors).forEach(function(name) { Object.keys(exports.ccolors).forEach(function(name) {
exports.ccolors[name].forEach(function(offset) { exports.ccolors[name].forEach(function(offset) {
if (typeof offset === 'number') { if (typeof offset === 'number') {
exports.ncolors[offset] = name;
exports.ccolors[offset] = exports.colorNames[name]; exports.ccolors[offset] = exports.colorNames[name];
return; return;
} }
for (var i = offset[0], l = offset[1]; i <= l; i++) { for (var i = offset[0], l = offset[1]; i <= l; i++) {
exports.ncolors[i] = name;
exports.ccolors[i] = exports.colorNames[name]; exports.ccolors[i] = exports.colorNames[name];
} }
}); });

View File

@ -85,6 +85,10 @@ function Program(options) {
|| this.isTerminator || this.isTerminator
|| this.isLXDE; || this.isLXDE;
// xterm and rxvt - not accurate
this.isRxvt = /rxvt/i.test(process.env.COLORTERM);
this.isXterm = false;
this._buf = ''; this._buf = '';
this._flush = this.flush.bind(this); this._flush = this.flush.bind(this);
@ -329,10 +333,24 @@ Program.prototype.listen = function() {
}); });
// Output // Output
this.output.on('resize', function() { function resize() {
self.cols = self.output.columns; self.cols = self.output.columns;
self.rows = self.output.rows; self.rows = self.output.rows;
self.emit('resize'); self.emit('resize');
}
this.output.on('resize', function() {
if (!self.options.resizeTimeout) {
return resize();
}
if (self._resizeTimer) {
clearTimeout(self._resizeTimer);
delete self._resizeTimer;
}
var time = typeof self.options.resizeTimeout === 'number'
? self.options.resizeTimeout
: 300;
self._resizeTimer = setTimeout(resize, time);
}); });
}; };
@ -1378,6 +1396,18 @@ Program.prototype._bindResponse = function(s) {
return; return;
} }
// OSC Ps ; Pt BEL
// OSC Ps ; Pt ST
// Set Text Parameters
if (parts = /^\x1b\](\d+);([^\x07\x1b]+)(?:\x07|\x1b\\)/.exec(s)) {
out.event = 'text-params';
out.code = 'Set Text Parameters';
out.ps = +s[1];
out.pt = s[2];
this.emit('response', out);
this.emit('response ' + out.event, out);
}
}; };
Program.prototype.response = function(name, text, callback, noBypass) { Program.prototype.response = function(name, text, callback, noBypass) {
@ -1595,7 +1625,7 @@ Program.prototype.cursorShape = function(shape, blink) {
break; break;
} }
return true; return true;
} else if (this.term('xterm')) { } else if (this.term('xterm') || this.term('screen')) {
switch (shape) { switch (shape) {
case 'block': case 'block':
if (!blink) { if (!blink) {
@ -1625,28 +1655,38 @@ Program.prototype.cursorShape = function(shape, blink) {
}; };
Program.prototype.cursorColor = function(color) { Program.prototype.cursorColor = function(color) {
if (this.term('xterm') || this.term('rxvt')) { if (this.term('xterm') || this.term('rxvt') || this.term('screen')) {
this._twrite('\x1b]12;' + color + '\x07'); this._twrite('\x1b]12;' + color + '\x07');
return true; return true;
} }
return false; return false;
}; };
Program.prototype.cursorReset =
Program.prototype.resetCursor = function() { Program.prototype.resetCursor = function() {
if (this.term('rxvt')) { if (this.term('xterm') || this.term('rxvt') || this.term('screen')) {
// urxvt doesnt support OSC 112
this._twrite('\x1b]12;white\007');
this._twrite('\x1b[0 q');
return true;
} else if (this.term('xterm')) {
// XXX // XXX
// return this.resetColors(); // return this.resetColors();
this._twrite('\x1b[0 q');
this._twrite('\x1b]112\x07'); this._twrite('\x1b]112\x07');
// urxvt doesnt support OSC 112
this._twrite('\x1b]12;white\x07');
return true; return true;
} }
return false; return false;
}; };
Program.prototype.getTextParams = function(param, callback) {
return this.response('text-params', '\x1b]' + param + ';?\x07', function(err, data) {
if (err) return callback(err);
return callback(null, data.pt);
});
};
Program.prototype.getCursorColor = function(callback) {
return this.getTextParams(12, callback);
};
/** /**
* Normal * Normal
*/ */

View File

@ -261,6 +261,7 @@ function Screen(options) {
debug: options.debug, debug: options.debug,
dump: options.dump, dump: options.dump,
term: options.term, term: options.term,
resizeTimeout: options.resizeTimeout,
tput: true, tput: true,
buffer: true, buffer: true,
zero: true zero: true
@ -269,6 +270,7 @@ function Screen(options) {
this.program.setupTput(); this.program.setupTput();
this.program.useBuffer = true; this.program.useBuffer = true;
this.program.zero = true; this.program.zero = true;
this.program.options.resizeTimeout = options.resizeTimeout;
} }
this.tput = this.program.tput; this.tput = this.program.tput;
@ -325,56 +327,30 @@ function Screen(options) {
this.title = options.title; this.title = options.title;
} }
if (options.artificialCursor) { options.cursor = options.cursor || {
this.artificialCursor = true; artificial: options.artificialCursor,
this.cursorShape = options.cursorShape || 'block'; shape: options.cursorShape,
this.cursorBlink = options.cursorBlink || false; blink: options.cursorBlink,
this.cursorColor = options.cursorColor || null; color: options.cursorColor
if (this.cursorColor) { };
this.cursorColor = colors.convert(this.cursorColor);
}
this.cursorState = 1;
this._cursorHidden = true;
var hideCursor = this.program.hideCursor;
this.program.hideCursor = function() {
hideCursor.call(self.program);
self._cursorHidden = true;
if (self.renders) self.render();
};
var showCursor = this.program.showCursor;
this.program.showCursor = function() {
self._cursorHidden = false;
if (self.program._exiting) showCursor.call(self.program);
if (self.renders) self.render();
};
this._blink = setInterval(function() {
if (!self.cursorBlink) return;
self.cursorState ^= 1;
if (self.renders) self.render();
}, 500);
}
function resize() { this.cursor = {
artificial: options.cursor.artificial || false,
shape: options.cursor.shape || 'block',
blink: options.cursor.blink || false,
color: options.cursor.color || null,
_set: false,
_state: 1,
_hidden: true
};
this.program.on('resize', function() {
self.alloc(); self.alloc();
self.render(); self.render();
(function emit(el) { (function emit(el) {
el.emit('resize'); el.emit('resize');
el.children.forEach(emit); el.children.forEach(emit);
})(self); })(self);
}
this.program.on('resize', function() {
if (!self.options.resizeTimeout) {
return resize();
}
if (self._resizeTimer) {
clearTimeout(self._resizeTimer);
delete self._resizeTimer;
}
var time = typeof self.options.resizeTimeout === 'number'
? self.options.resizeTimeout
: 300;
self._resizeTimer = setTimeout(resize, time);
}); });
this.program.on('focus', function() { this.program.on('focus', function() {
@ -385,7 +361,24 @@ function Screen(options) {
self.emit('blur'); self.emit('blur');
}); });
this.enter(); this.on('newListener', function fn(type) {
if (type === 'keypress' || type.indexOf('key ') === 0 || type === 'mouse') {
if (type === 'keypress' || type.indexOf('key ') === 0) self._listenKeys();
if (type === 'mouse') self._listenMouse();
}
if (type === 'mouse'
|| type === 'click'
|| type === 'mouseover'
|| type === 'mouseout'
|| type === 'mousedown'
|| type === 'mouseup'
|| type === 'mousewheel'
|| type === 'wheeldown'
|| type === 'wheelup'
|| type === 'mousemove') {
self._listenMouse();
}
});
// XXX Not used right now since we're using our own EventEmitter: // XXX Not used right now since we're using our own EventEmitter:
this.setMaxListeners(Infinity); this.setMaxListeners(Infinity);
@ -419,24 +412,7 @@ function Screen(options) {
self.leave(); self.leave();
}); });
this.on('newListener', function fn(type) { this.enter();
if (type === 'keypress' || type.indexOf('key ') === 0 || type === 'mouse') {
if (type === 'keypress' || type.indexOf('key ') === 0) self._listenKeys();
if (type === 'mouse') self._listenMouse();
}
if (type === 'mouse'
|| type === 'click'
|| type === 'mouseover'
|| type === 'mouseout'
|| type === 'mousedown'
|| type === 'mouseup'
|| type === 'mousewheel'
|| type === 'wheeldown'
|| type === 'wheelup'
|| type === 'mousemove') {
self._listenMouse();
}
});
} }
Screen.global = null; Screen.global = null;
@ -457,6 +433,14 @@ Screen.prototype.__defineSetter__('title', function(title) {
Screen.prototype.enter = function() { Screen.prototype.enter = function() {
if (this.program.isAlt) return; if (this.program.isAlt) return;
if (!this.cursor._set) {
if (this.options.cursor.shape) {
this.cursorShape(this.cursor.shape, this.cursor.blink);
}
if (this.options.cursor.color) {
this.cursorColor(this.cursor.color);
}
}
this.program.alternateBuffer(); this.program.alternateBuffer();
this.program.hideCursor(); this.program.hideCursor();
this.program.cup(0, 0); this.program.cup(0, 0);
@ -477,6 +461,7 @@ Screen.prototype.leave = function() {
this.program.disableMouse(); this.program.disableMouse();
} }
this.program.normalBuffer(); this.program.normalBuffer();
if (this.cursor._set) this.cursorReset();
this.program.flush(); this.program.flush();
}; };
@ -705,10 +690,6 @@ Screen.prototype.render = function() {
}); });
this._ci = -1; this._ci = -1;
// Cannot use this.rows here because of the resize delay:
// `rows` and `cols` get set instantly by the resize, but `alloc` has a 300ms
// delay before the new cells are added, which will sometimes throw. Measure
// by lines.
this.draw(0, this.lines.length - 1); this.draw(0, this.lines.length - 1);
// XXX Workaround to deal with cursor pos before the screen has rendered and // XXX Workaround to deal with cursor pos before the screen has rendered and
@ -951,8 +932,7 @@ Screen.prototype.draw = function(start, end) {
line = this.lines[y]; line = this.lines[y];
o = this.olines[y]; o = this.olines[y];
// if (!line.dirty) continue; if (!line.dirty && !(this.cursor.artificial && y === this.program.y)) {
if (!line.dirty && !(this.artificialCursor && y === this.program.y)) {
continue; continue;
} }
line.dirty = false; line.dirty = false;
@ -960,36 +940,19 @@ Screen.prototype.draw = function(start, end) {
out = ''; out = '';
attr = this.dattr; attr = this.dattr;
// Cannot use this.cols here because of the resize delay:
// `rows` and `cols` get set instantly by the resize, but `alloc` has a
// 300ms delay before the new cells are added, which will sometimes throw.
// Measure by line length.
for (x = 0; x < line.length; x++) { for (x = 0; x < line.length; x++) {
data = line[x][0]; data = line[x][0];
ch = line[x][1]; ch = line[x][1];
if (this.artificialCursor // Render the artificial cursor.
&& !this._cursorHidden if (this.cursor.artificial
&& this.cursorState && !this.cursor._hidden
&& this.cursor._state
&& x === this.program.x && x === this.program.x
&& y === this.program.y) { && y === this.program.y) {
if (this.cursorShape === 'line') { var cattr = this._cursorAttr(this.cursor, data);
data &= ~(0x1ff << 9); if (cattr.ch) ch = cattr.ch;
data |= 7 << 9; data = cattr.attr;
ch = '\u2502';
} else if (this.cursorShape === 'underline') {
data &= ~(0x1ff << 9);
data |= 7 << 9;
data |= 2 << 18;
} else if (this.cursorShape === 'block') {
data &= ~(0x1ff << 9);
data |= 7 << 9;
data |= 8 << 18;
}
if (this.cursorColor != null) {
data &= ~(0x1ff << 9);
data |= this.cursorColor << 9;
}
} }
// Take advantage of xterm's back_color_erase feature by using a // Take advantage of xterm's back_color_erase feature by using a
@ -1817,30 +1780,139 @@ Screen.prototype.copyToClipboard = function(text) {
}; };
Screen.prototype.cursorShape = function(shape, blink) { Screen.prototype.cursorShape = function(shape, blink) {
if (this.artificialCursor) { var self = this;
this.cursorShape = shape;
this.cursorBlink = blink; this.cursor.shape = shape || 'block';
this.cursor.blink = blink || false;
this.cursor._set = true;
if (this.cursor.artificial) {
if (!this.program.hideCursor_old) {
var hideCursor = this.program.hideCursor;
this.program.hideCursor_old = this.program.hideCursor;
this.program.hideCursor = function() {
hideCursor.call(self.program);
self.cursor._hidden = true;
if (self.renders) self.render();
};
}
if (!this.program.showCursor_old) {
var showCursor = this.program.showCursor;
this.program.showCursor_old = this.program.showCursor;
this.program.showCursor = function() {
self.cursor._hidden = false;
if (self.program._exiting) showCursor.call(self.program);
if (self.renders) self.render();
};
}
if (!this._cursorBlink) {
this._cursorBlink = setInterval(function() {
if (!self.cursor.blink) return;
self.cursor._state ^= 1;
if (self.renders) self.render();
}, 500);
this._cursorBlink.unref();
}
return true; return true;
} }
return this.program.cursorShape(shape, blink);
return this.program.cursorShape(this.cursor.shape, this.cursor.blink);
}; };
Screen.prototype.cursorColor = function(color) { Screen.prototype.cursorColor = function(color) {
if (this.artificialCursor) { this.cursor.color = color != null
this.cursorColor = colors.convert(color); ? colors.convert(color)
: null;
this.cursor._set = true;
if (this.cursor.artificial) {
return true; return true;
} }
return this.program.cursorColor(color);
return this.program.cursorColor(colors.ncolors[this.cursor.color]);
}; };
Screen.prototype.cursorReset =
Screen.prototype.resetCursor = function() { Screen.prototype.resetCursor = function() {
if (this.artificialCursor) { this.cursor.shape = 'block';
this.cursorShape = 'block'; this.cursor.blink = false;
this.cursorBlink = false; this.cursor.color = null;
this.cursorColor = null; this.cursor._set = false;
if (this.cursor.artificial) {
this.cursor.artificial = false;
if (this.program.hideCursor_old) {
this.program.hideCursor = this.program.hideCursor_old;
delete this.program.hideCursor_old;
}
if (this.program.showCursor_old) {
this.program.showCursor = this.program.showCursor_old;
delete this.program.showCursor_old;
}
if (this._cursorBlink) {
clearInterval(this._cursorBlink);
delete this._cursorBlink;
}
return true; return true;
} }
return this.program.resetCursor();
return this.program.cursorReset();
};
Screen.prototype._cursorAttr = function(cursor, dattr) {
var attr = dattr || this.dattr
, cattr
, ch;
if (cursor.shape === 'line') {
attr &= ~(0x1ff << 9);
attr |= 7 << 9;
ch = '\u2502';
} else if (cursor.shape === 'underline') {
attr &= ~(0x1ff << 9);
attr |= 7 << 9;
attr |= 2 << 18;
} else if (cursor.shape === 'block') {
attr &= ~(0x1ff << 9);
attr |= 7 << 9;
attr |= 8 << 18;
} else if (typeof cursor.shape === 'object' && cursor.shape) {
cattr = Element.prototype.sattr.call(cursor,
cursor.shape,
cursor.shape.fg,
cursor.shape.bg);
if (cursor.shape.bold || cursor.shape.underline
|| cursor.shape.blink || cursor.shape.inverse
|| cursor.shape.invisible) {
attr &= ~(0x1ff << 18);
attr |= ((cattr >> 18) & 0x1ff) << 18;
}
if (cursor.shape.fg) {
attr &= ~(0x1ff << 9);
attr |= ((cattr >> 9) & 0x1ff) << 9;
}
if (cursor.shape.bg) {
attr &= ~(0x1ff << 0);
attr |= cattr & 0x1ff;
}
if (cursor.shape.ch) {
ch = cursor.shape.ch;
}
}
if (cursor.color != null) {
attr &= ~(0x1ff << 9);
attr |= cursor.color << 9;
}
return {
ch: ch,
attr: attr
};
}; };
/** /**
@ -4572,12 +4644,12 @@ List.prototype.appendItem = function(item) {
if (this.mouse) { if (this.mouse) {
item.on('click', function(data) { item.on('click', function(data) {
self.focus();
if (self.items[self.selected] === item) { if (self.items[self.selected] === item) {
self.emit('action', item, self.selected); self.emit('action', item, self.selected);
self.emit('select', item, self.selected); self.emit('select', item, self.selected);
return; return;
} }
self.focus();
self.select(item); self.select(item);
self.screen.render(); self.screen.render();
}); });

View File

@ -4,11 +4,13 @@ var blessed = require('../')
screen = blessed.screen({ screen = blessed.screen({
dump: __dirname + '/logs/widget.log', dump: __dirname + '/logs/widget.log',
title: 'widget test', title: 'widget test',
artificialCursor: true, resizeTimeout: 300,
cursorShape: 'line', cursor: {
cursorBlink: true, artificial: true,
cursorColor: null shape: 'line',
//cursorColor: 'red' blink: true,
color: null
}
}); });
screen.append(blessed.text({ screen.append(blessed.text({