Skip to content

Commit 517f2e9

Browse files
committed
refactor: implement new History and CursorText class
1 parent b706706 commit 517f2e9

File tree

6 files changed

+129
-46
lines changed

6 files changed

+129
-46
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,5 @@ typings/
6060
# JSDoc
6161
docs/
6262
jsdoc/
63+
64+
/temp

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Node.js light and interactive standard input (stdin) crafted for REPL (like) exp
1414
</p>
1515

1616
## Requirements
17-
- [Node.js](https://nodejs.org/en/) v14 or higher
17+
- [Node.js](https://nodejs.org/en/) v22 or higher
1818

1919
## Getting Started
2020

index.js

Lines changed: 27 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
// Import Node.js Dependencies
2-
import { emitKeypressEvents } from "readline";
2+
import { emitKeypressEvents } from "node:readline";
33

44
// Import Third-party Dependencies
55
import strLength from "string-length";
66

77
// Import Internal Dependencies
88
import { localMatchOf } from "./src/utils.js";
9+
import { History } from "./src/history.js";
910

1011
/**
1112
* @async
@@ -25,9 +26,7 @@ export default async function stdin(query = "question", options = {}) {
2526
}
2627

2728
const { history = [], autocomplete = [] } = options;
28-
if (!Array.isArray(history)) {
29-
throw new TypeError("history must be an Array Object");
30-
}
29+
const histo = new History(history);
3130

3231
emitKeypressEvents(process.stdin);
3332
if (!process.stdin.isTTY) {
@@ -46,26 +45,21 @@ export default async function stdin(query = "question", options = {}) {
4645
return new Promise((resolve) => {
4746
let rawStr = "";
4847
let currentCursorPosition = 0;
49-
let currentHistoryIndex = history.length;
5048
let isOriginalStr = true;
51-
let autoCompletionActivated = false;
52-
let autoCompletionStr = "";
53-
let realCompletionStr = "";
49+
let completionHint = "";
50+
let completionHintStripped = "";
5451

55-
// eslint-disable-next-line
5652
function clearAutoCompletion(forceClean = false) {
57-
if (autoCompletionActivated) {
53+
if (completionHint.length > 0) {
5854
process.stdout.clearLine(1);
5955

60-
autoCompletionActivated = false;
61-
autoCompletionStr = "";
56+
completionHint = "";
6257
}
6358
else if (forceClean) {
6459
process.stdout.clearLine(1);
6560
}
6661
}
6762

68-
// eslint-disable-next-line
6963
function searchForCompletion(str, forceNextMatch = false) {
7064
if (noRefComplete.length === 0) {
7165
return true;
@@ -74,11 +68,10 @@ export default async function stdin(query = "question", options = {}) {
7468

7569
clearAutoCompletion();
7670
if (localMatch !== null && localMatch !== "") {
77-
realCompletionStr = localMatch;
78-
autoCompletionStr = `\x1b[90m${localMatch}\x1b[39m`;
79-
autoCompletionActivated = true;
80-
process.stdout.write(typeof str === "undefined" ? autoCompletionStr : `${str}${autoCompletionStr}`);
81-
process.stdout.moveCursor(-strLength(autoCompletionStr), 0);
71+
completionHintStripped = localMatch;
72+
completionHint = `\x1b[90m${localMatch}\x1b[39m`;
73+
process.stdout.write(typeof str === "undefined" ? completionHint : `${str}${completionHint}`);
74+
process.stdout.moveCursor(-strLength(completionHint), 0);
8275

8376
return false;
8477
}
@@ -93,18 +86,12 @@ export default async function stdin(query = "question", options = {}) {
9386
process.stdin.pause();
9487
process.stdout.write("\n");
9588
process.stdin.removeListener("keypress", listener);
96-
const uniqueHistorySet = new Set(history);
9789

9890
const trimedRawStr = rawStr.trim();
99-
const result = trimedRawStr === "" ? null : trimedRawStr;
100-
101-
if (result !== null) {
102-
uniqueHistorySet.add(rawStr);
91+
if (trimedRawStr !== "") {
92+
histo.push(trimedRawStr);
10393
}
104-
history.splice(0, history.length);
105-
history.push(...uniqueHistorySet);
106-
107-
resolve(result);
94+
resolve(trimedRawStr);
10895
}
10996
else if (key.name === "left") {
11097
if (currentCursorPosition <= 0) {
@@ -122,45 +109,45 @@ export default async function stdin(query = "question", options = {}) {
122109
return;
123110
}
124111

125-
if (!autoCompletionActivated) {
112+
if (completionHint.length === 0) {
126113
return;
127114
}
128115

129116
clearAutoCompletion();
130-
process.stdout.write(realCompletionStr);
131-
rawStr += realCompletionStr;
132-
currentCursorPosition += realCompletionStr.length;
133-
realCompletionStr = "";
117+
process.stdout.write(completionHintStripped);
118+
rawStr += completionHintStripped;
119+
currentCursorPosition += completionHintStripped.length;
120+
completionHintStripped = "";
134121
searchForCompletion(void 0, true);
135122
}
136123
else if (key.name === "up" || key.name === "down") {
137-
const moveIndexValue = key.name === "up" ? -1 : 1;
138-
const nextIndex = currentHistoryIndex + moveIndexValue;
139124
const rawStrCopy = rawStr;
140125

141-
if (history.length === 0) {
126+
if (histo.length === 0) {
142127
if (isOriginalStr && rawStrCopy.trim() !== "") {
143-
history.push(rawStrCopy);
128+
histo.push(rawStrCopy);
129+
144130
isOriginalStr = false;
145131
}
146132

147133
return;
148134
}
149-
if (nextIndex < 0 || nextIndex >= history.length) {
135+
136+
const hasSwitchedHistory = key.name === "up" ? histo.up() : histo.down();
137+
if (!hasSwitchedHistory) {
150138
return;
151139
}
152140

153141
clearAutoCompletion();
154-
rawStr = history[nextIndex];
142+
rawStr = histo.current;
155143
process.stdout.moveCursor(-rawStrCopy.length, 0);
156144
process.stdout.clearLine(1);
157145
process.stdout.write(rawStr);
158146

159147
currentCursorPosition = rawStr.length;
160-
currentHistoryIndex = nextIndex;
161148

162149
if (isOriginalStr && rawStrCopy.trim() !== "") {
163-
history.push(rawStrCopy);
150+
histo.push(rawStrCopy);
164151
isOriginalStr = false;
165152
}
166153
}

package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"exports": "./index.js",
77
"scripts": {
88
"prepublishOnly": "pkg-ok",
9-
"test": "node --test test/",
9+
"test": "node --test test/**/*.test.js",
1010
"coverage": "c8 -r html npm test",
1111
"lint": "eslint ."
1212
},
@@ -43,12 +43,11 @@
4343
"string-length": "^6.0.0"
4444
},
4545
"devDependencies": {
46-
"@nodesecure/eslint-config": "^1.7.0",
47-
"c8": "^8.0.0",
48-
"eslint": "^8.39.0",
46+
"@openally/config.eslint": "^2.1.0",
47+
"c8": "^10.1.3",
4948
"pkg-ok": "^3.0.0"
5049
},
5150
"engines": {
52-
"node": ">=14"
51+
"node": ">=22"
5352
}
5453
}

