Skip to content
Draft
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
42 changes: 32 additions & 10 deletions lib/elements/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ class TextPrompt extends Prompt {
this.errorMsg = opts.error || `Please Enter A Valid Value`;
this.cursor = Number(!!this.initial);
this.cursorOffset = 0;
this.hasStartedEditing = false;
this.clear = clear(``, this.out.columns);
this.render();
}

set value(v) {
if (!v && this.initial) {
if (!v && this.initial && !this.hasStartedEditing) {
this.placeholder = true;
this.rendered = color.gray(this.transform.render(this.initial));
} else {
Expand Down Expand Up @@ -105,22 +106,33 @@ class TextPrompt extends Prompt {
this.render();
}

// Convert placeholder to editable text if needed
convertPlaceholder() {
if (this.placeholder) {
this.hasStartedEditing = true;
this.value = this.initial;
this.cursor = this.value.length;
this.cursorOffset = 0;
}
}

moveCursor(n) {
if (this.placeholder) return;
this.cursor = this.cursor+n;
this.cursorOffset += n;
}

_(c, key) {
this.convertPlaceholder();
let s1 = this.value.slice(0, this.cursor);
let s2 = this.value.slice(this.cursor);
this.value = `${s1}${c}${s2}`;
this.red = false;
this.cursor = this.placeholder ? 0 : s1.length+1;
this.cursor = s1.length + 1;
this.render();
}

delete() {
this.convertPlaceholder();
if (this.isCursorAtStart()) return this.bell();
let s1 = this.value.slice(0, this.cursor-1);
let s2 = this.value.slice(this.cursor);
Expand All @@ -136,7 +148,8 @@ class TextPrompt extends Prompt {
}

deleteForward() {
if(this.cursor*this.scale >= this.rendered.length || this.placeholder) return this.bell();
this.convertPlaceholder();
if (this.isCursorAtEnd()) return this.bell();
let s1 = this.value.slice(0, this.cursor);
let s2 = this.value.slice(this.cursor+1);
this.value = `${s1}${s2}`;
Expand All @@ -150,7 +163,8 @@ class TextPrompt extends Prompt {
}

deleteToStart() {
if (this.isCursorAtStart() || this.placeholder) return this.bell();
this.convertPlaceholder();
if (this.isCursorAtStart()) return this.bell();
let s2 = this.value.slice(this.cursor);
this.value = s2;
this.red = false;
Expand All @@ -160,7 +174,8 @@ class TextPrompt extends Prompt {
}

deleteWord() {
if (this.isCursorAtStart() || this.placeholder) return this.bell();
this.convertPlaceholder();
if (this.isCursorAtStart()) return this.bell();
let s1 = this.value.slice(0, this.cursor);
let s2 = this.value.slice(this.cursor);

Expand All @@ -187,33 +202,40 @@ class TextPrompt extends Prompt {
}

first() {
this.convertPlaceholder();
if(this.isCursorAtStart()) return this.bell();
this.cursor = 0;
this.cursorOffset = 0;
this.render();
}

last() {
this.convertPlaceholder();
this.cursor = this.value.length;
this.cursorOffset = 0;
this.render();
}

left() {
if (this.cursor <= 0 || this.placeholder) return this.bell();
this.convertPlaceholder();
if (this.cursor <= 0) return this.bell();
this.moveCursor(-1);
this.render();
}

right() {
if (this.cursor*this.scale >= this.rendered.length || this.placeholder) return this.bell();
this.convertPlaceholder();
if (this.cursor*this.scale >= this.rendered.length) return this.bell();
this.moveCursor(1);
this.render();
}

isCursorAtStart() {
return this.cursor === 0 || (this.placeholder && this.cursor === 1);
return this.cursor === 0;
}

isCursorAtEnd() {
return this.cursor === this.rendered.length || (this.placeholder && this.cursor === this.rendered.length + 1)
return this.cursor*this.scale >= this.rendered.length;
}

render() {
Expand Down
169 changes: 169 additions & 0 deletions test/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,172 @@ test('deleteWord', (t) => {
textPrompt.submit();
t.end();
});

test('initial value creates placeholder', (t) => {
t.plan(3);

const textPrompt = new TextPrompt({ message: 'Test', initial: 'initial value' });

t.ok(textPrompt.placeholder, 'placeholder is true');
t.same(textPrompt.value, '', 'value is empty');
t.same(textPrompt.initial, 'initial value', 'initial value is set');

t.end();
});

test('typing converts placeholder and edits initial value', (t) => {
t.plan(5);

const textPrompt = new TextPrompt({ message: 'Test', initial: 'terkelg' });

// Initially placeholder
t.ok(textPrompt.placeholder, 'starts with placeholder');
t.same(textPrompt.value, '', 'value is empty initially');

// Type a character
textPrompt._('a', {});

t.notOk(textPrompt.placeholder, 'placeholder is false after typing');
t.same(textPrompt.value, 'terkelga', 'value is initial + typed character');
t.ok(textPrompt.hasStartedEditing, 'hasStartedEditing is true');

t.end();
});

test('deleting all characters does not restore placeholder', (t) => {
t.plan(4);

const textPrompt = new TextPrompt({ message: 'Test', initial: 'terkelg' });

// Start editing
textPrompt._('a', {});
t.same(textPrompt.value, 'terkelga', 'value is initial + typed character');

// Delete all characters - move to end first, then delete until empty
textPrompt.last();
while (textPrompt.value.length > 0) {
textPrompt.delete();
}

t.same(textPrompt.value, '', 'value is empty after deleting all');
t.notOk(textPrompt.placeholder, 'placeholder does not return');
t.ok(textPrompt.hasStartedEditing, 'hasStartedEditing remains true');

t.end();
});

test('deleteToStart with placeholder converts and works', (t) => {
t.plan(4);

const textPrompt = new TextPrompt({ message: 'Test', initial: 'hello world' });

t.ok(textPrompt.placeholder, 'starts with placeholder');

// Ctrl+U should convert placeholder and delete everything before cursor (which is at end)
textPrompt.deleteToStart();

t.notOk(textPrompt.placeholder, 'placeholder is false after deleteToStart');
t.same(textPrompt.value, '', 'all text deleted (cursor was at end)');
t.ok(textPrompt.hasStartedEditing, 'hasStartedEditing is true');

t.end();
});

test('deleteWord with placeholder converts and works', (t) => {
t.plan(4);

const textPrompt = new TextPrompt({ message: 'Test', initial: 'hello world' });

t.ok(textPrompt.placeholder, 'starts with placeholder');

// Ctrl+W should convert placeholder and delete last word
textPrompt.deleteWord();

t.notOk(textPrompt.placeholder, 'placeholder is false after deleteWord');
t.same(textPrompt.value, 'hello ', 'last word deleted');
t.ok(textPrompt.hasStartedEditing, 'hasStartedEditing is true');

t.end();
});

test('arrow keys convert placeholder', (t) => {
t.plan(6);

const textPrompt = new TextPrompt({ message: 'Test', initial: 'hello' });

t.ok(textPrompt.placeholder, 'starts with placeholder');

// Left arrow should convert placeholder
textPrompt.left();

t.notOk(textPrompt.placeholder, 'placeholder is false after left arrow');
t.same(textPrompt.value, 'hello', 'value is initial');
t.ok(textPrompt.hasStartedEditing, 'hasStartedEditing is true');

// Right arrow should also work (though we're already converted)
const textPrompt2 = new TextPrompt({ message: 'Test', initial: 'hello' });
textPrompt2.right();
t.notOk(textPrompt2.placeholder, 'placeholder is false after right arrow');
t.ok(textPrompt2.hasStartedEditing, 'hasStartedEditing is true');

t.end();
});

test('backspace with placeholder converts and works', (t) => {
t.plan(4);

const textPrompt = new TextPrompt({ message: 'Test', initial: 'hello' });

t.ok(textPrompt.placeholder, 'starts with placeholder');

// Backspace should convert placeholder (cursor at end, so deletes last char)
textPrompt.delete();

t.notOk(textPrompt.placeholder, 'placeholder is false after delete');
t.same(textPrompt.value, 'hell', 'last character deleted');
t.ok(textPrompt.hasStartedEditing, 'hasStartedEditing is true');

t.end();
});

test('first and last with placeholder convert', (t) => {
t.plan(6);

const textPrompt = new TextPrompt({ message: 'Test', initial: 'hello' });

t.ok(textPrompt.placeholder, 'starts with placeholder');

// Ctrl+A (first) should convert placeholder
textPrompt.first();

t.notOk(textPrompt.placeholder, 'placeholder is false after first');
t.same(textPrompt.value, 'hello', 'value is initial');
t.ok(textPrompt.hasStartedEditing, 'hasStartedEditing is true');

// Ctrl+E (last) should also convert
const textPrompt2 = new TextPrompt({ message: 'Test', initial: 'hello' });
textPrompt2.last();
t.notOk(textPrompt2.placeholder, 'placeholder is false after last');
t.ok(textPrompt2.hasStartedEditing, 'hasStartedEditing is true');

t.end();
});

test('reset does not restore placeholder after editing', (t) => {
t.plan(4);

const textPrompt = new TextPrompt({ message: 'Test', initial: 'hello' });

// Start editing
textPrompt._('a', {});
t.ok(textPrompt.hasStartedEditing, 'hasStartedEditing is true');

// Reset
textPrompt.reset();

t.same(textPrompt.value, '', 'value is empty after reset');
t.notOk(textPrompt.placeholder, 'placeholder does not return after reset');
t.ok(textPrompt.hasStartedEditing, 'hasStartedEditing remains true');

t.end();
});
Loading