diff --git a/lib/program.js b/lib/program.js index ac82f16..7d722d5 100644 --- a/lib/program.js +++ b/lib/program.js @@ -1105,6 +1105,63 @@ Program.prototype.response = function(name, text, callback) { return this.write(text); }; +Program.prototype._wrapCursor = function(nl) { + if (this.tput) { + //if (this.tput.bools.eat_newline_glitch) { + // return; + //} + if (this.tput.bools.auto_right_margin) { + this.x = 0; + this.y++; + return; + } + this.x--; + return; + } + this.x = 0; + this.y++; +}; + +// TODO: Make a private write for all of the methods here +// which does not call _parseChar. Make public write call +// _parseChar. +Program.prototype._parseChar = function(text, attr) { + for (var i = 0; i < text.length; i++) { + var cs = false; + while (text[i] === '\x1b') { + i++; + if (text[i] === '[' || text === ']') { + cs = true; + continue; + } + if (cs && (text[i] === ';' || (text[i] >= '0' && text[i] <= '9'))) { + continue; + } + if (text[i] === '\\' && text[i + 1] === '\x1b') i++; + i++; + cs = false; + break; + } + if (text[i] === '\n') { + if (this.tput + && this.tput.bools.eat_newline_glitch + && this.x >= this.cols) { + ; + } else { + this.x = 0; + this.y++; + } + } else if (text[i] === '\r') { + this.x = 0; + } else { + this.x++; + if (this.x >= this.width) { + this._wrapCursor(); + } + } + } +}; + Program.prototype.write = Program.prototype.echo = function(text, attr) { // if (this.output === process.stdout) { @@ -1261,6 +1318,9 @@ Program.prototype.return = function() { Program.prototype.nel = Program.prototype.newline = Program.prototype.feed = function() { + if (this.tput && this.tput.bools.eat_newline_glitch && this.x >= this.width) { + return; + } this.x = 0; this.y++; this._ncoords(); diff --git a/lib/widget.js b/lib/widget.js index 8967662..5f825b7 100644 --- a/lib/widget.js +++ b/lib/widget.js @@ -677,6 +677,64 @@ Screen.prototype.deleteLine = function(n, y, top, bottom) { } }; +// This is how ncurses does it. +// Scroll down (up cursor-wise). +// This will only work for top line deletion as opposed to arbitrary lines. +Screen.prototype.insertLineNC = function(n, y, top, bottom) { + if (!this.tput + || !this.tput.strings.change_scroll_region + || !this.tput.strings.delete_line + || !this.tput.strings.insert_line) return; + + if (y !== top) return this.insertLine.apply(this, arguments); + + this.program.sc(); + this.program.csr(top, bottom); + this.program.cup(top, 0); + this.program.dl(n); + this.program.csr(0, this.height - 1); + // this.program.cup(top, 0); + this.program.rc(); + + var j = bottom + 1; + + while (n--) { + this.lines.splice(j, 0, this.blankLine()); + this.lines.splice(y, 1); + this.olines.splice(j, 0, this.blankLine()); + this.olines.splice(y, 1); + } +}; + +// This is how ncurses does it. +// Scroll up (down cursor-wise). +// This will only work for bottom line deletion as opposed to arbitrary lines. +Screen.prototype.deleteLineNC = function(n, y, top, bottom) { + if (!this.tput + || !this.tput.strings.change_scroll_region + || !this.tput.strings.delete_line + || !this.tput.strings.insert_line) return; + + if (y !== bottom) return this.deleteLine.apply(this, arguments); + + this.program.sc(); + this.program.csr(top, bottom); + this.program.cup(bottom, 0); + this.program.write(Array(n + 1).join('\n')); + this.program.csr(0, this.height - 1); + // this.program.cup(bottom - 1, 0); + this.program.rc(); + + var j = bottom + 1; + + while (n--) { + this.lines.splice(j, 0, this.blankLine()); + this.lines.splice(y, 1); + this.olines.splice(j, 0, this.blankLine()); + this.olines.splice(y, 1); + } +}; + Screen.prototype.insertBottom = function(top, bottom) { return this.deleteLine(1, top, top, bottom); }; @@ -768,12 +826,18 @@ Screen.prototype.draw = function(start, end) { , attr , fg , bg - , flags; + , flags + , acs + , bc; var lx = -1 , ly = -1 , o; + var bcx = -1 + , bcy = -1 + , bca = -1; + // var cx = this.program.x // , cy = this.program.y // , ch = this.program.cursorHidden; @@ -797,6 +861,65 @@ Screen.prototype.draw = function(start, end) { data = line[x][0]; ch = line[x][1]; + // Take advantage of xterm's back_color_erase + // feature by using a lookahead. + if (this.tput.bools.back_color_erase && ch === ' ' && line[x + 1]) { + var ne = false + , cl = true; + + for (var xi = x; xi < this.cols; xi++) { + if (line[xi][0] !== data || line[xi][1] !== ' ') { + cl = false; + break; + } + if (line[xi][0] !== o[xi][0] || line[xi][1] !== o[xi][1]) { + ne = true; + } + } + + if (cl && ne) { + if (data !== attr) { + out += this.codeAttr(data); + attr = data; + } + out += this.tput.el(0); + for (var xi = x; xi < this.cols; xi++) { + o[xi][0] = data; + o[xi][1] = ' '; + } + break; + } + + // comment the break in the first xi loop above for this to work. + // if (!ne) { + // attr = data; + // break; + // } + } + + // Take advantage of xterm's back_color_erase feature. + // Stop spitting out so many damn spaces. + if (0) if (this.tput.bools.back_color_erase) { + if (ch === ' ' && (bca === -1 || bca === data)) { + if (bcy === -1) { + bcx = x; + bcy = y; + bca = data; + } + //ch = ''; + } else if (bcy !== -1) { + // If it's more than ten spaces, just EL and jump forward. + // Might break things since it could alter the output buffer. + // out += x - bcx > 10 + // ? this.tput.el(0) + this.tput.cuf(x - bcx) + // : Array((x - bcx) + 1).join(' '); + out += Array((x - bcx) + 1).join(' '); + bcx = -1, bcy = -1, bca = -1; + } + } + + // Optimize by comparing the real output + // buffer to the pending output buffer. if (data === o[x][0] && ch === o[x][1]) { if (lx === -1) { lx = x; @@ -890,6 +1013,7 @@ Screen.prototype.draw = function(start, end) { } } +/* // Attempt to use ACS for supported characters. // This is not ideal, but it's how ncurses works. // There are a lot of terminals that support ACS @@ -929,15 +1053,50 @@ Screen.prototype.draw = function(start, end) { // ch = this.tput.utoa[ch] || '?'; // } } +*/ + + if (this.tput.strings.enter_alt_charset_mode) { + if (!this.tput.brokenACS || !this.tput.unicode) { + if (this.tput.acscr[ch]) { + if (acs) { + ch = this.tput.acscr[ch]; + } else { + ch = this.tput.smacs() + + this.tput.acscr[ch] + acs = true; + } + } else if (acs) { + ch = this.tput.rmacs() + ch; + acs = false; + } + } + } else { + // if (this.program.term('sun') && ch > '~') { + // if (this.tput.numbers.U8 !== 1 && ch > '~') { + if (this.tput.numbers.U8 !== 1 && this.utoa[ch]) { + ch = this.tput.utoa[ch] || '?'; + } + } + + if (0) if (bcy !== -1) ch = ''; out += ch; attr = data; } + if (0) if (bcy !== -1) { + out += this.tput.el(0); + bcx = -1, bcy = -1, bca = -1; + } + if (attr !== this.dattr) { out += '\x1b[m'; } + //if (this.tput.bools.back_color_erase && out && lx === -1) { + // out = this._replaceSpaces(out); + //} + if (this.tput) { if (out) this.program.write(this.tput.cup(y, 0) + out); } else { @@ -951,6 +1110,27 @@ Screen.prototype.draw = function(start, end) { this.program.restoreCursor(); }; +Screen.prototype._replaceSpaces = function(out) { + if (!this._sr) this._sr = this._spaceReplacer.bind(this); + return out.replace(/ {5,}/g, this._sr); +}; + +Screen.prototype._spaceReplacer = function(ch) { + return this.tput.el(0) + this.tput.cuf(ch.length); +}; + +Screen.prototype._replaceSpaces = function(out) { + if (!this._sr) this._sr = this._spaceReplacer.bind(this); + // / +(?=(?:\x1b\[m)?(?:\x1b\[[\d;]*H|$))/g, + return out.replace( + / {5,}(?=(?:\x1b\[[\d;]*m)*(?:\x1b\[[\d;]*H|$))/g, + this._sr); +}; + +Screen.prototype._spaceReplacer = function(ch) { + return this.tput.el(0); +}; + Screen.prototype._reduceColor = function(col) { if (this.tput) { if (col >= 16 && this.tput.colors <= 16) { @@ -1633,7 +1813,7 @@ Element.prototype._focus = function() { , visible = el.height - el.iheight; if (ryi < el.childBase) { - el.scrollTo(ryi - 0); + el.scrollTo(ryi); this.screen.render(); } else if (ryi >= el.childBase + visible) { el.scrollTo(ryi); diff --git a/test/widget-form.js b/test/widget-form.js index 04288c6..1811da6 100644 --- a/test/widget-form.js +++ b/test/widget-form.js @@ -12,7 +12,7 @@ var form = blessed.form({ height: 12, bg: 'green', content: 'foobar', - border: { + border_: { type: 'ch', ch: ' ', style: { inverse: true }