src/cursortext.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export class CursorText {
2+
#cursor = 0;
3+
#str = "";
4+
5+
add(char) {
6+
this.#str += char;
7+
8+
return this;
9+
}
10+
11+
remove() {
12+
if (this.#str.length > 0) {
13+
this.#str = this.#str.slice(0, this.#str.length - 1);
14+
}
15+
16+
return this;
17+
}
18+
19+
synchronize() {
20+
this.#cursor = this.#str.length;
21+
22+
return this;
23+
}
24+
25+
left() {
26+
this.#cursor--;
27+
}
28+
29+
right() {
30+
this.#cursor++;
31+
}
32+
33+
get cursor() {
34+
return this.#cursor;
35+
}
36+
37+
toString() {
38+
return this.#str.trim();
39+
}
40+
}

src/history.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
2+
export class History {
3+
#commands = [];
4+
#index = 0;
5+
6+
/**
7+
* @param {!string[]} commands
8+
*/
9+
constructor(commands) {
10+
if (!Array.isArray(commands)) {
11+
throw new TypeError("commands must be an Array");
12+
}
13+
14+
this.#commands = commands;
15+
this.#index = this.#commands.length;
16+
}
17+
18+
push(command, noDuplicateCheck = false) {
19+
if (noDuplicateCheck || !this.#commands.includes(command)) {
20+
this.#commands.push(command);
21+
}
22+
}
23+
24+
get current() {
25+
if (this.#index === this.#commands.length) {
26+
return "";
27+
}
28+
29+
return this.#commands[this.#index];
30+
}
31+
32+
get length() {
33+
return this.#commands.length;
34+
}
35+
36+
down() {
37+
if (this.#index < this.#commands.length) {
38+
this.#index++;
39+
40+
return true;
41+
}
42+
43+
return false;
44+
}
45+
46+
up() {
47+
if (this.#index > 0) {
48+
this.#index--;
49+
50+
return true;
51+
}
52+
53+
return false;
54+
}
55+
}

0 commit comments

Comments
 (0)