From 771ff1d0f246774ebf9423804a8a2d825dbe23ed Mon Sep 17 00:00:00 2001 From: Krishna Acondy Date: Thu, 1 Apr 2021 02:07:29 +0100 Subject: [PATCH] fix(text-input): make cursor reflect current position (#300) --- lib/elements/text.js | 31 ++++++++++++++++++++--- test/text.js | 59 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 test/text.js diff --git a/lib/elements/text.js b/lib/elements/text.js index eee731b5..ee78181a 100644 --- a/lib/elements/text.js +++ b/lib/elements/text.js @@ -25,6 +25,7 @@ class TextPrompt extends Prompt { this.value = ``; this.errorMsg = opts.error || `Please Enter A Valid Value`; this.cursor = Number(!!this.initial); + this.cursorOffset = 0; this.clear = clear(``, this.out.columns); this.render(); } @@ -48,6 +49,7 @@ class TextPrompt extends Prompt { reset() { this.value = ``; this.cursor = Number(!!this.initial); + this.cursorOffset = 0; this.fire(); this.render(); } @@ -78,6 +80,8 @@ class TextPrompt extends Prompt { async submit() { this.value = this.value || this.initial; + this.cursorOffset = 0; + this.cursor = this.rendered.length; await this.validate(); if (this.error) { this.red = true; @@ -104,6 +108,7 @@ class TextPrompt extends Prompt { moveCursor(n) { if (this.placeholder) return; this.cursor = this.cursor+n; + this.cursorOffset += n; } _(c, key) { @@ -116,12 +121,17 @@ class TextPrompt extends Prompt { } delete() { - if (this.cursor === 0) return this.bell(); + if (this.isCursorAtStart()) return this.bell(); let s1 = this.value.slice(0, this.cursor-1); let s2 = this.value.slice(this.cursor); this.value = `${s1}${s2}`; this.red = false; - this.moveCursor(-1); + if (this.isCursorAtStart()) { + this.cursorOffset = 0 + } else { + this.cursorOffset++; + this.moveCursor(-1); + } this.render(); } @@ -131,6 +141,11 @@ class TextPrompt extends Prompt { let s2 = this.value.slice(this.cursor+1); this.value = `${s1}${s2}`; this.red = false; + if (this.isCursorAtEnd()) { + this.cursorOffset = 0; + } else { + this.cursorOffset++; + } this.render(); } @@ -156,6 +171,14 @@ class TextPrompt extends Prompt { this.render(); } + isCursorAtStart() { + return this.cursor === 0 || (this.placeholder && this.cursor === 1); + } + + isCursorAtEnd() { + return this.cursor === this.rendered.length || (this.placeholder && this.cursor === this.rendered.length + 1) + } + render() { if (this.closed) return; if (!this.firstRender) { @@ -178,8 +201,8 @@ class TextPrompt extends Prompt { .reduce((a, l, i) => a + `\n${i ? ' ' : figures.pointerSmall} ${color.red().italic(l)}`, ``); } - this.out.write(erase.line + cursor.to(0) + this.outputText + cursor.save + this.outputError + cursor.restore); + this.out.write(erase.line + cursor.to(0) + this.outputText + cursor.save + this.outputError + cursor.restore + cursor.move(this.cursorOffset, 0)); } } -module.exports = TextPrompt; +module.exports = TextPrompt; \ No newline at end of file diff --git a/test/text.js b/test/text.js new file mode 100644 index 00000000..37d7493c --- /dev/null +++ b/test/text.js @@ -0,0 +1,59 @@ +'use strict'; + +const test = require('tape'); +const TextPrompt = require('../lib/elements/text'); + +test('move', (t) => { + t.plan(6); + + const textPrompt = new TextPrompt(); + textPrompt.value = 'Hello, world!'; + textPrompt.last() + textPrompt.render() + + t.same(textPrompt.cursorOffset, 0, 'cursorOffset is 0 at start'); + t.same(textPrompt.cursor, textPrompt.rendered.length, 'cursor starts at 0') + + textPrompt.right(); + t.same(textPrompt.cursorOffset, 0, 'cursorOffset is unaffected when moved right from the end'); + t.same(textPrompt.cursor, textPrompt.rendered.length, 'cursor is unaffected when moved right from the end') + + textPrompt.left(); + t.equal(textPrompt.cursorOffset, -1, 'cursorOffset is -1 when moved left'); + + textPrompt.right(); + t.equal(textPrompt.cursorOffset, 0, 'cursorOffset is 0 when moved left'); + + t.end(); +}); + +test('delete', (t) => { + t.plan(4); + + const textPrompt = new TextPrompt(); + textPrompt.value = 'Hello, world!'; + textPrompt.last(); + textPrompt.render(); + + textPrompt.delete(); + t.same(textPrompt.cursorOffset, 0, 'cursorOffset is 0 after delete'); + t.same(textPrompt.cursor, textPrompt.rendered.length, 'cursor stays at end of line') + + textPrompt.left(); + textPrompt.deleteForward() + t.same(textPrompt.cursorOffset, 0, 'cursorOffset is 0 after deleteForward'); + t.same(textPrompt.cursor, textPrompt.rendered.length, 'cursor stays at end of line') + + textPrompt.submit(); + t.end() +}); + +test('submit', (t) => { + t.plan(2) + const textPrompt = new TextPrompt(); + textPrompt.value = 'Hello, world!'; + textPrompt.submit() + + t.same(textPrompt.cursorOffset, 0, 'cursorOffset is reset on submit') + t.same(textPrompt.cursor, textPrompt.rendered.length, 'cursor is reset to end of line on submit') +})