diff --git a/README.md b/README.md index 59c4d5a..40eba2c 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,7 @@ The screen on which every other node renders. - **mouse** - received on mouse events. - **keypress** - received on key events. - **element [name]** - global events received for all elements. +- **key [name]** - received on key event for [name]. ##### Methods: @@ -215,6 +216,11 @@ The screen on which every other node renders. - **focusPop()/focusLast()** - pop element off the focus stack. - **saveFocus()** - save the focused element. - **restoreFocus()** - restore the saved focused element. +- **key(name, listener)** - bind a keypress listener for a specific key. +- **spawn(file, args, options)** - spawn a process in the foreground, return to + blessed app after exit. +- **exec(file, args, options, callback)** - spawn a process in the foreground, + return to blessed app after exit. executes callback on error or exit. #### Element (from Node) @@ -271,6 +277,7 @@ The base element. - **keypress** - received on key events for this element. - **move** - received when the element is moved. - **resize** - received when the element is resized. +- **key [name]** - received on key event for [name]. ##### Methods: @@ -281,6 +288,7 @@ The base element. - **show()** - show element. - **toggle()** - toggle hidden/shown. - **focus()** - focus element. +- **key(name, listener)** - bind a keypress listener for a specific key. #### Box (from Element) @@ -507,7 +515,7 @@ an element has the `position: absolute` CSS property. When an element is created, it can be given coordinates in its constructor: ``` js -var box = new blessed.Box({ +var box = blessed.box({ left: 'center', top: 'center', bg: 'yellow', @@ -604,7 +612,7 @@ 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(...); +var box = blessed.box(...); box.setContent('line 1\nline 2'); box.insertBottom('line 3'); box.insertBottom('line 4'); diff --git a/lib/widget.js b/lib/widget.js index 3ae3e60..01f2940 100644 --- a/lib/widget.js +++ b/lib/widget.js @@ -41,14 +41,9 @@ function Node(options) { (options.children || []).forEach(this.append.bind(this)); - if (this.type === 'screen' && !this.focused) { - this.focused = this.children[0]; - } - - var self = this; - this.on('keypress', function(ch, key) { - self.emit('key ' + key.name, key); - }); + // if (this.type === 'screen' && !this.focused) { + // this.focused = this.children[0]; + // } } Node.uid = 0; @@ -57,10 +52,6 @@ Node.prototype.__proto__ = EventEmitter.prototype; Node.prototype.type = 'node'; -Node.prototype.key = function(name, listener) { - return this.on('key ' + name, listener); -}; - Node.prototype.prepend = function(element) { var old = element.parent; @@ -300,12 +291,9 @@ function Screen(options) { }); this.on('newListener', function fn(type) { - if (type === 'keypress' || type === 'mouse') { - if (type === 'keypress') self._listenKeys(); + if (type === 'keypress' || type.indexOf('key ') === 0 || type === 'mouse') { + if (type === 'keypress' || type.indexOf('key ') === 0) self._listenKeys(); if (type === 'mouse') self._listenMouse(); - // if (self._listenedKeys && self._listenedMouse) { - // self.removeListener('newListener', fn); - // } } }); } @@ -316,7 +304,7 @@ Screen.prototype.__proto__ = Node.prototype; Screen.prototype.type = 'screen'; -// TODO: Bubble events. +// TODO: Bubble and capture events throughout the tree. Screen.prototype._listenMouse = function(el) { var self = this; @@ -431,6 +419,7 @@ Screen.prototype._listenKeys = function(el) { if (!grabKeys) { self.emit('keypress', ch, key); + self.emit('key ' + key.name, ch, key); } // If something changed from the screen key handler, stop. @@ -440,6 +429,8 @@ Screen.prototype._listenKeys = function(el) { if (~self.input.indexOf(focused)) { focused.emit('keypress', ch, key); + focused.emit('key ' + key.name, ch, key); + // self.emit('element keypress', focused, ch, key); } }); }; @@ -554,6 +545,15 @@ Screen.prototype.insertTop = function(top, bottom) { return this.insertLine(1, top, top, bottom); }; +Screen.prototype.deleteBottom = function(top, bottom) { + return this.clearRegion(0, this.width, bottom, bottom); +}; + +Screen.prototype.deleteTop = function(top, bottom) { + // return this.insertBottom(top, bottom); + return this.deleteLine(1, top, top, bottom); +}; + Screen.prototype.draw = function(start, end) { var x , y @@ -802,6 +802,108 @@ Screen.prototype.fillRegion = function(attr, ch, xi, xl, yi, yl) { } }; +Screen.prototype.key = function(key, listener) { + return this.on('key ' + key, listener); +}; + +Screen.prototype.spawn = function(file, args, options) { + if (!Array.isArray(args)) { + options = args; + args = []; + } + + var options = options || {} + , spawn = require('child_process').spawn + , screen = this; + + options.stdio = 'inherit'; + + screen.program.normalBuffer(); + screen.program.showCursor(); + + var _listenedMouse = screen._listenedMouse; + if (_listenedMouse) { + screen.program.disableMouse(); + } + + var write = process.stdout.write; + process.stdout.write = function() {}; + + try { + process.stdin.pause(); + } catch (e) { + ; + } + + var resume = function() { + if (resume.done) return; + resume.done = true; + + try { + process.stdin.resume(); + } catch (e) { + ; + } + process.stdout.write = write; + + screen.program.alternateBuffer(); + screen.program.hideCursor(); + if (_listenedMouse) { + screen.program.enableMouse(); + } + + screen.alloc(); + screen.render(); + }; + + var ps = spawn(file, args, options); + + ps.on('error', resume); + + ps.on('exit', resume); + + return ps; +}; + +Screen.prototype.exec = function(file, args, options, callback) { + var callback = arguments[arguments.length-1] + , ps = this.spawn(file, args, options); + + ps.on('error', function(err) { + return callback(err, false); + }); + + ps.on('exit', function(code) { + return callback(null, code === 0); + }); + + return ps; +}; + +Screen.prototype.readEditor = function(callback) { + var fs = require('fs') + , editor = process.env.EDITOR || 'vi' + , file = '/tmp/blessed.' + Math.random().toString(36) + , args = [file] + , opt; + + opt = { + stdio: 'inherit', + env: process.env, + cwd: process.env.HOME + }; + + return this.exec(editor, args, opt, function(err, success) { + if (err) return callback(err); + return fs.readFile(file, 'utf8', function(err, data) { + return fs.unlink(file, function() { + if (err) return callback(err); + return callback(null, data); + }); + }); + }); +}; + /** * Element */ @@ -885,7 +987,7 @@ function Element(options) { || type === 'wheelup' || type === 'mousemove') { self.screen._listenMouse(self); - } else if (type === 'keypress') { + } else if (type === 'keypress' || type.indexOf('key ') === 0) { self.screen._listenKeys(self); } }); @@ -1365,6 +1467,11 @@ Element.prototype.__defineSetter__('rright', function(val) { Element.prototype.__defineSetter__('rtop', function(val) { if (this.position.top === val) return; this.emit('move'); + // if (this._lastPos) { + // this.screen.clearRegion( + // this._lastPos.xi, this._lastPos.xl, + // this._lastPos.yi, this._lastPos.yl); + // } this.screen.clearRegion( this.left, this.left + this.width, this.top, this.top + this.height); @@ -1383,6 +1490,10 @@ Element.prototype.__defineSetter__('rbottom', function(val) { return this.options.bottom = this.position.bottom = val; }); +Element.prototype.key = function(key, listener) { + return this.on('key ' + key, listener); +}; + /** * Box */ @@ -1755,16 +1866,32 @@ Box.prototype.insertTop = function(line) { 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(); + this._clines.splice((this.childBase || 0) + (this.border ? 1 : 0), 0, line); + this.setContent(this._clines.join('\n'), true); }; Box.prototype.insertBottom = function(line) { 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(); + this._clines.splice((this.childBase || 0) + this.height - (this.border ? 2 : 0), 0, line); + this.setContent(this._clines.join('\n'), true); +}; + +Box.prototype.deleteTop = function() { + if (this._lastPos && this._lastPos.xi === 0 && this._lastPos.xl === this.screen.width) { + this.screen.deleteTop(this._lastPos.yi, this._lastPos.yl - 1); + } + this._clines.splice((this.childBase || 0) + (this.border ? 1 : 0), 1); + this.setContent(this._clines.join('\n'), true); +}; + +Box.prototype.deleteBottom = function() { + if (this._lastPos && this._lastPos.xi === 0 && this._lastPos.xl === this.screen.width) { + this.screen.deleteBottom(this._lastPos.yi, this._lastPos.yl - 1); + } + this._clines.splice((this.childBase || 0) + this.height - (this.border ? 2 : 0), 1); + this.setContent(this._clines.join('\n'), true); }; /** @@ -2438,6 +2565,71 @@ Textbox.prototype.setEditor = function(callback) { }); }; +Textbox.prototype.editor = +Textbox.prototype.readEditor = +Textbox.prototype.setEditor = function(callback) { + var self = this; + + this.focus(); + + var fs = require('fs') + , editor = process.env.EDITOR || 'vi' + , file = '/tmp/blessed.' + Math.random().toString(36) + , args = [file] + , opt; + + opt = { + stdio: 'inherit', + env: process.env, + cwd: process.env.HOME + }; + + return this.screen.exec(editor, args, opt, function(err, success) { + if (err) return callback(err); + return fs.readFile(file, 'utf8', function(err, data) { + fs.unlink(file); + + if (err) return callback(err); + value = value.replace(/[\r\n]/g, ''); + self.value = 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); + }); + }); +}; + +Textbox.prototype.editor = +Textbox.prototype.readEditor = +Textbox.prototype.setEditor = function(callback) { + var self = this; + + this.focus(); + + return this.screen.readEditor(function(err, value) { + if (err) return callback(err); + value = value.replace(/[\r\n]/g, ''); + self.value = 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); + }); +}; + /** * Textarea */ diff --git a/test/widget-insert.js b/test/widget-insert.js new file mode 100644 index 0000000..f459e7f --- /dev/null +++ b/test/widget-insert.js @@ -0,0 +1,31 @@ +var blessed = require('blessed'); + +var screen = blessed.screen({ + tput: true +}); + +var box = blessed.box({ + parent: screen, + align: 'center', + bg: 'blue', + height: 5, + top: 'center', + left: 0, + content: 'line 1' +}); + +screen.render(); + +box.insertBottom('line 2'); +box.insertTop('line 0'); + +screen.render(); + +setTimeout(function() { + box.deleteTop(); + screen.render(); +}, 2000); + +screen.key('q', function() { + process.exit(0); +}); diff --git a/test/widget.js b/test/widget.js index 32b9a8d..02464cb 100644 --- a/test/widget.js +++ b/test/widget.js @@ -1,5 +1,8 @@ -var blessed = require('blessed') - , screen = blessed.Screen({ tput: true }); +var blessed = require('blessed'); + +var screen = new blessed.Screen({ + tput: true +}); screen.append(new blessed.Text({ top: 0,