Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Textarea cursor movements #445

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions lib/widgets/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
* Modules
*/

var Node = require('./node');
var Element = require('./element');
var Node = require("./node");
var Element = require("./element");

/**
* Text
Expand All @@ -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
Expand Down
220 changes: 203 additions & 17 deletions lib/widgets/textarea.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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') {
Expand All @@ -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);
}
}
}

Expand All @@ -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() {
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down