diff --git a/lib/widgets/text.js b/lib/widgets/text.js index c3b44b22..5ecde3f4 100644 --- a/lib/widgets/text.js +++ b/lib/widgets/text.js @@ -8,8 +8,8 @@ * Modules */ -var Node = require('./node'); -var Element = require('./element'); +var Node = require("./node"); +var Element = require("./element"); /** * Text @@ -20,13 +20,13 @@ function Text(options) { return new Text(options); } options = options || {}; - options.shrink = true; + options.shrink = "shrink" in options ? options.shrink : true; Element.call(this, options); } Text.prototype.__proto__ = Element.prototype; -Text.prototype.type = 'text'; +Text.prototype.type = "text"; /** * Expose diff --git a/lib/widgets/textarea.js b/lib/widgets/textarea.js index dc94609c..67ac49c0 100644 --- a/lib/widgets/textarea.js +++ b/lib/widgets/textarea.js @@ -34,6 +34,9 @@ function Textarea(options) { this.screen._listenKeys(this); + this.offsetY = 0; + this.offsetX = 0; + this.value = options.value || ''; this.__updateCursor = this._updateCursor.bind(this); @@ -69,15 +72,48 @@ Textarea.prototype.__proto__ = Input.prototype; Textarea.prototype.type = 'textarea'; +Textarea.prototype.getCursor = function(){ + return {x: this.offsetX, y: this.offsetY}; +} + +Textarea.prototype.setCursor = function(x, y){ + this.offsetX = x; + this.offsetY = y; +} + +Textarea.prototype.moveCursor = function(x, y){ + var prevLine = (this._clines.length - 1) + this.offsetY; + let sync = false; + if (y <= 0 && y > (this._clines.length * -1)){ + sync = this.offsetY !== y; + this.offsetY = y; + } + var currentLine = (this._clines.length - 1) + this.offsetY; + var currentText = this._clines[currentLine]; + + if (sync){ + var prevText = this._clines[prevLine]; + var positionFromBegin = Math.max(this.strWidth(prevText) + this.offsetX, 0); + x = (Math.max(0, this.strWidth(currentText) - positionFromBegin)) * -1; + } + if (x <= 0 && x >= (this.strWidth(currentText) * -1)){ + this.offsetX = x; + } + this._updateCursor(true); + this.screen.render(); +} + Textarea.prototype._updateCursor = function(get) { if (this.screen.focused !== this) { return; } var lpos = get ? this.lpos : this._getCoords(); + if (!lpos) return; - var last = this._clines[this._clines.length - 1] + const currentLine = (this._clines.length - 1) + this.offsetY + var currentText = this._clines[currentLine] , program = this.screen.program , line , cx @@ -87,12 +123,12 @@ Textarea.prototype._updateCursor = function(get) { // and the last cline appears to always be empty from the // _typeScroll `+ '\n'` thing. // Maybe not necessary anymore? - if (last === '' && this.value[this.value.length - 1] !== '\n') { - last = this._clines[this._clines.length - 2] || ''; + if (currentText === '' && this.value[this.value.length - 1] !== '\n') { + //currentText = this._clines[currentLine - 1] || ''; } line = Math.min( - this._clines.length - 1 - (this.childBase || 0), + currentLine - (this.childBase || 0), (lpos.yl - lpos.yi) - this.iheight - 1); // When calling clearValue() on a full textarea with a border, the first @@ -101,7 +137,7 @@ Textarea.prototype._updateCursor = function(get) { line = Math.max(0, line); cy = lpos.yi + this.itop + line; - cx = lpos.xi + this.ileft + this.strWidth(last); + cx = this.offsetX + lpos.xi + this.ileft + this.strWidth(currentText); // XXX Not sure, but this may still sometimes // cause problems when leaving editor. @@ -214,11 +250,33 @@ Textarea.prototype._listener = function(ch, key) { if (key.name === 'enter') { ch = '\n'; } + const cursor = this.getCursor(); // TODO: Handle directional keys. if (key.name === 'left' || key.name === 'right' - || key.name === 'up' || key.name === 'down') { - ; + || key.name === 'up' || key.name === 'down' + || key.name === 'end'|| key.name === 'home') { + + if (key.name === "left") { + cursor.x--; + } else if (key.name === "right") { + cursor.x++; + } + if (key.name === "up") { + cursor.y--; + } else if (key.name === "down") { + cursor.y++; + } + + if (key.name === "end") { + cursor.x = 0; + } else if (key.name === "home") { + const currentLine = (this._clines.length - 1) + this.offsetY + const currentLineLength = this.strWidth(this._clines[currentLine] ?? '') + cursor.x = -currentLineLength; + } + + this.moveCursor(cursor.x, cursor.y); } if (this.options.keys && key.ctrl && key.name === 'e') { @@ -232,19 +290,146 @@ Textarea.prototype._listener = function(ch, key) { } else if (key.name === 'backspace') { if (this.value.length) { if (this.screen.fullUnicode) { - if (unicode.isSurrogate(this.value, this.value.length - 2)) { - // || unicode.isCombining(this.value, this.value.length - 1)) { - this.value = this.value.slice(0, -2); - } else { + } else { + if (cursor.x === 0 && cursor.y === 0){ this.value = this.value.slice(0, -1); + } else { + + const realLines = this._clines.real.slice(); + const fakeLines = this._clines.fake.slice(); + const mapper = this._clines.rtof; + + const currentLine = (realLines.length - 1) + cursor.y; + + const fakeLineIndex = mapper[currentLine]; + + let fakeCursorPosition = 0; + for (let i = 0; i <= currentLine; i++) { + if (mapper[i] === fakeLineIndex) { + fakeCursorPosition += this.strWidth(realLines[i]); + } + } + fakeCursorPosition += cursor.x; + + let realCursorPosition = this.strWidth(realLines[currentLine]) + cursor.x; + + if (fakeLines[fakeLineIndex] === ''){ + fakeLines.splice(fakeLineIndex, 1); + } else if (cursor.x === -this.strWidth(realLines[currentLine])) { + if (currentLine > 0){ + const lineLengthBefore = this.strWidth(realLines[currentLine - 1] ?? '') + + if (mapper[currentLine] !== mapper[currentLine - 1]){ + const currentLineString = fakeLines.splice(fakeLineIndex, 1); + fakeLines[fakeLineIndex - 1] += currentLineString; + } else { + + } + + const predict = this._wrapContent(fakeLines.join('\n'), this.width - this.iwidth) + + cursor.x = -(this.strWidth(predict[currentLine - 1] ?? '') - lineLengthBefore); + if (predict.real.length === realLines.length){ + cursor.y--; + } + } + } else { + fakeLines[fakeLineIndex] = fakeLines[fakeLineIndex].slice(0, fakeCursorPosition - 1) + fakeLines[fakeLineIndex].slice(fakeCursorPosition); + const predict = this._wrapContent(fakeLines.join('\n'), this.width - this.iwidth) + cursor.x = -(this.strWidth(predict.real[currentLine]) - realCursorPosition + 1); + if (predict.real.length !== realLines.length){ + cursor.y++; + } + + } + this.value = fakeLines.join('\n'); + this.setCursor(cursor.x, cursor.y); } + } + } + } else if (key.name === 'delete') { + if (this.value.length) { + if (this.screen.fullUnicode) { } else { - this.value = this.value.slice(0, -1); + const currentLine = (this._clines.length - 1) + cursor.y + if (cursor.x === 0 && cursor.y === 0){ + + } else { + const realLines = this._clines.real.slice(); + const fakeLines = this._clines.fake.slice(); + const mapper = this._clines.rtof; + + const currentLine = (realLines.length - 1) + cursor.y + + const fakeLineIndex = mapper[currentLine]; + + let fakeCursorPosition = 0; + for (let i = 0; i <= currentLine; i++) { + if (mapper[i] === fakeLineIndex) { + fakeCursorPosition += this.strWidth(realLines[i]); + } + } + fakeCursorPosition += cursor.x; + + let realCursorPosition = this.strWidth(realLines[currentLine]) + cursor.x; + + if (fakeLines[fakeLineIndex] === ''){ + const nextLineLength = this.strWidth(fakeLines[fakeLineIndex + 1] ?? '') + fakeLines.splice(fakeLineIndex, 1); + cursor.y++; + cursor.x = -nextLineLength; + } else { + if (fakeLineIndex < fakeLines.length - 1){ + if (cursor.x === -this.strWidth(realLines[currentLine])) { + fakeLines[fakeLineIndex] = fakeLines[fakeLineIndex].substring(1); + } else{ + fakeLines[fakeLineIndex] = fakeLines[fakeLineIndex].slice(0, fakeCursorPosition) + fakeLines[fakeLineIndex].slice(fakeCursorPosition + 1); + } + const predict = this._wrapContent(fakeLines.join('\n'), this.width - this.iwidth) + cursor.x = -(this.strWidth(predict.real[currentLine]) - realCursorPosition); + if (predict.real.length !== realLines.length){ + cursor.y++; + } + } + } + this.value = fakeLines.join('\n'); + this.setCursor(cursor.x, cursor.y); + } } } } else if (ch) { if (!/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(ch)) { - this.value += ch; + if (cursor.x === 0 && cursor.y === 0){ + this.value += ch; + } else if (cursor.x >= (this.value.length * -1)) { + const realLines = this._clines.real.slice(); + const fakeLines = this._clines.fake.slice(); + const mapper = this._clines.rtof; + + const currentLine = (realLines.length - 1) + cursor.y + + const fakeLineIndex = mapper[currentLine]; + let fakeCursorPosition = 0; + for (let i = 0; i <= currentLine; i++) { + if (mapper[i] === fakeLineIndex) { + fakeCursorPosition += this.strWidth(realLines[i]); + } + } + fakeCursorPosition += cursor.x; + + fakeLines[fakeLineIndex] = fakeLines[fakeLineIndex].slice(0, fakeCursorPosition) + ch + fakeLines[fakeLineIndex].slice(fakeCursorPosition); + + const predict = this._wrapContent(fakeLines.join('\n'), this.width - this.iwidth) + if (ch === '\n'){ + if (predict.real.length === realLines.length){ + cursor.y++; + } + cursor.x = -this.strWidth(predict[predict.length - 1 + cursor.y]); + } + + this.value = fakeLines.join('\n'); + this.setCursor(cursor.x, cursor.y); + } } } @@ -255,10 +440,11 @@ Textarea.prototype._listener = function(ch, key) { Textarea.prototype._typeScroll = function() { // XXX Workaround - var height = this.height - this.iheight; - if (this._clines.length - this.childBase > height) { - this.scroll(this._clines.length); - } + //var height = this.height - this.iheight; + //if (this._clines.length - this.childBase > height) { + const currentLine = (this._clines.length - 1) + this.offsetY; + this.setScroll(currentLine); + //} }; Textarea.prototype.getValue = function() { diff --git a/package.json b/package.json index 7e923b39..f650dd96 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ { - "name": "blessed", - "description": "A high-level terminal interface library for node.js.", + "name": "semi-blessed", + "description": "A fork of BLESSED library with extra features.", "author": "Christopher Jeffrey", - "version": "0.1.81", + "version": "1.0.5", "license": "MIT", "main": "./lib/blessed.js", "bin": "./bin/tput.js", "preferGlobal": false, - "repository": "git://github.com/chjj/blessed.git", - "homepage": "https://github.com/chjj/blessed", - "bugs": { "url": "http://github.com/chjj/blessed/issues" }, + "repository": "git://github.com/freeart/blessed.git", + "homepage": "https://github.com/freeart/blessed", + "bugs": { "url": "http://github.com/freeart/blessed/issues" }, "keywords": ["curses", "tui", "tput", "terminfo", "termcap"], "tags": ["curses", "tui", "tput", "terminfo", "termcap"], "engines": {