Image and Terminal element.

This commit is contained in:
Christopher Jeffrey 2015-02-11 17:20:22 -08:00
parent 267e77a089
commit 982e075a90
4 changed files with 186 additions and 107 deletions

View File

@ -871,24 +871,37 @@ A radio button which can be used in a form element.
#### Terminal (from Box) #### Terminal (from Box)
A box which spins up a pseudo terminal and renders the output. (Requires A box which spins up a pseudo terminal and renders the output. Useful for
term.js to be installed). writing a terminal multiplexer, or something similar to an mc-like file
manager. Requires term.js and pty.js to be installed. See
`example/multiplex.js` for an example terminal multiplexer.
##### Options: ##### Options:
- inherits all from Box. - inherits all from Box.
- **handler** - handler for input data.
- **shell** - name of shell. `$SHELL` by default.
- **args** - args for shell.
- **cursor** - can be `line`, `underline`, and `block`.
- Other options similar to term.js'.
##### Properties: ##### Properties:
- inherits all from Box. - inherits all from Box.
- **term** - reference to the headless term.js terminal.
- **pty** - reference to the pty.js pseudo terminal.
##### Events: ##### Events:
- inherits all from Box. - inherits all from Box.
- **title** - window title from terminal.
- Other events similar to ScrollableBox.
##### Methods: ##### Methods:
- inherits all from Box. - inherits all from Box.
- **write(data)** - write data to the terminal.
- Other methods similar to ScrollableBox.
#### Image (from Box) #### Image (from Box)
@ -900,8 +913,9 @@ terminals.
##### Options: ##### Options:
- inherits all from Box. - inherits all from Box.
- **file** - path to image - **file** - path to image.
- **w3m** - path to w3mimgdisplay - **w3m** - path to w3mimgdisplay. if a proper w3mimgdisplay path is not given,
blessed will search the entire disk for the binary.
##### Properties: ##### Properties:
@ -918,6 +932,7 @@ terminals.
- **clearImage(callback)** - clear the current image. - **clearImage(callback)** - clear the current image.
- **imageSize(img, callback)** - get the size of an image file in pixels. - **imageSize(img, callback)** - get the size of an image file in pixels.
- **termSize(callback)** - get the size of the terminal in pixels. - **termSize(callback)** - get the size of the terminal in pixels.
- **getPixelRatio(callback)** - get the pixel to cell ratio for the terminal.
### Positioning ### Positioning

View File

