├── .gitignore ├── .jscsrc ├── .jshintrc ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── tput.js ├── browser ├── Makefile └── transform.js ├── example ├── ansi-viewer │ ├── LICENSE │ ├── README.md │ ├── ansi-art.list │ ├── index.js │ ├── package.json │ └── singlebyte.js ├── blessed-telnet.js ├── index.js ├── multiplex.js ├── ping ├── simple-form.js ├── time.js └── widget.js ├── img ├── ansi-viewer.png ├── screenshot.png ├── v0.1.0-1.gif ├── v0.1.0-2.gif ├── v0.1.0-3.gif ├── v0.1.0-4.gif └── v0.1.0.gif ├── index.js ├── lib ├── alias.js ├── blessed.js ├── colors.js ├── events.js ├── gpmclient.js ├── helpers.js ├── keys.js ├── program.js ├── tput.js ├── unicode.js ├── widget.js └── widgets │ ├── ansiimage.js │ ├── bigtext.js │ ├── box.js │ ├── button.js │ ├── checkbox.js │ ├── element.js │ ├── filemanager.js │ ├── form.js │ ├── image.js │ ├── input.js │ ├── layout.js │ ├── line.js │ ├── list.js │ ├── listbar.js │ ├── listtable.js │ ├── loading.js │ ├── log.js │ ├── message.js │ ├── node.js │ ├── overlayimage.js │ ├── progressbar.js │ ├── prompt.js │ ├── question.js │ ├── radiobutton.js │ ├── radioset.js │ ├── screen.js │ ├── scrollablebox.js │ ├── scrollabletext.js │ ├── table.js │ ├── terminal.js │ ├── text.js │ ├── textarea.js │ ├── textbox.js │ └── video.js ├── package.json ├── test ├── git.diff ├── helpers.js ├── logs │ └── .gitignore ├── lorem.txt ├── program-mouse.js ├── tail.js ├── terminfo ├── test-image.png ├── tput ├── tput.js ├── widget-autopad.js ├── widget-bigtext.js ├── widget-csr.js ├── widget-dock-noborder.js ├── widget-dock.js ├── widget-exit.js ├── widget-file.js ├── widget-form.js ├── widget-image.js ├── widget-insert.js ├── widget-layout.js ├── widget-listbar.js ├── widget-listtable.js ├── widget-log.js ├── widget-nested-attr.js ├── widget-noalt.js ├── widget-nowrap.js ├── widget-obscure-sides.js ├── widget-padding.js ├── widget-play.js ├── widget-png.js ├── widget-pos.js ├── widget-prompt.js ├── widget-record.js ├── widget-scrollable-boxes.js ├── widget-shadow.js ├── widget-shrink-fail-2.js ├── widget-shrink-fail.js ├── widget-shrink-padding.js ├── widget-table.js ├── widget-term-blessed.js ├── widget-termswitch.js ├── widget-textarea.js ├── widget-unicode.js ├── widget-valign.js ├── widget-video.js └── widget.js ├── usr ├── fonts │ ├── AUTHORS │ ├── LICENSE │ ├── README │ ├── ter-u14b.json │ └── ter-u14n.json ├── linux ├── windows-ansi ├── xterm ├── xterm-256color ├── xterm.termcap └── xterm.terminfo └── vendor └── tng.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "airbnb", 3 | 4 | "esnext": false, 5 | "requireTrailingComma": null, 6 | "requireCommaBeforeLineBreak": null, 7 | "requireCamelCaseOrUpperCaseIdentifiers": null, 8 | "requirePaddingNewLinesBeforeLineComments": null, 9 | "requirePaddingNewLinesAfterBlocks": null, 10 | "safeContextKeyword": "self", 11 | 12 | "maximumLineLength": 110, 13 | "maxErrors": 1000, 14 | "requireSpaceAfterLineComment": true 15 | } 16 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": false, 3 | "curly": false, 4 | "eqeqeq": true, 5 | "freeze": true, 6 | "latedef": "nofunc", 7 | "maxparams": 7, 8 | "noarg": true, 9 | "shadow": "inner", 10 | "undef": true, 11 | "unused": true, 12 | 13 | "boss": true, 14 | "expr": true, 15 | "eqnull": true, 16 | "evil": true, 17 | "loopfunc": true, 18 | "proto": true, 19 | "supernew": true, 20 | 21 | "-W018": true, 22 | "-W064": true, 23 | "-W086": true, 24 | "+W032": true, 25 | 26 | "browser": false, 27 | "browserify": false, 28 | "node": true, 29 | "nonstandard": true, 30 | "typed": true, 31 | "worker": false, 32 | 33 | "camelcase": false, 34 | "indent": 2, 35 | "maxlen": 110, 36 | "newcap": true, 37 | "quotmark": "single", 38 | 39 | "laxbreak": true, 40 | "laxcomma": true 41 | } 42 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | test/ 3 | img/ 4 | node_modules/ 5 | .jshintrc 6 | .jscsrc 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Blessed v0.1.0 - new terminal goodies for node.js 2 | 3 | ![blessed](https://raw.githubusercontent.com/chjj/blessed/master/img/v0.1.0-3.gif) 4 | 5 | The features demonstrated in the above gif __element transparency/shadow__ and 6 | __border docking__. 7 | 8 | ## New useful options for your typewriter application: 9 | 10 | - __`transparent` option__ - Lower element opacity to 50%. This will display 11 | dimmed elements and content behind the foreground element using a naive color 12 | blending function (good enough for a terminal's limited amount of colors). 13 | works best with 256color terminals. (see widget-shadow.js) 14 | 15 | - __`shadow` option__ - Give the element a translucent shadow. Automatically 16 | darkens the background behind it. (see widget-shadow.js) 17 | 18 | - __`dockBorders` option__ - Element borders will automatically "dock" to each 19 | other. Instead of overlapping the borders end up connecting. (see 20 | widget-dock.js) 21 | 22 | - __`autoPadding` default__ - Auto padding is now enabled by default, meaning 23 | blessed will automatically position elements inside their parent's border. 24 | 25 | - __`rleft` property__ - Relative offsets are now default element properties 26 | (`left` instead of `rleft`). 27 | 28 | - __`draggable` property__ - Make any element draggable with the mouse. (see 29 | widget-shadow.js or widget-dock.js) 30 | 31 | - __`Table` and `ListTable` elements__ - Tables with a high quality rendering. 32 | (see widget-table.js and widget-listtable.js) 33 | 34 | - __`Log` element__ - A top to bottom logger box with scrollback and other 35 | features. (see widget-log.js) 36 | 37 | - __Obscurable borders__ - In addition to docking borders, it's possible to 38 | obscure borders by sliding them off the screen with negative offsets. (see 39 | widget-dock.js) 40 | 41 | - __Percentage expressions__ - Like CSS, arithmetic can now be performed on 42 | percentages. e.g. `width: '50%-1'`. This is useful for overlapping borders on 43 | elements with a percentage width. (see widget-dock.js) 44 | 45 | ## Other features that weren't mentioned before: 46 | 47 | - __`setHover` option__ - Set a hover text box to follow cursor on mouseover, 48 | similar to how a web browser handles the "title" attribute. (see widget.js) 49 | 50 | - __`Terminal` element__ - Spin up a pseudo terminal as a blessed element. 51 | useful for writing a terminal multiplexer. (requires term.js and pty.js as 52 | optional dependencies). (see example/multiplex.js) 53 | 54 | - __`Image` element__ - Uses `w3mimgdisplay` to draw real images your terminal. 55 | this is much easier than calling w3mimgdisplay by hand. Image elements behave 56 | like any other element, although it is wise to use `width: 'shrink', height: 57 | 'shrink'`. (see widget-image.js) 58 | 59 | --- 60 | 61 | The major things that justified the 0.1.0 release were fixes and stabilization 62 | of api (`autoPadding`/`rleft`/`left`). Scrolling boxes were almost completely 63 | revamped to work a bit smarter. 64 | 65 | --- 66 | 67 | ## Things yet to come: 68 | 69 | - __@secrettriangle's [improvements](https://github.com/slap-editor/slap) for 70 | textareas__ - This allows for real text navigation. 71 | 72 | - __Gravity and margin layouts__ 73 | 74 | This is something that's been in the idea bin for a while. Every element could 75 | potentially have properties like: 76 | 77 | ``` 78 | gravity: 'bottomleft', 79 | margin: 5, 80 | `` 81 | 82 | In other words, just a more complex `float` system than what the CSSOM is used 83 | to. 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2015, Christopher Jeffrey and contributors 2 | https://github.com/chjj/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /bin/tput.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var blessed = require('../') 4 | , argv = process.argv.slice(2) 5 | , cmd = argv.shift() 6 | , tput; 7 | 8 | tput = blessed.tput({ 9 | terminal: process.env.TERM, 10 | termcap: !!process.env.USE_TERMCAP, 11 | extended: true 12 | }); 13 | 14 | if (tput[cmd]) { 15 | process.stdout.write(tput[cmd].apply(tput, argv)); 16 | } 17 | -------------------------------------------------------------------------------- /browser/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @cd .. && browserify -e index.js -o browser/blessed.js 3 | 4 | clean: 5 | @rm -f blessed.js 6 | 7 | .PHONY: clean all 8 | -------------------------------------------------------------------------------- /browser/transform.js: -------------------------------------------------------------------------------- 1 | /** 2 | * transform.js - browserify workaround for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | var Transform = require('stream').Transform 8 | , path = require('path') 9 | , fs = require('fs'); 10 | 11 | /** 12 | * Transformer 13 | */ 14 | 15 | function transformer(code) { 16 | var stream = new Transform; 17 | stream._transform = function(chunk, encoding, callback) { 18 | return callback(null, chunk); 19 | }; 20 | stream._flush = function(callback) { 21 | if (code) { 22 | stream.push(code); 23 | } 24 | return callback(); 25 | }; 26 | return stream; 27 | } 28 | 29 | /** 30 | * Explicitly require all widgets in widget.js 31 | */ 32 | 33 | var widgets = fs.readdirSync(__dirname + '/../lib/widgets'); 34 | 35 | var requireWidgets = widgets.reduce(function(out, name) { 36 | name = path.basename(name, '.js'); 37 | out += '\nrequire(\'./widgets/' + name + '\');'; 38 | return out; 39 | }, ''); 40 | 41 | /** 42 | * Do not make filesystem calls in tput.js for 43 | * terminfo or termcap, just use xterm terminfo/cap. 44 | */ 45 | 46 | var infoPath = path.resolve(__dirname, '..', 'usr', 'xterm-256color') 47 | , capPath = path.resolve(__dirname, '..', 'usr', 'xterm.termcap'); 48 | 49 | var infoPathFake = path.resolve( 50 | path.sep, 'usr', 'share', 'terminfo', 51 | path.basename(infoPath)[0], 52 | path.basename(infoPath) 53 | ); 54 | 55 | function readMethods() { 56 | Tput._infoBuffer = new Buffer(TERMINFO, 'base64'); 57 | 58 | Tput.prototype.readTerminfo = function() { 59 | this.terminal = TERMINFO_NAME; 60 | return this.parseTerminfo(Tput._infoBuffer, TERMINFO_PATH); 61 | }; 62 | 63 | Tput.cpaths = []; 64 | Tput.termcap = TERMCAP; 65 | 66 | Tput.prototype._readTermcap = Tput.prototype.readTermcap; 67 | Tput.prototype.readTermcap = function() { 68 | this.terminal = TERMCAP_NAME; 69 | return this._readTermcap(this.terminal); 70 | }; 71 | 72 | Tput.prototype.detectUnicode = function() { 73 | return true; 74 | }; 75 | } 76 | 77 | readMethods = readMethods.toString().slice(24, -2) 78 | .replace(/^ /gm, '') 79 | .replace('TERMINFO', JSON.stringify(fs.readFileSync(infoPath, 'base64'))) 80 | .replace('TERMINFO_NAME', JSON.stringify(path.basename(infoPath))) 81 | .replace('TERMINFO_PATH', JSON.stringify(infoPathFake)) 82 | .replace('TERMCAP', JSON.stringify(fs.readFileSync(capPath, 'utf8'))) 83 | .replace('TERMCAP_NAME', JSON.stringify(path.basename(capPath, '.termcap'))); 84 | 85 | /** 86 | * Helpers 87 | */ 88 | 89 | function end(file, offset) { 90 | return file.split(path.sep).slice(-offset).join('/'); 91 | } 92 | 93 | /** 94 | * Expose 95 | */ 96 | 97 | module.exports = function(file) { 98 | if (end(file, 2) === 'lib/widget.js') { 99 | return transformer(requireWidgets); 100 | } 101 | if (end(file, 2) === 'lib/tput.js') { 102 | return transformer(readMethods); 103 | } 104 | return transformer(); 105 | }; 106 | -------------------------------------------------------------------------------- /example/ansi-viewer/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Christopher Jeffrey 2 | https://github.com/chjj/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /example/ansi-viewer/README.md: -------------------------------------------------------------------------------- 1 | # ansi-viewer 2 | 3 | A terminal app to view ANSI art from http://artscene.textfiles.com/ansi/. 4 | 5 | ![ansi-viewer](https://raw.githubusercontent.com/chjj/blessed/master/img/ansi-viewer.png) 6 | 7 | ## Contribution and License Agreement 8 | 9 | If you contribute code to this project, you are implicitly allowing your code 10 | to be distributed under the MIT license. You are also implicitly verifying that 11 | all code is your original work. `` 12 | 13 | ## License 14 | 15 | Copyright (c) 2015, Christopher Jeffrey. (MIT License) 16 | 17 | See LICENSE for more info. 18 | -------------------------------------------------------------------------------- /example/ansi-viewer/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ansi-viewer 3 | * ANSI art viewer for node. 4 | * Copyright (c) 2015, Christopher Jeffrey and contributors (MIT License). 5 | * https://github.com/chjj/blessed 6 | */ 7 | 8 | var blessed = require('blessed') 9 | , request = require('request') 10 | , singlebyte = require('./singlebyte') 11 | , fs = require('fs'); 12 | 13 | // $ wget -r -o log --tries=10 'http://artscene.textfiles.com/ansi/' 14 | // $ grep 'http.*\.ans$' log | awk '{ print $3 }' > ansi-art.list 15 | 16 | var urls = fs.readFileSync(__dirname + '/ansi-art.list', 'utf8').trim().split('\n'); 17 | 18 | var map = urls.reduce(function(map, url) { 19 | map[/([^.\/]+\/[^.\/]+)\.ans$/.exec(url)[1]] = url; 20 | return map; 21 | }, {}); 22 | 23 | var max = Object.keys(map).reduce(function(out, text) { 24 | return Math.max(out, text.length); 25 | }, 0) + 6; 26 | 27 | var screen = blessed.screen({ 28 | smartCSR: true, 29 | dockBorders: true 30 | }); 31 | 32 | var art = blessed.terminal({ 33 | parent: screen, 34 | left: 0, 35 | top: 0, 36 | height: 60, 37 | // some are 78/80, some are 80/82 38 | width: 82, 39 | border: 'line', 40 | tags: true, 41 | label: ' {bold}{cyan-fg}ANSI Art{/cyan-fg}{/bold} (Drag Me) ', 42 | handler: function() {}, 43 | draggable: true 44 | }); 45 | 46 | var list = blessed.list({ 47 | parent: screen, 48 | label: ' {bold}{cyan-fg}Art List{/cyan-fg}{/bold} (Drag Me) ', 49 | tags: true, 50 | draggable: true, 51 | top: 0, 52 | right: 0, 53 | width: max, 54 | height: '50%', 55 | keys: true, 56 | vi: true, 57 | mouse: true, 58 | border: 'line', 59 | scrollbar: { 60 | ch: ' ', 61 | track: { 62 | bg: 'cyan' 63 | }, 64 | style: { 65 | inverse: true 66 | } 67 | }, 68 | style: { 69 | item: { 70 | hover: { 71 | bg: 'blue' 72 | } 73 | }, 74 | selected: { 75 | bg: 'blue', 76 | bold: true 77 | } 78 | }, 79 | search: function(callback) { 80 | prompt.input('Search:', '', function(err, value) { 81 | if (err) return; 82 | return callback(null, value); 83 | }); 84 | } 85 | }); 86 | 87 | var status = blessed.box({ 88 | parent: screen, 89 | bottom: 0, 90 | right: 0, 91 | height: 1, 92 | width: 'shrink', 93 | style: { 94 | bg: 'blue' 95 | }, 96 | content: 'Select your piece of ANSI art (`/` to search).' 97 | }); 98 | 99 | var loader = blessed.loading({ 100 | parent: screen, 101 | top: 'center', 102 | left: 'center', 103 | height: 5, 104 | align: 'center', 105 | width: '50%', 106 | tags: true, 107 | hidden: true, 108 | border: 'line' 109 | }); 110 | 111 | var msg = blessed.message({ 112 | parent: screen, 113 | top: 'center', 114 | left: 'center', 115 | height: 'shrink', 116 | width: '50%', 117 | align: 'center', 118 | tags: true, 119 | hidden: true, 120 | border: 'line' 121 | }); 122 | 123 | var prompt = blessed.prompt({ 124 | parent: screen, 125 | top: 'center', 126 | left: 'center', 127 | height: 'shrink', 128 | width: 'shrink', 129 | keys: true, 130 | vi: true, 131 | mouse: true, 132 | tags: true, 133 | border: 'line', 134 | hidden: true 135 | }); 136 | 137 | list.setItems(Object.keys(map)); 138 | 139 | list.on('select', function(el, selected) { 140 | if (list._.rendering) return; 141 | 142 | var name = el.getText(); 143 | var url = map[name]; 144 | 145 | status.setContent(url); 146 | 147 | list._.rendering = true; 148 | loader.load('Loading...'); 149 | 150 | request({ 151 | uri: url, 152 | encoding: null 153 | }, function(err, res, body) { 154 | list._.rendering = false; 155 | loader.stop(); 156 | 157 | if (err) { 158 | return msg.error(err.message); 159 | } 160 | 161 | if (!body) { 162 | return msg.error('No body.'); 163 | } 164 | 165 | return cp437ToUtf8(body, function(err, body) { 166 | if (err) { 167 | return msg.error(err.message); 168 | } 169 | 170 | if (process.argv[2] === '--debug') { 171 | var filename = name.replace(/\//g, '.') + '.ans'; 172 | fs.writeFileSync(__dirname + '/' + filename, body); 173 | } 174 | 175 | // Remove text: 176 | body = body.replace('Downloaded From P-80 International Information Systems 304-744-2253', ''); 177 | 178 | // Remove MCI codes: 179 | body = body.replace(/%[A-Z0-9]{2}/g, ''); 180 | 181 | // ^A (SOH) seems to need to produce CRLF in some cases?? 182 | // body = body.replace(/\x01/g, '\r\n'); 183 | 184 | // Reset and write the art: 185 | art.term.reset(); 186 | art.term.write(body); 187 | art.term.cursorHidden = true; 188 | 189 | screen.render(); 190 | 191 | if (process.argv[2] === '--debug' || process.argv[2] === '--save') { 192 | takeScreenshot(name); 193 | } 194 | }); 195 | }); 196 | }); 197 | 198 | list.items.forEach(function(item, i) { 199 | var text = item.getText(); 200 | item.setHover(map[text]); 201 | }); 202 | 203 | list.focus(); 204 | list.enterSelected(0); 205 | 206 | screen.key('h', function() { 207 | list.toggle(); 208 | if (list.visible) list.focus(); 209 | }); 210 | 211 | screen.key('r', function() { 212 | shuffle(); 213 | }); 214 | 215 | screen.key('S-s', function() { 216 | takeScreenshot(list.ritems[list.selected]); 217 | }); 218 | 219 | screen.key('s', function() { 220 | slideshow(); 221 | }); 222 | 223 | screen.key('q', function() { 224 | return process.exit(0); 225 | }); 226 | 227 | screen.render(); 228 | 229 | /** 230 | * Helpers 231 | */ 232 | 233 | // https://github.com/chjj/blessed/issues/127 234 | // https://github.com/Mithgol/node-singlebyte 235 | 236 | function cp437ToUtf8(buf, callback) { 237 | try { 238 | return callback(null, singlebyte.bufToStr(buf, 'cp437')); 239 | } catch (e) { 240 | return callback(e); 241 | } 242 | } 243 | 244 | // Animating ANSI art doesn't work for screenshots. 245 | var ANIMATING = [ 246 | 'bbs/void3', 247 | 'holiday/xmasfwks', 248 | 'unsorted/diver', 249 | 'unsorted/mash-chp', 250 | 'unsorted/ryans47', 251 | 'unsorted/xmasfwks' 252 | ]; 253 | 254 | function takeScreenshot(name) { 255 | var filename = name.replace(/\//g, '.') + '.ans.sgr'; 256 | var image; 257 | // Animating art hangs terminal during screenshot as of right now. 258 | if (~ANIMATING.indexOf(name)) { 259 | image = blessed.element.prototype.screenshot.call(art, 260 | 0 - art.ileft, art.width - art.iright, 261 | 0 - art.itop, art.height - art.ibottom); 262 | } else { 263 | image = art.screenshot(); 264 | } 265 | fs.writeFileSync(__dirname + '/' + filename, image); 266 | msg.display('Screenshot taken.'); 267 | } 268 | 269 | function slideshow() { 270 | if (!screen._.slideshow) { 271 | screen._.slideshow = setInterval(function slide() { 272 | if (screen.lockKeys) return; 273 | var i = (list.items.length - 1) * Math.random() | 0; 274 | list.enterSelected(i); 275 | return slide; 276 | }(), 3000); 277 | msg.display('Slideshow started.'); 278 | } else { 279 | clearInterval(screen._.slideshow); 280 | delete screen._.slideshow; 281 | msg.display('Slideshow stopped.'); 282 | } 283 | } 284 | 285 | function shuffle() { 286 | var items = Object.keys(map).sort(function(key) { 287 | return Math.random() > 0.5 ? 1 : -1; 288 | }); 289 | list.setItems(items); 290 | screen.render(); 291 | msg.display('Shuffled items.'); 292 | } 293 | -------------------------------------------------------------------------------- /example/ansi-viewer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ansi-viewer", 3 | "description": "ANSI art viewer for node", 4 | "author": "Christopher Jeffrey", 5 | "version": "0.0.1", 6 | "main": "./index.js", 7 | "bin": "./index.js", 8 | "preferGlobal": false, 9 | "repository": "git://github.com/chjj/blessed.git", 10 | "homepage": "https://github.com/chjj/blessed", 11 | "bugs": { "url": "http://github.com/chjj/blessed/issues" }, 12 | "keywords": ["ansi", "art"], 13 | "tags": ["ansi", "art"], 14 | "dependencies": { 15 | "blessed": ">=0.1.5", 16 | "term.js": "0.0.4", 17 | "request": "2.55.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/blessed-telnet.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * blessed-telnet.js 5 | * https://github.com/chjj/blessed 6 | * Copyright (c) 2013-2015, Christopher Jeffrey (MIT License) 7 | * A blessed telnet server. 8 | * See: https://github.com/TooTallNate/node-telnet 9 | */ 10 | 11 | process.title = 'blessed-telnet'; 12 | 13 | var fs = require('fs'); 14 | var path = require('path'); 15 | var blessed = require('blessed'); 16 | var telnet = require('telnet2'); 17 | 18 | var server = telnet({ tty: true }, function(client) { 19 | client.on('debug', function(msg) { 20 | console.error(msg); 21 | }); 22 | 23 | client.on('term', function(terminal) { 24 | screen.terminal = terminal; 25 | screen.render(); 26 | }); 27 | 28 | client.on('size', function(width, height) { 29 | client.columns = width; 30 | client.rows = height; 31 | client.emit('resize'); 32 | }); 33 | 34 | var screen = blessed.screen({ 35 | smartCSR: true, 36 | input: client, 37 | output: client, 38 | terminal: 'xterm-256color', 39 | fullUnicode: true 40 | }); 41 | 42 | client.on('close', function() { 43 | if (!screen.destroyed) { 44 | screen.destroy(); 45 | } 46 | }); 47 | 48 | screen.on('destroy', function() { 49 | if (client.writable) { 50 | client.destroy(); 51 | } 52 | }); 53 | 54 | if (test === 'widget-simple') { 55 | return simpleTest(screen); 56 | } 57 | 58 | loadTest(screen, test); 59 | }); 60 | 61 | function simpleTest(screen) { 62 | screen.data.main = blessed.box({ 63 | parent: screen, 64 | width: '80%', 65 | height: '90%', 66 | border: 'line', 67 | content: 'Welcome to my server. Here is your own private session.', 68 | style: { 69 | bg: 'red' 70 | } 71 | }); 72 | 73 | screen.key('i', function() { 74 | screen.data.main.style.bg = 'blue'; 75 | screen.render(); 76 | }); 77 | 78 | screen.key(['C-c', 'q'], function(ch, key) { 79 | screen.destroy(); 80 | }); 81 | 82 | screen.render(); 83 | } 84 | 85 | var test = process.argv[2] || path.resolve(__dirname, '../test/widget-shadow.js'); 86 | if (~test.indexOf('widget-png.js')) process.argv.length = 2; 87 | test = path.resolve(process.cwd(), test); 88 | 89 | function loadTest(screen, name) { 90 | var Screen = blessed.screen; 91 | blessed.screen = function() { return screen; }; 92 | var path = require.resolve(name); 93 | delete require.cache[path]; 94 | require(name); 95 | blessed.screen = Screen; 96 | } 97 | 98 | server.listen(2300); 99 | console.log('Listening on 2300...'); 100 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Example Program for Blessed 3 | * Copyright (c) 2013, Christopher Jeffrey (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | var blessed = require('../') 8 | , program = blessed.program(); 9 | 10 | process.title = 'blessed'; 11 | 12 | program.on('keypress', function(ch, key) { 13 | if (key.name === 'q') { 14 | program.clear(); 15 | program.disableMouse(); 16 | program.showCursor(); 17 | program.normalBuffer(); 18 | process.exit(0); 19 | } 20 | }); 21 | 22 | program.on('mouse', function(data) { 23 | if (data.action === 'mouseup') return; 24 | program.move(1, program.rows); 25 | program.eraseInLine('right'); 26 | if (data.action === 'wheelup') { 27 | program.write('Mouse wheel up at: ' + data.x + ', ' + data.y); 28 | } else if (data.action === 'wheeldown') { 29 | program.write('Mouse wheel down at: ' + data.x + ', ' + data.y); 30 | } else if (data.action === 'mousedown' && data.button === 'left') { 31 | program.write('Left button down at: ' + data.x + ', ' + data.y); 32 | } else if (data.action === 'mousedown' && data.button === 'right') { 33 | program.write('Right button down at: ' + data.x + ', ' + data.y); 34 | } else { 35 | program.write('Mouse at: ' + data.x + ', ' + data.y); 36 | } 37 | program.move(data.x, data.y); 38 | program.bg('red'); 39 | program.write(' '); 40 | program.bg('!red'); 41 | }); 42 | 43 | program.on('focus', function() { 44 | program.move(1, program.rows); 45 | program.write('Gained focus.'); 46 | }); 47 | 48 | program.on('blur', function() { 49 | program.move(1, program.rows); 50 | program.write('Lost focus.'); 51 | }); 52 | 53 | program.alternateBuffer(); 54 | program.enableMouse(); 55 | program.hideCursor(); 56 | program.clear(); 57 | 58 | program.move(1, 1); 59 | program.bg('black'); 60 | program.write('Hello world', 'blue fg'); 61 | program.setx((program.cols / 2 | 0) - 4); 62 | program.down(5); 63 | program.write('Hi again!'); 64 | program.bg('!black'); 65 | program.feed(); 66 | 67 | program.getCursor(function(err, data) { 68 | if (!err) { 69 | program.write('Cursor is at: ' + data.x + ', ' + data.y + '.'); 70 | program.feed(); 71 | } 72 | 73 | program.charset('SCLD'); 74 | program.write('abcdefghijklmnopqrstuvwxyz0123456789'); 75 | program.charset('US'); 76 | program.setx(1); 77 | }); 78 | -------------------------------------------------------------------------------- /example/multiplex.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * multiplex.js 5 | * https://github.com/chjj/blessed 6 | * Copyright (c) 2013-2015, Christopher Jeffrey (MIT License) 7 | * A terminal multiplexer created by blessed. 8 | */ 9 | 10 | process.title = 'multiplex.js'; 11 | 12 | var blessed = require('blessed') 13 | , screen; 14 | 15 | screen = blessed.screen({ 16 | smartCSR: true, 17 | log: process.env.HOME + '/blessed-terminal.log', 18 | fullUnicode: true, 19 | dockBorders: true, 20 | ignoreDockContrast: true 21 | }); 22 | 23 | var topleft = blessed.terminal({ 24 | parent: screen, 25 | cursor: 'line', 26 | cursorBlink: true, 27 | screenKeys: false, 28 | label: ' multiplex.js ', 29 | left: 0, 30 | top: 0, 31 | width: '50%', 32 | height: '50%', 33 | border: 'line', 34 | style: { 35 | fg: 'default', 36 | bg: 'default', 37 | focus: { 38 | border: { 39 | fg: 'green' 40 | } 41 | } 42 | } 43 | }); 44 | 45 | topleft.pty.on('data', function(data) { 46 | screen.log(JSON.stringify(data)); 47 | }); 48 | 49 | var topright = blessed.terminal({ 50 | parent: screen, 51 | cursor: 'block', 52 | cursorBlink: true, 53 | screenKeys: false, 54 | label: ' multiplex.js ', 55 | left: '50%-1', 56 | top: 0, 57 | width: '50%+1', 58 | height: '50%', 59 | border: 'line', 60 | style: { 61 | fg: 'red', 62 | bg: 'black', 63 | focus: { 64 | border: { 65 | fg: 'green' 66 | } 67 | } 68 | } 69 | }); 70 | 71 | var bottomleft = blessed.terminal({ 72 | parent: screen, 73 | cursor: 'block', 74 | cursorBlink: true, 75 | screenKeys: false, 76 | label: ' multiplex.js ', 77 | left: 0, 78 | top: '50%-1', 79 | width: '50%', 80 | height: '50%+1', 81 | border: 'line', 82 | style: { 83 | fg: 'default', 84 | bg: 'default', 85 | focus: { 86 | border: { 87 | fg: 'green' 88 | } 89 | } 90 | } 91 | }); 92 | 93 | var bottomright = blessed.terminal({ 94 | parent: screen, 95 | cursor: 'block', 96 | cursorBlink: true, 97 | screenKeys: false, 98 | label: ' multiplex.js ', 99 | left: '50%-1', 100 | top: '50%-1', 101 | width: '50%+1', 102 | height: '50%+1', 103 | border: 'line', 104 | style: { 105 | fg: 'default', 106 | bg: 'default', 107 | focus: { 108 | border: { 109 | fg: 'green' 110 | } 111 | } 112 | } 113 | }); 114 | 115 | [topleft, topright, bottomleft, bottomright].forEach(function(term) { 116 | term.enableDrag(function(mouse) { 117 | return !!mouse.ctrl; 118 | }); 119 | term.on('title', function(title) { 120 | screen.title = title; 121 | term.setLabel(' ' + title + ' '); 122 | screen.render(); 123 | }); 124 | term.on('click', term.focus.bind(term)); 125 | }); 126 | 127 | topleft.focus(); 128 | 129 | screen.key('C-q', function() { 130 | topleft.kill(); 131 | topright.kill(); 132 | bottomleft.kill(); 133 | bottomright.kill(); 134 | return screen.destroy(); 135 | }); 136 | 137 | screen.program.key('S-tab', function() { 138 | screen.focusNext(); 139 | screen.render(); 140 | }); 141 | 142 | screen.render(); 143 | -------------------------------------------------------------------------------- /example/simple-form.js: -------------------------------------------------------------------------------- 1 | var blessed = require('blessed') 2 | , screen = blessed.screen(); 3 | 4 | var form = blessed.form({ 5 | parent: screen, 6 | keys: true, 7 | left: 0, 8 | top: 0, 9 | width: 30, 10 | height: 4, 11 | bg: 'green', 12 | content: 'Submit or cancel?' 13 | }); 14 | 15 | var submit = blessed.button({ 16 | parent: form, 17 | mouse: true, 18 | keys: true, 19 | shrink: true, 20 | padding: { 21 | left: 1, 22 | right: 1 23 | }, 24 | left: 10, 25 | top: 2, 26 | shrink: true, 27 | name: 'submit', 28 | content: 'submit', 29 | style: { 30 | bg: 'blue', 31 | focus: { 32 | bg: 'red' 33 | }, 34 | hover: { 35 | bg: 'red' 36 | } 37 | } 38 | }); 39 | 40 | var cancel = blessed.button({ 41 | parent: form, 42 | mouse: true, 43 | keys: true, 44 | shrink: true, 45 | padding: { 46 | left: 1, 47 | right: 1 48 | }, 49 | left: 20, 50 | top: 2, 51 | shrink: true, 52 | name: 'cancel', 53 | content: 'cancel', 54 | style: { 55 | bg: 'blue', 56 | focus: { 57 | bg: 'red' 58 | }, 59 | hover: { 60 | bg: 'red' 61 | } 62 | } 63 | }); 64 | 65 | submit.on('press', function() { 66 | form.submit(); 67 | }); 68 | 69 | cancel.on('press', function() { 70 | form.reset(); 71 | }); 72 | 73 | form.on('submit', function(data) { 74 | form.setContent('Submitted.'); 75 | screen.render(); 76 | }); 77 | 78 | form.on('reset', function(data) { 79 | form.setContent('Canceled.'); 80 | screen.render(); 81 | }); 82 | 83 | screen.key('q', function() { 84 | process.exit(0); 85 | }); 86 | 87 | screen.render(); 88 | -------------------------------------------------------------------------------- /example/widget.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../'); 2 | 3 | // Create a screen object. 4 | var screen = blessed.screen(); 5 | 6 | // Create a box perfectly centered horizontally and vertically. 7 | var box = blessed.box({ 8 | top: 'center', 9 | left: 'center', 10 | width: '50%', 11 | height: '50%', 12 | content: 'Hello {bold}world{/bold}!', 13 | tags: true, 14 | border: { 15 | type: 'line' 16 | }, 17 | style: { 18 | fg: 'white', 19 | bg: 'magenta', 20 | border: { 21 | fg: '#ffffff' 22 | }, 23 | hover: { 24 | bg: 'green' 25 | } 26 | } 27 | }); 28 | 29 | // Append our box to the screen. 30 | screen.append(box); 31 | 32 | // If our box is clicked, change the content. 33 | box.on('click', function(data) { 34 | box.setContent('{center}Some different {red-fg}content{/red-fg}.{/center}'); 35 | screen.render(); 36 | }); 37 | 38 | // If box is focused, handle `enter` and give us some more content. 39 | box.key('enter', function() { 40 | box.setContent('{right}Even different {black-fg}content{/black-fg}.{/right}\n'); 41 | box.setLine(1, 'bar'); 42 | box.insertLine(1, 'foo'); 43 | screen.render(); 44 | }); 45 | 46 | // Quit on Escape, q, or Control-C. 47 | screen.key(['escape', 'q', 'C-c'], function(ch, key) { 48 | return process.exit(0); 49 | }); 50 | 51 | // Focus our element. 52 | box.focus(); 53 | 54 | // Render the screen. 55 | screen.render(); 56 | -------------------------------------------------------------------------------- /img/ansi-viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chjj/blessed/eab243fc7ad27f1d2932db6134f7382825ee3488/img/ansi-viewer.png -------------------------------------------------------------------------------- /img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chjj/blessed/eab243fc7ad27f1d2932db6134f7382825ee3488/img/screenshot.png -------------------------------------------------------------------------------- /img/v0.1.0-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chjj/blessed/eab243fc7ad27f1d2932db6134f7382825ee3488/img/v0.1.0-1.gif -------------------------------------------------------------------------------- /img/v0.1.0-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chjj/blessed/eab243fc7ad27f1d2932db6134f7382825ee3488/img/v0.1.0-2.gif -------------------------------------------------------------------------------- /img/v0.1.0-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chjj/blessed/eab243fc7ad27f1d2932db6134f7382825ee3488/img/v0.1.0-3.gif -------------------------------------------------------------------------------- /img/v0.1.0-4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chjj/blessed/eab243fc7ad27f1d2932db6134f7382825ee3488/img/v0.1.0-4.gif -------------------------------------------------------------------------------- /img/v0.1.0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chjj/blessed/eab243fc7ad27f1d2932db6134f7382825ee3488/img/v0.1.0.gif -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/blessed'); 2 | -------------------------------------------------------------------------------- /lib/blessed.js: -------------------------------------------------------------------------------- 1 | /** 2 | * blessed - a high-level terminal interface library for node.js 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Blessed 9 | */ 10 | 11 | function blessed() { 12 | return blessed.program.apply(null, arguments); 13 | } 14 | 15 | blessed.program = blessed.Program = require('./program'); 16 | blessed.tput = blessed.Tput = require('./tput'); 17 | blessed.widget = require('./widget'); 18 | blessed.colors = require('./colors'); 19 | blessed.unicode = require('./unicode'); 20 | blessed.helpers = require('./helpers'); 21 | 22 | blessed.helpers.sprintf = blessed.tput.sprintf; 23 | blessed.helpers.tryRead = blessed.tput.tryRead; 24 | blessed.helpers.merge(blessed, blessed.helpers); 25 | 26 | blessed.helpers.merge(blessed, blessed.widget); 27 | 28 | /** 29 | * Expose 30 | */ 31 | 32 | module.exports = blessed; 33 | -------------------------------------------------------------------------------- /lib/events.js: -------------------------------------------------------------------------------- 1 | /** 2 | * events.js - event emitter for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | var slice = Array.prototype.slice; 8 | 9 | /** 10 | * EventEmitter 11 | */ 12 | 13 | function EventEmitter() { 14 | if (!this._events) this._events = {}; 15 | } 16 | 17 | EventEmitter.prototype.setMaxListeners = function(n) { 18 | this._maxListeners = n; 19 | }; 20 | 21 | EventEmitter.prototype.addListener = function(type, listener) { 22 | if (!this._events[type]) { 23 | this._events[type] = listener; 24 | } else if (typeof this._events[type] === 'function') { 25 | this._events[type] = [this._events[type], listener]; 26 | } else { 27 | this._events[type].push(listener); 28 | } 29 | this._emit('newListener', [type, listener]); 30 | }; 31 | 32 | EventEmitter.prototype.on = EventEmitter.prototype.addListener; 33 | 34 | EventEmitter.prototype.removeListener = function(type, listener) { 35 | var handler = this._events[type]; 36 | if (!handler) return; 37 | 38 | if (typeof handler === 'function' || handler.length === 1) { 39 | delete this._events[type]; 40 | this._emit('removeListener', [type, listener]); 41 | return; 42 | } 43 | 44 | for (var i = 0; i < handler.length; i++) { 45 | if (handler[i] === listener || handler[i].listener === listener) { 46 | handler.splice(i, 1); 47 | this._emit('removeListener', [type, listener]); 48 | return; 49 | } 50 | } 51 | }; 52 | 53 | EventEmitter.prototype.off = EventEmitter.prototype.removeListener; 54 | 55 | EventEmitter.prototype.removeAllListeners = function(type) { 56 | if (type) { 57 | delete this._events[type]; 58 | } else { 59 | this._events = {}; 60 | } 61 | }; 62 | 63 | EventEmitter.prototype.once = function(type, listener) { 64 | function on() { 65 | this.removeListener(type, on); 66 | return listener.apply(this, arguments); 67 | } 68 | on.listener = listener; 69 | return this.on(type, on); 70 | }; 71 | 72 | EventEmitter.prototype.listeners = function(type) { 73 | return typeof this._events[type] === 'function' 74 | ? [this._events[type]] 75 | : this._events[type] || []; 76 | }; 77 | 78 | EventEmitter.prototype._emit = function(type, args) { 79 | var handler = this._events[type] 80 | , ret; 81 | 82 | // if (type !== 'event') { 83 | // this._emit('event', [type.replace(/^element /, '')].concat(args)); 84 | // } 85 | 86 | if (!handler) { 87 | if (type === 'error') { 88 | throw new args[0]; 89 | } 90 | return; 91 | } 92 | 93 | if (typeof handler === 'function') { 94 | return handler.apply(this, args); 95 | } 96 | 97 | for (var i = 0; i < handler.length; i++) { 98 | if (handler[i].apply(this, args) === false) { 99 | ret = false; 100 | } 101 | } 102 | 103 | return ret !== false; 104 | }; 105 | 106 | EventEmitter.prototype.emit = function(type) { 107 | var args = slice.call(arguments, 1) 108 | , params = slice.call(arguments) 109 | , el = this; 110 | 111 | this._emit('event', params); 112 | 113 | if (this.type === 'screen') { 114 | return this._emit(type, args); 115 | } 116 | 117 | if (this._emit(type, args) === false) { 118 | return false; 119 | } 120 | 121 | type = 'element ' + type; 122 | args.unshift(this); 123 | // `element` prefix 124 | // params = [type].concat(args); 125 | // no `element` prefix 126 | // params.splice(1, 0, this); 127 | 128 | do { 129 | // el._emit('event', params); 130 | if (!el._events[type]) continue; 131 | if (el._emit(type, args) === false) { 132 | return false; 133 | } 134 | } while (el = el.parent); 135 | 136 | return true; 137 | }; 138 | 139 | // For hooking into the main EventEmitter if we want to. 140 | // Might be better to do things this way being that it 141 | // will always be compatible with node, not to mention 142 | // it gives us domain support as well. 143 | // Node.prototype._emit = Node.prototype.emit; 144 | // Node.prototype.emit = function(type) { 145 | // var args, el; 146 | // 147 | // if (this.type === 'screen') { 148 | // return this._emit.apply(this, arguments); 149 | // } 150 | // 151 | // this._emit.apply(this, arguments); 152 | // if (this._bubbleStopped) return false; 153 | // 154 | // args = slice.call(arguments, 1); 155 | // el = this; 156 | // 157 | // args.unshift('element ' + type, this); 158 | // this._bubbleStopped = false; 159 | // //args.push(stopBubble); 160 | // 161 | // do { 162 | // if (!el._events || !el._events[type]) continue; 163 | // el._emit.apply(el, args); 164 | // if (this._bubbleStopped) return false; 165 | // } while (el = el.parent); 166 | // 167 | // return true; 168 | // }; 169 | // 170 | // Node.prototype._addListener = Node.prototype.addListener; 171 | // Node.prototype.on = 172 | // Node.prototype.addListener = function(type, listener) { 173 | // function on() { 174 | // if (listener.apply(this, arguments) === false) { 175 | // this._bubbleStopped = true; 176 | // } 177 | // } 178 | // on.listener = listener; 179 | // return this._addListener(type, on); 180 | // }; 181 | 182 | /** 183 | * Expose 184 | */ 185 | 186 | exports = EventEmitter; 187 | exports.EventEmitter = EventEmitter; 188 | 189 | module.exports = exports; 190 | -------------------------------------------------------------------------------- /lib/gpmclient.js: -------------------------------------------------------------------------------- 1 | /** 2 | * gpmclient.js - support the gpm mouse protocol 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | var net = require('net'); 8 | var fs = require('fs'); 9 | var EventEmitter = require('events').EventEmitter; 10 | 11 | var GPM_USE_MAGIC = false; 12 | 13 | var GPM_MOVE = 1 14 | , GPM_DRAG = 2 15 | , GPM_DOWN = 4 16 | , GPM_UP = 8; 17 | 18 | var GPM_DOUBLE = 32 19 | , GPM_MFLAG = 128; 20 | 21 | var GPM_REQ_NOPASTE = 3 22 | , GPM_HARD = 256; 23 | 24 | var GPM_MAGIC = 0x47706D4C; 25 | var GPM_SOCKET = '/dev/gpmctl'; 26 | 27 | // typedef struct Gpm_Connect { 28 | // unsigned short eventMask, defaultMask; 29 | // unsigned short minMod, maxMod; 30 | // int pid; 31 | // int vc; 32 | // } Gpm_Connect; 33 | 34 | function send_config(socket, Gpm_Connect, callback) { 35 | var buffer; 36 | if (GPM_USE_MAGIC) { 37 | buffer = new Buffer(20); 38 | buffer.writeUInt32LE(GPM_MAGIC, 0); 39 | buffer.writeUInt16LE(Gpm_Connect.eventMask, 4); 40 | buffer.writeUInt16LE(Gpm_Connect.defaultMask, 6); 41 | buffer.writeUInt16LE(Gpm_Connect.minMod, 8); 42 | buffer.writeUInt16LE(Gpm_Connect.maxMod, 10); 43 | buffer.writeInt16LE(process.pid, 12); 44 | buffer.writeInt16LE(Gpm_Connect.vc, 16); 45 | } else { 46 | buffer = new Buffer(16); 47 | buffer.writeUInt16LE(Gpm_Connect.eventMask, 0); 48 | buffer.writeUInt16LE(Gpm_Connect.defaultMask, 2); 49 | buffer.writeUInt16LE(Gpm_Connect.minMod, 4); 50 | buffer.writeUInt16LE(Gpm_Connect.maxMod, 6); 51 | buffer.writeInt16LE(Gpm_Connect.pid, 8); 52 | buffer.writeInt16LE(Gpm_Connect.vc, 12); 53 | } 54 | socket.write(buffer, function() { 55 | if (callback) callback(); 56 | }); 57 | } 58 | 59 | // typedef struct Gpm_Event { 60 | // unsigned char buttons, modifiers; // try to be a multiple of 4 61 | // unsigned short vc; 62 | // short dx, dy, x, y; // displacement x,y for this event, and absolute x,y 63 | // enum Gpm_Etype type; 64 | // // clicks e.g. double click are determined by time-based processing 65 | // int clicks; 66 | // enum Gpm_Margin margin; 67 | // // wdx/y: displacement of wheels in this event. Absolute values are not 68 | // // required, because wheel movement is typically used for scrolling 69 | // // or selecting fields, not for cursor positioning. The application 70 | // // can determine when the end of file or form is reached, and not 71 | // // go any further. 72 | // // A single mouse will use wdy, "vertical scroll" wheel. 73 | // short wdx, wdy; 74 | // } Gpm_Event; 75 | 76 | function parseEvent(raw) { 77 | var evnt = {}; 78 | evnt.buttons = raw[0]; 79 | evnt.modifiers = raw[1]; 80 | evnt.vc = raw.readUInt16LE(2); 81 | evnt.dx = raw.readInt16LE(4); 82 | evnt.dy = raw.readInt16LE(6); 83 | evnt.x = raw.readInt16LE(8); 84 | evnt.y = raw.readInt16LE(10); 85 | evnt.type = raw.readInt16LE(12); 86 | evnt.clicks = raw.readInt32LE(16); 87 | evnt.margin = raw.readInt32LE(20); 88 | evnt.wdx = raw.readInt16LE(24); 89 | evnt.wdy = raw.readInt16LE(26); 90 | return evnt; 91 | } 92 | 93 | function GpmClient(options) { 94 | if (!(this instanceof GpmClient)) { 95 | return new GpmClient(options); 96 | } 97 | 98 | EventEmitter.call(this); 99 | 100 | var pid = process.pid; 101 | 102 | // check tty for /dev/tty[n] 103 | var path; 104 | try { 105 | path = fs.readlinkSync('/proc/' + pid + '/fd/0'); 106 | } catch (e) { 107 | ; 108 | } 109 | var tty = /tty[0-9]+$/.exec(path); 110 | if (tty === null) { 111 | // TODO: should also check for /dev/input/.. 112 | } 113 | 114 | var vc; 115 | if (tty) { 116 | tty = tty[0]; 117 | vc = +/[0-9]+$/.exec(tty)[0]; 118 | } 119 | 120 | var self = this; 121 | 122 | if (tty) { 123 | fs.stat(GPM_SOCKET, function(err, stat) { 124 | if (err || !stat.isSocket()) { 125 | return; 126 | } 127 | 128 | var conf = { 129 | eventMask: 0xffff, 130 | defaultMask: GPM_MOVE | GPM_HARD, 131 | minMod: 0, 132 | maxMod: 0xffff, 133 | pid: pid, 134 | vc: vc 135 | }; 136 | 137 | var gpm = net.createConnection(GPM_SOCKET); 138 | this.gpm = gpm; 139 | 140 | gpm.on('connect', function() { 141 | send_config(gpm, conf, function() { 142 | conf.pid = 0; 143 | conf.vc = GPM_REQ_NOPASTE; 144 | //send_config(gpm, conf); 145 | }); 146 | }); 147 | 148 | gpm.on('data', function(packet) { 149 | var evnt = parseEvent(packet); 150 | switch (evnt.type & 15) { 151 | case GPM_MOVE: 152 | if (evnt.dx || evnt.dy) { 153 | self.emit('move', evnt.buttons, evnt.modifiers, evnt.x, evnt.y); 154 | } 155 | if (evnt.wdx || evnt.wdy) { 156 | self.emit('mousewheel', 157 | evnt.buttons, evnt.modifiers, 158 | evnt.x, evnt.y, evnt.wdx, evnt.wdy); 159 | } 160 | break; 161 | case GPM_DRAG: 162 | if (evnt.dx || evnt.dy) { 163 | self.emit('drag', evnt.buttons, evnt.modifiers, evnt.x, evnt.y); 164 | } 165 | if (evnt.wdx || evnt.wdy) { 166 | self.emit('mousewheel', 167 | evnt.buttons, evnt.modifiers, 168 | evnt.x, evnt.y, evnt.wdx, evnt.wdy); 169 | } 170 | break; 171 | case GPM_DOWN: 172 | self.emit('btndown', evnt.buttons, evnt.modifiers, evnt.x, evnt.y); 173 | if (evnt.type & GPM_DOUBLE) { 174 | self.emit('dblclick', evnt.buttons, evnt.modifiers, evnt.x, evnt.y); 175 | } 176 | break; 177 | case GPM_UP: 178 | self.emit('btnup', evnt.buttons, evnt.modifiers, evnt.x, evnt.y); 179 | if (!(evnt.type & GPM_MFLAG)) { 180 | self.emit('click', evnt.buttons, evnt.modifiers, evnt.x, evnt.y); 181 | } 182 | break; 183 | } 184 | }); 185 | 186 | gpm.on('error', function() { 187 | self.stop(); 188 | }); 189 | }); 190 | } 191 | } 192 | 193 | GpmClient.prototype.__proto__ = EventEmitter.prototype; 194 | 195 | GpmClient.prototype.stop = function() { 196 | if (this.gpm) { 197 | this.gpm.end(); 198 | } 199 | delete this.gpm; 200 | }; 201 | 202 | GpmClient.prototype.ButtonName = function(btn) { 203 | if (btn & 4) return 'left'; 204 | if (btn & 2) return 'middle'; 205 | if (btn & 1) return 'right'; 206 | return ''; 207 | }; 208 | 209 | GpmClient.prototype.hasShiftKey = function(mod) { 210 | return (mod & 1) ? true : false; 211 | }; 212 | 213 | GpmClient.prototype.hasCtrlKey = function(mod) { 214 | return (mod & 4) ? true : false; 215 | }; 216 | 217 | GpmClient.prototype.hasMetaKey = function(mod) { 218 | return (mod & 8) ? true : false; 219 | }; 220 | 221 | module.exports = GpmClient; 222 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * helpers.js - helpers for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var fs = require('fs'); 12 | 13 | var unicode = require('./unicode'); 14 | 15 | /** 16 | * Helpers 17 | */ 18 | 19 | var helpers = exports; 20 | 21 | helpers.merge = function(a, b) { 22 | Object.keys(b).forEach(function(key) { 23 | a[key] = b[key]; 24 | }); 25 | return a; 26 | }; 27 | 28 | helpers.asort = function(obj) { 29 | return obj.sort(function(a, b) { 30 | a = a.name.toLowerCase(); 31 | b = b.name.toLowerCase(); 32 | 33 | if (a[0] === '.' && b[0] === '.') { 34 | a = a[1]; 35 | b = b[1]; 36 | } else { 37 | a = a[0]; 38 | b = b[0]; 39 | } 40 | 41 | return a > b ? 1 : (a < b ? -1 : 0); 42 | }); 43 | }; 44 | 45 | helpers.hsort = function(obj) { 46 | return obj.sort(function(a, b) { 47 | return b.index - a.index; 48 | }); 49 | }; 50 | 51 | helpers.findFile = function(start, target) { 52 | return (function read(dir) { 53 | var files, file, stat, out; 54 | 55 | if (dir === '/dev' || dir === '/sys' 56 | || dir === '/proc' || dir === '/net') { 57 | return null; 58 | } 59 | 60 | try { 61 | files = fs.readdirSync(dir); 62 | } catch (e) { 63 | files = []; 64 | } 65 | 66 | for (var i = 0; i < files.length; i++) { 67 | file = files[i]; 68 | 69 | if (file === target) { 70 | return (dir === '/' ? '' : dir) + '/' + file; 71 | } 72 | 73 | try { 74 | stat = fs.lstatSync((dir === '/' ? '' : dir) + '/' + file); 75 | } catch (e) { 76 | stat = null; 77 | } 78 | 79 | if (stat && stat.isDirectory() && !stat.isSymbolicLink()) { 80 | out = read((dir === '/' ? '' : dir) + '/' + file); 81 | if (out) return out; 82 | } 83 | } 84 | 85 | return null; 86 | })(start); 87 | }; 88 | 89 | // Escape text for tag-enabled elements. 90 | helpers.escape = function(text) { 91 | return text.replace(/[{}]/g, function(ch) { 92 | return ch === '{' ? '{open}' : '{close}'; 93 | }); 94 | }; 95 | 96 | helpers.parseTags = function(text, screen) { 97 | return helpers.Element.prototype._parseTags.call( 98 | { parseTags: true, screen: screen || helpers.Screen.global }, text); 99 | }; 100 | 101 | helpers.generateTags = function(style, text) { 102 | var open = '' 103 | , close = ''; 104 | 105 | Object.keys(style || {}).forEach(function(key) { 106 | var val = style[key]; 107 | if (typeof val === 'string') { 108 | val = val.replace(/^light(?!-)/, 'light-'); 109 | val = val.replace(/^bright(?!-)/, 'bright-'); 110 | open = '{' + val + '-' + key + '}' + open; 111 | close += '{/' + val + '-' + key + '}'; 112 | } else { 113 | if (val === true) { 114 | open = '{' + key + '}' + open; 115 | close += '{/' + key + '}'; 116 | } 117 | } 118 | }); 119 | 120 | if (text != null) { 121 | return open + text + close; 122 | } 123 | 124 | return { 125 | open: open, 126 | close: close 127 | }; 128 | }; 129 | 130 | helpers.attrToBinary = function(style, element) { 131 | return helpers.Element.prototype.sattr.call(element || {}, style); 132 | }; 133 | 134 | helpers.stripTags = function(text) { 135 | if (!text) return ''; 136 | return text 137 | .replace(/{(\/?)([\w\-,;!#]*)}/g, '') 138 | .replace(/\x1b\[[\d;]*m/g, ''); 139 | }; 140 | 141 | helpers.cleanTags = function(text) { 142 | return helpers.stripTags(text).trim(); 143 | }; 144 | 145 | helpers.dropUnicode = function(text) { 146 | if (!text) return ''; 147 | return text 148 | .replace(unicode.chars.all, '??') 149 | .replace(unicode.chars.combining, '') 150 | .replace(unicode.chars.surrogate, '?'); 151 | }; 152 | 153 | helpers.__defineGetter__('Screen', function() { 154 | if (!helpers._screen) { 155 | helpers._screen = require('./widgets/screen'); 156 | } 157 | return helpers._screen; 158 | }); 159 | 160 | helpers.__defineGetter__('Element', function() { 161 | if (!helpers._element) { 162 | helpers._element = require('./widgets/element'); 163 | } 164 | return helpers._element; 165 | }); 166 | -------------------------------------------------------------------------------- /lib/widget.js: -------------------------------------------------------------------------------- 1 | /** 2 | * widget.js - high-level interface for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | var widget = exports; 8 | 9 | widget.classes = [ 10 | 'Node', 11 | 'Screen', 12 | 'Element', 13 | 'Box', 14 | 'Text', 15 | 'Line', 16 | 'ScrollableBox', 17 | 'ScrollableText', 18 | 'BigText', 19 | 'List', 20 | 'Form', 21 | 'Input', 22 | 'Textarea', 23 | 'Textbox', 24 | 'Button', 25 | 'ProgressBar', 26 | 'FileManager', 27 | 'Checkbox', 28 | 'RadioSet', 29 | 'RadioButton', 30 | 'Prompt', 31 | 'Question', 32 | 'Message', 33 | 'Loading', 34 | 'Listbar', 35 | 'Log', 36 | 'Table', 37 | 'ListTable', 38 | 'Terminal', 39 | 'Image', 40 | 'ANSIImage', 41 | 'OverlayImage', 42 | 'Video', 43 | 'Layout' 44 | ]; 45 | 46 | widget.classes.forEach(function(name) { 47 | var file = name.toLowerCase(); 48 | widget[name] = widget[file] = require('./widgets/' + file); 49 | }); 50 | 51 | widget.aliases = { 52 | 'ListBar': 'Listbar', 53 | 'PNG': 'ANSIImage' 54 | }; 55 | 56 | Object.keys(widget.aliases).forEach(function(key) { 57 | var name = widget.aliases[key]; 58 | widget[key] = widget[name]; 59 | widget[key.toLowerCase()] = widget[name]; 60 | }); 61 | -------------------------------------------------------------------------------- /lib/widgets/ansiimage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ansiimage.js - render PNGS/GIFS as ANSI 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var cp = require('child_process'); 12 | 13 | var colors = require('../colors'); 14 | 15 | var Node = require('./node'); 16 | var Box = require('./box'); 17 | 18 | var tng = require('../../vendor/tng'); 19 | 20 | /** 21 | * ANSIImage 22 | */ 23 | 24 | function ANSIImage(options) { 25 | var self = this; 26 | 27 | if (!(this instanceof Node)) { 28 | return new ANSIImage(options); 29 | } 30 | 31 | options = options || {}; 32 | options.shrink = true; 33 | 34 | Box.call(this, options); 35 | 36 | this.scale = this.options.scale || 1.0; 37 | this.options.animate = this.options.animate !== false; 38 | this._noFill = true; 39 | 40 | if (this.options.file) { 41 | this.setImage(this.options.file); 42 | } 43 | 44 | this.screen.on('prerender', function() { 45 | var lpos = self.lpos; 46 | if (!lpos) return; 47 | // prevent image from blending with itself if there are alpha channels 48 | self.screen.clearRegion(lpos.xi, lpos.xl, lpos.yi, lpos.yl); 49 | }); 50 | 51 | this.on('destroy', function() { 52 | self.stop(); 53 | }); 54 | } 55 | 56 | ANSIImage.prototype.__proto__ = Box.prototype; 57 | 58 | ANSIImage.prototype.type = 'ansiimage'; 59 | 60 | ANSIImage.curl = function(url) { 61 | try { 62 | return cp.execFileSync('curl', 63 | ['-s', '-A', '', url], 64 | { stdio: ['ignore', 'pipe', 'ignore'] }); 65 | } catch (e) { 66 | ; 67 | } 68 | try { 69 | return cp.execFileSync('wget', 70 | ['-U', '', '-O', '-', url], 71 | { stdio: ['ignore', 'pipe', 'ignore'] }); 72 | } catch (e) { 73 | ; 74 | } 75 | throw new Error('curl or wget failed.'); 76 | }; 77 | 78 | ANSIImage.prototype.setImage = function(file) { 79 | this.file = typeof file === 'string' ? file : null; 80 | 81 | if (/^https?:/.test(file)) { 82 | file = ANSIImage.curl(file); 83 | } 84 | 85 | var width = this.position.width; 86 | var height = this.position.height; 87 | 88 | if (width != null) { 89 | width = this.width; 90 | } 91 | 92 | if (height != null) { 93 | height = this.height; 94 | } 95 | 96 | try { 97 | this.setContent(''); 98 | 99 | this.img = tng(file, { 100 | colors: colors, 101 | width: width, 102 | height: height, 103 | scale: this.scale, 104 | ascii: this.options.ascii, 105 | speed: this.options.speed, 106 | filename: this.file 107 | }); 108 | 109 | if (width == null || height == null) { 110 | this.width = this.img.cellmap[0].length; 111 | this.height = this.img.cellmap.length; 112 | } 113 | 114 | if (this.img.frames && this.options.animate) { 115 | this.play(); 116 | } else { 117 | this.cellmap = this.img.cellmap; 118 | } 119 | } catch (e) { 120 | this.setContent('Image Error: ' + e.message); 121 | this.img = null; 122 | this.cellmap = null; 123 | } 124 | }; 125 | 126 | ANSIImage.prototype.play = function() { 127 | var self = this; 128 | if (!this.img) return; 129 | return this.img.play(function(bmp, cellmap) { 130 | self.cellmap = cellmap; 131 | self.screen.render(); 132 | }); 133 | }; 134 | 135 | ANSIImage.prototype.pause = function() { 136 | if (!this.img) return; 137 | return this.img.pause(); 138 | }; 139 | 140 | ANSIImage.prototype.stop = function() { 141 | if (!this.img) return; 142 | return this.img.stop(); 143 | }; 144 | 145 | ANSIImage.prototype.clearImage = function() { 146 | this.stop(); 147 | this.setContent(''); 148 | this.img = null; 149 | this.cellmap = null; 150 | }; 151 | 152 | ANSIImage.prototype.render = function() { 153 | var coords = this._render(); 154 | if (!coords) return; 155 | 156 | if (this.img && this.cellmap) { 157 | this.img.renderElement(this.cellmap, this); 158 | } 159 | 160 | return coords; 161 | }; 162 | 163 | /** 164 | * Expose 165 | */ 166 | 167 | module.exports = ANSIImage; 168 | -------------------------------------------------------------------------------- /lib/widgets/bigtext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * bigtext.js - bigtext element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var fs = require('fs'); 12 | 13 | var Node = require('./node'); 14 | var Box = require('./box'); 15 | 16 | /** 17 | * BigText 18 | */ 19 | 20 | function BigText(options) { 21 | if (!(this instanceof Node)) { 22 | return new BigText(options); 23 | } 24 | options = options || {}; 25 | options.font = options.font 26 | || __dirname + '/../../usr/fonts/ter-u14n.json'; 27 | options.fontBold = options.font 28 | || __dirname + '/../../usr/fonts/ter-u14b.json'; 29 | this.fch = options.fch; 30 | this.ratio = {}; 31 | this.font = this.loadFont(options.font); 32 | this.fontBold = this.loadFont(options.font); 33 | Box.call(this, options); 34 | if (this.style.bold) { 35 | this.font = this.fontBold; 36 | } 37 | } 38 | 39 | BigText.prototype.__proto__ = Box.prototype; 40 | 41 | BigText.prototype.type = 'bigtext'; 42 | 43 | BigText.prototype.loadFont = function(filename) { 44 | var self = this 45 | , data 46 | , font; 47 | 48 | data = JSON.parse(fs.readFileSync(filename, 'utf8')); 49 | 50 | this.ratio.width = data.width; 51 | this.ratio.height = data.height; 52 | 53 | function convertLetter(ch, lines) { 54 | var line, i; 55 | 56 | while (lines.length > self.ratio.height) { 57 | lines.shift(); 58 | lines.pop(); 59 | } 60 | 61 | lines = lines.map(function(line) { 62 | var chs = line.split(''); 63 | chs = chs.map(function(ch) { 64 | return ch === ' ' ? 0 : 1; 65 | }); 66 | while (chs.length < self.ratio.width) { 67 | chs.push(0); 68 | } 69 | return chs; 70 | }); 71 | 72 | while (lines.length < self.ratio.height) { 73 | line = []; 74 | for (i = 0; i < self.ratio.width; i++) { 75 | line.push(0); 76 | } 77 | lines.push(line); 78 | } 79 | 80 | return lines; 81 | } 82 | 83 | font = Object.keys(data.glyphs).reduce(function(out, ch) { 84 | var lines = data.glyphs[ch].map; 85 | out[ch] = convertLetter(ch, lines); 86 | return out; 87 | }, {}); 88 | 89 | delete font[' ']; 90 | 91 | return font; 92 | }; 93 | 94 | BigText.prototype.setContent = function(content) { 95 | this.content = ''; 96 | this.text = content || ''; 97 | }; 98 | 99 | BigText.prototype.render = function() { 100 | if (this.position.width == null || this._shrinkWidth) { 101 | // if (this.width - this.iwidth < this.ratio.width * this.text.length + 1) { 102 | this.position.width = this.ratio.width * this.text.length + 1; 103 | this._shrinkWidth = true; 104 | // } 105 | } 106 | if (this.position.height == null || this._shrinkHeight) { 107 | // if (this.height - this.iheight < this.ratio.height + 0) { 108 | this.position.height = this.ratio.height + 0; 109 | this._shrinkHeight = true; 110 | // } 111 | } 112 | 113 | var coords = this._render(); 114 | if (!coords) return; 115 | 116 | var lines = this.screen.lines 117 | , left = coords.xi + this.ileft 118 | , top = coords.yi + this.itop 119 | , right = coords.xl - this.iright 120 | , bottom = coords.yl - this.ibottom; 121 | 122 | var dattr = this.sattr(this.style) 123 | , bg = dattr & 0x1ff 124 | , fg = (dattr >> 9) & 0x1ff 125 | , flags = (dattr >> 18) & 0x1ff 126 | , attr = (flags << 18) | (bg << 9) | fg; 127 | 128 | for (var x = left, i = 0; x < right; x += this.ratio.width, i++) { 129 | var ch = this.text[i]; 130 | if (!ch) break; 131 | var map = this.font[ch]; 132 | if (!map) continue; 133 | for (var y = top; y < Math.min(bottom, top + this.ratio.height); y++) { 134 | if (!lines[y]) continue; 135 | var mline = map[y - top]; 136 | if (!mline) continue; 137 | for (var mx = 0; mx < this.ratio.width; mx++) { 138 | var mcell = mline[mx]; 139 | if (mcell == null) break; 140 | if (this.fch && this.fch !== ' ') { 141 | lines[y][x + mx][0] = dattr; 142 | lines[y][x + mx][1] = mcell === 1 ? this.fch : this.ch; 143 | } else { 144 | lines[y][x + mx][0] = mcell === 1 ? attr : dattr; 145 | lines[y][x + mx][1] = mcell === 1 ? ' ' : this.ch; 146 | } 147 | } 148 | lines[y].dirty = true; 149 | } 150 | } 151 | 152 | return coords; 153 | }; 154 | 155 | /** 156 | * Expose 157 | */ 158 | 159 | module.exports = BigText; 160 | -------------------------------------------------------------------------------- /lib/widgets/box.js: -------------------------------------------------------------------------------- 1 | /** 2 | * box.js - box element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Element = require('./element'); 13 | 14 | /** 15 | * Box 16 | */ 17 | 18 | function Box(options) { 19 | if (!(this instanceof Node)) { 20 | return new Box(options); 21 | } 22 | options = options || {}; 23 | Element.call(this, options); 24 | } 25 | 26 | Box.prototype.__proto__ = Element.prototype; 27 | 28 | Box.prototype.type = 'box'; 29 | 30 | /** 31 | * Expose 32 | */ 33 | 34 | module.exports = Box; 35 | -------------------------------------------------------------------------------- /lib/widgets/button.js: -------------------------------------------------------------------------------- 1 | /** 2 | * button.js - button element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Input = require('./input'); 13 | 14 | /** 15 | * Button 16 | */ 17 | 18 | function Button(options) { 19 | var self = this; 20 | 21 | if (!(this instanceof Node)) { 22 | return new Button(options); 23 | } 24 | 25 | options = options || {}; 26 | 27 | if (options.autoFocus == null) { 28 | options.autoFocus = false; 29 | } 30 | 31 | Input.call(this, options); 32 | 33 | this.on('keypress', function(ch, key) { 34 | if (key.name === 'enter' || key.name === 'space') { 35 | return self.press(); 36 | } 37 | }); 38 | 39 | if (this.options.mouse) { 40 | this.on('click', function() { 41 | return self.press(); 42 | }); 43 | } 44 | } 45 | 46 | Button.prototype.__proto__ = Input.prototype; 47 | 48 | Button.prototype.type = 'button'; 49 | 50 | Button.prototype.press = function() { 51 | this.focus(); 52 | this.value = true; 53 | var result = this.emit('press'); 54 | delete this.value; 55 | return result; 56 | }; 57 | 58 | /** 59 | * Expose 60 | */ 61 | 62 | module.exports = Button; 63 | -------------------------------------------------------------------------------- /lib/widgets/checkbox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * checkbox.js - checkbox element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Input = require('./input'); 13 | 14 | /** 15 | * Checkbox 16 | */ 17 | 18 | function Checkbox(options) { 19 | var self = this; 20 | 21 | if (!(this instanceof Node)) { 22 | return new Checkbox(options); 23 | } 24 | 25 | options = options || {}; 26 | 27 | Input.call(this, options); 28 | 29 | this.text = options.content || options.text || ''; 30 | this.checked = this.value = options.checked || false; 31 | 32 | this.on('keypress', function(ch, key) { 33 | if (key.name === 'enter' || key.name === 'space') { 34 | self.toggle(); 35 | self.screen.render(); 36 | } 37 | }); 38 | 39 | if (options.mouse) { 40 | this.on('click', function() { 41 | self.toggle(); 42 | self.screen.render(); 43 | }); 44 | } 45 | 46 | this.on('focus', function() { 47 | var lpos = self.lpos; 48 | if (!lpos) return; 49 | self.screen.program.lsaveCursor('checkbox'); 50 | self.screen.program.cup(lpos.yi, lpos.xi + 1); 51 | self.screen.program.showCursor(); 52 | }); 53 | 54 | this.on('blur', function() { 55 | self.screen.program.lrestoreCursor('checkbox', true); 56 | }); 57 | } 58 | 59 | Checkbox.prototype.__proto__ = Input.prototype; 60 | 61 | Checkbox.prototype.type = 'checkbox'; 62 | 63 | Checkbox.prototype.render = function() { 64 | this.clearPos(true); 65 | this.setContent('[' + (this.checked ? 'x' : ' ') + '] ' + this.text, true); 66 | return this._render(); 67 | }; 68 | 69 | Checkbox.prototype.check = function() { 70 | if (this.checked) return; 71 | this.checked = this.value = true; 72 | this.emit('check'); 73 | }; 74 | 75 | Checkbox.prototype.uncheck = function() { 76 | if (!this.checked) return; 77 | this.checked = this.value = false; 78 | this.emit('uncheck'); 79 | }; 80 | 81 | Checkbox.prototype.toggle = function() { 82 | return this.checked 83 | ? this.uncheck() 84 | : this.check(); 85 | }; 86 | 87 | /** 88 | * Expose 89 | */ 90 | 91 | module.exports = Checkbox; 92 | -------------------------------------------------------------------------------- /lib/widgets/filemanager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * filemanager.js - file manager element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var path = require('path') 12 | , fs = require('fs'); 13 | 14 | var helpers = require('../helpers'); 15 | 16 | var Node = require('./node'); 17 | var List = require('./list'); 18 | 19 | /** 20 | * FileManager 21 | */ 22 | 23 | function FileManager(options) { 24 | var self = this; 25 | 26 | if (!(this instanceof Node)) { 27 | return new FileManager(options); 28 | } 29 | 30 | options = options || {}; 31 | options.parseTags = true; 32 | // options.label = ' {blue-fg}%path{/blue-fg} '; 33 | 34 | List.call(this, options); 35 | 36 | this.cwd = options.cwd || process.cwd(); 37 | this.file = this.cwd; 38 | this.value = this.cwd; 39 | 40 | if (options.label && ~options.label.indexOf('%path')) { 41 | this._label.setContent(options.label.replace('%path', this.cwd)); 42 | } 43 | 44 | this.on('select', function(item) { 45 | var value = item.content.replace(/\{[^{}]+\}/g, '').replace(/@$/, '') 46 | , file = path.resolve(self.cwd, value); 47 | 48 | return fs.stat(file, function(err, stat) { 49 | if (err) { 50 | return self.emit('error', err, file); 51 | } 52 | self.file = file; 53 | self.value = file; 54 | if (stat.isDirectory()) { 55 | self.emit('cd', file, self.cwd); 56 | self.cwd = file; 57 | if (options.label && ~options.label.indexOf('%path')) { 58 | self._label.setContent(options.label.replace('%path', file)); 59 | } 60 | self.refresh(); 61 | } else { 62 | self.emit('file', file); 63 | } 64 | }); 65 | }); 66 | } 67 | 68 | FileManager.prototype.__proto__ = List.prototype; 69 | 70 | FileManager.prototype.type = 'file-manager'; 71 | 72 | FileManager.prototype.refresh = function(cwd, callback) { 73 | if (!callback) { 74 | callback = cwd; 75 | cwd = null; 76 | } 77 | 78 | var self = this; 79 | 80 | if (cwd) this.cwd = cwd; 81 | else cwd = this.cwd; 82 | 83 | return fs.readdir(cwd, function(err, list) { 84 | if (err && err.code === 'ENOENT') { 85 | self.cwd = cwd !== process.env.HOME 86 | ? process.env.HOME 87 | : '/'; 88 | return self.refresh(callback); 89 | } 90 | 91 | if (err) { 92 | if (callback) return callback(err); 93 | return self.emit('error', err, cwd); 94 | } 95 | 96 | var dirs = [] 97 | , files = []; 98 | 99 | list.unshift('..'); 100 | 101 | list.forEach(function(name) { 102 | var f = path.resolve(cwd, name) 103 | , stat; 104 | 105 | try { 106 | stat = fs.lstatSync(f); 107 | } catch (e) { 108 | ; 109 | } 110 | 111 | if ((stat && stat.isDirectory()) || name === '..') { 112 | dirs.push({ 113 | name: name, 114 | text: '{light-blue-fg}' + name + '{/light-blue-fg}/', 115 | dir: true 116 | }); 117 | } else if (stat && stat.isSymbolicLink()) { 118 | files.push({ 119 | name: name, 120 | text: '{light-cyan-fg}' + name + '{/light-cyan-fg}@', 121 | dir: false 122 | }); 123 | } else { 124 | files.push({ 125 | name: name, 126 | text: name, 127 | dir: false 128 | }); 129 | } 130 | }); 131 | 132 | dirs = helpers.asort(dirs); 133 | files = helpers.asort(files); 134 | 135 | list = dirs.concat(files).map(function(data) { 136 | return data.text; 137 | }); 138 | 139 | self.setItems(list); 140 | self.select(0); 141 | self.screen.render(); 142 | 143 | self.emit('refresh'); 144 | 145 | if (callback) callback(); 146 | }); 147 | }; 148 | 149 | FileManager.prototype.pick = function(cwd, callback) { 150 | if (!callback) { 151 | callback = cwd; 152 | cwd = null; 153 | } 154 | 155 | var self = this 156 | , focused = this.screen.focused === this 157 | , hidden = this.hidden 158 | , onfile 159 | , oncancel; 160 | 161 | function resume() { 162 | self.removeListener('file', onfile); 163 | self.removeListener('cancel', oncancel); 164 | if (hidden) { 165 | self.hide(); 166 | } 167 | if (!focused) { 168 | self.screen.restoreFocus(); 169 | } 170 | self.screen.render(); 171 | } 172 | 173 | this.on('file', onfile = function(file) { 174 | resume(); 175 | return callback(null, file); 176 | }); 177 | 178 | this.on('cancel', oncancel = function() { 179 | resume(); 180 | return callback(); 181 | }); 182 | 183 | this.refresh(cwd, function(err) { 184 | if (err) return callback(err); 185 | 186 | if (hidden) { 187 | self.show(); 188 | } 189 | 190 | if (!focused) { 191 | self.screen.saveFocus(); 192 | self.focus(); 193 | } 194 | 195 | self.screen.render(); 196 | }); 197 | }; 198 | 199 | FileManager.prototype.reset = function(cwd, callback) { 200 | if (!callback) { 201 | callback = cwd; 202 | cwd = null; 203 | } 204 | this.cwd = cwd || this.options.cwd; 205 | this.refresh(callback); 206 | }; 207 | 208 | /** 209 | * Expose 210 | */ 211 | 212 | module.exports = FileManager; 213 | -------------------------------------------------------------------------------- /lib/widgets/form.js: -------------------------------------------------------------------------------- 1 | /** 2 | * form.js - form element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Box = require('./box'); 13 | 14 | /** 15 | * Form 16 | */ 17 | 18 | function Form(options) { 19 | var self = this; 20 | 21 | if (!(this instanceof Node)) { 22 | return new Form(options); 23 | } 24 | 25 | options = options || {}; 26 | 27 | options.ignoreKeys = true; 28 | Box.call(this, options); 29 | 30 | if (options.keys) { 31 | this.screen._listenKeys(this); 32 | this.on('element keypress', function(el, ch, key) { 33 | if ((key.name === 'tab' && !key.shift) 34 | || (el.type === 'textbox' && options.autoNext && key.name === 'enter') 35 | || key.name === 'down' 36 | || (options.vi && key.name === 'j')) { 37 | if (el.type === 'textbox' || el.type === 'textarea') { 38 | if (key.name === 'j') return; 39 | if (key.name === 'tab') { 40 | // Workaround, since we can't stop the tab from being added. 41 | el.emit('keypress', null, { name: 'backspace' }); 42 | } 43 | el.emit('keypress', '\x1b', { name: 'escape' }); 44 | } 45 | self.focusNext(); 46 | return; 47 | } 48 | 49 | if ((key.name === 'tab' && key.shift) 50 | || key.name === 'up' 51 | || (options.vi && key.name === 'k')) { 52 | if (el.type === 'textbox' || el.type === 'textarea') { 53 | if (key.name === 'k') return; 54 | el.emit('keypress', '\x1b', { name: 'escape' }); 55 | } 56 | self.focusPrevious(); 57 | return; 58 | } 59 | 60 | if (key.name === 'escape') { 61 | self.focus(); 62 | return; 63 | } 64 | }); 65 | } 66 | } 67 | 68 | Form.prototype.__proto__ = Box.prototype; 69 | 70 | Form.prototype.type = 'form'; 71 | 72 | Form.prototype._refresh = function() { 73 | // XXX Possibly remove this if statement and refresh on every focus. 74 | // Also potentially only include *visible* focusable elements. 75 | // This would remove the need to check for _selected.visible in previous() 76 | // and next(). 77 | if (!this._children) { 78 | var out = []; 79 | 80 | this.children.forEach(function fn(el) { 81 | if (el.keyable) out.push(el); 82 | el.children.forEach(fn); 83 | }); 84 | 85 | this._children = out; 86 | } 87 | }; 88 | 89 | Form.prototype._visible = function() { 90 | return !!this._children.filter(function(el) { 91 | return el.visible; 92 | }).length; 93 | }; 94 | 95 | Form.prototype.next = function() { 96 | this._refresh(); 97 | 98 | if (!this._visible()) return; 99 | 100 | if (!this._selected) { 101 | this._selected = this._children[0]; 102 | if (!this._selected.visible) return this.next(); 103 | if (this.screen.focused !== this._selected) return this._selected; 104 | } 105 | 106 | var i = this._children.indexOf(this._selected); 107 | if (!~i || !this._children[i + 1]) { 108 | this._selected = this._children[0]; 109 | if (!this._selected.visible) return this.next(); 110 | return this._selected; 111 | } 112 | 113 | this._selected = this._children[i + 1]; 114 | if (!this._selected.visible) return this.next(); 115 | return this._selected; 116 | }; 117 | 118 | Form.prototype.previous = function() { 119 | this._refresh(); 120 | 121 | if (!this._visible()) return; 122 | 123 | if (!this._selected) { 124 | this._selected = this._children[this._children.length - 1]; 125 | if (!this._selected.visible) return this.previous(); 126 | if (this.screen.focused !== this._selected) return this._selected; 127 | } 128 | 129 | var i = this._children.indexOf(this._selected); 130 | if (!~i || !this._children[i - 1]) { 131 | this._selected = this._children[this._children.length - 1]; 132 | if (!this._selected.visible) return this.previous(); 133 | return this._selected; 134 | } 135 | 136 | this._selected = this._children[i - 1]; 137 | if (!this._selected.visible) return this.previous(); 138 | return this._selected; 139 | }; 140 | 141 | Form.prototype.focusNext = function() { 142 | var next = this.next(); 143 | if (next) next.focus(); 144 | }; 145 | 146 | Form.prototype.focusPrevious = function() { 147 | var previous = this.previous(); 148 | if (previous) previous.focus(); 149 | }; 150 | 151 | Form.prototype.resetSelected = function() { 152 | this._selected = null; 153 | }; 154 | 155 | Form.prototype.focusFirst = function() { 156 | this.resetSelected(); 157 | this.focusNext(); 158 | }; 159 | 160 | Form.prototype.focusLast = function() { 161 | this.resetSelected(); 162 | this.focusPrevious(); 163 | }; 164 | 165 | Form.prototype.submit = function() { 166 | var out = {}; 167 | 168 | this.children.forEach(function fn(el) { 169 | if (el.value != null) { 170 | var name = el.name || el.type; 171 | if (Array.isArray(out[name])) { 172 | out[name].push(el.value); 173 | } else if (out[name]) { 174 | out[name] = [out[name], el.value]; 175 | } else { 176 | out[name] = el.value; 177 | } 178 | } 179 | el.children.forEach(fn); 180 | }); 181 | 182 | this.emit('submit', out); 183 | 184 | return this.submission = out; 185 | }; 186 | 187 | Form.prototype.cancel = function() { 188 | this.emit('cancel'); 189 | }; 190 | 191 | Form.prototype.reset = function() { 192 | this.children.forEach(function fn(el) { 193 | switch (el.type) { 194 | case 'screen': 195 | break; 196 | case 'box': 197 | break; 198 | case 'text': 199 | break; 200 | case 'line': 201 | break; 202 | case 'scrollable-box': 203 | break; 204 | case 'list': 205 | el.select(0); 206 | return; 207 | case 'form': 208 | break; 209 | case 'input': 210 | break; 211 | case 'textbox': 212 | el.clearInput(); 213 | return; 214 | case 'textarea': 215 | el.clearInput(); 216 | return; 217 | case 'button': 218 | delete el.value; 219 | break; 220 | case 'progress-bar': 221 | el.setProgress(0); 222 | break; 223 | case 'file-manager': 224 | el.refresh(el.options.cwd); 225 | return; 226 | case 'checkbox': 227 | el.uncheck(); 228 | return; 229 | case 'radio-set': 230 | break; 231 | case 'radio-button': 232 | el.uncheck(); 233 | return; 234 | case 'prompt': 235 | break; 236 | case 'question': 237 | break; 238 | case 'message': 239 | break; 240 | case 'info': 241 | break; 242 | case 'loading': 243 | break; 244 | case 'list-bar': 245 | //el.select(0); 246 | break; 247 | case 'dir-manager': 248 | el.refresh(el.options.cwd); 249 | return; 250 | case 'terminal': 251 | el.write(''); 252 | return; 253 | case 'image': 254 | //el.clearImage(); 255 | return; 256 | } 257 | el.children.forEach(fn); 258 | }); 259 | 260 | this.emit('reset'); 261 | }; 262 | 263 | /** 264 | * Expose 265 | */ 266 | 267 | module.exports = Form; 268 | -------------------------------------------------------------------------------- /lib/widgets/image.js: -------------------------------------------------------------------------------- 1 | /** 2 | * image.js - image element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Box = require('./box'); 13 | 14 | /** 15 | * Image 16 | */ 17 | 18 | function Image(options) { 19 | if (!(this instanceof Node)) { 20 | return new Image(options); 21 | } 22 | 23 | options = options || {}; 24 | options.type = options.itype || options.type || 'ansi'; 25 | 26 | Box.call(this, options); 27 | 28 | if (options.type === 'ansi' && this.type !== 'ansiimage') { 29 | var ANSIImage = require('./ansiimage'); 30 | Object.getOwnPropertyNames(ANSIImage.prototype).forEach(function(key) { 31 | if (key === 'type') return; 32 | Object.defineProperty(this, key, 33 | Object.getOwnPropertyDescriptor(ANSIImage.prototype, key)); 34 | }, this); 35 | ANSIImage.call(this, options); 36 | return this; 37 | } 38 | 39 | if (options.type === 'overlay' && this.type !== 'overlayimage') { 40 | var OverlayImage = require('./overlayimage'); 41 | Object.getOwnPropertyNames(OverlayImage.prototype).forEach(function(key) { 42 | if (key === 'type') return; 43 | Object.defineProperty(this, key, 44 | Object.getOwnPropertyDescriptor(OverlayImage.prototype, key)); 45 | }, this); 46 | OverlayImage.call(this, options); 47 | return this; 48 | } 49 | 50 | throw new Error('`type` must either be `ansi` or `overlay`.'); 51 | } 52 | 53 | Image.prototype.__proto__ = Box.prototype; 54 | 55 | Image.prototype.type = 'image'; 56 | 57 | /** 58 | * Expose 59 | */ 60 | 61 | module.exports = Image; 62 | -------------------------------------------------------------------------------- /lib/widgets/input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * input.js - abstract input element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Box = require('./box'); 13 | 14 | /** 15 | * Input 16 | */ 17 | 18 | function Input(options) { 19 | if (!(this instanceof Node)) { 20 | return new Input(options); 21 | } 22 | options = options || {}; 23 | Box.call(this, options); 24 | } 25 | 26 | Input.prototype.__proto__ = Box.prototype; 27 | 28 | Input.prototype.type = 'input'; 29 | 30 | /** 31 | * Expose 32 | */ 33 | 34 | module.exports = Input; 35 | -------------------------------------------------------------------------------- /lib/widgets/layout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * layout.js - layout element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Element = require('./element'); 13 | 14 | /** 15 | * Layout 16 | */ 17 | 18 | function Layout(options) { 19 | if (!(this instanceof Node)) { 20 | return new Layout(options); 21 | } 22 | 23 | options = options || {}; 24 | 25 | if ((options.width == null 26 | && (options.left == null && options.right == null)) 27 | || (options.height == null 28 | && (options.top == null && options.bottom == null))) { 29 | throw new Error('`Layout` must have a width and height!'); 30 | } 31 | 32 | options.layout = options.layout || 'inline'; 33 | 34 | Element.call(this, options); 35 | 36 | if (options.renderer) { 37 | this.renderer = options.renderer; 38 | } 39 | } 40 | 41 | Layout.prototype.__proto__ = Element.prototype; 42 | 43 | Layout.prototype.type = 'layout'; 44 | 45 | Layout.prototype.isRendered = function(el) { 46 | if (!el.lpos) return false; 47 | return (el.lpos.xl - el.lpos.xi) > 0 48 | && (el.lpos.yl - el.lpos.yi) > 0; 49 | }; 50 | 51 | Layout.prototype.getLast = function(i) { 52 | while (this.children[--i]) { 53 | var el = this.children[i]; 54 | if (this.isRendered(el)) return el; 55 | } 56 | }; 57 | 58 | Layout.prototype.getLastCoords = function(i) { 59 | var last = this.getLast(i); 60 | if (last) return last.lpos; 61 | }; 62 | 63 | Layout.prototype._renderCoords = function() { 64 | var coords = this._getCoords(true); 65 | var children = this.children; 66 | this.children = []; 67 | this._render(); 68 | this.children = children; 69 | return coords; 70 | }; 71 | 72 | Layout.prototype.renderer = function(coords) { 73 | var self = this; 74 | 75 | // The coordinates of the layout element 76 | var width = coords.xl - coords.xi 77 | , height = coords.yl - coords.yi 78 | , xi = coords.xi 79 | , yi = coords.yi; 80 | 81 | // The current row offset in cells (which row are we on?) 82 | var rowOffset = 0; 83 | 84 | // The index of the first child in the row 85 | var rowIndex = 0; 86 | var lastRowIndex = 0; 87 | 88 | // Figure out the highest width child 89 | if (this.options.layout === 'grid') { 90 | var highWidth = this.children.reduce(function(out, el) { 91 | out = Math.max(out, el.width); 92 | return out; 93 | }, 0); 94 | } 95 | 96 | return function iterator(el, i) { 97 | // Make our children shrinkable. If they don't have a height, for 98 | // example, calculate it for them. 99 | el.shrink = true; 100 | 101 | // Find the previous rendered child's coordinates 102 | var last = self.getLast(i); 103 | 104 | // If there is no previously rendered element, we are on the first child. 105 | if (!last) { 106 | el.position.left = 0; 107 | el.position.top = 0; 108 | } else { 109 | // Otherwise, figure out where to place this child. We'll start by 110 | // setting it's `left`/`x` coordinate to right after the previous 111 | // rendered element. This child will end up directly to the right of it. 112 | el.position.left = last.lpos.xl - xi; 113 | 114 | // Make sure the position matches the highest width element 115 | if (self.options.layout === 'grid') { 116 | // Compensate with width: 117 | // el.position.width = el.width + (highWidth - el.width); 118 | // Compensate with position: 119 | el.position.left += highWidth - (last.lpos.xl - last.lpos.xi); 120 | } 121 | 122 | // If our child does not overlap the right side of the Layout, set it's 123 | // `top`/`y` to the current `rowOffset` (the coordinate for the current 124 | // row). 125 | if (el.position.left + el.width <= width) { 126 | el.position.top = rowOffset; 127 | } else { 128 | // Otherwise we need to start a new row and calculate a new 129 | // `rowOffset` and `rowIndex` (the index of the child on the current 130 | // row). 131 | rowOffset += self.children.slice(rowIndex, i).reduce(function(out, el) { 132 | if (!self.isRendered(el)) return out; 133 | out = Math.max(out, el.lpos.yl - el.lpos.yi); 134 | return out; 135 | }, 0); 136 | lastRowIndex = rowIndex; 137 | rowIndex = i; 138 | el.position.left = 0; 139 | el.position.top = rowOffset; 140 | } 141 | } 142 | 143 | // Make sure the elements on lower rows graviatate up as much as possible 144 | if (self.options.layout === 'inline') { 145 | var above = null; 146 | var abovea = Infinity; 147 | for (var j = lastRowIndex; j < rowIndex; j++) { 148 | var l = self.children[j]; 149 | if (!self.isRendered(l)) continue; 150 | var abs = Math.abs(el.position.left - (l.lpos.xi - xi)); 151 | // if (abs < abovea && (l.lpos.xl - l.lpos.xi) <= el.width) { 152 | if (abs < abovea) { 153 | above = l; 154 | abovea = abs; 155 | } 156 | } 157 | if (above) { 158 | el.position.top = above.lpos.yl - yi; 159 | } 160 | } 161 | 162 | // If our child overflows the Layout, do not render it! 163 | // Disable this feature for now. 164 | if (el.position.top + el.height > height) { 165 | // Returning false tells blessed to ignore this child. 166 | // return false; 167 | } 168 | }; 169 | }; 170 | 171 | Layout.prototype.render = function() { 172 | this._emit('prerender'); 173 | 174 | var coords = this._renderCoords(); 175 | if (!coords) { 176 | delete this.lpos; 177 | return; 178 | } 179 | 180 | if (coords.xl - coords.xi <= 0) { 181 | coords.xl = Math.max(coords.xl, coords.xi); 182 | return; 183 | } 184 | 185 | if (coords.yl - coords.yi <= 0) { 186 | coords.yl = Math.max(coords.yl, coords.yi); 187 | return; 188 | } 189 | 190 | this.lpos = coords; 191 | 192 | if (this.border) coords.xi++, coords.xl--, coords.yi++, coords.yl--; 193 | if (this.tpadding) { 194 | coords.xi += this.padding.left, coords.xl -= this.padding.right; 195 | coords.yi += this.padding.top, coords.yl -= this.padding.bottom; 196 | } 197 | 198 | var iterator = this.renderer(coords); 199 | 200 | if (this.border) coords.xi--, coords.xl++, coords.yi--, coords.yl++; 201 | if (this.tpadding) { 202 | coords.xi -= this.padding.left, coords.xl += this.padding.right; 203 | coords.yi -= this.padding.top, coords.yl += this.padding.bottom; 204 | } 205 | 206 | this.children.forEach(function(el, i) { 207 | if (el.screen._ci !== -1) { 208 | el.index = el.screen._ci++; 209 | } 210 | var rendered = iterator(el, i); 211 | if (rendered === false) { 212 | delete el.lpos; 213 | return; 214 | } 215 | // if (el.screen._rendering) { 216 | // el._rendering = true; 217 | // } 218 | el.render(); 219 | // if (el.screen._rendering) { 220 | // el._rendering = false; 221 | // } 222 | }); 223 | 224 | this._emit('render', [coords]); 225 | 226 | return coords; 227 | }; 228 | 229 | /** 230 | * Expose 231 | */ 232 | 233 | module.exports = Layout; 234 | -------------------------------------------------------------------------------- /lib/widgets/line.js: -------------------------------------------------------------------------------- 1 | /** 2 | * line.js - line element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Box = require('./box'); 13 | 14 | /** 15 | * Line 16 | */ 17 | 18 | function Line(options) { 19 | if (!(this instanceof Node)) { 20 | return new Line(options); 21 | } 22 | 23 | options = options || {}; 24 | 25 | var orientation = options.orientation || 'vertical'; 26 | delete options.orientation; 27 | 28 | if (orientation === 'vertical') { 29 | options.width = 1; 30 | } else { 31 | options.height = 1; 32 | } 33 | 34 | Box.call(this, options); 35 | 36 | this.ch = !options.type || options.type === 'line' 37 | ? orientation === 'horizontal' ? '─' : '│' 38 | : options.ch || ' '; 39 | 40 | this.border = { 41 | type: 'bg', 42 | __proto__: this 43 | }; 44 | 45 | this.style.border = this.style; 46 | } 47 | 48 | Line.prototype.__proto__ = Box.prototype; 49 | 50 | Line.prototype.type = 'line'; 51 | 52 | /** 53 | * Expose 54 | */ 55 | 56 | module.exports = Line; 57 | -------------------------------------------------------------------------------- /lib/widgets/listtable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * listtable.js - list table element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Box = require('./box'); 13 | var List = require('./list'); 14 | var Table = require('./table'); 15 | 16 | /** 17 | * ListTable 18 | */ 19 | 20 | function ListTable(options) { 21 | var self = this; 22 | 23 | if (!(this instanceof Node)) { 24 | return new ListTable(options); 25 | } 26 | 27 | options = options || {}; 28 | 29 | // options.shrink = true; 30 | options.normalShrink = true; 31 | options.style = options.style || {}; 32 | options.style.border = options.style.border || {}; 33 | options.style.header = options.style.header || {}; 34 | options.style.cell = options.style.cell || {}; 35 | this.__align = options.align || 'center'; 36 | delete options.align; 37 | 38 | options.style.selected = options.style.cell.selected; 39 | options.style.item = options.style.cell; 40 | 41 | var border = options.border; 42 | if (border 43 | && border.top === false 44 | && border.bottom === false 45 | && border.left === false 46 | && border.right === false) { 47 | delete options.border; 48 | } 49 | 50 | List.call(this, options); 51 | 52 | options.border = border; 53 | 54 | this._header = new Box({ 55 | parent: this, 56 | left: this.screen.autoPadding ? 0 : this.ileft, 57 | top: 0, 58 | width: 'shrink', 59 | height: 1, 60 | style: options.style.header, 61 | tags: options.parseTags || options.tags 62 | }); 63 | 64 | this.on('scroll', function() { 65 | self._header.setFront(); 66 | self._header.rtop = self.childBase; 67 | if (!self.screen.autoPadding) { 68 | self._header.rtop = self.childBase + (self.border ? 1 : 0); 69 | } 70 | }); 71 | 72 | this.pad = options.pad != null 73 | ? options.pad 74 | : 2; 75 | 76 | this.setData(options.rows || options.data); 77 | 78 | this.on('attach', function() { 79 | self.setData(self.rows); 80 | }); 81 | 82 | this.on('resize', function() { 83 | var selected = self.selected; 84 | self.setData(self.rows); 85 | self.select(selected); 86 | self.screen.render(); 87 | }); 88 | } 89 | 90 | ListTable.prototype.__proto__ = List.prototype; 91 | 92 | ListTable.prototype.type = 'list-table'; 93 | 94 | ListTable.prototype._calculateMaxes = Table.prototype._calculateMaxes; 95 | 96 | ListTable.prototype.setRows = 97 | ListTable.prototype.setData = function(rows) { 98 | var self = this 99 | , align = this.__align 100 | , selected = this.selected 101 | , original = this.items.slice() 102 | , sel = this.ritems[this.selected]; 103 | 104 | if (this.visible && this.lpos) { 105 | this.clearPos(); 106 | } 107 | 108 | this.clearItems(); 109 | 110 | this.rows = rows || []; 111 | 112 | this._calculateMaxes(); 113 | 114 | if (!this._maxes) return; 115 | 116 | this.addItem(''); 117 | 118 | this.rows.forEach(function(row, i) { 119 | var isHeader = i === 0; 120 | var text = ''; 121 | row.forEach(function(cell, i) { 122 | var width = self._maxes[i]; 123 | var clen = self.strWidth(cell); 124 | 125 | if (i !== 0) { 126 | text += ' '; 127 | } 128 | 129 | while (clen < width) { 130 | if (align === 'center') { 131 | cell = ' ' + cell + ' '; 132 | clen += 2; 133 | } else if (align === 'left') { 134 | cell = cell + ' '; 135 | clen += 1; 136 | } else if (align === 'right') { 137 | cell = ' ' + cell; 138 | clen += 1; 139 | } 140 | } 141 | 142 | if (clen > width) { 143 | if (align === 'center') { 144 | cell = cell.substring(1); 145 | clen--; 146 | } else if (align === 'left') { 147 | cell = cell.slice(0, -1); 148 | clen--; 149 | } else if (align === 'right') { 150 | cell = cell.substring(1); 151 | clen--; 152 | } 153 | } 154 | 155 | text += cell; 156 | }); 157 | if (isHeader) { 158 | self._header.setContent(text); 159 | } else { 160 | self.addItem(text); 161 | } 162 | }); 163 | 164 | this._header.setFront(); 165 | 166 | // Try to find our old item if it still exists. 167 | sel = this.ritems.indexOf(sel); 168 | if (~sel) { 169 | this.select(sel); 170 | } else if (this.items.length === original.length) { 171 | this.select(selected); 172 | } else { 173 | this.select(Math.min(selected, this.items.length - 1)); 174 | } 175 | }; 176 | 177 | ListTable.prototype._select = ListTable.prototype.select; 178 | ListTable.prototype.select = function(i) { 179 | if (i === 0) { 180 | i = 1; 181 | } 182 | if (i <= this.childBase) { 183 | this.setScroll(this.childBase - 1); 184 | } 185 | return this._select(i); 186 | }; 187 | 188 | ListTable.prototype.render = function() { 189 | var self = this; 190 | 191 | var coords = this._render(); 192 | if (!coords) return; 193 | 194 | this._calculateMaxes(); 195 | 196 | if (!this._maxes) return coords; 197 | 198 | var lines = this.screen.lines 199 | , xi = coords.xi 200 | , yi = coords.yi 201 | , rx 202 | , ry 203 | , i; 204 | 205 | var battr = this.sattr(this.style.border); 206 | 207 | var height = coords.yl - coords.yi - this.ibottom; 208 | 209 | var border = this.border; 210 | if (!this.border && this.options.border) { 211 | border = this.options.border; 212 | } 213 | 214 | if (!border || this.options.noCellBorders) return coords; 215 | 216 | // Draw border with correct angles. 217 | ry = 0; 218 | for (i = 0; i < height + 1; i++) { 219 | if (!lines[yi + ry]) break; 220 | rx = 0; 221 | self._maxes.slice(0, -1).forEach(function(max) { 222 | rx += max; 223 | if (!lines[yi + ry][xi + rx + 1]) return; 224 | // center 225 | if (ry === 0) { 226 | // top 227 | rx++; 228 | lines[yi + ry][xi + rx][0] = battr; 229 | lines[yi + ry][xi + rx][1] = '\u252c'; // '┬' 230 | // XXX If we alter iheight and itop for no borders - nothing should be written here 231 | if (!border.top) { 232 | lines[yi + ry][xi + rx][1] = '\u2502'; // '│' 233 | } 234 | lines[yi + ry].dirty = true; 235 | } else if (ry === height) { 236 | // bottom 237 | rx++; 238 | lines[yi + ry][xi + rx][0] = battr; 239 | lines[yi + ry][xi + rx][1] = '\u2534'; // '┴' 240 | // XXX If we alter iheight and ibottom for no borders - nothing should be written here 241 | if (!border.bottom) { 242 | lines[yi + ry][xi + rx][1] = '\u2502'; // '│' 243 | } 244 | lines[yi + ry].dirty = true; 245 | } else { 246 | // middle 247 | rx++; 248 | } 249 | }); 250 | ry += 1; 251 | } 252 | 253 | // Draw internal borders. 254 | for (ry = 1; ry < height; ry++) { 255 | if (!lines[yi + ry]) break; 256 | rx = 0; 257 | self._maxes.slice(0, -1).forEach(function(max) { 258 | rx += max; 259 | if (!lines[yi + ry][xi + rx + 1]) return; 260 | if (self.options.fillCellBorders !== false) { 261 | var lbg = lines[yi + ry][xi + rx][0] & 0x1ff; 262 | rx++; 263 | lines[yi + ry][xi + rx][0] = (battr & ~0x1ff) | lbg; 264 | } else { 265 | rx++; 266 | lines[yi + ry][xi + rx][0] = battr; 267 | } 268 | lines[yi + ry][xi + rx][1] = '\u2502'; // '│' 269 | lines[yi + ry].dirty = true; 270 | }); 271 | } 272 | 273 | return coords; 274 | }; 275 | 276 | /** 277 | * Expose 278 | */ 279 | 280 | module.exports = ListTable; 281 | -------------------------------------------------------------------------------- /lib/widgets/loading.js: -------------------------------------------------------------------------------- 1 | /** 2 | * loading.js - loading element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Box = require('./box'); 13 | var Text = require('./text'); 14 | 15 | /** 16 | * Loading 17 | */ 18 | 19 | function Loading(options) { 20 | if (!(this instanceof Node)) { 21 | return new Loading(options); 22 | } 23 | 24 | options = options || {}; 25 | 26 | Box.call(this, options); 27 | 28 | this._.icon = new Text({ 29 | parent: this, 30 | align: 'center', 31 | top: 2, 32 | left: 1, 33 | right: 1, 34 | height: 1, 35 | content: '|' 36 | }); 37 | } 38 | 39 | Loading.prototype.__proto__ = Box.prototype; 40 | 41 | Loading.prototype.type = 'loading'; 42 | 43 | Loading.prototype.load = function(text) { 44 | var self = this; 45 | 46 | // XXX Keep above: 47 | // var parent = this.parent; 48 | // this.detach(); 49 | // parent.append(this); 50 | 51 | this.show(); 52 | this.setContent(text); 53 | 54 | if (this._.timer) { 55 | this.stop(); 56 | } 57 | 58 | this.screen.lockKeys = true; 59 | 60 | this._.timer = setInterval(function() { 61 | if (self._.icon.content === '|') { 62 | self._.icon.setContent('/'); 63 | } else if (self._.icon.content === '/') { 64 | self._.icon.setContent('-'); 65 | } else if (self._.icon.content === '-') { 66 | self._.icon.setContent('\\'); 67 | } else if (self._.icon.content === '\\') { 68 | self._.icon.setContent('|'); 69 | } 70 | self.screen.render(); 71 | }, 200); 72 | }; 73 | 74 | Loading.prototype.stop = function() { 75 | this.screen.lockKeys = false; 76 | this.hide(); 77 | if (this._.timer) { 78 | clearInterval(this._.timer); 79 | delete this._.timer; 80 | } 81 | this.screen.render(); 82 | }; 83 | 84 | /** 85 | * Expose 86 | */ 87 | 88 | module.exports = Loading; 89 | -------------------------------------------------------------------------------- /lib/widgets/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * log.js - log element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var util = require('util'); 12 | 13 | var nextTick = global.setImmediate || process.nextTick.bind(process); 14 | 15 | var Node = require('./node'); 16 | var ScrollableText = require('./scrollabletext'); 17 | 18 | /** 19 | * Log 20 | */ 21 | 22 | function Log(options) { 23 | var self = this; 24 | 25 | if (!(this instanceof Node)) { 26 | return new Log(options); 27 | } 28 | 29 | options = options || {}; 30 | 31 | ScrollableText.call(this, options); 32 | 33 | this.scrollback = options.scrollback != null 34 | ? options.scrollback 35 | : Infinity; 36 | this.scrollOnInput = options.scrollOnInput; 37 | 38 | this.on('set content', function() { 39 | if (!self._userScrolled || self.scrollOnInput) { 40 | nextTick(function() { 41 | self.setScrollPerc(100); 42 | self._userScrolled = false; 43 | self.screen.render(); 44 | }); 45 | } 46 | }); 47 | } 48 | 49 | Log.prototype.__proto__ = ScrollableText.prototype; 50 | 51 | Log.prototype.type = 'log'; 52 | 53 | Log.prototype.log = 54 | Log.prototype.add = function() { 55 | var args = Array.prototype.slice.call(arguments); 56 | if (typeof args[0] === 'object') { 57 | args[0] = util.inspect(args[0], true, 20, true); 58 | } 59 | var text = util.format.apply(util, args); 60 | this.emit('log', text); 61 | var ret = this.pushLine(text); 62 | if (this._clines.fake.length > this.scrollback) { 63 | this.shiftLine(0, (this.scrollback / 3) | 0); 64 | } 65 | return ret; 66 | }; 67 | 68 | Log.prototype._scroll = Log.prototype.scroll; 69 | Log.prototype.scroll = function(offset, always) { 70 | if (offset === 0) return this._scroll(offset, always); 71 | this._userScrolled = true; 72 | var ret = this._scroll(offset, always); 73 | if (this.getScrollPerc() === 100) { 74 | this._userScrolled = false; 75 | } 76 | return ret; 77 | }; 78 | 79 | /** 80 | * Expose 81 | */ 82 | 83 | module.exports = Log; 84 | -------------------------------------------------------------------------------- /lib/widgets/message.js: -------------------------------------------------------------------------------- 1 | /** 2 | * message.js - message element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Box = require('./box'); 13 | 14 | /** 15 | * Message / Error 16 | */ 17 | 18 | function Message(options) { 19 | if (!(this instanceof Node)) { 20 | return new Message(options); 21 | } 22 | 23 | options = options || {}; 24 | options.tags = true; 25 | 26 | Box.call(this, options); 27 | } 28 | 29 | Message.prototype.__proto__ = Box.prototype; 30 | 31 | Message.prototype.type = 'message'; 32 | 33 | Message.prototype.log = 34 | Message.prototype.display = function(text, time, callback) { 35 | var self = this; 36 | 37 | if (typeof time === 'function') { 38 | callback = time; 39 | time = null; 40 | } 41 | 42 | if (time == null) time = 3; 43 | 44 | // Keep above: 45 | // var parent = this.parent; 46 | // this.detach(); 47 | // parent.append(this); 48 | 49 | if (this.scrollable) { 50 | this.screen.saveFocus(); 51 | this.focus(); 52 | this.scrollTo(0); 53 | } 54 | 55 | this.show(); 56 | this.setContent(text); 57 | this.screen.render(); 58 | 59 | if (time === Infinity || time === -1 || time === 0) { 60 | var end = function() { 61 | if (end.done) return; 62 | end.done = true; 63 | if (self.scrollable) { 64 | try { 65 | self.screen.restoreFocus(); 66 | } catch (e) { 67 | ; 68 | } 69 | } 70 | self.hide(); 71 | self.screen.render(); 72 | if (callback) callback(); 73 | }; 74 | 75 | setTimeout(function() { 76 | self.onScreenEvent('keypress', function fn(ch, key) { 77 | if (key.name === 'mouse') return; 78 | if (self.scrollable) { 79 | if ((key.name === 'up' || (self.options.vi && key.name === 'k')) 80 | || (key.name === 'down' || (self.options.vi && key.name === 'j')) 81 | || (self.options.vi && key.name === 'u' && key.ctrl) 82 | || (self.options.vi && key.name === 'd' && key.ctrl) 83 | || (self.options.vi && key.name === 'b' && key.ctrl) 84 | || (self.options.vi && key.name === 'f' && key.ctrl) 85 | || (self.options.vi && key.name === 'g' && !key.shift) 86 | || (self.options.vi && key.name === 'g' && key.shift)) { 87 | return; 88 | } 89 | } 90 | if (self.options.ignoreKeys && ~self.options.ignoreKeys.indexOf(key.name)) { 91 | return; 92 | } 93 | self.removeScreenEvent('keypress', fn); 94 | end(); 95 | }); 96 | // XXX May be affected by new element.options.mouse option. 97 | if (!self.options.mouse) return; 98 | self.onScreenEvent('mouse', function fn(data) { 99 | if (data.action === 'mousemove') return; 100 | self.removeScreenEvent('mouse', fn); 101 | end(); 102 | }); 103 | }, 10); 104 | 105 | return; 106 | } 107 | 108 | setTimeout(function() { 109 | self.hide(); 110 | self.screen.render(); 111 | if (callback) callback(); 112 | }, time * 1000); 113 | }; 114 | 115 | Message.prototype.error = function(text, time, callback) { 116 | return this.display('{red-fg}Error: ' + text + '{/red-fg}', time, callback); 117 | }; 118 | 119 | /** 120 | * Expose 121 | */ 122 | 123 | module.exports = Message; 124 | -------------------------------------------------------------------------------- /lib/widgets/node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * node.js - base abstract node for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var EventEmitter = require('../events').EventEmitter; 12 | 13 | /** 14 | * Node 15 | */ 16 | 17 | function Node(options) { 18 | var self = this; 19 | var Screen = require('./screen'); 20 | 21 | if (!(this instanceof Node)) { 22 | return new Node(options); 23 | } 24 | 25 | EventEmitter.call(this); 26 | 27 | options = options || {}; 28 | this.options = options; 29 | 30 | this.screen = this.screen || options.screen; 31 | 32 | if (!this.screen) { 33 | if (this.type === 'screen') { 34 | this.screen = this; 35 | } else if (Screen.total === 1) { 36 | this.screen = Screen.global; 37 | } else if (options.parent) { 38 | this.screen = options.parent; 39 | while (this.screen && this.screen.type !== 'screen') { 40 | this.screen = this.screen.parent; 41 | } 42 | } else if (Screen.total) { 43 | // This _should_ work in most cases as long as the element is appended 44 | // synchronously after the screen's creation. Throw error if not. 45 | this.screen = Screen.instances[Screen.instances.length - 1]; 46 | process.nextTick(function() { 47 | if (!self.parent) { 48 | throw new Error('Element (' + self.type + ')' 49 | + ' was not appended synchronously after the' 50 | + ' screen\'s creation. Please set a `parent`' 51 | + ' or `screen` option in the element\'s constructor' 52 | + ' if you are going to use multiple screens and' 53 | + ' append the element later.'); 54 | } 55 | }); 56 | } else { 57 | throw new Error('No active screen.'); 58 | } 59 | } 60 | 61 | this.parent = options.parent || null; 62 | this.children = []; 63 | this.$ = this._ = this.data = {}; 64 | this.uid = Node.uid++; 65 | this.index = this.index != null ? this.index : -1; 66 | 67 | if (this.type !== 'screen') { 68 | this.detached = true; 69 | } 70 | 71 | if (this.parent) { 72 | this.parent.append(this); 73 | } 74 | 75 | (options.children || []).forEach(this.append.bind(this)); 76 | } 77 | 78 | Node.uid = 0; 79 | 80 | Node.prototype.__proto__ = EventEmitter.prototype; 81 | 82 | Node.prototype.type = 'node'; 83 | 84 | Node.prototype.insert = function(element, i) { 85 | var self = this; 86 | 87 | if (element.screen && element.screen !== this.screen) { 88 | throw new Error('Cannot switch a node\'s screen.'); 89 | } 90 | 91 | element.detach(); 92 | element.parent = this; 93 | element.screen = this.screen; 94 | 95 | if (i === 0) { 96 | this.children.unshift(element); 97 | } else if (i === this.children.length) { 98 | this.children.push(element); 99 | } else { 100 | this.children.splice(i, 0, element); 101 | } 102 | 103 | element.emit('reparent', this); 104 | this.emit('adopt', element); 105 | 106 | (function emit(el) { 107 | var n = el.detached !== self.detached; 108 | el.detached = self.detached; 109 | if (n) el.emit('attach'); 110 | el.children.forEach(emit); 111 | })(element); 112 | 113 | if (!this.screen.focused) { 114 | this.screen.focused = element; 115 | } 116 | }; 117 | 118 | Node.prototype.prepend = function(element) { 119 | this.insert(element, 0); 120 | }; 121 | 122 | Node.prototype.append = function(element) { 123 | this.insert(element, this.children.length); 124 | }; 125 | 126 | Node.prototype.insertBefore = function(element, other) { 127 | var i = this.children.indexOf(other); 128 | if (~i) this.insert(element, i); 129 | }; 130 | 131 | Node.prototype.insertAfter = function(element, other) { 132 | var i = this.children.indexOf(other); 133 | if (~i) this.insert(element, i + 1); 134 | }; 135 | 136 | Node.prototype.remove = function(element) { 137 | if (element.parent !== this) return; 138 | 139 | var i = this.children.indexOf(element); 140 | if (!~i) return; 141 | 142 | element.clearPos(); 143 | 144 | element.parent = null; 145 | 146 | this.children.splice(i, 1); 147 | 148 | i = this.screen.clickable.indexOf(element); 149 | if (~i) this.screen.clickable.splice(i, 1); 150 | i = this.screen.keyable.indexOf(element); 151 | if (~i) this.screen.keyable.splice(i, 1); 152 | 153 | element.emit('reparent', null); 154 | this.emit('remove', element); 155 | 156 | (function emit(el) { 157 | var n = el.detached !== true; 158 | el.detached = true; 159 | if (n) el.emit('detach'); 160 | el.children.forEach(emit); 161 | })(element); 162 | 163 | if (this.screen.focused === element) { 164 | this.screen.rewindFocus(); 165 | } 166 | }; 167 | 168 | Node.prototype.detach = function() { 169 | if (this.parent) this.parent.remove(this); 170 | }; 171 | 172 | Node.prototype.free = function() { 173 | return; 174 | }; 175 | 176 | Node.prototype.destroy = function() { 177 | this.detach(); 178 | this.forDescendants(function(el) { 179 | el.free(); 180 | el.destroyed = true; 181 | el.emit('destroy'); 182 | }, this); 183 | }; 184 | 185 | Node.prototype.forDescendants = function(iter, s) { 186 | if (s) iter(this); 187 | this.children.forEach(function emit(el) { 188 | iter(el); 189 | el.children.forEach(emit); 190 | }); 191 | }; 192 | 193 | Node.prototype.forAncestors = function(iter, s) { 194 | var el = this; 195 | if (s) iter(this); 196 | while (el = el.parent) { 197 | iter(el); 198 | } 199 | }; 200 | 201 | Node.prototype.collectDescendants = function(s) { 202 | var out = []; 203 | this.forDescendants(function(el) { 204 | out.push(el); 205 | }, s); 206 | return out; 207 | }; 208 | 209 | Node.prototype.collectAncestors = function(s) { 210 | var out = []; 211 | this.forAncestors(function(el) { 212 | out.push(el); 213 | }, s); 214 | return out; 215 | }; 216 | 217 | Node.prototype.emitDescendants = function() { 218 | var args = Array.prototype.slice(arguments) 219 | , iter; 220 | 221 | if (typeof args[args.length - 1] === 'function') { 222 | iter = args.pop(); 223 | } 224 | 225 | return this.forDescendants(function(el) { 226 | if (iter) iter(el); 227 | el.emit.apply(el, args); 228 | }, true); 229 | }; 230 | 231 | Node.prototype.emitAncestors = function() { 232 | var args = Array.prototype.slice(arguments) 233 | , iter; 234 | 235 | if (typeof args[args.length - 1] === 'function') { 236 | iter = args.pop(); 237 | } 238 | 239 | return this.forAncestors(function(el) { 240 | if (iter) iter(el); 241 | el.emit.apply(el, args); 242 | }, true); 243 | }; 244 | 245 | Node.prototype.hasDescendant = function(target) { 246 | return (function find(el) { 247 | for (var i = 0; i < el.children.length; i++) { 248 | if (el.children[i] === target) { 249 | return true; 250 | } 251 | if (find(el.children[i]) === true) { 252 | return true; 253 | } 254 | } 255 | return false; 256 | })(this); 257 | }; 258 | 259 | Node.prototype.hasAncestor = function(target) { 260 | var el = this; 261 | while (el = el.parent) { 262 | if (el === target) return true; 263 | } 264 | return false; 265 | }; 266 | 267 | Node.prototype.get = function(name, value) { 268 | if (this.data.hasOwnProperty(name)) { 269 | return this.data[name]; 270 | } 271 | return value; 272 | }; 273 | 274 | Node.prototype.set = function(name, value) { 275 | return this.data[name] = value; 276 | }; 277 | 278 | /** 279 | * Expose 280 | */ 281 | 282 | module.exports = Node; 283 | -------------------------------------------------------------------------------- /lib/widgets/progressbar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * progressbar.js - progress bar element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Input = require('./input'); 13 | 14 | /** 15 | * ProgressBar 16 | */ 17 | 18 | function ProgressBar(options) { 19 | var self = this; 20 | 21 | if (!(this instanceof Node)) { 22 | return new ProgressBar(options); 23 | } 24 | 25 | options = options || {}; 26 | 27 | Input.call(this, options); 28 | 29 | this.filled = options.filled || 0; 30 | if (typeof this.filled === 'string') { 31 | this.filled = +this.filled.slice(0, -1); 32 | } 33 | this.value = this.filled; 34 | 35 | this.pch = options.pch || ' '; 36 | 37 | // XXX Workaround that predates the usage of `el.ch`. 38 | if (options.ch) { 39 | this.pch = options.ch; 40 | this.ch = ' '; 41 | } 42 | if (options.bch) { 43 | this.ch = options.bch; 44 | } 45 | 46 | if (!this.style.bar) { 47 | this.style.bar = {}; 48 | this.style.bar.fg = options.barFg; 49 | this.style.bar.bg = options.barBg; 50 | } 51 | 52 | this.orientation = options.orientation || 'horizontal'; 53 | 54 | if (options.keys) { 55 | this.on('keypress', function(ch, key) { 56 | var back, forward; 57 | if (self.orientation === 'horizontal') { 58 | back = ['left', 'h']; 59 | forward = ['right', 'l']; 60 | } else if (self.orientation === 'vertical') { 61 | back = ['down', 'j']; 62 | forward = ['up', 'k']; 63 | } 64 | if (key.name === back[0] || (options.vi && key.name === back[1])) { 65 | self.progress(-5); 66 | self.screen.render(); 67 | return; 68 | } 69 | if (key.name === forward[0] || (options.vi && key.name === forward[1])) { 70 | self.progress(5); 71 | self.screen.render(); 72 | return; 73 | } 74 | }); 75 | } 76 | 77 | if (options.mouse) { 78 | this.on('click', function(data) { 79 | var x, y, m, p; 80 | if (!self.lpos) return; 81 | if (self.orientation === 'horizontal') { 82 | x = data.x - self.lpos.xi; 83 | m = (self.lpos.xl - self.lpos.xi) - self.iwidth; 84 | p = x / m * 100 | 0; 85 | } else if (self.orientation === 'vertical') { 86 | y = data.y - self.lpos.yi; 87 | m = (self.lpos.yl - self.lpos.yi) - self.iheight; 88 | p = y / m * 100 | 0; 89 | } 90 | self.setProgress(p); 91 | }); 92 | } 93 | } 94 | 95 | ProgressBar.prototype.__proto__ = Input.prototype; 96 | 97 | ProgressBar.prototype.type = 'progress-bar'; 98 | 99 | ProgressBar.prototype.render = function() { 100 | var ret = this._render(); 101 | if (!ret) return; 102 | 103 | var xi = ret.xi 104 | , xl = ret.xl 105 | , yi = ret.yi 106 | , yl = ret.yl 107 | , dattr; 108 | 109 | if (this.border) xi++, yi++, xl--, yl--; 110 | 111 | if (this.orientation === 'horizontal') { 112 | xl = xi + ((xl - xi) * (this.filled / 100)) | 0; 113 | } else if (this.orientation === 'vertical') { 114 | yi = yi + ((yl - yi) - (((yl - yi) * (this.filled / 100)) | 0)); 115 | } 116 | 117 | dattr = this.sattr(this.style.bar); 118 | 119 | this.screen.fillRegion(dattr, this.pch, xi, xl, yi, yl); 120 | 121 | if (this.content) { 122 | var line = this.screen.lines[yi]; 123 | for (var i = 0; i < this.content.length; i++) { 124 | line[xi + i][1] = this.content[i]; 125 | } 126 | line.dirty = true; 127 | } 128 | 129 | return ret; 130 | }; 131 | 132 | ProgressBar.prototype.progress = function(filled) { 133 | this.filled += filled; 134 | if (this.filled < 0) this.filled = 0; 135 | else if (this.filled > 100) this.filled = 100; 136 | if (this.filled === 100) { 137 | this.emit('complete'); 138 | } 139 | this.value = this.filled; 140 | }; 141 | 142 | ProgressBar.prototype.setProgress = function(filled) { 143 | this.filled = 0; 144 | this.progress(filled); 145 | }; 146 | 147 | ProgressBar.prototype.reset = function() { 148 | this.emit('reset'); 149 | this.filled = 0; 150 | this.value = this.filled; 151 | }; 152 | 153 | /** 154 | * Expose 155 | */ 156 | 157 | module.exports = ProgressBar; 158 | -------------------------------------------------------------------------------- /lib/widgets/prompt.js: -------------------------------------------------------------------------------- 1 | /** 2 | * prompt.js - prompt element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Box = require('./box'); 13 | var Button = require('./button'); 14 | var Textbox = require('./textbox'); 15 | 16 | /** 17 | * Prompt 18 | */ 19 | 20 | function Prompt(options) { 21 | if (!(this instanceof Node)) { 22 | return new Prompt(options); 23 | } 24 | 25 | options = options || {}; 26 | 27 | options.hidden = true; 28 | 29 | Box.call(this, options); 30 | 31 | this._.input = new Textbox({ 32 | parent: this, 33 | top: 3, 34 | height: 1, 35 | left: 2, 36 | right: 2, 37 | bg: 'black' 38 | }); 39 | 40 | this._.okay = new Button({ 41 | parent: this, 42 | top: 5, 43 | height: 1, 44 | left: 2, 45 | width: 6, 46 | content: 'Okay', 47 | align: 'center', 48 | bg: 'black', 49 | hoverBg: 'blue', 50 | autoFocus: false, 51 | mouse: true 52 | }); 53 | 54 | this._.cancel = new Button({ 55 | parent: this, 56 | top: 5, 57 | height: 1, 58 | shrink: true, 59 | left: 10, 60 | width: 8, 61 | content: 'Cancel', 62 | align: 'center', 63 | bg: 'black', 64 | hoverBg: 'blue', 65 | autoFocus: false, 66 | mouse: true 67 | }); 68 | } 69 | 70 | Prompt.prototype.__proto__ = Box.prototype; 71 | 72 | Prompt.prototype.type = 'prompt'; 73 | 74 | Prompt.prototype.input = 75 | Prompt.prototype.setInput = 76 | Prompt.prototype.readInput = function(text, value, callback) { 77 | var self = this; 78 | var okay, cancel; 79 | 80 | if (!callback) { 81 | callback = value; 82 | value = ''; 83 | } 84 | 85 | // Keep above: 86 | // var parent = this.parent; 87 | // this.detach(); 88 | // parent.append(this); 89 | 90 | this.show(); 91 | this.setContent(' ' + text); 92 | 93 | this._.input.value = value; 94 | 95 | this.screen.saveFocus(); 96 | 97 | this._.okay.on('press', okay = function() { 98 | self._.input.submit(); 99 | }); 100 | 101 | this._.cancel.on('press', cancel = function() { 102 | self._.input.cancel(); 103 | }); 104 | 105 | this._.input.readInput(function(err, data) { 106 | self.hide(); 107 | self.screen.restoreFocus(); 108 | self._.okay.removeListener('press', okay); 109 | self._.cancel.removeListener('press', cancel); 110 | return callback(err, data); 111 | }); 112 | 113 | this.screen.render(); 114 | }; 115 | 116 | /** 117 | * Expose 118 | */ 119 | 120 | module.exports = Prompt; 121 | -------------------------------------------------------------------------------- /lib/widgets/question.js: -------------------------------------------------------------------------------- 1 | /** 2 | * question.js - question element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Box = require('./box'); 13 | var Button = require('./button'); 14 | 15 | /** 16 | * Question 17 | */ 18 | 19 | function Question(options) { 20 | if (!(this instanceof Node)) { 21 | return new Question(options); 22 | } 23 | 24 | options = options || {}; 25 | options.hidden = true; 26 | 27 | Box.call(this, options); 28 | 29 | this._.okay = new Button({ 30 | screen: this.screen, 31 | parent: this, 32 | top: 2, 33 | height: 1, 34 | left: 2, 35 | width: 6, 36 | content: 'Okay', 37 | align: 'center', 38 | bg: 'black', 39 | hoverBg: 'blue', 40 | autoFocus: false, 41 | mouse: true 42 | }); 43 | 44 | this._.cancel = new Button({ 45 | screen: this.screen, 46 | parent: this, 47 | top: 2, 48 | height: 1, 49 | shrink: true, 50 | left: 10, 51 | width: 8, 52 | content: 'Cancel', 53 | align: 'center', 54 | bg: 'black', 55 | hoverBg: 'blue', 56 | autoFocus: false, 57 | mouse: true 58 | }); 59 | } 60 | 61 | Question.prototype.__proto__ = Box.prototype; 62 | 63 | Question.prototype.type = 'question'; 64 | 65 | Question.prototype.ask = function(text, callback) { 66 | var self = this; 67 | var press, okay, cancel; 68 | 69 | // Keep above: 70 | // var parent = this.parent; 71 | // this.detach(); 72 | // parent.append(this); 73 | 74 | this.show(); 75 | this.setContent(' ' + text); 76 | 77 | this.onScreenEvent('keypress', press = function(ch, key) { 78 | if (key.name === 'mouse') return; 79 | if (key.name !== 'enter' 80 | && key.name !== 'escape' 81 | && key.name !== 'q' 82 | && key.name !== 'y' 83 | && key.name !== 'n') { 84 | return; 85 | } 86 | done(null, key.name === 'enter' || key.name === 'y'); 87 | }); 88 | 89 | this._.okay.on('press', okay = function() { 90 | done(null, true); 91 | }); 92 | 93 | this._.cancel.on('press', cancel = function() { 94 | done(null, false); 95 | }); 96 | 97 | this.screen.saveFocus(); 98 | this.focus(); 99 | 100 | function done(err, data) { 101 | self.hide(); 102 | self.screen.restoreFocus(); 103 | self.removeScreenEvent('keypress', press); 104 | self._.okay.removeListener('press', okay); 105 | self._.cancel.removeListener('press', cancel); 106 | return callback(err, data); 107 | } 108 | 109 | this.screen.render(); 110 | }; 111 | 112 | /** 113 | * Expose 114 | */ 115 | 116 | module.exports = Question; 117 | -------------------------------------------------------------------------------- /lib/widgets/radiobutton.js: -------------------------------------------------------------------------------- 1 | /** 2 | * radiobutton.js - radio button element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Checkbox = require('./checkbox'); 13 | 14 | /** 15 | * RadioButton 16 | */ 17 | 18 | function RadioButton(options) { 19 | var self = this; 20 | 21 | if (!(this instanceof Node)) { 22 | return new RadioButton(options); 23 | } 24 | 25 | options = options || {}; 26 | 27 | Checkbox.call(this, options); 28 | 29 | this.on('check', function() { 30 | var el = self; 31 | while (el = el.parent) { 32 | if (el.type === 'radio-set' 33 | || el.type === 'form') break; 34 | } 35 | el = el || self.parent; 36 | el.forDescendants(function(el) { 37 | if (el.type !== 'radio-button' || el === self) { 38 | return; 39 | } 40 | el.uncheck(); 41 | }); 42 | }); 43 | } 44 | 45 | RadioButton.prototype.__proto__ = Checkbox.prototype; 46 | 47 | RadioButton.prototype.type = 'radio-button'; 48 | 49 | RadioButton.prototype.render = function() { 50 | this.clearPos(true); 51 | this.setContent('(' + (this.checked ? '*' : ' ') + ') ' + this.text, true); 52 | return this._render(); 53 | }; 54 | 55 | RadioButton.prototype.toggle = RadioButton.prototype.check; 56 | 57 | /** 58 | * Expose 59 | */ 60 | 61 | module.exports = RadioButton; 62 | -------------------------------------------------------------------------------- /lib/widgets/radioset.js: -------------------------------------------------------------------------------- 1 | /** 2 | * radioset.js - radio set element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Box = require('./box'); 13 | 14 | /** 15 | * RadioSet 16 | */ 17 | 18 | function RadioSet(options) { 19 | if (!(this instanceof Node)) { 20 | return new RadioSet(options); 21 | } 22 | options = options || {}; 23 | // Possibly inherit parent's style. 24 | // options.style = this.parent.style; 25 | Box.call(this, options); 26 | } 27 | 28 | RadioSet.prototype.__proto__ = Box.prototype; 29 | 30 | RadioSet.prototype.type = 'radio-set'; 31 | 32 | /** 33 | * Expose 34 | */ 35 | 36 | module.exports = RadioSet; 37 | -------------------------------------------------------------------------------- /lib/widgets/scrollabletext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * scrollabletext.js - scrollable text element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var ScrollableBox = require('./scrollablebox'); 13 | 14 | /** 15 | * ScrollableText 16 | */ 17 | 18 | function ScrollableText(options) { 19 | if (!(this instanceof Node)) { 20 | return new ScrollableText(options); 21 | } 22 | options = options || {}; 23 | options.alwaysScroll = true; 24 | ScrollableBox.call(this, options); 25 | } 26 | 27 | ScrollableText.prototype.__proto__ = ScrollableBox.prototype; 28 | 29 | ScrollableText.prototype.type = 'scrollable-text'; 30 | 31 | /** 32 | * Expose 33 | */ 34 | 35 | module.exports = ScrollableText; 36 | -------------------------------------------------------------------------------- /lib/widgets/text.js: -------------------------------------------------------------------------------- 1 | /** 2 | * text.js - text element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Element = require('./element'); 13 | 14 | /** 15 | * Text 16 | */ 17 | 18 | function Text(options) { 19 | if (!(this instanceof Node)) { 20 | return new Text(options); 21 | } 22 | options = options || {}; 23 | options.shrink = true; 24 | Element.call(this, options); 25 | } 26 | 27 | Text.prototype.__proto__ = Element.prototype; 28 | 29 | Text.prototype.type = 'text'; 30 | 31 | /** 32 | * Expose 33 | */ 34 | 35 | module.exports = Text; 36 | -------------------------------------------------------------------------------- /lib/widgets/textbox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * textbox.js - textbox element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var Node = require('./node'); 12 | var Textarea = require('./textarea'); 13 | 14 | /** 15 | * Textbox 16 | */ 17 | 18 | function Textbox(options) { 19 | if (!(this instanceof Node)) { 20 | return new Textbox(options); 21 | } 22 | 23 | options = options || {}; 24 | 25 | options.scrollable = false; 26 | 27 | Textarea.call(this, options); 28 | 29 | this.secret = options.secret; 30 | this.censor = options.censor; 31 | } 32 | 33 | Textbox.prototype.__proto__ = Textarea.prototype; 34 | 35 | Textbox.prototype.type = 'textbox'; 36 | 37 | Textbox.prototype.__olistener = Textbox.prototype._listener; 38 | Textbox.prototype._listener = function(ch, key) { 39 | if (key.name === 'enter') { 40 | this._done(null, this.value); 41 | return; 42 | } 43 | return this.__olistener(ch, key); 44 | }; 45 | 46 | Textbox.prototype.setValue = function(value) { 47 | var visible, val; 48 | if (value == null) { 49 | value = this.value; 50 | } 51 | if (this._value !== value) { 52 | value = value.replace(/\n/g, ''); 53 | this.value = value; 54 | this._value = value; 55 | if (this.secret) { 56 | this.setContent(''); 57 | } else if (this.censor) { 58 | this.setContent(Array(this.value.length + 1).join('*')); 59 | } else { 60 | visible = -(this.width - this.iwidth - 1); 61 | val = this.value.replace(/\t/g, this.screen.tabc); 62 | this.setContent(val.slice(visible)); 63 | } 64 | this._updateCursor(); 65 | } 66 | }; 67 | 68 | Textbox.prototype.submit = function() { 69 | if (!this.__listener) return; 70 | return this.__listener('\r', { name: 'enter' }); 71 | }; 72 | 73 | /** 74 | * Expose 75 | */ 76 | 77 | module.exports = Textbox; 78 | -------------------------------------------------------------------------------- /lib/widgets/video.js: -------------------------------------------------------------------------------- 1 | /** 2 | * video.js - video element for blessed 3 | * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | /** 8 | * Modules 9 | */ 10 | 11 | var cp = require('child_process'); 12 | 13 | var Node = require('./node'); 14 | var Box = require('./box'); 15 | var Terminal = require('./terminal'); 16 | 17 | /** 18 | * Video 19 | */ 20 | 21 | function Video(options) { 22 | var self = this 23 | , shell 24 | , args; 25 | 26 | if (!(this instanceof Node)) { 27 | return new Video(options); 28 | } 29 | 30 | options = options || {}; 31 | 32 | Box.call(this, options); 33 | 34 | if (this.exists('mplayer')) { 35 | shell = 'mplayer'; 36 | args = ['-vo', 'caca', '-quiet', options.file]; 37 | } else if (this.exists('mpv')) { 38 | shell = 'mpv'; 39 | args = ['--vo', 'caca', '--really-quiet', options.file]; 40 | } else { 41 | this.parseTags = true; 42 | this.setContent('{red-fg}{bold}Error:{/bold}' 43 | + ' mplayer or mpv not installed.{/red-fg}'); 44 | return this; 45 | } 46 | 47 | var opts = { 48 | parent: this, 49 | left: 0, 50 | top: 0, 51 | width: this.width - this.iwidth, 52 | height: this.height - this.iheight, 53 | shell: shell, 54 | args: args.slice() 55 | }; 56 | 57 | this.now = Date.now() / 1000 | 0; 58 | this.start = opts.start || 0; 59 | if (this.start) { 60 | if (shell === 'mplayer') { 61 | opts.args.unshift('-ss', this.start + ''); 62 | } else if (shell === 'mpv') { 63 | opts.args.unshift('--start', this.start + ''); 64 | } 65 | } 66 | 67 | var DISPLAY = process.env.DISPLAY; 68 | delete process.env.DISPLAY; 69 | this.tty = new Terminal(opts); 70 | process.env.DISPLAY = DISPLAY; 71 | 72 | this.on('click', function() { 73 | self.tty.pty.write('p'); 74 | }); 75 | 76 | // mplayer/mpv cannot resize itself in the terminal, so we have 77 | // to restart it at the correct start time. 78 | this.on('resize', function() { 79 | self.tty.destroy(); 80 | 81 | var opts = { 82 | parent: self, 83 | left: 0, 84 | top: 0, 85 | width: self.width - self.iwidth, 86 | height: self.height - self.iheight, 87 | shell: shell, 88 | args: args.slice() 89 | }; 90 | 91 | var watched = (Date.now() / 1000 | 0) - self.now; 92 | self.now = Date.now() / 1000 | 0; 93 | self.start += watched; 94 | if (shell === 'mplayer') { 95 | opts.args.unshift('-ss', self.start + ''); 96 | } else if (shell === 'mpv') { 97 | opts.args.unshift('--start', self.start + ''); 98 | } 99 | 100 | var DISPLAY = process.env.DISPLAY; 101 | delete process.env.DISPLAY; 102 | self.tty = new Terminal(opts); 103 | process.env.DISPLAY = DISPLAY; 104 | self.screen.render(); 105 | }); 106 | } 107 | 108 | Video.prototype.__proto__ = Box.prototype; 109 | 110 | Video.prototype.type = 'video'; 111 | 112 | Video.prototype.exists = function(program) { 113 | try { 114 | return !!+cp.execSync('type ' 115 | + program + ' > /dev/null 2> /dev/null' 116 | + ' && echo 1', { encoding: 'utf8' }).trim(); 117 | } catch (e) { 118 | return false; 119 | } 120 | }; 121 | 122 | /** 123 | * Expose 124 | */ 125 | 126 | module.exports = Video; 127 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blessed", 3 | "description": "A high-level terminal interface library for node.js.", 4 | "author": "Christopher Jeffrey", 5 | "version": "0.1.81", 6 | "license": "MIT", 7 | "main": "./lib/blessed.js", 8 | "bin": "./bin/tput.js", 9 | "preferGlobal": false, 10 | "repository": "git://github.com/chjj/blessed.git", 11 | "homepage": "https://github.com/chjj/blessed", 12 | "bugs": { "url": "http://github.com/chjj/blessed/issues" }, 13 | "keywords": ["curses", "tui", "tput", "terminfo", "termcap"], 14 | "tags": ["curses", "tui", "tput", "terminfo", "termcap"], 15 | "engines": { 16 | "node": ">= 0.8.0" 17 | }, 18 | "browserify": { 19 | "transform": ["./browser/transform.js"] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/git.diff: -------------------------------------------------------------------------------- 1 | diff --git a/lib/widget.js b/lib/widget.js 2 | index 8785046..4bfefd3 100644 3 | --- a/lib/widget.js 4 | +++ b/lib/widget.js 5 | @@ -511,6 +511,9 @@ Element.prototype.__defineGetter__('left', function() { 6 | }); 7 |  8 | Element.prototype.__defineGetter__('right', function() { 9 | + //if (this.options.right == null && this.options.left != null) { 10 | + // return this.screen.cols - (this.left + this.width); 11 | + //} 12 | return (this.parent.right || 0) + this.position.right; 13 | }); 14 |  15 | @@ -532,6 +535,9 @@ Element.prototype.__defineGetter__('top', function() { 16 | }); 17 |  18 | Element.prototype.__defineGetter__('bottom', function() { 19 | + //if (this.options.bottom == null && this.options.top != null) { 20 | + // return this.screen.rows - (this.top + this.height); 21 | + //} 22 | return (this.parent.bottom || 0) + this.position.bottom; 23 | }); 24 |  25 | @@ -671,7 +677,20 @@ Box.prototype.render = function(stop) { 26 | var cb = this.childBase 27 | , xxl = xl - (this.border ? 1 : 0) 28 | , xxi; 29 | - while (cb--) for (xxi = xi + (this.border ? 1 : 0); xxi < xxl; xxi++) ci++; 30 | + while (cb--) { 31 | + for (xxi = xi + (this.border ? 1 : 0); xxi < xxl; xxi++) { 32 | + if (this.content[ci] === '\n' || this.content[ci] === '\r') { 33 | + ci++; 34 | + if (!cb) break; 35 | + cb--; 36 | + } else if (this.content[ci] === '\x1b') { 37 | + for (; ci < this.content.length; ci++) { 38 | + if (this.content[ci] === 'm') break; 39 | + } 40 | + } 41 | + ci++; 42 | + } 43 | + } 44 | } 45 |  46 | var ret = { 47 | @@ -683,6 +702,8 @@ Box.prototype.render = function(stop) { 48 |  49 | if (stop) return ret; 50 |  51 | + var lastEscape, hasEscapes, c; 52 | + 53 | for (; yi < yl; yi++) { 54 | if (!lines[yi]) break; 55 | for (xi = this.left; xi < xl; xi++) { 56 | @@ -708,22 +729,52 @@ Box.prototype.render = function(stop) { 57 | } else { 58 | attr = ((this.bold << 18) + (this.underline << 18)) | (this.fg << 9) | this.bg; 59 | ch = this.content[ci++] || ' '; 60 | + 61 | + // Handle escape codes. 62 | + // NOTE: We could also change around `attr`, that might be cleaner. 63 | + // NOTE: Currently, this will not work with newline handling. 64 | + if (lastEscape) { 65 | + ch = lastEscape + ch; 66 | + lastEscape = ''; 67 | + } 68 | + if (ch === '\x1b') { 69 | + hasEscapes = true; 70 | + if (c = /^\x1b\[\d+(?:;\d+)*m/.exec(this.content.substring(ci - 1))) { 71 | + ci += c[0].length - 1; 72 | + if (!this.content[c[0].length]) { 73 | + // Problem: No character to put here 74 | + // needs to wrap around below. 75 | + lastEscape = c[0]; 76 | + ch = ' '; 77 | + } else { 78 | + ch = c[0] + this.content[ci]; 79 | + } 80 | + ci++; 81 | + } 82 | + } 83 | + if (hasEscapes && xi === xl - 1) { 84 | + ch += '\x1b[m'; 85 | + } 86 | } 87 |  88 | - // TODO: Allow newlines. 89 | - //if (ch === '\n' || ch === '\r') { 90 | - // ch = ' '; 91 | - // xl = xl - 1 - (this.border ? 1 : 0); 92 | - // for (; xi < xl; xi++) { 93 | - // cell = lines[yi][xi]; 94 | - // if (!cell) break; 95 | - // if (attr !== cell[0] || ch !== cell[1]) { 96 | - // lines[yi][xi][0] = attr; 97 | - // lines[yi][xi][1] = ch; 98 | - // lines[yi].dirty = true; 99 | - // } 100 | - // } 101 | - //} 102 | + // Handle newlines. 103 | + if (ch === '\n' || ch === '\r') { 104 | + ch = ' '; 105 | + if (hasEscapes) ch += '\x1b[m'; 106 | + var xxl = xl - (this.border ? 1 : 0); 107 | + for (; xi < xxl; xi++) { 108 | + attr = ((this.bold << 18) + (this.underline << 18)) | (this.fg << 9) | this.bg; 109 | + cell = lines[yi][xi]; 110 | + if (!cell) break; 111 | + if (attr !== cell[0] || ch !== cell[1]) { 112 | + lines[yi][xi][0] = attr; 113 | + lines[yi][xi][1] = ch; 114 | + lines[yi].dirty = true; 115 | + } 116 | + } 117 | + if (this.border) xi--; 118 | + continue; 119 | + } 120 |  121 | if (attr !== cell[0] || ch !== cell[1]) { 122 | lines[yi][xi][0] = attr; 123 | @@ -958,6 +1009,7 @@ List.prototype.__proto__ = ScrollableBox.prototype; 124 | List.prototype.add = function(item) { 125 | var self = this; 126 |  127 | + // TODO: Use box here and get rid of text. 128 | var item = new Text({ 129 | screen: this.screen, 130 | parent: this, 131 | diff --git a/test/widget.js b/test/widget.js 132 | index a392a0e..1e62e8c 100644 133 | --- a/test/widget.js 134 | +++ b/test/widget.js 135 | @@ -136,7 +136,7 @@ var progress = new blessed.ProgressBar({ 136 |  137 | screen.append(progress); 138 |  139 | -var lorem = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; 140 | +var lorem = 'Lorem ipsum \x1b[41mdolor sit amet, \nconsectetur adipisicing elit, \x1b[43msed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; 141 |  142 | var stext = new blessed.ScrollableText({ 143 | screen: screen, 144 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../'), 2 | screen = blessed.screen(); 3 | 4 | console.log(blessed.helpers.parseTags('{red-fg}This should be red.{/red-fg}')); 5 | console.log(blessed.helpers.parseTags('{green-bg}This should have a green background.{/green-bg}')); 6 | -------------------------------------------------------------------------------- /test/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /test/lorem.txt: -------------------------------------------------------------------------------- 1 | Non eram nescius Brute cum quae summis ingeniis exquisitaque doctrina philosophi Graeco sermone tractavissent ea Latinis litteris mandaremus fore ut hic noster labor in varias reprehensiones incurreret nam quibusdam et iis quidem non admodum indoctis totum hoc displicet philosophari quidam autem non tam id reprehendunt si remissius agatur sed tantum studium tamque multam operam ponendam in eo non arbitrantur erunt etiam et ii quidem eruditi Graecis litteris contemnentes Latinas qui se dicant in Graecis legendis operam malle consumere postremo aliquos futuros suspicor qui me ad alias litteras vocent genus hoc scribendi etsi sit elegans personae tamen et dignitatis esse negent Contra quos omnis dicendum breviter existimo Quamquam philosophiae quidem vituperatoribus satis responsum est eo libro quo a nobis philosophia defensa et collaudata est cum esset accusata et vituperata ab Hortensio qui liber cum et tibi probatus videretur et iis quos ego posse iudicare arbitrarer plura suscepi veritus ne movere hominum studia viderer retinere non posse Qui autem si maxime hoc placeat moderatius tamen id volunt fieri difficilem quandam temperantiam postulant in eo quod semel admissum coerceri reprimique non potest ut propemodum iustioribus utamur illis qui omnino avocent a philosophia quam his qui rebus infinitis modum constituant in reque eo meliore quo maior sit mediocritatem desiderent Sive enim ad sapientiam perveniri potest non paranda nobis solum ea sed fruenda etiam sapientia est sive hoc difficile est tamen nec modus est ullus investigandi veri nisi inveneris et quaerendi defatigatio turpis est cum id quod quaeritur sit pulcherrimum etenim si delectamur cum scribimus quis est tam invidus qui ab eo nos abducat sin laboramus quis est qui alienae modum statuat industriae nam ut Terentianus Chremes non inhumanus qui novum vicinum non vult fodere aut arare aut aliquid ferre denique non enim illum ab industria sed ab inliberali labore deterret sic isti curiosi quos offendit noster minime nobis iniucundus labor Iis igitur est difficilius satis facere qui se Latina scripta dicunt contemnere in quibus hoc primum est in quo admirer cur in gravissimis rebus non delectet eos sermo patrius cum idem fabellas Latinas ad verbum e Graecis expressas non inviti legant quis enim tam inimicus paene nomini Romano est qui Ennii Medeam aut Antiopam Pacuvii spernat aut reiciat quod se isdem Euripidis fabulis delectari dicat Latinas litteras oderit Quid si nos non interpretum fungimur munere sed tuemur ea quae dicta sunt ab iis quos probamus eisque nostrum iudicium et nostrum scribendi ordinem adiungimus quid habent cur Graeca anteponant iis quae et splendide dicta sint neque sint conversa de Graecis nam si dicent ab illis has res esse tractatas ne ipsos quidem Graecos est cur tam multos legant quam legendi sunt quid enim est a Chrysippo praetermissum in Stoicis legimus tamen Diogenem Antipatrum Mnesarchum Panaetium multos alios in primisque familiarem nostrum Posidonium quid Theophrastus mediocriterne delectat cum tractat locos ab Aristotele ante tractatos quid Epicurei num desistunt de isdem de quibus et ab Epicuro scriptum est et ab antiquis ad arbitrium suum scribere quodsi Graeci leguntur a Graecis isdem de rebus alia ratione compositis quid est cur nostri a nostris non legantur -------------------------------------------------------------------------------- /test/program-mouse.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var blessed = require('../') 4 | , util = require('util') 5 | , program; 6 | 7 | program = blessed.program({ 8 | dump: __dirname + '/logs/mouse.log' 9 | }); 10 | 11 | // program.setMouse({ 12 | // allMotion: true, 13 | // //utfMouse: true 14 | // urxvtMouse: true 15 | // }, true); 16 | 17 | program.alternateBuffer(); 18 | program.enableMouse(); 19 | program.hideCursor(); 20 | 21 | program.setMouse({ sendFocus: true }, true); 22 | //program._currentMouse.sendFocus = true; 23 | //program.enableMouse(program._currentMouse); 24 | //program.write('\x1b[?1004h'); 25 | 26 | program.on('mouse', function(data) { 27 | program.cup(data.y, data.x); 28 | program.write(' ', 'blue bg'); 29 | program.cup(0, 0); 30 | program.write(util.inspect(data)); 31 | }); 32 | 33 | program.on('resize', function(data) { 34 | setTimeout(function() { 35 | program.clear(); 36 | program.cup(0, 0); 37 | program.write(util.inspect({ cols: program.cols, rows: program.rows })); 38 | }, 200); 39 | }); 40 | 41 | process.on('SIGWINCH', function(data) { 42 | setTimeout(function() { 43 | program.cup(1, 0); 44 | program.write(util.inspect({ winch: true, cols: program.cols, rows: program.rows })); 45 | }, 200); 46 | }); 47 | 48 | program.on('focus', function(data) { 49 | program.clear(); 50 | program.cup(0, 0); 51 | program.write('FOCUSIN'); 52 | }); 53 | 54 | program.on('blur', function(data) { 55 | program.clear(); 56 | program.cup(0, 0); 57 | program.write('FOCUSOUT'); 58 | }); 59 | 60 | program.key(['q', 'escape', 'C-c'], function() { 61 | program.showCursor(); 62 | program.disableMouse(); 63 | program.normalBuffer(); 64 | process.exit(0); 65 | }); 66 | 67 | program.on('keypress', function(ch, data) { 68 | if (data.name === 'mouse') return; 69 | program.clear(); 70 | program.cup(0, 0); 71 | program.write(util.inspect(data)); 72 | }); 73 | 74 | // program.getCursor(function(err, data) { 75 | // program.write(util.inspect(data)); 76 | // }); 77 | 78 | // program.manipulateWindow(18, function(err, data) { 79 | // program.write(util.inspect(data)); 80 | // }); 81 | -------------------------------------------------------------------------------- /test/tail.js: -------------------------------------------------------------------------------- 1 | // `tail -f` a file. 2 | module.exports = function(file) { 3 | var self = this 4 | , fs = require('fs') 5 | , StringDecoder = require('string_decoder').StringDecoder 6 | , decode = new StringDecoder('utf8') 7 | , buffer = new Buffer(64 * 1024) 8 | , Stream = require('stream').Stream 9 | , s = new Stream 10 | , buff = '' 11 | , pos = 0; 12 | 13 | s.readable = true; 14 | s.destroy = function() { 15 | s.destroyed = true; 16 | s.emit('end'); 17 | s.emit('close'); 18 | }; 19 | 20 | fs.open(file, 'a+', 0644, function(err, fd) { 21 | if (err) { 22 | s.emit('error', err); 23 | s.destroy(); 24 | return; 25 | } 26 | 27 | (function read() { 28 | if (s.destroyed) { 29 | fs.close(fd); 30 | return; 31 | } 32 | 33 | return fs.read(fd, buffer, 0, buffer.length, pos, function(err, bytes) { 34 | if (err) { 35 | s.emit('error', err); 36 | s.destroy(); 37 | return; 38 | } 39 | 40 | if (!bytes) { 41 | if (buff) { 42 | stream.emit('line', buff); 43 | buff = ''; 44 | } 45 | return setTimeout(read, 1000); 46 | } 47 | 48 | var data = decode.write(buffer.slice(0, bytes)); 49 | 50 | s.emit('data', data); 51 | 52 | var data = (buff + data).split(/\n+/) 53 | , l = data.length - 1 54 | , i = 0; 55 | 56 | for (; i < l; i++) { 57 | s.emit('line', data[i]); 58 | } 59 | 60 | buff = data[l]; 61 | 62 | pos += bytes; 63 | 64 | return read(); 65 | }); 66 | })(); 67 | }); 68 | 69 | return s; 70 | }; 71 | -------------------------------------------------------------------------------- /test/test-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chjj/blessed/eab243fc7ad27f1d2932db6134f7382825ee3488/test/test-image.png -------------------------------------------------------------------------------- /test/tput: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | term="$1" 4 | 5 | dir=$(dirname $(readlink -f "$0")) 6 | cd "$dir/.." 7 | 8 | if test -z "$term"; then 9 | term="$dir/../usr/xterm" 10 | set -- "$term" "$@" 11 | fi 12 | 13 | node test/tput.js "$@" | grep -v 'dir:\|file:' | tee test/logs/terminfo.log 14 | out=$(git diff --color=always --no-index test/terminfo test/logs/terminfo.log) 15 | 16 | if test -n "$out"; then 17 | echo "$out" | less -c -R 18 | else 19 | echo 'Files are identical.' 20 | fi 21 | -------------------------------------------------------------------------------- /test/tput.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tput for node.js 3 | * Copyright (c) 2013, Christopher Jeffrey (MIT License) 4 | * https://github.com/chjj/blessed 5 | */ 6 | 7 | // Compile xterm terminfo/termcap: 8 | // $ tic -a -I -1 usr/xterm.terminfo 9 | // $ tic -a -C -U usr/xterm.termcap 10 | 11 | // Compile xterm terminfo/termcap: 12 | // $ tic -a -1 usr/xterm.terminfo 13 | // $ tic -a -1 usr/xterm.terminfo && ls ~/.terminfo 14 | // $ tic -a -1 -o usr usr/xterm.terminfo && mv usr/x/xterm usr/ && rm -rf usr/v usr/x 15 | // $ tic -a -1 -o usr usr/xterm.terminfo && mv usr/x/xterm-256color usr/ && rm -rf usr/v usr/x 16 | 17 | // Check tput output: 18 | // $ node test/tput.js xterm | tee out 19 | // $ node test/tput.js xterm --ifile usr/xterm | tee out 20 | // $ node test/tput.js xterm-256color --ifile usr/xterm-256color | tee out 21 | // $ node test/tput.js vt102 --termcap | tee out 22 | // $ node test/tput.js xterm --termcap --cfile usr/xterm.termcap | tee out 23 | // $ node test/tput.js xterm --iprefix ~/.terminfo | tee out 24 | // $ node test/tput.js xterm-256color --ifile ~/.terminfo/x/xterm-256color | tee out 25 | // $ cdiff test/terminfo out 26 | 27 | var blessed = require('../'); 28 | 29 | // Simple argument parser 30 | // Copyright (c) 2012, Christopher Jeffrey (MIT License) 31 | 32 | function parseArg() { 33 | var argv = process.argv.slice(2) 34 | , options = []; 35 | 36 | function getarg() { 37 | var arg = argv.shift(); 38 | 39 | if (arg.indexOf('--') === 0) { 40 | // e.g. --opt 41 | arg = arg.split('='); 42 | if (arg.length > 1) { 43 | // e.g. --opt=val 44 | argv.unshift(arg.slice(1).join('=')); 45 | } 46 | arg = arg[0]; 47 | } else if (arg[0] === '-') { 48 | if (arg.length > 2) { 49 | // e.g. -abc 50 | argv = arg.substring(1).split('').map(function(ch) { 51 | return '-' + ch; 52 | }).concat(argv); 53 | arg = argv.shift(); 54 | } else { 55 | // e.g. -a 56 | } 57 | } else { 58 | // e.g. foo 59 | } 60 | 61 | return arg; 62 | } 63 | 64 | while (argv.length) { 65 | arg = getarg(); 66 | if (arg.indexOf('-') === 0) { 67 | arg = arg.replace(/^--?/, ''); 68 | if (argv[0] && argv[0].indexOf('-') !== 0) { 69 | options[arg] = argv.shift(); 70 | } else { 71 | options[arg] = true; 72 | } 73 | } else { 74 | options.push(arg); 75 | } 76 | } 77 | 78 | return options; 79 | } 80 | 81 | var argv = parseArg(); 82 | 83 | var tput = blessed.tput({ 84 | terminal: argv[0] !== 'all' && argv[0] !== 'rand' 85 | ? argv[0] || __dirname + '/../usr/xterm' 86 | : null, 87 | extended: true, 88 | debug: true, 89 | termcap: argv.termcap, 90 | terminfoFile: argv.i || argv.ifile, 91 | terminfoPrefix: argv.p || argv.iprefix, 92 | termcapFile: argv.c || argv.cfile 93 | }); 94 | 95 | if (argv[0] === 'all') { 96 | var rl = require('readline').createInterface({ 97 | input: process.stdin, 98 | output: process.stdout 99 | }); 100 | 101 | var text = '\x1b[31mWARNING:\x1b[m ' 102 | + 'This will compile every single terminfo file on your disk.\n' 103 | + 'It will probably use a lot of CPU.\n' 104 | + 'Do you wish to proceed? (Y/n) '; 105 | 106 | rl.question(text, function(result) { 107 | result = result.trim().toLowerCase(); 108 | if (result !== 'y') return process.exit(0); 109 | console.log('\x1b[32m(You bet your ass I wish to proceed.)\x1b[m'); 110 | blessed.tput.print( 111 | '$<1000/>.$<1000/>.$<1000/>.$<100/>Let\'s go...', 112 | process.stdout.write.bind(process.stdout), 113 | function() { 114 | tput.compileAll(argv[1]); 115 | process.exit(0); 116 | } 117 | ); 118 | }); 119 | 120 | return; 121 | } 122 | 123 | if (argv[0] === 'rand') { 124 | var terms = tput.getAll() 125 | , term; 126 | 127 | term = terms[(terms.length - 1) * Math.random() | 0]; 128 | 129 | console.log('Compiling ' + term + '...'); 130 | tput.compileTerminfo(term); 131 | console.log('Compiled ' + term + '.'); 132 | 133 | return; 134 | } 135 | 136 | // console.log('Max colors: %d.', tput.colors); 137 | 138 | // console.log(tput.strings.acs_chars.split('').map(function(ch) { return ch.charCodeAt(0); })); 139 | // console.log(JSON.stringify(tput.strings.acs_chars)); 140 | 141 | // process.stdout.write(blessed.tput.sprintf('%-10s\n', 'hello')); 142 | 143 | // tput._compile({ name: 'xterm' }, 'set_attributes', 144 | // '%?%p9%t\u001b(0%e\u001b(B%;\u001b[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m'); 145 | 146 | // console.log(tput.setaf(4) + 'foo' + tput.sgr0()); 147 | // console.log(tput.setaf(4) + 'foo' + tput.sgr(0)); 148 | 149 | // tput.padding = true; 150 | // tput._print('hello$<1000/>world', console.log, function() { 151 | // tput._print('$<1000/>foo$<1000/>bar', console.log, process.exit); 152 | // }); 153 | -------------------------------------------------------------------------------- /test/widget-autopad.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/autopad.log', 6 | smartCSR: true, 7 | autoPadding: true, 8 | warnings: true 9 | }); 10 | 11 | var box1 = blessed.box({ 12 | parent: screen, 13 | top: 'center', 14 | left: 'center', 15 | width: 20, 16 | height: 10, 17 | border: 'line' 18 | }); 19 | 20 | var box2 = blessed.box({ 21 | parent: box1, 22 | top: 0, 23 | left: 0, 24 | width: 10, 25 | height: 5, 26 | border: 'line' 27 | }); 28 | 29 | screen.key('q', function() { 30 | return screen.destroy(); 31 | }); 32 | 33 | screen.render(); 34 | -------------------------------------------------------------------------------- /test/widget-bigtext.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/bigtext.log', 6 | smartCSR: true, 7 | warnings: true 8 | }); 9 | 10 | var box = blessed.bigtext({ 11 | parent: screen, 12 | content: 'Hello', 13 | shrink: true, 14 | width: '80%', 15 | // height: '80%', 16 | height: 'shrink', 17 | // width: 'shrink', 18 | border: 'line', 19 | fch: ' ', 20 | ch: '\u2592', 21 | style: { 22 | fg: 'red', 23 | bg: 'blue', 24 | bold: false 25 | } 26 | }); 27 | 28 | screen.key('q', function() { 29 | return screen.destroy(); 30 | }); 31 | 32 | screen.render(); 33 | 34 | -------------------------------------------------------------------------------- /test/widget-csr.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/csr.log', 6 | smartCSR: true, 7 | warnings: true 8 | }); 9 | 10 | var lorem = require('fs').readFileSync(__dirname + '/git.diff', 'utf8'); 11 | 12 | var cleanSides = screen.cleanSides; 13 | function expectClean(value) { 14 | screen.cleanSides = function(el) { 15 | var ret = cleanSides.apply(this, arguments); 16 | if (ret !== value) { 17 | throw new Error('Failed. Expected ' 18 | + value + ' from cleanSides. Got ' 19 | + ret + '.'); 20 | } 21 | return ret; 22 | }; 23 | } 24 | 25 | /* 26 | blessed.box({ 27 | parent: screen, 28 | left: 0, 29 | top: 'center', 30 | width: '50%', 31 | height: 2, 32 | style: { 33 | bg: 'green' 34 | }, 35 | content: 'This will disallow CSR.' 36 | }); 37 | expectClean(false); 38 | */ 39 | 40 | var btext = blessed.box({ 41 | parent: screen, 42 | left: 'center', 43 | top: 'center', 44 | width: '80%', 45 | height: '80%', 46 | style: { 47 | bg: 'green' 48 | }, 49 | border: 'line', 50 | content: 'CSR should still work.' 51 | }); 52 | btext._oscroll = btext.scroll; 53 | btext.scroll = function(offset, always) { 54 | expectClean(true); 55 | return btext._oscroll(offset, always); 56 | }; 57 | 58 | var text = blessed.scrollabletext({ 59 | parent: screen, 60 | content: lorem, 61 | border: 'line', 62 | left: 'center', 63 | top: 'center', 64 | draggable: true, 65 | width: '50%', 66 | height: '50%', 67 | mouse: true, 68 | keys: true, 69 | vi: true 70 | }); 71 | 72 | text._oscroll = text.scroll; 73 | text.scroll = function(offset, always) { 74 | var el = this; 75 | var value = true; 76 | if (el.left < 0) value = true; 77 | if (el.top < 0) value = false; 78 | if (el.left + el.width > screen.width) value = true; 79 | if (el.top + el.height > screen.height) value = false; 80 | expectClean(value); 81 | return text._oscroll(offset, always); 82 | }; 83 | 84 | text.focus(); 85 | 86 | screen.key('q', function() { 87 | return screen.destroy(); 88 | }); 89 | 90 | screen.render(); 91 | -------------------------------------------------------------------------------- /test/widget-dock-noborder.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/dock.log', 6 | smartCSR: true, 7 | dockBorders: true, 8 | warnings: true 9 | }); 10 | 11 | blessed.box({ 12 | parent: screen, 13 | left: -1, 14 | top: -1, 15 | width: '50%+1', 16 | height: '50%+1', 17 | border: 'line', 18 | content: 'Foo' 19 | }); 20 | 21 | blessed.box({ 22 | parent: screen, 23 | left: '50%-1', 24 | top: -1, 25 | width: '50%+3', 26 | height: '50%+1', 27 | content: 'Bar', 28 | border: 'line' 29 | }); 30 | 31 | blessed.box({ 32 | parent: screen, 33 | left: -1, 34 | top: '50%-1', 35 | width: '50%+1', 36 | height: '50%+3', 37 | border: 'line', 38 | content: 'Foo' 39 | }); 40 | 41 | blessed.listtable({ 42 | parent: screen, 43 | left: '50%-1', 44 | top: '50%-1', 45 | width: '50%+3', 46 | height: '50%+3', 47 | border: 'line', 48 | align: 'center', 49 | tags: true, 50 | keys: true, 51 | vi: true, 52 | mouse: true, 53 | style: { 54 | header: { 55 | fg: 'blue', 56 | bold: true 57 | }, 58 | cell: { 59 | fg: 'magenta', 60 | selected: { 61 | bg: 'blue' 62 | } 63 | } 64 | }, 65 | data: [ 66 | [ 'Animals', 'Foods', 'Times', 'Numbers' ], 67 | [ 'Elephant', 'Apple', '1:00am', 'One' ], 68 | [ 'Bird', 'Orange', '2:15pm', 'Two' ], 69 | [ 'T-Rex', 'Taco', '8:45am', 'Three' ], 70 | [ 'Mouse', 'Cheese', '9:05am', 'Four' ] 71 | ] 72 | }).focus(); 73 | 74 | // blessed.box({ 75 | // parent: screen, 76 | // left: '50%-1', 77 | // top: '50%-1', 78 | // width: '50%+1', 79 | // height: '50%+1', 80 | // border: 'line', 81 | // content: 'Bar' 82 | // }); 83 | 84 | screen.key('q', function() { 85 | return screen.destroy(); 86 | }); 87 | 88 | screen.render(); 89 | -------------------------------------------------------------------------------- /test/widget-dock.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/dock.log', 6 | smartCSR: true, 7 | dockBorders: true, 8 | warnings: true 9 | }); 10 | 11 | var topleft = blessed.box({ 12 | parent: screen, 13 | left: 0, 14 | top: 0, 15 | width: '50%', 16 | height: '50%', 17 | border: { 18 | type: 'line', 19 | left: false, 20 | top: false, 21 | right: true, 22 | bottom: false 23 | }, 24 | // border: 'line', 25 | content: 'Foo' 26 | }); 27 | 28 | var topright = blessed.box({ 29 | parent: screen, 30 | left: '50%-1', 31 | top: 0, 32 | width: '50%+1', 33 | height: '50%', 34 | border: { 35 | type: 'line', 36 | left: true, 37 | top: false, 38 | right: false, 39 | bottom: false 40 | }, 41 | // border: 'line', 42 | content: 'Bar' 43 | }); 44 | 45 | var bottomleft = blessed.box({ 46 | parent: screen, 47 | left: 0, 48 | top: '50%-1', 49 | width: '50%', 50 | height: '50%+1', 51 | border: { 52 | type: 'line', 53 | left: false, 54 | top: true, 55 | right: false, 56 | bottom: false 57 | }, 58 | border: 'line', 59 | content: 'Foo' 60 | }); 61 | 62 | var bottomright = blessed.listtable({ 63 | parent: screen, 64 | left: '50%-1', 65 | top: '50%-1', 66 | width: '50%+1', 67 | height: '50%+1', 68 | border: { 69 | type: 'line', 70 | left: true, 71 | top: true, 72 | right: false, 73 | bottom: false 74 | }, 75 | // border: 'line', 76 | align: 'center', 77 | tags: true, 78 | keys: true, 79 | vi: true, 80 | mouse: true, 81 | style: { 82 | header: { 83 | fg: 'blue', 84 | bold: true 85 | }, 86 | cell: { 87 | fg: 'magenta', 88 | selected: { 89 | bg: 'blue' 90 | } 91 | } 92 | }, 93 | data: [ 94 | [ 'Animals', 'Foods', 'Times', 'Numbers' ], 95 | [ 'Elephant', 'Apple', '1:00am', 'One' ], 96 | [ 'Bird', 'Orange', '2:15pm', 'Two' ], 97 | [ 'T-Rex', 'Taco', '8:45am', 'Three' ], 98 | [ 'Mouse', 'Cheese', '9:05am', 'Four' ] 99 | ] 100 | }); 101 | 102 | bottomright.focus(); 103 | 104 | var over = blessed.box({ 105 | parent: screen, 106 | left: 'center', 107 | top: 'center', 108 | width: '50%', 109 | height: '50%', 110 | draggable: true, 111 | border: { 112 | type: 'line', 113 | left: false, 114 | top: true, 115 | right: true, 116 | bottom: true 117 | }, 118 | content: 'Drag Me' 119 | }); 120 | 121 | screen.key('q', function() { 122 | return screen.destroy(); 123 | }); 124 | 125 | screen.render(); 126 | -------------------------------------------------------------------------------- /test/widget-exit.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../'); 2 | 3 | var screen = blessed.screen({ 4 | dump: __dirname + '/logs/exit.log', 5 | smartCSR: true, 6 | autoPadding: true, 7 | warnings: true, 8 | ignoreLocked: ['C-q'] 9 | }); 10 | 11 | var box = blessed.prompt({ 12 | parent: screen, 13 | left: 'center', 14 | top: 'center', 15 | width: '70%', 16 | height: 'shrink', 17 | border: 'line' 18 | }); 19 | 20 | screen.render(); 21 | 22 | box.input('Input: ', '', function(err, data) { 23 | screen.destroy(); 24 | if (process.argv[2] === 'resume') { 25 | process.stdin.resume(); 26 | } else if (process.argv[2] === 'end') { 27 | process.stdin.setRawMode(false); 28 | process.stdin.end(); 29 | } 30 | if (err) throw err; 31 | console.log('Input: ' + data); 32 | }); 33 | 34 | screen.key('C-q', function(ch, key) { 35 | return screen.destroy(); 36 | }); 37 | -------------------------------------------------------------------------------- /test/widget-file.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../'); 2 | 3 | var screen = blessed.screen({ 4 | tput: true, 5 | smartCSR: true, 6 | dump: __dirname + '/logs/file.log', 7 | warnings: true 8 | }); 9 | 10 | var fm = blessed.filemanager({ 11 | parent: screen, 12 | border: 'line', 13 | style: { 14 | selected: { 15 | bg: 'blue' 16 | } 17 | }, 18 | height: 'half', 19 | width: 'half', 20 | top: 'center', 21 | left: 'center', 22 | label: ' {blue-fg}%path{/blue-fg} ', 23 | cwd: process.env.HOME, 24 | keys: true, 25 | vi: true, 26 | scrollbar: { 27 | bg: 'white', 28 | ch: ' ' 29 | } 30 | }); 31 | 32 | var box = blessed.box({ 33 | parent: screen, 34 | style: { 35 | bg: 'green' 36 | }, 37 | border: 'line', 38 | height: 'half', 39 | width: 'half', 40 | top: 'center', 41 | left: 'center', 42 | hidden: true 43 | }); 44 | 45 | fm.refresh(); 46 | 47 | screen.render(); 48 | 49 | screen.key('q', function() { 50 | screen.destroy(); 51 | }); 52 | 53 | screen.key(['s', 'p'], function() { 54 | fm.hide(); 55 | screen.render(); 56 | setTimeout(function() { 57 | fm.pick(function(err, file) { 58 | box.show(); 59 | box.setContent(err ? err + '' : file); 60 | screen.render(); 61 | setTimeout(function() { 62 | box.hide(); 63 | fm.reset(function() { 64 | fm.show(); 65 | screen.render(); 66 | }); 67 | }, 2000); 68 | }); 69 | }, 2000); 70 | }); 71 | -------------------------------------------------------------------------------- /test/widget-form.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/form.log', 6 | warnings: true 7 | }); 8 | 9 | var form = blessed.form({ 10 | parent: screen, 11 | mouse: true, 12 | keys: true, 13 | vi: true, 14 | left: 0, 15 | top: 0, 16 | width: '100%', 17 | //height: 12, 18 | style: { 19 | bg: 'green', 20 | // border: { 21 | // inverse: true 22 | // }, 23 | scrollbar: { 24 | inverse: true 25 | } 26 | }, 27 | content: 'foobar', 28 | scrollable: true, 29 | // border: { 30 | // type: 'ch', 31 | // ch: ' ' 32 | // }, 33 | scrollbar: { 34 | ch: ' ' 35 | } 36 | //alwaysScroll: true 37 | }); 38 | 39 | form.on('submit', function(data) { 40 | output.setContent(JSON.stringify(data, null, 2)); 41 | screen.render(); 42 | }); 43 | 44 | form.key('d', function() { 45 | form.scroll(1, true); 46 | screen.render(); 47 | }); 48 | 49 | form.key('u', function() { 50 | form.scroll(-1, true); 51 | screen.render(); 52 | }); 53 | 54 | var set = blessed.radioset({ 55 | parent: form, 56 | left: 1, 57 | top: 1, 58 | shrink: true, 59 | //padding: 1, 60 | //content: 'f', 61 | style: { 62 | bg: 'magenta' 63 | } 64 | }); 65 | 66 | var radio1 = blessed.radiobutton({ 67 | parent: set, 68 | mouse: true, 69 | keys: true, 70 | shrink: true, 71 | style: { 72 | bg: 'magenta' 73 | }, 74 | height: 1, 75 | left: 0, 76 | top: 0, 77 | name: 'radio1', 78 | content: 'radio1' 79 | }); 80 | 81 | var radio2 = blessed.radiobutton({ 82 | parent: set, 83 | mouse: true, 84 | keys: true, 85 | shrink: true, 86 | style: { 87 | bg: 'magenta' 88 | }, 89 | height: 1, 90 | left: 15, 91 | top: 0, 92 | name: 'radio2', 93 | content: 'radio2' 94 | }); 95 | 96 | var text = blessed.textbox({ 97 | parent: form, 98 | mouse: true, 99 | keys: true, 100 | style: { 101 | bg: 'blue' 102 | }, 103 | height: 1, 104 | width: 20, 105 | left: 1, 106 | top: 3, 107 | name: 'text' 108 | }); 109 | 110 | text.on('focus', function() { 111 | text.readInput(); 112 | }); 113 | 114 | var check = blessed.checkbox({ 115 | parent: form, 116 | mouse: true, 117 | keys: true, 118 | shrink: true, 119 | style: { 120 | bg: 'magenta' 121 | }, 122 | height: 1, 123 | left: 28, 124 | top: 1, 125 | name: 'check', 126 | content: 'check' 127 | }); 128 | 129 | var check2 = blessed.checkbox({ 130 | parent: form, 131 | mouse: true, 132 | keys: true, 133 | shrink: true, 134 | style: { 135 | bg: 'magenta' 136 | }, 137 | height: 1, 138 | left: 28, 139 | top: 14, 140 | name: 'foooooooo2', 141 | content: 'foooooooo2' 142 | }); 143 | 144 | var submit = blessed.button({ 145 | parent: form, 146 | mouse: true, 147 | keys: true, 148 | shrink: true, 149 | padding: { 150 | left: 1, 151 | right: 1 152 | }, 153 | left: 29, 154 | top: 3, 155 | shrink: true, 156 | name: 'submit', 157 | content: 'submit', 158 | style: { 159 | bg: 'blue', 160 | focus: { 161 | bg: 'red' 162 | } 163 | } 164 | }); 165 | 166 | submit.on('press', function() { 167 | form.submit(); 168 | }); 169 | 170 | var box1 = blessed.box({ 171 | parent: form, 172 | left: 1, 173 | top: 10, 174 | height: 10, 175 | width: 10, 176 | content: 'one', 177 | style: { 178 | bg: 'cyan' 179 | } 180 | }); 181 | 182 | var box2 = blessed.box({ 183 | parent: box1, 184 | left: 1, 185 | top: 2, 186 | height: 8, 187 | width: 9, 188 | content: 'two', 189 | style: { 190 | bg: 'magenta' 191 | } 192 | }); 193 | 194 | var box3 = blessed.box({ 195 | parent: box2, 196 | left: 1, 197 | top: 2, 198 | height: 6, 199 | width: 8, 200 | content: 'three', 201 | style: { 202 | bg: 'yellow' 203 | } 204 | }); 205 | 206 | var box4 = blessed.box({ 207 | parent: box3, 208 | left: 1, 209 | top: 2, 210 | height: 4, 211 | width: 7, 212 | content: 'four', 213 | style: { 214 | bg: 'blue' 215 | } 216 | }); 217 | 218 | var output = blessed.scrollabletext({ 219 | parent: form, 220 | mouse: true, 221 | keys: true, 222 | left: 0, 223 | top: 20, 224 | height: 5, 225 | left: 0, 226 | right: 0, 227 | style: { 228 | bg: 'red' 229 | }, 230 | content: 'foobar' 231 | }); 232 | 233 | var bottom = blessed.line({ 234 | parent: form, 235 | type: 'line', 236 | orientation: 'horizontal', 237 | left: 0, 238 | right: 0, 239 | top: 50, 240 | style: { 241 | fg: 'blue' 242 | } 243 | }); 244 | 245 | screen.key('q', function() { 246 | return screen.destroy(); 247 | }); 248 | 249 | form.focus(); 250 | 251 | form.submit(); 252 | 253 | screen.render(); 254 | -------------------------------------------------------------------------------- /test/widget-image.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/image.log', 6 | smartCSR: true, 7 | warnings: true 8 | }); 9 | 10 | // To ensure our w3mimgdisplay search works: 11 | if (process.argv[2] === 'find') { 12 | blessed.image.w3mdisplay = '/does/not/exist'; 13 | process.argv.length = 2; 14 | } 15 | 16 | var file = process.argv[2] || __dirname + '/test-image.png'; 17 | 18 | var image = blessed.image({ 19 | parent: screen, 20 | type: 'overlay', 21 | left: 'center', 22 | top: 'center', 23 | width: 'shrink', 24 | height: 'shrink', 25 | style: { 26 | bg: 'green' 27 | }, 28 | draggable: true 29 | }); 30 | 31 | setTimeout(function() { 32 | image.setImage(file, function() { 33 | // XXX For some reason the image sometimes envelopes 34 | // the entire screen at the end if this is uncommented: 35 | // NOTE: Might have to do with an uncached ratio and 36 | // a bad termSize being reported. 37 | screen.render(); 38 | setTimeout(function() { 39 | image.rtop = 4; 40 | image.rleft = 10; 41 | screen.render(); 42 | setTimeout(function() { 43 | image.rtop = 2; 44 | image.rleft = 7; 45 | screen.render(); 46 | setTimeout(function() { 47 | image.detach(); 48 | screen.render(); 49 | setTimeout(function() { 50 | screen.append(image); 51 | image.enableMouse(); 52 | screen.render(); 53 | }, 1000); 54 | }, 1000); 55 | }, 1000); 56 | }, 5000); 57 | }); 58 | }, 1000); 59 | 60 | image.focus(); 61 | 62 | screen.key('i', function() { 63 | screen.displayImage(file); 64 | }); 65 | 66 | screen.key('q', function() { 67 | return screen.destroy(); 68 | }); 69 | 70 | screen.render(); 71 | -------------------------------------------------------------------------------- /test/widget-insert.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../'); 2 | 3 | var screen = blessed.screen({ 4 | dump: __dirname + '/logs/insert.log', 5 | warnings: true 6 | }); 7 | 8 | var box = blessed.box({ 9 | parent: screen, 10 | //align: 'center', 11 | style: { 12 | bg: 'blue' 13 | }, 14 | height: 5, 15 | top: 'center', 16 | left: 0, 17 | width: 12, 18 | tags: true, 19 | content: '{yellow-fg}line{/yellow-fg}{|}1', 20 | //valign: 'middle' 21 | }); 22 | 23 | screen.render(); 24 | 25 | box.insertBottom('{yellow-fg}line{/yellow-fg}{|}2'); 26 | box.insertTop('{yellow-fg}line{/yellow-fg}{|}0'); 27 | 28 | screen.render(); 29 | 30 | setTimeout(function() { 31 | box.deleteTop(); 32 | screen.render(); 33 | }, 2000); 34 | 35 | screen.key('q', function() { 36 | screen.destroy(); 37 | }); 38 | -------------------------------------------------------------------------------- /test/widget-layout.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/layout.log', 6 | smartCSR: true, 7 | autoPadding: true, 8 | warnings: true 9 | }); 10 | 11 | var layout = blessed.layout({ 12 | parent: screen, 13 | top: 'center', 14 | left: 'center', 15 | width: '50%', 16 | height: '50%', 17 | border: 'line', 18 | layout: process.argv[2] === 'grid' ? 'grid' : 'inline', 19 | style: { 20 | bg: 'red', 21 | border: { 22 | fg: 'blue' 23 | } 24 | } 25 | }); 26 | 27 | var box1 = blessed.box({ 28 | parent: layout, 29 | top: 'center', 30 | left: 'center', 31 | width: 20, 32 | height: 10, 33 | border: 'line', 34 | content: '1' 35 | }); 36 | 37 | var box2 = blessed.box({ 38 | parent: layout, 39 | top: 0, 40 | left: 0, 41 | width: 10, 42 | height: 5, 43 | border: 'line', 44 | content: '2' 45 | }); 46 | 47 | var box3 = blessed.box({ 48 | parent: layout, 49 | top: 0, 50 | left: 0, 51 | width: 10, 52 | height: 5, 53 | border: 'line', 54 | content: '3' 55 | }); 56 | 57 | var box4 = blessed.box({ 58 | parent: layout, 59 | top: 0, 60 | left: 0, 61 | width: 10, 62 | height: 5, 63 | border: 'line', 64 | content: '4' 65 | }); 66 | 67 | var box5 = blessed.box({ 68 | parent: layout, 69 | top: 0, 70 | left: 0, 71 | width: 10, 72 | height: 5, 73 | border: 'line', 74 | content: '5' 75 | }); 76 | 77 | var box6 = blessed.box({ 78 | parent: layout, 79 | top: 0, 80 | left: 0, 81 | width: 10, 82 | height: 5, 83 | border: 'line', 84 | content: '6' 85 | }); 86 | 87 | var box7 = blessed.box({ 88 | parent: layout, 89 | top: 0, 90 | left: 0, 91 | width: 10, 92 | height: 5, 93 | border: 'line', 94 | content: '7' 95 | }); 96 | 97 | var box8 = blessed.box({ 98 | parent: layout, 99 | top: 'center', 100 | left: 'center', 101 | width: 20, 102 | height: 10, 103 | border: 'line', 104 | content: '8' 105 | }); 106 | 107 | var box9 = blessed.box({ 108 | parent: layout, 109 | top: 0, 110 | left: 0, 111 | width: 10, 112 | height: 5, 113 | border: 'line', 114 | content: '9' 115 | }); 116 | 117 | var box10 = blessed.box({ 118 | parent: layout, 119 | top: 'center', 120 | left: 'center', 121 | width: 20, 122 | height: 10, 123 | border: 'line', 124 | content: '10' 125 | }); 126 | 127 | var box11 = blessed.box({ 128 | parent: layout, 129 | top: 0, 130 | left: 0, 131 | width: 10, 132 | height: 5, 133 | border: 'line', 134 | content: '11' 135 | }); 136 | 137 | var box12 = blessed.box({ 138 | parent: layout, 139 | top: 'center', 140 | left: 'center', 141 | width: 20, 142 | height: 10, 143 | border: 'line', 144 | content: '12' 145 | }); 146 | 147 | if (process.argv[2] !== 'grid') { 148 | for (var i = 0; i < 10; i++) { 149 | blessed.box({ 150 | parent: layout, 151 | // width: i % 2 === 0 ? 10 : 20, 152 | // height: i % 2 === 0 ? 5 : 10, 153 | width: Math.random() > 0.5 ? 10 : 20, 154 | height: Math.random() > 0.5 ? 5 : 10, 155 | border: 'line', 156 | content: (i + 1 + 12) + '' 157 | }); 158 | } 159 | } 160 | 161 | screen.key('q', function() { 162 | return screen.destroy(); 163 | }); 164 | 165 | screen.render(); 166 | -------------------------------------------------------------------------------- /test/widget-listbar.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | var auto = true; 5 | 6 | screen = blessed.screen({ 7 | dump: __dirname + '/logs/listbar.log', 8 | autoPadding: auto, 9 | warnings: true 10 | }); 11 | 12 | var box = blessed.box({ 13 | parent: screen, 14 | top: 0, 15 | right: 0, 16 | width: 'shrink', 17 | height: 'shrink', 18 | content: '...' 19 | }); 20 | 21 | var bar = blessed.listbar({ 22 | //parent: screen, 23 | bottom: 0, 24 | left: 3, 25 | right: 3, 26 | height: auto ? 'shrink' : 3, 27 | mouse: true, 28 | keys: true, 29 | autoCommandKeys: true, 30 | border: 'line', 31 | vi: true, 32 | style: { 33 | bg: 'green', 34 | item: { 35 | bg: 'red', 36 | hover: { 37 | bg: 'blue' 38 | }, 39 | //focus: { 40 | // bg: 'blue' 41 | //} 42 | }, 43 | selected: { 44 | bg: 'blue' 45 | } 46 | }, 47 | commands: { 48 | 'one': { 49 | keys: ['a'], 50 | callback: function() { 51 | box.setContent('Pressed one.'); 52 | screen.render(); 53 | } 54 | }, 55 | 'two': function() { 56 | box.setContent('Pressed two.'); 57 | screen.render(); 58 | }, 59 | 'three': function() { 60 | box.setContent('Pressed three.'); 61 | screen.render(); 62 | }, 63 | 'four': function() { 64 | box.setContent('Pressed four.'); 65 | screen.render(); 66 | }, 67 | 'five': function() { 68 | box.setContent('Pressed five.'); 69 | screen.render(); 70 | }, 71 | 'six': function() { 72 | box.setContent('Pressed six.'); 73 | screen.render(); 74 | }, 75 | 'seven': function() { 76 | box.setContent('Pressed seven.'); 77 | screen.render(); 78 | }, 79 | 'eight': function() { 80 | box.setContent('Pressed eight.'); 81 | screen.render(); 82 | }, 83 | 'nine': function() { 84 | box.setContent('Pressed nine.'); 85 | screen.render(); 86 | }, 87 | 'ten': function() { 88 | box.setContent('Pressed ten.'); 89 | screen.render(); 90 | }, 91 | 'eleven': function() { 92 | box.setContent('Pressed eleven.'); 93 | screen.render(); 94 | }, 95 | 'twelve': function() { 96 | box.setContent('Pressed twelve.'); 97 | screen.render(); 98 | }, 99 | 'thirteen': function() { 100 | box.setContent('Pressed thirteen.'); 101 | screen.render(); 102 | }, 103 | 'fourteen': function() { 104 | box.setContent('Pressed fourteen.'); 105 | screen.render(); 106 | }, 107 | 'fifteen': function() { 108 | box.setContent('Pressed fifteen.'); 109 | screen.render(); 110 | } 111 | } 112 | }); 113 | 114 | screen.append(bar); 115 | 116 | bar.focus(); 117 | 118 | screen.key('q', function() { 119 | return screen.destroy(); 120 | }); 121 | 122 | screen.render(); 123 | -------------------------------------------------------------------------------- /test/widget-listtable.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/listtable.log', 6 | autoPadding: false, 7 | fullUnicode: true, 8 | warnings: true 9 | }); 10 | 11 | var DU = '杜'; 12 | var JUAN = '鹃'; 13 | 14 | /* 15 | var box = blessed.box({ 16 | parent: screen, 17 | top: 'center', 18 | left: 'center', 19 | data: null, 20 | border: 'line', 21 | align: 'center', 22 | tags: true, 23 | keys: true, 24 | width: '90%', 25 | height: '80%', 26 | style: { 27 | bg: 'red' 28 | } 29 | }); 30 | */ 31 | 32 | var table = blessed.listtable({ 33 | //parent: screen, 34 | top: 'center', 35 | left: 'center', 36 | data: null, 37 | border: 'line', 38 | align: 'center', 39 | tags: true, 40 | keys: true, 41 | //width: '80%', 42 | width: 'shrink', 43 | height: '70%', 44 | vi: true, 45 | mouse: true, 46 | style: { 47 | border: { 48 | fg: 'red' 49 | }, 50 | header: { 51 | fg: 'blue', 52 | bold: true 53 | }, 54 | cell: { 55 | fg: 'magenta', 56 | selected: { 57 | bg: 'blue' 58 | } 59 | } 60 | } 61 | }); 62 | 63 | var data1 = [ 64 | [ 'Animals', 'Foods', 'Times' ], 65 | [ 'Elephant', 'Apple', '1:00am' ], 66 | [ 'Bird', 'Orange', '2:15pm' ], 67 | [ 'T-Rex', 'Taco', '8:45am' ], 68 | [ 'Mouse', 'Cheese', '9:05am' ] 69 | ]; 70 | 71 | data1[1][0] = '{red-fg}' + data1[1][0] + '{/red-fg}'; 72 | data1[2][0] += ' (' + DU + JUAN + ')'; 73 | 74 | var data2 = [ 75 | [ 'Animals', 'Foods', 'Times', 'Numbers' ], 76 | [ 'Elephant', 'Apple', '1:00am', 'One' ], 77 | [ 'Bird', 'Orange', '2:15pm', 'Two' ], 78 | [ 'T-Rex', 'Taco', '8:45am', 'Three' ], 79 | [ 'Mouse', 'Cheese', '9:05am', 'Four' ] 80 | ]; 81 | 82 | data2[1][0] = '{red-fg}' + data2[1][0] + '{/red-fg}'; 83 | data2[2][0] += ' (' + DU + JUAN + ')'; 84 | 85 | screen.key('q', function() { 86 | return screen.destroy(); 87 | }); 88 | 89 | table.focus(); 90 | 91 | table.setData(data2); 92 | 93 | screen.append(table); 94 | 95 | screen.render(); 96 | 97 | setTimeout(function() { 98 | table.setData(data1); 99 | screen.render(); 100 | }, 3000); 101 | -------------------------------------------------------------------------------- /test/widget-log.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/logger.log', 6 | smartCSR: true, 7 | autoPadding: false, 8 | warnings: true 9 | }); 10 | 11 | var logger = blessed.log({ 12 | parent: screen, 13 | top: 'center', 14 | left: 'center', 15 | width: '50%', 16 | height: '50%', 17 | border: 'line', 18 | tags: true, 19 | keys: true, 20 | vi: true, 21 | mouse: true, 22 | scrollback: 100, 23 | scrollbar: { 24 | ch: ' ', 25 | track: { 26 | bg: 'yellow' 27 | }, 28 | style: { 29 | inverse: true 30 | } 31 | } 32 | }); 33 | 34 | logger.focus(); 35 | 36 | setInterval(function() { 37 | logger.log('Hello {#0fe1ab-fg}world{/}: {bold}%s{/bold}.', Date.now().toString(36)); 38 | if (Math.random() < 0.30) { 39 | logger.log({foo:{bar:{baz:true}}}); 40 | } 41 | screen.render(); 42 | }, 1000).unref(); 43 | 44 | screen.key('q', function() { 45 | return screen.destroy(); 46 | }); 47 | 48 | screen.render(); 49 | -------------------------------------------------------------------------------- /test/widget-nested-attr.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/nested-attr.log', 6 | warnings: true 7 | }); 8 | 9 | blessed.box({ 10 | parent: screen, 11 | left: 'center', 12 | top: 'center', 13 | width: '80%', 14 | height: '80%', 15 | style: { 16 | bg: 'black', 17 | fg: 'yellow' 18 | }, 19 | tags: true, 20 | border: 'line', 21 | content: '{red-fg}hello {blue-fg}how{/blue-fg}' 22 | + ' {yellow-bg}are{/yellow-bg} you?{/red-fg}' 23 | }); 24 | 25 | screen.key('q', function() { 26 | return screen.destroy(); 27 | }); 28 | 29 | screen.render(); 30 | -------------------------------------------------------------------------------- /test/widget-noalt.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/noalt.log', 6 | title: 'widget-noalt test', 7 | noAlt: true, 8 | warnings: true 9 | }); 10 | 11 | var list = blessed.list({ 12 | parent: screen, 13 | align: 'center', 14 | mouse: true, 15 | keys: true, 16 | vi: true, 17 | width: '50%', 18 | height: 'shrink', 19 | //border: 'line', 20 | top: 5, 21 | //bottom: 2, 22 | left: 0, 23 | style: { 24 | fg: 'blue', 25 | bg: 'default', 26 | selected: { 27 | bg: 'green' 28 | } 29 | }, 30 | items: [ 31 | 'one', 32 | 'two', 33 | 'three' 34 | ] 35 | }); 36 | 37 | list.select(0); 38 | 39 | list.on('select', function(item) { 40 | console.log(item.getText()); 41 | screen.destroy(); 42 | }); 43 | 44 | screen.key('C-c', function() { 45 | screen.destroy(); 46 | }); 47 | 48 | list.focus(); 49 | 50 | screen.render(); 51 | -------------------------------------------------------------------------------- /test/widget-nowrap.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | , blessed = require('../') 3 | , screen; 4 | 5 | // {open}xxxx{close} xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 6 | // xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx {red-bg}xxxx xxxx xxxx{/red-bg} 7 | 8 | screen = blessed.screen({ 9 | dump: __dirname + '/logs/nowrap.log', 10 | warnings: true 11 | }); 12 | 13 | var box = blessed.box({ 14 | parent: screen, 15 | width: 60, 16 | wrap: false, 17 | tags: true, 18 | content: fs.readFileSync(__filename, 'utf8') 19 | //content: '{red-bg}' + blessed.escape('{{{{{}{bold}x{/bold}}') + '{/red-bg}' 20 | // + '\nescaped: {green-fg}{escape}{{{}{{a{bold}b{/bold}c{/escape}{/green-fg}' 21 | }); 22 | 23 | box.focus(); 24 | 25 | screen.key('q', function() { 26 | return screen.destroy(); 27 | }); 28 | 29 | screen.render(); 30 | -------------------------------------------------------------------------------- /test/widget-obscure-sides.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../'); 2 | 3 | var screen = blessed.screen({ 4 | tput: true, 5 | smartCSR: true, 6 | dump: __dirname + '/logs/obscure-sides.log', 7 | autoPadding: true, 8 | warnings: true 9 | }); 10 | 11 | var box = blessed.box({ 12 | parent: screen, 13 | scrollable: true, 14 | alwaysScroll: true, 15 | border: { 16 | type: 'bg', 17 | ch: ' ' 18 | }, 19 | style: { 20 | bg: 'blue', 21 | border: { 22 | inverse: true 23 | }, 24 | scrollbar: { 25 | bg: 'white' 26 | } 27 | }, 28 | height: 10, 29 | width: 30, 30 | top: 'center', 31 | left: 'center', 32 | cwd: process.env.HOME, 33 | keys: true, 34 | vi: true, 35 | scrollbar: { 36 | ch: ' ' 37 | } 38 | }); 39 | 40 | var child = blessed.box({ 41 | parent: box, 42 | content: 'hello', 43 | style: { 44 | bg: 'green' 45 | }, 46 | // border: 'line', 47 | height: 5, 48 | width: 20, 49 | top: 2, 50 | left: 15 51 | }); 52 | 53 | var child2 = blessed.box({ 54 | parent: box, 55 | content: 'hello', 56 | style: { 57 | bg: 'green', 58 | }, 59 | border: 'line', 60 | height: 5, 61 | width: 20, 62 | top: 25, 63 | left: -5 64 | }); 65 | 66 | box.focus(); 67 | 68 | screen.render(); 69 | 70 | screen.key('q', function() { 71 | screen.destroy(); 72 | }); 73 | -------------------------------------------------------------------------------- /test/widget-padding.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/padding.log', 6 | warnings: true 7 | }); 8 | 9 | blessed.box({ 10 | parent: screen, 11 | border: 'line', 12 | style: { 13 | bg: 'red', 14 | }, 15 | content: 'hello world\nhi', 16 | align: 'center', 17 | left: 'center', 18 | top: 'center', 19 | width: 22, 20 | height: 10, 21 | padding: 2 22 | }); 23 | 24 | screen.key('q', function() { 25 | return screen.destroy(); 26 | }); 27 | 28 | screen.render(); 29 | -------------------------------------------------------------------------------- /test/widget-play.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../'); 2 | 3 | var screen = blessed.screen({ 4 | dump: __dirname + '/logs/play.log', 5 | smartCSR: true, 6 | warnings: true 7 | }); 8 | 9 | var frames = require(__dirname + '/frames.json'); 10 | 11 | var timer = setInterval(function() { 12 | if (!frames.length) { 13 | clearInterval(timer); 14 | return screen.destroy(); 15 | } 16 | process.stdout.write(frames.shift()); 17 | }, 100); 18 | -------------------------------------------------------------------------------- /test/widget-png.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../'); 2 | var fs = require('fs'); 3 | 4 | var argv = {}; 5 | 6 | process.argv = process.argv.map(function(arg, i) { 7 | if (/^--\w+=/.test(arg)) { 8 | arg = arg.split('='); 9 | if (/^[0-9.]+$/.test(arg[1])) arg[1] = +arg[1]; 10 | argv[arg[0].replace(/^--/, '')] = arg[1]; 11 | return; 12 | } 13 | if (arg.indexOf('--') === 0) { 14 | arg = arg.slice(2); 15 | argv[arg] = true; 16 | return; 17 | } 18 | return arg; 19 | }).filter(Boolean); 20 | 21 | var screen = blessed.screen({ 22 | tput: true, 23 | smartCSR: true, 24 | dump: __dirname + '/logs/png.log', 25 | warnings: true 26 | }); 27 | 28 | var box1 = blessed.box({ 29 | parent: screen, 30 | left: 4, 31 | top: 3, 32 | width: 10, 33 | height: 6, 34 | border: 'line', 35 | style: { 36 | bg: 'green' 37 | }, 38 | content: fs.readFileSync(__dirname + '/lorem.txt', 'utf8') 39 | }); 40 | 41 | var box2 = blessed.box({ 42 | parent: screen, 43 | left: 20, 44 | top: 8, 45 | width: 40, 46 | height: 15, 47 | border: 'line', 48 | style: { 49 | bg: 'green' 50 | }, 51 | content: fs.readFileSync(__dirname + '/lorem.txt', 'utf8') 52 | }); 53 | 54 | var file = process.argv[2]; 55 | var testImage = __dirname + '/test-image.png'; 56 | var spinfox = __dirname + '/spinfox.png'; 57 | 58 | // XXX I'm not sure of the license of this file, 59 | // so I'm not going to redistribute it in the repo. 60 | var url = 'https://people.mozilla.org/~dolske/apng/spinfox.png'; 61 | 62 | if (!file) { 63 | try { 64 | if (!fs.existsSync(spinfox)) { 65 | var buf = blessed.ansiimage.curl(url); 66 | fs.writeFileSync(spinfox, buf); 67 | } 68 | file = spinfox; 69 | } catch (e) { 70 | file = testImage; 71 | } 72 | } 73 | 74 | if (!argv.width && !argv.height && !argv.scale) { 75 | argv.width = 20; 76 | } 77 | 78 | var png = blessed.image({ 79 | parent: screen, 80 | // border: 'line', 81 | width: argv.width, 82 | height: argv.height, 83 | top: 2, 84 | left: 0, 85 | file: file, 86 | draggable: true, 87 | type: 'ansi', 88 | scale: argv.scale, 89 | ascii: argv.ascii, 90 | optimization: argv.optimization, 91 | speed: argv.speed 92 | }); 93 | 94 | screen.render(); 95 | 96 | screen.key('q', function() { 97 | clearInterval(timeout); 98 | screen.destroy(); 99 | }); 100 | 101 | var timeout = setInterval(function() { 102 | if (png.right <= 0) { 103 | clearInterval(timeout); 104 | return; 105 | } 106 | png.left++; 107 | screen.render(); 108 | }, 100); 109 | 110 | if (timeout.unref) timeout.unref(); 111 | 112 | screen.key(['h', 'left'], function() { 113 | png.left -= 2; 114 | }); 115 | 116 | screen.key(['k', 'up'], function() { 117 | png.top -= 2; 118 | }); 119 | 120 | screen.key(['l', 'right'], function() { 121 | png.left += 2; 122 | }); 123 | 124 | screen.key(['j', 'down'], function() { 125 | png.top += 2; 126 | }); 127 | 128 | screen.on('keypress', function() { 129 | clearInterval(timeout); 130 | }); 131 | 132 | png.on('mousedown', function() { 133 | clearInterval(timeout); 134 | }); 135 | -------------------------------------------------------------------------------- /test/widget-pos.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , assert = require('assert') 3 | , screen; 4 | 5 | screen = blessed.screen({ 6 | dump: __dirname + '/logs/pos.log' 7 | }); 8 | 9 | // My terminal size at the time of writing these tests: 10 | screen.program.cols = 154; 11 | screen.program.rows = 19; 12 | screen.alloc(); 13 | 14 | var main = blessed.box({ 15 | //width: '75%', 16 | //height: '75%', 17 | width: 115, 18 | height: 14, 19 | style: { 20 | bg: 'yellow' 21 | }, 22 | top: 2, 23 | left: 2, 24 | content: 'Welcome to my program' 25 | }); 26 | 27 | screen.append(main); 28 | 29 | var inner = blessed.box({ 30 | width: '50%', 31 | height: '50%', 32 | //width: 57, 33 | //height: 7, 34 | style: { 35 | bg: 'blue' 36 | }, 37 | top: 2, 38 | left: 2, 39 | content: 'Hello' 40 | }); 41 | 42 | main.append(inner); 43 | 44 | inner.setContent(inner.content + '\n' + JSON.stringify({ 45 | aleft: inner.aleft, 46 | aright: inner.aright, 47 | atop: inner.atop, 48 | abottom: inner.abottom, 49 | width: inner.width, 50 | height: inner.height, 51 | rleft: inner.rleft, 52 | rright: inner.rright, 53 | rtop: inner.rtop, 54 | rbottom: inner.rbottom 55 | })); 56 | 57 | assert.equal(inner.width, 57); 58 | assert.equal(inner.height, 7); 59 | 60 | assert.equal(inner.aleft, 4); 61 | assert.equal(inner.aright, 93); 62 | assert.equal(inner.atop, 4); 63 | assert.equal(inner.abottom, 8); 64 | 65 | assert.equal(inner.rleft, 2); 66 | assert.equal(inner.rright, 56); 67 | assert.equal(inner.rtop, 2); 68 | assert.equal(inner.rbottom, 5); 69 | 70 | // Change left to half of the parent width. 71 | inner.rleft = '50%'; 72 | assert.equal(inner.aleft, 59); 73 | 74 | // Change left to half of the screen width. 75 | inner.aleft = '50%'; 76 | assert.equal(inner.aleft, screen.width / 2 | 0); 77 | 78 | // Test implied height/width. 79 | reset(inner, { 80 | top: 5, 81 | bottom: 5, 82 | left: 5, 83 | right: 5 84 | }); 85 | 86 | assert.equal(inner.width, 105); 87 | assert.equal(inner.height, 4); 88 | 89 | // Demonstrate the difference between `left: 5`, and `.aleft = 5` (relative vs. absolute): 90 | inner.atop = inner.abottom = inner.aleft = inner.aright = 5; 91 | 92 | assert.equal(inner.width, 144); 93 | assert.equal(inner.height, 9); 94 | 95 | // Test center keyword 96 | reset(inner, { 97 | width: '50%', 98 | height: '50%', 99 | left: 'center', 100 | top: 'center' 101 | }); 102 | 103 | assert.equal(inner.rleft, 29); 104 | assert.equal(inner.rtop, 4); 105 | 106 | // TODO: Start storing position.left, etc. as absolute? 107 | 108 | screen.on('keypress', function(ch, key) { 109 | if (key.name === 'escape' || key.name === 'q') { 110 | return screen.destroy(); 111 | } 112 | }); 113 | 114 | screen.render(); 115 | 116 | function reset(el, pos) { 117 | pos = pos || {}; 118 | el.position.width = el.options.width = pos.width; 119 | el.position.height = el.options.height = pos.height; 120 | el.position.left = el.options.left = pos.left; 121 | el.position.right = el.options.right = pos.right; 122 | el.position.top = el.options.top = pos.top; 123 | el.position.bottom = el.options.bottom = pos.bottom; 124 | } 125 | -------------------------------------------------------------------------------- /test/widget-prompt.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../'); 2 | 3 | var screen = blessed.screen({ 4 | tput: true, 5 | smartCSR: true, 6 | dump: __dirname + '/logs/prompt.log', 7 | autoPadding: true, 8 | warnings: true 9 | }); 10 | 11 | var prompt = blessed.prompt({ 12 | parent: screen, 13 | border: 'line', 14 | height: 'shrink', 15 | width: 'half', 16 | top: 'center', 17 | left: 'center', 18 | label: ' {blue-fg}Prompt{/blue-fg} ', 19 | tags: true, 20 | keys: true, 21 | vi: true 22 | }); 23 | 24 | var question = blessed.question({ 25 | parent: screen, 26 | border: 'line', 27 | height: 'shrink', 28 | width: 'half', 29 | top: 'center', 30 | left: 'center', 31 | label: ' {blue-fg}Question{/blue-fg} ', 32 | tags: true, 33 | keys: true, 34 | vi: true 35 | }); 36 | 37 | var msg = blessed.message({ 38 | parent: screen, 39 | border: 'line', 40 | height: 'shrink', 41 | width: 'half', 42 | top: 'center', 43 | left: 'center', 44 | label: ' {blue-fg}Message{/blue-fg} ', 45 | tags: true, 46 | keys: true, 47 | hidden: true, 48 | vi: true 49 | }); 50 | 51 | var loader = blessed.loading({ 52 | parent: screen, 53 | border: 'line', 54 | height: 'shrink', 55 | width: 'half', 56 | top: 'center', 57 | left: 'center', 58 | label: ' {blue-fg}Loader{/blue-fg} ', 59 | tags: true, 60 | keys: true, 61 | hidden: true, 62 | vi: true 63 | }); 64 | 65 | prompt.input('Question?', '', function(err, value) { 66 | question.ask('Question?', function(err, value) { 67 | msg.display('Hello world!', 3, function(err) { 68 | msg.display('Hello world again!', -1, function(err) { 69 | loader.load('Loading...'); 70 | setTimeout(function() { 71 | loader.stop(); 72 | screen.destroy(); 73 | }, 3000); 74 | }); 75 | }); 76 | }); 77 | }); 78 | 79 | screen.key('q', function() { 80 | screen.destroy(); 81 | }); 82 | 83 | screen.render(); 84 | -------------------------------------------------------------------------------- /test/widget-record.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , fs = require('fs'); 3 | 4 | var screen = blessed.screen({ 5 | dump: __dirname + '/logs/record.log', 6 | smartCSR: true, 7 | warnings: true 8 | }); 9 | 10 | var btext = blessed.box({ 11 | parent: screen, 12 | left: 'center', 13 | top: 'center', 14 | width: '80%', 15 | height: '80%', 16 | style: { 17 | bg: 'green' 18 | }, 19 | border: 'line', 20 | content: 'CSR should still work.' 21 | }); 22 | 23 | var text = blessed.scrollabletext({ 24 | parent: screen, 25 | content: fs.readFileSync(__dirname + '/git.diff', 'utf8'), 26 | border: 'line', 27 | left: 'center', 28 | top: 'center', 29 | draggable: true, 30 | width: '50%', 31 | height: '50%', 32 | mouse: true, 33 | keys: true, 34 | vi: true 35 | }); 36 | 37 | text.focus(); 38 | 39 | var frames = []; 40 | 41 | var timer = setInterval(function() { 42 | frames.push(screen.screenshot()); 43 | }, 100); 44 | 45 | screen.key('C-q', function() { 46 | fs.writeFileSync(__dirname + '/frames.json', JSON.stringify(frames)); 47 | clearInterval(timer); 48 | return screen.destroy(); 49 | }); 50 | 51 | screen.render(); 52 | -------------------------------------------------------------------------------- /test/widget-scrollable-boxes.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/scrollable-boxes.log', 6 | smartCSR: true, 7 | warnings: true 8 | }); 9 | 10 | var box = blessed.box({ 11 | parent: screen, 12 | //padding: 2, 13 | scrollable: true, 14 | left: 'center', 15 | top: 'center', 16 | width: '80%', 17 | height: '80%', 18 | style: { 19 | bg: 'green' 20 | }, 21 | border: 'line', 22 | content: 'foobar', 23 | keys: true, 24 | vi: true, 25 | alwaysScroll: true, 26 | scrollbar: { 27 | ch: ' ', 28 | inverse: true 29 | } 30 | }); 31 | 32 | var text = blessed.box({ 33 | parent: box, 34 | content: 'hello1\nhello2\nhello3\nhello4', 35 | padding: 2, 36 | style: { 37 | bg: 'red' 38 | }, 39 | left: 2, 40 | top: 30, 41 | width: '50%', 42 | height: 6 43 | }); 44 | 45 | var text2 = blessed.box({ 46 | parent: box, 47 | content: 'world', 48 | padding: 1, 49 | style: { 50 | bg: 'red' 51 | }, 52 | left: 2, 53 | top: 50, 54 | width: '50%', 55 | height: 3 56 | }); 57 | 58 | var box2 = blessed.box({ 59 | parent: box, 60 | scrollable: true, 61 | content: 'foo-one\nfoo-two\nfoo-three', 62 | padding: 2, 63 | left: 'center', 64 | top: 20, 65 | width: '80%', 66 | height: 9, 67 | border: 'line', 68 | style: { 69 | bg: 'magenta', 70 | focus: { 71 | bg: 'blue' 72 | }, 73 | hover: { 74 | bg: 'red' 75 | } 76 | // scrollbar: { 77 | // inverse: true 78 | // } 79 | }, 80 | keys: true, 81 | vi: true, 82 | alwaysScroll: true 83 | // scrollbar: { 84 | // ch: ' ' 85 | // } 86 | }); 87 | 88 | var box3 = blessed.box({ 89 | parent: box2, 90 | scrollable: true, 91 | //content: 'foo1\nfoo2\nfoo3\nfoo4\nfoo5\nfoo6\nfoo7\nf008', 92 | //left: 'center', 93 | left: 3, 94 | top: 3, 95 | content: 'foo', 96 | //shrink: true, 97 | height: 4, 98 | width: 5, 99 | //width: '80%', 100 | //height: 5, 101 | border: 'line', 102 | style: { 103 | bg: 'yellow', 104 | focus: { 105 | bg: 'blue' 106 | }, 107 | hover: { 108 | bg: 'red' 109 | } 110 | // scrollbar: { 111 | // inverse: true 112 | // } 113 | }, 114 | keys: true, 115 | vi: true, 116 | alwaysScroll: true 117 | // scrollbar: { 118 | // ch: ' ' 119 | // } 120 | }); 121 | 122 | screen.key('q', function() { 123 | return screen.destroy(); 124 | }); 125 | 126 | box.focus(); 127 | 128 | screen.render(); 129 | -------------------------------------------------------------------------------- /test/widget-shadow.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/shadow.log', 6 | smartCSR: true, 7 | dockBorders: true, 8 | warnings: true 9 | }); 10 | 11 | var bg = blessed.box({ 12 | parent: screen, 13 | shadow: true, 14 | left: 0, 15 | top: 0, 16 | right: 0, 17 | bottom: 0, 18 | style: { 19 | bg: 'lightblue' 20 | }, 21 | content: 'Foo' 22 | }); 23 | 24 | var under = blessed.box({ 25 | parent: screen, 26 | shadow: true, 27 | left: 10, 28 | top: 4, 29 | width: '40%', 30 | height: '30%', 31 | style: { 32 | bg: 'yellow' 33 | }, 34 | border: 'line', 35 | tags: true 36 | }); 37 | 38 | var over = blessed.box({ 39 | parent: screen, 40 | shadow: true, 41 | left: 'center', 42 | top: 'center', 43 | width: '50%', 44 | height: '50%', 45 | style: { 46 | bg: 'red', 47 | transparent: true 48 | }, 49 | border: 'line', 50 | draggable: true, 51 | tags: true, 52 | content: '{green-bg}{red-fg}{bold} --Drag Me-- {/}' 53 | }); 54 | 55 | over.key('left', function() { 56 | over.left -= 2; 57 | screen.render(); 58 | }); 59 | 60 | over.key('up', function() { 61 | over.top -= 1; 62 | screen.render(); 63 | }); 64 | 65 | over.key('right', function() { 66 | over.left += 2; 67 | screen.render(); 68 | }); 69 | 70 | over.key('down', function() { 71 | over.top += 1; 72 | screen.render(); 73 | }); 74 | 75 | over.focus(); 76 | 77 | screen.key('q', function() { 78 | return screen.destroy(); 79 | }); 80 | 81 | var lorem = 'Non eram nescius Brute cum quae summis ingeniis exquisitaque' 82 | + ' doctrina philosophi Graeco sermone tractavissent ea Latinis litteris mandaremus' 83 | + ' fore ut hic noster labor in varias reprehensiones incurreret nam quibusdam et' 84 | + ' iis quidem non admodum indoctis totum hoc displicet philosophari quidam autem' 85 | + ' non tam id reprehendunt si remissius agatur sed tantum studium tamque multam' 86 | + ' operam ponendam in eo non arbitrantur erunt etiam et ii quidem eruditi Graecis' 87 | + ' litteris contemnentes Latinas qui se dicant in Graecis legendis operam malle' 88 | + ' consumere postremo aliquos futuros suspicor qui me ad alias litteras vocent' 89 | + ' genus hoc scribendi etsi sit elegans personae tamen et dignitatis esse negent' 90 | + ' Contra quos omnis dicendum breviter existimo Quamquam philosophiae quidem' 91 | + ' vituperatoribus satis responsum est eo libro quo a nobis philosophia defensa et' 92 | + ' collaudata est cum esset accusata et vituperata ab Hortensio qui liber cum et' 93 | + ' tibi probatus videretur et iis quos ego posse iudicare arbitrarer plura suscepi' 94 | + ' veritus ne movere hominum studia viderer retinere non posse Qui autem si maxime' 95 | + ' hoc placeat moderatius tamen id volunt fieri difficilem quandam temperantiam' 96 | + ' postulant in eo quod semel admissum coerceri reprimique non potest ut' 97 | + ' propemodum iustioribus utamur illis qui omnino avocent a philosophia quam his' 98 | + ' qui rebus infinitis modum constituant in reque eo meliore quo maior sit' 99 | + ' mediocritatem desiderent Sive enim ad sapientiam perveniri potest non paranda' 100 | + ' nobis solum ea sed fruenda etiam sapientia est sive hoc difficile est tamen nec' 101 | + ' modus est ullus investigandi veri nisi inveneris et quaerendi defatigatio' 102 | + ' turpis est cum id quod quaeritur sit pulcherrimum etenim si delectamur cum' 103 | + ' scribimus quis est tam invidus qui ab eo nos abducat sin laboramus quis est qui' 104 | + ' alienae modum statuat industriae nam ut Terentianus Chremes non inhumanus qui' 105 | + ' novum vicinum non vult fodere aut arare aut aliquid ferre denique non enim' 106 | + ' illum ab industria sed ab inliberali labore deterret sic isti curiosi quos' 107 | + ' offendit noster minime nobis iniucundus labor Iis igitur est difficilius satis' 108 | + ' facere qui se Latina scripta dicunt contemnere in quibus hoc primum est in quo' 109 | + ' admirer cur in gravissimis rebus non delectet eos sermo patrius cum idem' 110 | + ' fabellas Latinas ad verbum e Graecis expressas non inviti legant quis enim tam' 111 | + ' inimicus paene nomini Romano est qui Ennii Medeam aut Antiopam Pacuvii spernat' 112 | + ' aut reiciat quod se isdem Euripidis fabulis delectari dicat Latinas litteras' 113 | + ' oderit Quid si nos non interpretum fungimur munere sed tuemur ea quae dicta' 114 | + ' sunt ab iis quos probamus eisque nostrum iudicium et nostrum scribendi ordinem' 115 | + ' adiungimus quid habent cur Graeca anteponant iis quae et splendide dicta sint' 116 | + ' neque sint conversa de Graecis nam si dicent ab illis has res esse tractatas ne' 117 | + ' ipsos quidem Graecos est cur tam multos legant quam legendi sunt quid enim est' 118 | + ' a Chrysippo praetermissum in Stoicis legimus tamen Diogenem Antipatrum' 119 | + ' Mnesarchum Panaetium multos alios in primisque familiarem nostrum Posidonium' 120 | + ' quid Theophrastus mediocriterne delectat cum tractat locos ab Aristotele ante' 121 | + ' tractatos quid Epicurei num desistunt de isdem de quibus et ab Epicuro scriptum' 122 | + ' est et ab antiquis ad arbitrium suum scribere quodsi Graeci leguntur a Graecis' 123 | + ' isdem de rebus alia ratione compositis quid est cur nostri a nostris non' 124 | + ' legantur'; 125 | 126 | bg.setContent(lorem); 127 | 128 | screen.render(); 129 | -------------------------------------------------------------------------------- /test/widget-shrink-fail-2.js: -------------------------------------------------------------------------------- 1 | var blessed = require('blessed'); 2 | var screen = blessed.screen({ 3 | autoPadding: true, 4 | warnings: true 5 | }); 6 | 7 | var tab = blessed.box({ 8 | parent: screen, 9 | top: 2, 10 | left: 0, 11 | right: 0, 12 | bottom: 0, 13 | scrollable: true, 14 | keys: true, 15 | vi: true, 16 | alwaysScroll: true, 17 | scrollbar: { 18 | ch: ' ' 19 | }, 20 | style: { 21 | scrollbar: { 22 | inverse: true 23 | } 24 | } 25 | }); 26 | 27 | tab._.data = blessed.text({ 28 | parent: tab, 29 | top: 0, 30 | left: 3, 31 | height: 'shrink', 32 | width: 'shrink', 33 | content: '', 34 | tags: true 35 | }); 36 | 37 | tab._.data.setContent(require('util').inspect(process, null, 6)); 38 | 39 | screen.key('q', function() { 40 | screen.destroy(); 41 | }); 42 | 43 | screen.render(); 44 | -------------------------------------------------------------------------------- /test/widget-shrink-fail.js: -------------------------------------------------------------------------------- 1 | var blessed = require('blessed'); 2 | var screen = blessed.screen({ 3 | autoPadding: true, 4 | warnings: true 5 | }); 6 | 7 | var tab = blessed.box({ 8 | parent: screen, 9 | top: 2, 10 | left: 0, 11 | right: 0, 12 | bottom: 0, 13 | scrollable: true, 14 | keys: true, 15 | vi: true, 16 | alwaysScroll: true, 17 | scrollbar: { 18 | ch: ' ' 19 | }, 20 | style: { 21 | scrollbar: { 22 | inverse: true 23 | } 24 | } 25 | }); 26 | 27 | var form = blessed.box({ 28 | parent: tab, 29 | top: 0, 30 | left: 1, 31 | right: 1, 32 | //height: 9, 33 | keys: true, 34 | mouse: true, 35 | // XXX Problem: 36 | height: 'shrink', 37 | label: ' {blue-fg}Form{/blue-fg} ', 38 | border: 'line', 39 | tags: true 40 | }); 41 | 42 | form._.ftext = blessed.text({ 43 | parent: form, 44 | top: 0, 45 | left: 0, 46 | height: 1, 47 | content: 'Foo', 48 | tags: true 49 | }); 50 | 51 | form._.foo = blessed.textbox({ 52 | parent: form, 53 | name: 'foo', 54 | inputOnFocus: true, 55 | top: 0, 56 | left: 9, 57 | right: 1, 58 | height: 1, 59 | style: { 60 | bg: 'black', 61 | focus: { 62 | bg: 'blue' 63 | }, 64 | hover: { 65 | bg: 'blue' 66 | } 67 | } 68 | }); 69 | 70 | form._.btext = blessed.text({ 71 | parent: form, 72 | top: 2, 73 | left: 0, 74 | height: 1, 75 | content: 'Bar', 76 | tags: true 77 | }); 78 | 79 | form._.bar = blessed.textbox({ 80 | parent: form, 81 | name: 'bar', 82 | inputOnFocus: true, 83 | top: 2, 84 | left: 9, 85 | right: 1, 86 | height: 1, 87 | style: { 88 | bg: 'black', 89 | focus: { 90 | bg: 'blue' 91 | }, 92 | hover: { 93 | bg: 'blue' 94 | } 95 | } 96 | }); 97 | 98 | form._.ztext = blessed.text({ 99 | parent: form, 100 | top: 4, 101 | left: 0, 102 | height: 1, 103 | content: 'Baz', 104 | tags: true 105 | }); 106 | 107 | form._.baz = blessed.textbox({ 108 | parent: form, 109 | name: 'baz', 110 | inputOnFocus: true, 111 | top: 4, 112 | left: 9, 113 | right: 1, 114 | height: 1, 115 | style: { 116 | bg: 'black', 117 | focus: { 118 | bg: 'blue' 119 | }, 120 | hover: { 121 | bg: 'blue' 122 | } 123 | } 124 | }); 125 | 126 | form._.submit = blessed.button({ 127 | parent: form, 128 | name: 'submit', 129 | top: 6, 130 | right: 1, 131 | height: 1, 132 | //width: 'shrink', 133 | width: 10, 134 | content: 'send', 135 | tags: true, 136 | style: { 137 | bg: 'black', 138 | focus: { 139 | bg: 'blue' 140 | }, 141 | hover: { 142 | bg: 'blue' 143 | } 144 | } 145 | }); 146 | 147 | form._.submit.on('press', function() { 148 | tabs.send._.form.submit(); 149 | }); 150 | 151 | form.on('submit', function(data) { 152 | screen.leave(); 153 | console.log(data); 154 | screen.destroy(); 155 | }); 156 | 157 | screen.key('q', function() { 158 | screen.destroy(); 159 | }); 160 | 161 | screen.render(); 162 | -------------------------------------------------------------------------------- /test/widget-shrink-padding.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/shrink-padding.log', 6 | warnings: true 7 | }); 8 | 9 | var outer = blessed.box({ 10 | parent: screen, 11 | //left: 0, 12 | //top: 0, 13 | //left: '50%', 14 | //top: '50%', 15 | left: 'center', 16 | top: 'center', 17 | padding: 1, 18 | shrink: true, 19 | style: { 20 | bg: 'green' 21 | } 22 | }); 23 | 24 | var inner = blessed.box({ 25 | parent: outer, 26 | left: 0, 27 | top: 0, 28 | //width: 5, 29 | //height: 5, 30 | shrink: true, 31 | content: 'foobar', 32 | //padding: 1, 33 | //content: 'f', 34 | style: { 35 | bg: 'magenta' 36 | } 37 | }); 38 | 39 | screen.key('q', function() { 40 | return screen.destroy(); 41 | }); 42 | 43 | screen.render(); 44 | -------------------------------------------------------------------------------- /test/widget-table.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/table.log', 6 | autoPadding: false, 7 | fullUnicode: true, 8 | warnings: true 9 | }); 10 | 11 | var DU = '杜'; 12 | var JUAN = '鹃'; 13 | 14 | var table = blessed.table({ 15 | //parent: screen, 16 | top: 'center', 17 | left: 'center', 18 | data: null, 19 | border: 'line', 20 | align: 'center', 21 | tags: true, 22 | //width: '80%', 23 | width: 'shrink', 24 | style: { 25 | border: { 26 | fg: 'red' 27 | }, 28 | header: { 29 | fg: 'blue', 30 | bold: true 31 | }, 32 | cell: { 33 | fg: 'magenta' 34 | } 35 | } 36 | }); 37 | 38 | var data1 = [ 39 | [ 'Animals', 'Foods', 'Times' ], 40 | [ 'Elephant', 'Apple', '1:00am' ], 41 | [ 'Bird', 'Orange', '2:15pm' ], 42 | [ 'T-Rex', 'Taco', '8:45am' ], 43 | [ 'Mouse', 'Cheese', '9:05am' ] 44 | ]; 45 | 46 | data1[1][0] = '{red-fg}' + data1[1][0] + '{/red-fg}'; 47 | data1[2][0] += ' (' + DU + JUAN + ')'; 48 | 49 | var data2 = [ 50 | [ 'Animals', 'Foods', 'Times', 'Numbers' ], 51 | [ 'Elephant', 'Apple', '1:00am', 'One' ], 52 | [ 'Bird', 'Orange', '2:15pm', 'Two' ], 53 | [ 'T-Rex', 'Taco', '8:45am', 'Three' ], 54 | [ 'Mouse', 'Cheese', '9:05am', 'Four' ] 55 | ]; 56 | 57 | data2[1][0] = '{red-fg}' + data2[1][0] + '{/red-fg}'; 58 | data2[2][0] += ' (' + DU + JUAN + ')'; 59 | 60 | screen.key('q', function() { 61 | return screen.destroy(); 62 | }); 63 | 64 | table.setData(data2); 65 | screen.append(table); 66 | screen.render(); 67 | 68 | setTimeout(function() { 69 | table.setData(data1); 70 | screen.render(); 71 | }, 3000); 72 | -------------------------------------------------------------------------------- /test/widget-term-blessed.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../'); 2 | 3 | var screen = blessed.screen({ 4 | dump: __dirname + '/logs/termblessed.log', 5 | smartCSR: true, 6 | warnings: true 7 | }); 8 | 9 | var terminal = blessed.terminal({ 10 | parent: screen, 11 | // cursor: 'line', 12 | cursorBlink: true, 13 | screenKeys: false, 14 | top: 'center', 15 | left: 'center', 16 | width: '90%', 17 | height: '90%', 18 | border: 'line', 19 | handler: function() {}, 20 | style: { 21 | fg: 'default', 22 | bg: 'default', 23 | focus: { 24 | border: { 25 | fg: 'green' 26 | } 27 | } 28 | } 29 | }); 30 | 31 | terminal.focus(); 32 | 33 | var term = terminal.term; 34 | 35 | var screen2 = blessed.screen({ 36 | dump: __dirname + '/logs/termblessed2.log', 37 | smartCSR: true, 38 | warnings: true, 39 | input: term, 40 | output: term 41 | }); 42 | 43 | var box1 = blessed.box({ 44 | parent: screen2, 45 | top: 'center', 46 | left: 'center', 47 | width: 20, 48 | height: 10, 49 | border: 'line', 50 | content: 'Hello world' 51 | }); 52 | 53 | screen.key('C-q', function() { 54 | // NOTE: 55 | // not necessary since screen.destroy causes terminal.term to be destroyed 56 | // (screen2's input and output are no longer readable/writable) 57 | // screen2.destroy(); 58 | return screen.destroy(); 59 | }); 60 | 61 | screen2.render(); 62 | screen.render(); 63 | -------------------------------------------------------------------------------- /test/widget-termswitch.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/termswitch.log', 6 | smartCSR: true, 7 | warnings: true 8 | }); 9 | 10 | var lorem = require('fs').readFileSync(__dirname + '/git.diff', 'utf8'); 11 | 12 | var btext = blessed.box({ 13 | parent: screen, 14 | left: 'center', 15 | top: 'center', 16 | width: '80%', 17 | height: '80%', 18 | style: { 19 | bg: 'green' 20 | }, 21 | border: 'line', 22 | content: 'CSR should still work.' 23 | }); 24 | 25 | var text = blessed.scrollabletext({ 26 | parent: screen, 27 | content: lorem, 28 | border: 'line', 29 | left: 'center', 30 | top: 'center', 31 | draggable: true, 32 | width: '50%', 33 | height: '50%', 34 | mouse: true, 35 | keys: true, 36 | vi: true 37 | }); 38 | 39 | text.focus(); 40 | 41 | screen.key('q', function() { 42 | return screen.destroy(); 43 | }); 44 | 45 | screen.render(); 46 | 47 | setTimeout(function() { 48 | // screen.setTerminal('vt100'); 49 | screen.terminal = 'vt100'; 50 | screen.render(); 51 | text.setContent(screen.program._terminal); 52 | screen.render(); 53 | }, 1000); 54 | -------------------------------------------------------------------------------- /test/widget-textarea.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/textarea.log', 6 | fullUnicode: true, 7 | warnings: true 8 | }); 9 | 10 | var box = blessed.textarea({ 11 | parent: screen, 12 | // Possibly support: 13 | // align: 'center', 14 | style: { 15 | bg: 'blue' 16 | }, 17 | height: 'half', 18 | width: 'half', 19 | top: 'center', 20 | left: 'center', 21 | tags: true 22 | }); 23 | 24 | screen.render(); 25 | 26 | screen.key('q', function() { 27 | screen.destroy(); 28 | }); 29 | 30 | screen.key('i', function() { 31 | box.readInput(function() {}); 32 | }); 33 | 34 | screen.key('e', function() { 35 | box.readEditor(function() {}); 36 | }); 37 | -------------------------------------------------------------------------------- /test/widget-unicode.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | , blessed = require('../') 3 | , unicode = blessed.unicode; 4 | 5 | var screen = blessed.screen({ 6 | dump: __dirname + '/logs/unicode.log', 7 | smartCSR: true, 8 | dockBorders: true, 9 | useBCE: true, 10 | fullUnicode: ~process.argv.indexOf('-') ? false : true, 11 | warnings: true 12 | }); 13 | 14 | /** 15 | * Unicode Characters 16 | */ 17 | 18 | // var DU = '杜'; 19 | var DU = unicode.fromCodePoint(0x675C); 20 | 21 | // var JUAN = '鹃'; 22 | var JUAN = unicode.fromCodePoint(0x9E43); 23 | 24 | // one flew over the 杜鹃's nest. 25 | // var DOUBLE = '杜鹃'; 26 | var DOUBLE = DU + JUAN; 27 | 28 | // var SURROGATE_DOUBLE = '𰀀'; 29 | // var SURROGATE_DOUBLE = String.fromCharCode(0xD880, 0xDC00); 30 | // var SURROGATE_DOUBLE = unicode.fromCodePoint(0x30000); 31 | 32 | // var SURROGATE_DOUBLE = '𠀀'; 33 | // var SURROGATE_DOUBLE = String.fromCharCode(0xd840, 0xdc00); 34 | var SURROGATE_DOUBLE = unicode.fromCodePoint(0x20000); 35 | 36 | // var SURROGATE_DOUBLE = '🉐'; 37 | // var SURROGATE_DOUBLE = String.fromCharCode(0xD83C, 0xDE50); 38 | // var SURROGATE_DOUBLE = unicode.fromCodePoint(0x1F250); 39 | 40 | // var SURROGATE_SINGLE = '𝌆'; 41 | // var SURROGATE_SINGLE = String.fromCharCode(0xD834, 0xDF06); 42 | var SURROGATE_SINGLE = unicode.fromCodePoint(0x1D306); 43 | 44 | // var COMBINE_NONSURROGATE = 's̀'.substring(1); // s + combining 45 | var COMBINE_NONSURROGATE = unicode.fromCodePoint(0x0300); 46 | 47 | // var COMBINE = 's𐨁'.substring(1); // s + combining 48 | // var COMBINE = String.fromCharCode(0xD802, 0xDE01); 49 | var COMBINE = unicode.fromCodePoint(0x10A01); 50 | 51 | /** 52 | * Content 53 | */ 54 | 55 | var lorem = fs.readFileSync(__dirname + '/lorem.txt', 'utf8'); 56 | 57 | lorem = lorem.replace(/e/gi, DOUBLE); 58 | //lorem = lorem.replace(/e/gi, DU); 59 | //lorem = lorem.replace(/r/gi, JUAN); 60 | lorem = lorem.replace(/a/gi, SURROGATE_DOUBLE); 61 | lorem = lorem.replace(/o/gi, SURROGATE_SINGLE); 62 | if (~process.argv.indexOf('s')) { 63 | lorem = lorem.replace(/s/gi, 's' + COMBINE); 64 | } else { 65 | lorem = lorem.replace('s', 's' + COMBINE); 66 | } 67 | 68 | // Surrogate pair emoticons from the SMP: 69 | lorem += '\n'; 70 | lorem += 'emoticons: '; 71 | for (var point = 0x1f600; point <= 0x1f64f; point++) { 72 | // These are technically single-width, 73 | // but they _look_ like they should be 74 | // double-width in gnome-terminal (they overlap). 75 | var emoticon = unicode.fromCodePoint(point); 76 | lorem += emoticon + ' '; 77 | } 78 | 79 | /** 80 | * UI 81 | */ 82 | 83 | var main = blessed.box({ 84 | parent: screen, 85 | left: 'center', 86 | top: 'center', 87 | width: '50%', 88 | height: '50%', 89 | style: { 90 | bg: 'grey' 91 | }, 92 | border: 'line', 93 | draggable: true, 94 | tags: true, 95 | // content: '{black-bg}{blue-fg}{bold}' + lorem + '{/}', 96 | // XXX {bold} breaks JUAN! 97 | content: '{black-bg}{light-blue-fg}' + lorem + '{/}', 98 | scrollable: true, 99 | alwaysScroll: true, 100 | keys: true, 101 | vi: true, 102 | mouse: true 103 | }); 104 | 105 | main.focus(); 106 | 107 | screen.key('q', function() { 108 | return screen.destroy(); 109 | }); 110 | 111 | screen.render(); 112 | -------------------------------------------------------------------------------- /test/widget-valign.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../') 2 | , screen; 3 | 4 | screen = blessed.screen({ 5 | dump: __dirname + '/logs/valign.log', 6 | smartCSR: true, 7 | autoPadding: false, 8 | warnings: true 9 | }); 10 | 11 | var box = blessed.box({ 12 | parent: screen, 13 | top: 'center', 14 | left: 'center', 15 | width: '50%', 16 | height: 5, 17 | align: 'center', 18 | valign: 'middle', 19 | // valign: 'bottom', 20 | content: 'Foobar.', 21 | border: 'line' 22 | }); 23 | 24 | screen.key('q', function() { 25 | return screen.destroy(); 26 | }); 27 | 28 | screen.render(); 29 | -------------------------------------------------------------------------------- /test/widget-video.js: -------------------------------------------------------------------------------- 1 | var blessed = require('../'); 2 | var fs = require('fs'); 3 | 4 | var screen = blessed.screen({ 5 | tput: true, 6 | smartCSR: true, 7 | dump: __dirname + '/logs/video.log', 8 | warnings: true 9 | }); 10 | 11 | var video = blessed.video({ 12 | parent: screen, 13 | left: 1, 14 | top: 1, 15 | width: '90%', 16 | height: '90%', 17 | border: 'line', 18 | file: process.argv[2] 19 | }); 20 | 21 | video.focus(); 22 | 23 | screen.render(); 24 | 25 | screen.key(['q', 'C-q', 'C-c'], function() { 26 | screen.destroy(); 27 | }); 28 | -------------------------------------------------------------------------------- /usr/fonts/AUTHORS: -------------------------------------------------------------------------------- 1 | Dimitar Zhekov 2 | -------------------------------------------------------------------------------- /usr/fonts/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Dimitar Toshkov Zhekov, 2 | with Reserved Font Name "Terminus Font". 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: 6 | http://scripts.sil.org/OFL 7 | 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide 15 | development of collaborative font projects, to support the font creation 16 | efforts of academic and linguistic communities, and to provide a free and 17 | open framework in which fonts may be shared and improved in partnership 18 | with others. 19 | 20 | The OFL allows the licensed fonts to be used, studied, modified and 21 | redistributed freely as long as they are not sold by themselves. The 22 | fonts, including any derivative works, can be bundled, embedded, 23 | redistributed and/or sold with any software provided that any reserved 24 | names are not used by derivative works. The fonts and derivatives, 25 | however, cannot be released under any other type of license. The 26 | requirement for fonts to remain under this license does not apply 27 | to any document created using the fonts or their derivatives. 28 | 29 | DEFINITIONS 30 | "Font Software" refers to the set of files released by the Copyright 31 | Holder(s) under this license and clearly marked as such. This may 32 | include source files, build scripts and documentation. 33 | 34 | "Reserved Font Name" refers to any names specified as such after the 35 | copyright statement(s). 36 | 37 | "Original Version" refers to the collection of Font Software components as 38 | distributed by the Copyright Holder(s). 39 | 40 | "Modified Version" refers to any derivative made by adding to, deleting, 41 | or substituting -- in part or in whole -- any of the components of the 42 | Original Version, by changing formats or by porting the Font Software to a 43 | new environment. 44 | 45 | "Author" refers to any designer, engineer, programmer, technical 46 | writer or other person who contributed to the Font Software. 47 | 48 | PERMISSION & CONDITIONS 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 51 | redistribute, and sell modified and unmodified copies of the Font 52 | Software, subject to the following conditions: 53 | 54 | 1) Neither the Font Software nor any of its individual components, 55 | in Original or Modified Versions, may be sold by itself. 56 | 57 | 2) Original or Modified Versions of the Font Software may be bundled, 58 | redistributed and/or sold with any software, provided that each copy 59 | contains the above copyright notice and this license. These can be 60 | included either as stand-alone text files, human-readable headers or 61 | in the appropriate machine-readable metadata fields within text or 62 | binary files as long as those fields can be easily viewed by the user. 63 | 64 | 3) No Modified Version of the Font Software may use the Reserved Font 65 | Name(s) unless explicit written permission is granted by the corresponding 66 | Copyright Holder. This restriction only applies to the primary font name as 67 | presented to the users. 68 | 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 70 | Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except to acknowledge the contribution(s) of the 72 | Copyright Holder(s) and the Author(s) or with their explicit written 73 | permission. 74 | 75 | 5) The Font Software, modified or unmodified, in part or in whole, 76 | must be distributed entirely under this license, and must not be 77 | distributed under any other license. The requirement for fonts to 78 | remain under this license does not apply to any document created 79 | using the Font Software. 80 | 81 | TERMINATION 82 | This license becomes null and void if any of the above conditions are 83 | not met. 84 | 85 | DISCLAIMER 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 94 | OTHER DEALINGS IN THE FONT SOFTWARE. 95 | -------------------------------------------------------------------------------- /usr/linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chjj/blessed/eab243fc7ad27f1d2932db6134f7382825ee3488/usr/linux -------------------------------------------------------------------------------- /usr/windows-ansi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chjj/blessed/eab243fc7ad27f1d2932db6134f7382825ee3488/usr/windows-ansi -------------------------------------------------------------------------------- /usr/xterm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chjj/blessed/eab243fc7ad27f1d2932db6134f7382825ee3488/usr/xterm -------------------------------------------------------------------------------- /usr/xterm-256color: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chjj/blessed/eab243fc7ad27f1d2932db6134f7382825ee3488/usr/xterm-256color --------------------------------------------------------------------------------