refactor artificial cursors and other features.
This commit is contained in:
parent
c3257e29dc
commit
9070e7f78f
29
README.md
29
README.md
|
@ -307,7 +307,7 @@ The screen on which every other node renders.
|
|||
true if successful.
|
||||
- **cursorColor(color)** - attempt to change cursor color. returns true if
|
||||
successful.
|
||||
- **resetCursor()** - attempt to reset cursor. returns true if successful.
|
||||
- **cursorReset()** - attempt to reset cursor. returns true if successful.
|
||||
|
||||
|
||||
#### Element (from Node)
|
||||
|
@ -989,15 +989,34 @@ allowing you to have a custom cursor that you control.
|
|||
|
||||
``` js
|
||||
var screen = blessed.screen({
|
||||
artificialCursor: true,
|
||||
cursorShape: 'line',
|
||||
cursorBlink: true,
|
||||
cursorColor: null // null for default
|
||||
cursor: {
|
||||
artificial: true,
|
||||
shape: 'line',
|
||||
blink: true,
|
||||
color: null // null for default
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -374,13 +374,17 @@ exports.ccolors = {
|
|||
]
|
||||
};
|
||||
|
||||
exports.ncolors = [];
|
||||
|
||||
Object.keys(exports.ccolors).forEach(function(name) {
|
||||
exports.ccolors[name].forEach(function(offset) {
|
||||
if (typeof offset === 'number') {
|
||||
exports.ncolors[offset] = name;
|
||||
exports.ccolors[offset] = exports.colorNames[name];
|
||||
return;
|
||||
}
|
||||
for (var i = offset[0], l = offset[1]; i <= l; i++) {
|
||||
exports.ncolors[i] = name;
|
||||
exports.ccolors[i] = exports.colorNames[name];
|
||||
}
|
||||
});
|
||||
|
|
|
@ -85,6 +85,10 @@ function Program(options) {
|
|||
|| this.isTerminator
|
||||
|| this.isLXDE;
|
||||
|
||||
// xterm and rxvt - not accurate
|
||||
this.isRxvt = /rxvt/i.test(process.env.COLORTERM);
|
||||
this.isXterm = false;
|
||||
|
||||
this._buf = '';
|
||||
this._flush = this.flush.bind(this);
|
||||
|
||||
|
@ -329,10 +333,24 @@ Program.prototype.listen = function() {
|
|||
});
|
||||
|
||||
// Output
|
||||
this.output.on('resize', function() {
|
||||
function resize() {
|
||||
self.cols = self.output.columns;
|
||||
self.rows = self.output.rows;
|
||||
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;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
@ -1595,7 +1625,7 @@ Program.prototype.cursorShape = function(shape, blink) {
|
|||
break;
|
||||
}
|
||||
return true;
|
||||
} else if (this.term('xterm')) {
|
||||
} else if (this.term('xterm') || this.term('screen')) {
|
||||
switch (shape) {
|
||||
case 'block':
|
||||
if (!blink) {
|
||||
|
@ -1625,28 +1655,38 @@ Program.prototype.cursorShape = function(shape, blink) {
|
|||
};
|
||||
|
||||
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');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Program.prototype.cursorReset =
|
||||
Program.prototype.resetCursor = function() {
|
||||
if (this.term('rxvt')) {
|
||||
// urxvt doesnt support OSC 112
|
||||
this._twrite('\x1b]12;white\007');
|
||||
this._twrite('\x1b[0 q');
|
||||
return true;
|
||||
} else if (this.term('xterm')) {
|
||||
if (this.term('xterm') || this.term('rxvt') || this.term('screen')) {
|
||||
// XXX
|
||||
// return this.resetColors();
|
||||
this._twrite('\x1b[0 q');
|
||||
this._twrite('\x1b]112\x07');
|
||||
// urxvt doesnt support OSC 112
|
||||
this._twrite('\x1b]12;white\x07');
|
||||
return true;
|
||||
}
|
||||
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
|
||||
*/
|
||||
|
|
280
lib/widget.js
280
lib/widget.js
|
@ -261,6 +261,7 @@ function Screen(options) {
|
|||
debug: options.debug,
|
||||
dump: options.dump,
|
||||
term: options.term,
|
||||
resizeTimeout: options.resizeTimeout,
|
||||
tput: true,
|
||||
buffer: true,
|
||||
zero: true
|
||||
|
@ -269,6 +270,7 @@ function Screen(options) {
|
|||
this.program.setupTput();
|
||||
this.program.useBuffer = true;
|
||||
this.program.zero = true;
|
||||
this.program.options.resizeTimeout = options.resizeTimeout;
|
||||
}
|
||||
|
||||
this.tput = this.program.tput;
|
||||
|
@ -325,56 +327,30 @@ function Screen(options) {
|
|||
this.title = options.title;
|
||||
}
|
||||
|
||||
if (options.artificialCursor) {
|
||||
this.artificialCursor = true;
|
||||
this.cursorShape = options.cursorShape || 'block';
|
||||
this.cursorBlink = options.cursorBlink || false;
|
||||
this.cursorColor = options.cursorColor || null;
|
||||
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();
|
||||
options.cursor = options.cursor || {
|
||||
artificial: options.artificialCursor,
|
||||
shape: options.cursorShape,
|
||||
blink: options.cursorBlink,
|
||||
color: options.cursorColor
|
||||
};
|
||||
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.render();
|
||||
(function emit(el) {
|
||||
el.emit('resize');
|
||||
el.children.forEach(emit);
|
||||
})(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() {
|
||||
|
@ -385,7 +361,24 @@ function Screen(options) {
|
|||
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:
|
||||
this.setMaxListeners(Infinity);
|
||||
|
@ -419,24 +412,7 @@ function Screen(options) {
|
|||
self.leave();
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
this.enter();
|
||||
}
|
||||
|
||||
Screen.global = null;
|
||||
|
@ -457,6 +433,14 @@ Screen.prototype.__defineSetter__('title', function(title) {
|
|||
|
||||
Screen.prototype.enter = function() {
|
||||
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.hideCursor();
|
||||
this.program.cup(0, 0);
|
||||
|
@ -477,6 +461,7 @@ Screen.prototype.leave = function() {
|
|||
this.program.disableMouse();
|
||||
}
|
||||
this.program.normalBuffer();
|
||||
if (this.cursor._set) this.cursorReset();
|
||||
this.program.flush();
|
||||
};
|
||||
|
||||
|
@ -705,10 +690,6 @@ Screen.prototype.render = function() {
|
|||
});
|
||||
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);
|
||||
|
||||
// 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];
|
||||
o = this.olines[y];
|
||||
|
||||
// if (!line.dirty) continue;
|
||||
if (!line.dirty && !(this.artificialCursor && y === this.program.y)) {
|
||||
if (!line.dirty && !(this.cursor.artificial && y === this.program.y)) {
|
||||
continue;
|
||||
}
|
||||
line.dirty = false;
|
||||
|
@ -960,36 +940,19 @@ Screen.prototype.draw = function(start, end) {
|
|||
out = '';
|
||||
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++) {
|
||||
data = line[x][0];
|
||||
ch = line[x][1];
|
||||
|
||||
if (this.artificialCursor
|
||||
&& !this._cursorHidden
|
||||
&& this.cursorState
|
||||
// Render the artificial cursor.
|
||||
if (this.cursor.artificial
|
||||
&& !this.cursor._hidden
|
||||
&& this.cursor._state
|
||||
&& x === this.program.x
|
||||
&& y === this.program.y) {
|
||||
if (this.cursorShape === 'line') {
|
||||
data &= ~(0x1ff << 9);
|
||||
data |= 7 << 9;
|
||||
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;
|
||||
}
|
||||
var cattr = this._cursorAttr(this.cursor, data);
|
||||
if (cattr.ch) ch = cattr.ch;
|
||||
data = cattr.attr;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (this.artificialCursor) {
|
||||
this.cursorShape = shape;
|
||||
this.cursorBlink = blink;
|
||||
var self = this;
|
||||
|
||||
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 this.program.cursorShape(shape, blink);
|
||||
|
||||
return this.program.cursorShape(this.cursor.shape, this.cursor.blink);
|
||||
};
|
||||
|
||||
Screen.prototype.cursorColor = function(color) {
|
||||
if (this.artificialCursor) {
|
||||
this.cursorColor = colors.convert(color);
|
||||
this.cursor.color = color != null
|
||||
? colors.convert(color)
|
||||
: null;
|
||||
this.cursor._set = true;
|
||||
|
||||
if (this.cursor.artificial) {
|
||||
return true;
|
||||
}
|
||||
return this.program.cursorColor(color);
|
||||
|
||||
return this.program.cursorColor(colors.ncolors[this.cursor.color]);
|
||||
};
|
||||
|
||||
Screen.prototype.cursorReset =
|
||||
Screen.prototype.resetCursor = function() {
|
||||
if (this.artificialCursor) {
|
||||
this.cursorShape = 'block';
|
||||
this.cursorBlink = false;
|
||||
this.cursorColor = null;
|
||||
this.cursor.shape = 'block';
|
||||
this.cursor.blink = false;
|
||||
this.cursor.color = 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 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) {
|
||||
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.focus();
|
||||
self.select(item);
|
||||
self.screen.render();
|
||||
});
|
||||
|
|
|
@ -4,11 +4,13 @@ var blessed = require('../')
|
|||
screen = blessed.screen({
|
||||
dump: __dirname + '/logs/widget.log',
|
||||
title: 'widget test',
|
||||
artificialCursor: true,
|
||||
cursorShape: 'line',
|
||||
cursorBlink: true,
|
||||
cursorColor: null
|
||||
//cursorColor: 'red'
|
||||
resizeTimeout: 300,
|
||||
cursor: {
|
||||
artificial: true,
|
||||
shape: 'line',
|
||||
blink: true,
|
||||
color: null
|
||||
}
|
||||
});
|
||||
|
||||
screen.append(blessed.text({
|
||||
|
|
Loading…
Reference in New Issue