@ -14,6 +14,9 @@ var blessed = require('blessed')
var left = blessed.terminal({ var left = blessed.terminal({
parent: screen, parent: screen,
cursor: 'line',
cursorBlink: true,
screenKeys: false,
left: 0, left: 0,
top: 2, top: 2,
bottom: 2, bottom: 2,
@ -32,6 +35,9 @@ var left = blessed.terminal({
var right = blessed.terminal({ var right = blessed.terminal({
parent: screen, parent: screen,
cursor: 'block',
cursorBlink: true,
screenKeys: false,
right: 2, right: 2,
top: 2, top: 2,
bottom: 2, bottom: 2,

View File

@ -5364,7 +5364,7 @@ ProgressBar.prototype.render = function() {
yi = yi + ((yl - yi) - (((yl - yi) * (this.filled / 100)) | 0)); yi = yi + ((yl - yi) - (((yl - yi) * (this.filled / 100)) | 0));
} }
dattr = this.sattr(this, this.style.bar.fg, this.style.bar.bg); dattr = this.sattr(this.style.bar, this.style.bar.fg, this.style.bar.bg);
this.screen.fillRegion(dattr, this.ch, xi, xl, yi, yl); this.screen.fillRegion(dattr, this.ch, xi, xl, yi, yl);
@ -6481,7 +6481,7 @@ function Terminal(options) {
} }
options = options || {}; options = options || {};
options.scrollable = true; options.scrollable = false;
Box.call(this, options); Box.call(this, options);
@ -6489,6 +6489,10 @@ function Terminal(options) {
this.shell = options.shell || process.env.SHELL || 'sh'; this.shell = options.shell || process.env.SHELL || 'sh';
this.args = options.args || []; 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 = this.style || {};
this.style.bg = this.style.bg || 'default'; this.style.bg = this.style.bg || 'default';
this.style.fg = this.style.fg || 'default'; this.style.fg = this.style.fg || 'default';
@ -6535,14 +6539,15 @@ Terminal.prototype.bootstrap = function() {
element.offsetParent = element; element.offsetParent = element;
this.term = require('term.js')({ this.term = require('term.js')({
//termName: this.screen.program.terminal,
termName: 'xterm', termName: 'xterm',
cols: this.width - this.iwidth, cols: this.width - this.iwidth,
rows: this.height - this.iheight, rows: this.height - this.iheight,
context: element, context: element,
document: element, document: element,
body: element, body: element,
parent: element parent: element,
cursorBlink: this.cursorBlink,
screenKeys: this.screenKeys
}); });
this.term.refresh = function() { this.term.refresh = function() {
@ -6623,15 +6628,11 @@ Terminal.prototype.bootstrap = function() {
}); });
this.on('focus', function() { this.on('focus', function() {
if (self.term.sendFocus) { self.term.focus();
self.handler('\x1b[I');
}
}); });
this.on('blur', function() { this.on('blur', function() {
if (self.term.sendFocus) { self.term.blur();
self.handler('\x1b[O');
}
}); });
this.term.on('title', function(title) { this.term.on('title', function(title) {
@ -6650,7 +6651,6 @@ Terminal.prototype.bootstrap = function() {
}); });
this.pty = require('pty.js').fork(this.shell, this.args, { this.pty = require('pty.js').fork(this.shell, this.args, {
//name: this.screen.program.terminal,
name: 'xterm', name: 'xterm',
cols: this.width - this.iwidth, cols: this.width - this.iwidth,
rows: this.height - this.iheight, rows: this.height - this.iheight,
@ -6687,7 +6687,7 @@ Terminal.prototype.render = function() {
var ret = this._render(); var ret = this._render();
if (!ret) return; if (!ret) return;
this.dattr = this.sattr(this, this.style.fg, this.style.bg); this.dattr = this.sattr(this.style, this.style.fg, this.style.bg);
var xi = ret.xi + this.ileft var xi = ret.xi + this.ileft
, xl = ret.xl - this.iright , xl = ret.xl - this.iright
@ -6702,7 +6702,7 @@ Terminal.prototype.render = function() {
if (!line || !this.term.lines[scrollback + y - yi]) break; if (!line || !this.term.lines[scrollback + y - yi]) break;
if (y === yi + this.term.y if (y === yi + this.term.y
// && this.term.cursorState && this.term.cursorState
&& this.screen.focused === this && this.screen.focused === this
&& (this.term.ydisp === this.term.ybase || this.term.selectMode) && (this.term.ydisp === this.term.ybase || this.term.selectMode)
&& !this.term.cursorHidden) { && !this.term.cursorHidden) {
@ -6713,21 +6713,42 @@ Terminal.prototype.render = function() {
for (var x = xi; x < xl; x++) { for (var x = xi; x < xl; x++) {
if (!line[x] || !this.term.lines[scrollback + y - yi][x - xi]) break; if (!line[x] || !this.term.lines[scrollback + y - yi][x - xi]) break;
if (x === cursor) { if (x === cursor) {
line[x][0] = 7; if (this.cursor === 'line') {
line[x][1] = ' '; line[x][0] = this.dattr;
line[x][1] = '\u2502';
} else if (this.cursor === 'underline') {
line[x][0] = this.dattr;
line[x][1] = '_';
} else if (this.cursor === 'block' || !this.cursor) {
line[x][0] = this.dattr | (8 << 18);
line[x][1] = ' ';
}
continue; continue;
} }
line[x][0] = this.term.lines[scrollback + y - yi][x - xi][0]; line[x][0] = this.term.lines[scrollback + y - yi][x - xi][0];
// default foreground = 257
if (((line[x][0] >> 9) & 0x1ff) === 257 && !((line[x][0] >> 18) & 8)) {
line[x][0] = (line[x][0] << 18) | (((this.dattr >> 9) & 0x1ff) << 9) | (line[x][0] & 0x1ff);
}
// default background = 256
if ((line[x][0] & 0x1ff) === 256 && !((line[x][0] >> 18) & 8)) {
line[x][0] = (line[x][0] << 18) | (((line[x][0] >> 9) & 0x1ff) << 9) | (this.dattr & 0x1ff);
}
line[x][1] = this.term.lines[scrollback + y - yi][x - xi][1]; line[x][1] = this.term.lines[scrollback + y - yi][x - xi][1];
// default foreground = 257
if (((line[x][0] >> 9) & 0x1ff) === 257) {
if (!((line[x][0] >> 18) & 8)) {
line[x][0] &= ~(0x1ff << 9);
line[x][0] |= ((this.dattr >> 9) & 0x1ff) << 9;
}
}
// default background = 256
if ((line[x][0] & 0x1ff) === 256) {
if (!((line[x][0] >> 18) & 8)) {
line[x][0] &= ~0x1ff;
line[x][0] |= this.dattr & 0x1ff;
// } else {
// line[x][0] &= ~(0x1ff << 9);
// line[x][0] |= ((this.dattr >> 9) & 0x1ff) << 9;
}
}
} }
line.dirty = true; line.dirty = true;
@ -6747,6 +6768,7 @@ Terminal.prototype._isMouse = function(buf) {
} }
} }
return (buf[0] === 0x1b && buf[1] === 0x5b && buf[2] === 0x4d) 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+)M/.test(s)
|| /^\x1b\[<(\d+;\d+;\d+)([mM])/.test(s) || /^\x1b\[<(\d+;\d+;\d+)([mM])/.test(s)
|| /^\x1b\[<(\d+;\d+;\d+;\d+)&w/.test(s) || /^\x1b\[<(\d+;\d+;\d+;\d+)&w/.test(s)
@ -6754,6 +6776,39 @@ Terminal.prototype._isMouse = function(buf) {
|| /^\x1b\[(O|I)/.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);
};
/** /**
* Image * Image
* Good example of w3mimgdisplay commands: * Good example of w3mimgdisplay commands:
@ -6771,12 +6826,44 @@ function Image(options) {
Box.call(this, options); Box.call(this, options);
if (options.w3m) {
Image.w3mdisplay = options.w3m;
}
if (Image.hasW3MDisplay == null) { if (Image.hasW3MDisplay == null) {
if (fs.existsSync(Image.w3mdisplay)) { if (fs.existsSync(Image.w3mdisplay)) {
Image.hasW3MDisplay = true; Image.hasW3MDisplay = true;
} else if (options.search !== false) {
var file = findFile('/', '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);
});
if (this.options.file || this.options.img) { if (this.options.file || this.options.img) {
this.setImage(this.options.file || this.options.img); this.setImage(this.options.file || this.options.img);
} }
@ -6822,12 +6909,6 @@ Image.prototype.spawn = function(file, args, opt, callback) {
Image.prototype.setImage = function(img, callback) { Image.prototype.setImage = function(img, callback) {
var self = this; var self = this;
if (Image.hasW3MDisplay == null) {
return this._waitForW3MDisplay(function() {
return self.setImage(img, callback);
});
}
if (Image.hasW3MDisplay === false) { if (Image.hasW3MDisplay === false) {
if (!callback) return; if (!callback) return;
return callback(new Error('W3M Image Display not available.')); return callback(new Error('W3M Image Display not available.'));
@ -6840,9 +6921,6 @@ Image.prototype.setImage = function(img, callback) {
this.file = img; this.file = img;
// NOTE: Fall back to screen.displayImage() instead of
// setImage() if w3mimgdisplay is not present?
function renderImage(ratio, callback) { function renderImage(ratio, callback) {
// clearImage unsets these: // clearImage unsets these:
var _file = self.file; var _file = self.file;
@ -6953,12 +7031,6 @@ Image.prototype.setImage = function(img, callback) {
Image.prototype.clearImage = function(callback) { Image.prototype.clearImage = function(callback) {
var self = this; var self = this;
if (Image.hasW3MDisplay == null) {
return this._waitForW3MDisplay(function() {
return self.clearImage(callback);
});
}
if (Image.hasW3MDisplay === false) { if (Image.hasW3MDisplay === false) {
if (!callback) return; if (!callback) return;
return callback(new Error('W3M Image Display not available.')); return callback(new Error('W3M Image Display not available.'));
@ -7007,12 +7079,6 @@ Image.prototype.imageSize = function(callback) {
var self = this; var self = this;
var img = this.file; var img = this.file;
if (Image.hasW3MDisplay == null) {
return this._waitForW3MDisplay(function() {
return self.imageSize(callback);
});
}
if (Image.hasW3MDisplay === false) { if (Image.hasW3MDisplay === false) {
if (!callback) return; if (!callback) return;
return callback(new Error('W3M Image Display not available.')); return callback(new Error('W3M Image Display not available.'));
@ -7063,12 +7129,6 @@ Image.prototype.imageSize = function(callback) {
Image.prototype.termSize = function(callback) { Image.prototype.termSize = function(callback) {
var self = this; var self = this;
if (Image.hasW3MDisplay == null) {
return this._waitForW3MDisplay(function() {
return self.termSize(callback);
});
}
if (Image.hasW3MDisplay === false) { if (Image.hasW3MDisplay === false) {
if (!callback) return; if (!callback) return;
return callback(new Error('W3M Image Display not available.')); return callback(new Error('W3M Image Display not available.'));
@ -7099,7 +7159,8 @@ Image.prototype.termSize = function(callback) {
if (!callback) return; if (!callback) return;
if (!buf.trim()) { if (!buf.trim()) {
// XXX Bug // Bug: w3mimgdisplay will sometimes
// output nothing. Try again:
return self.termSize(callback); return self.termSize(callback);
} }
@ -7140,57 +7201,6 @@ Image.prototype.displayImage = function(callback) {
return this.screen.displayImage(this.file, callback); return this.screen.displayImage(this.file, callback);
}; };
Image.prototype._waitForW3MDisplay = function(callback) {
var self = this;
if (Image.hasW3MDisplay == null) {
if (Image._checkingW3MDisplay) {
setTimeout(function() {
return self._waitForW3MDisplay(callback);
}, 500);
return;
}
Image._checkingW3MDisplay = true;
var ps = this.screen.spawn('find',
['/', '-name', 'w3mimgdisplay'],
{ stdio: ['ignore', 'pipe', 'ignore'] });
var buf = '';
ps.stdout.setEncoding('utf8');
ps.stdout.on('data', function(data) {
buf += data;
});
ps.on('error', function(err) {
Image.hasW3MDisplay = false;
Image._checkingW3MDisplay = false;
if (!callback) return;
return callback(err);
});
ps.on('exit', function(code) {
if (!buf.trim()) {
Image.hasW3MDisplay = false;
Image._checkingW3MDisplay = false;
if (!callback) return;
return callback(new Error('find failed.'));
}
Image._checkingW3MDisplay = false;
buf = buf.trim().split('\n')[0].trim();
Image.w3mdisplay = buf;
Image.hasW3MDisplay = true;
return self._waitForW3MDisplay(callback);
});
return;
}
if (callback) callback(null, Image.w3mdisplay);
};
/** /**
* Helpers * Helpers
*/ */
@ -7263,6 +7273,43 @@ var wideChars = new RegExp('(['
+ '\\uffe8-\\uffee' + '\\uffe8-\\uffee'
+ '])', 'g'); + '])', 'g');
function findFile(start, target) {
return (function read(dir) {
var files, file, stat, out;
if (dir === '/dev' || dir === '/sys' || dir === '/proc') {
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.statSync((dir === '/' ? '' : dir) + '/' + file);
} catch (e) {
stat = null;
}
if (stat && stat.isDirectory()) {
out = read((dir === '/' ? '' : dir) + '/' + file);
if (out) return out;
}
}
return null;
})(start);
}
/** /**
* Helpers * Helpers
*/ */

View File

@ -7,7 +7,10 @@ screen = blessed.screen({
}); });
// To ensure our w3mimgdisplay search works: // To ensure our w3mimgdisplay search works:
blessed.image.w3mdisplay = '/does/not/exist'; if (process.argv[2] === 'find') {
blessed.image.w3mdisplay = '/does/not/exist';
process.argv.length = 2;
}
var file = process.argv[2] || __dirname + '/test-image.png'; var file = process.argv[2] || __dirname + '/test-image.png';
@ -35,6 +38,14 @@ setTimeout(function() {
image.rtop = 2; image.rtop = 2;
image.rleft = 7; image.rleft = 7;
screen.render(); screen.render();
setTimeout(function() {
image.detach();
screen.render();
setTimeout(function() {
screen.append(image);
screen.render();
}, 1000);
}, 1000);
}, 1000); }, 1000);
}, 5000); }, 5000);
}); });