cliedit 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +91 -87
- package/dist/editor.d.ts +0 -9
- package/dist/editor.js +3 -1
- package/dist/editor.keys.d.ts +1 -10
- package/dist/editor.keys.js +43 -49
- package/dist/vendor/keypress.d.ts +17 -0
- package/dist/vendor/keypress.js +425 -0
- package/package.json +42 -43
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 CodeTease
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 CodeTease
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,88 +1,92 @@
|
|
|
1
|
-
# cliedit
|
|
2
|
-
|
|
3
|
-
A lightweight, zero-dependency
|
|
4
|
-
|
|
5
|
-
`cliedit` is designed to be imported into your own CLI application to provide a full-featured, TTY-based text editing experience. It's perfect for applications that need to ask the user for multi-line input, edit configuration files, or write commit messages.
|
|
6
|
-
|
|
7
|
-
It includes line wrapping, visual navigation, undo/redo, text selection, and clipboard support.
|
|
8
|
-
|
|
9
|
-
## Features
|
|
10
|
-
|
|
11
|
-
- **Raw Mode TTY:** Takes over the terminal for a full "app-like" feel.
|
|
12
|
-
- **Visual Line Wrapping:** Text wraps to fit the terminal width.
|
|
13
|
-
- **Visual Navigation:** `Up`/`Down` arrows move by visual rows, not logical lines.
|
|
14
|
-
- **Undo/Redo:** `Ctrl+Z` / `Ctrl+Y` for persistent history.
|
|
15
|
-
- **Text Selection:** `Ctrl+Arrow` keys to select text.
|
|
16
|
-
- **Clipboard Support:** `Ctrl+C` (Copy), `Ctrl+X` (Cut), `Ctrl+V` (Paste) for system clipboard (macOS/Windows).
|
|
17
|
-
- **File I/O:** Loads from and saves to the filesystem.
|
|
18
|
-
- **Search:** `Ctrl+W` to find text.
|
|
19
|
-
|
|
20
|
-
## Installation
|
|
21
|
-
```bash
|
|
22
|
-
npm install cliedit
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Usage
|
|
26
|
-
|
|
27
|
-
The package exports an `async` function `openEditor` that returns a `Promise`. The promise resolves when the user quits the editor.
|
|
28
|
-
```javascript
|
|
29
|
-
import { openEditor } from 'cliedit';
|
|
30
|
-
import path from 'path';
|
|
31
|
-
|
|
32
|
-
async function getCommitMessage() {
|
|
33
|
-
const tempFile = path.resolve(process.cwd(), 'COMMIT_MSG.txt');
|
|
34
|
-
console.log('Opening editor for commit message...');
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const result = await openEditor(tempFile);
|
|
38
|
-
|
|
39
|
-
// Give the terminal a moment to restore
|
|
40
|
-
await new Promise(res => setTimeout(res, 50));
|
|
41
|
-
|
|
42
|
-
if (result.saved) {
|
|
43
|
-
console.log('Message saved!');
|
|
44
|
-
console.log('---------------------');
|
|
45
|
-
console.log(result.content);
|
|
46
|
-
console.log('---------------------');
|
|
47
|
-
} else {
|
|
48
|
-
console.log('Editor quit without saving.');
|
|
49
|
-
}
|
|
50
|
-
} catch (err) {
|
|
51
|
-
console.error('Editor failed to start:', err);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
getCommitMessage();
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## Public API
|
|
59
|
-
|
|
60
|
-
`openEditor(filepath: string)`
|
|
61
|
-
|
|
62
|
-
Opens the editor for the specified file. If the file doesn't exist, it will be created upon saving.
|
|
63
|
-
- **Returns:** `Promise<{ saved: boolean; content: string }>`
|
|
64
|
-
* `saved`: `true` if the user saved (Ctrl+S), `false` otherwise (Ctrl+Q).
|
|
65
|
-
* `content`: The final content of the file as a string.
|
|
66
|
-
|
|
67
|
-
`CliEditor`
|
|
68
|
-
|
|
69
|
-
The main editor class. You can import this directly if you need to extend or instantiate the editor with custom logic.
|
|
70
|
-
```javascript
|
|
71
|
-
import { CliEditor } from 'cliedit';
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Types
|
|
75
|
-
|
|
76
|
-
Key types are also exported for convenience:
|
|
77
|
-
```javascript
|
|
78
|
-
import type {
|
|
79
|
-
DocumentState,
|
|
80
|
-
VisualRow,
|
|
81
|
-
EditorMode,
|
|
82
|
-
NormalizedRange,
|
|
83
|
-
} from 'cliedit';
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
##
|
|
87
|
-
|
|
1
|
+
# cliedit
|
|
2
|
+
|
|
3
|
+
A lightweight, zero-dependency, raw-mode terminal editor component for Node.js.
|
|
4
|
+
|
|
5
|
+
`cliedit` is designed to be imported into your own CLI application to provide a full-featured, TTY-based text editing experience. It's perfect for applications that need to ask the user for multi-line input, edit configuration files, or write commit messages.
|
|
6
|
+
|
|
7
|
+
It includes line wrapping, visual navigation, undo/redo, text selection, and clipboard support.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Raw Mode TTY:** Takes over the terminal for a full "app-like" feel.
|
|
12
|
+
- **Visual Line Wrapping:** Text wraps to fit the terminal width.
|
|
13
|
+
- **Visual Navigation:** `Up`/`Down` arrows move by visual rows, not logical lines.
|
|
14
|
+
- **Undo/Redo:** `Ctrl+Z` / `Ctrl+Y` for persistent history.
|
|
15
|
+
- **Text Selection:** `Ctrl+Arrow` keys to select text.
|
|
16
|
+
- **Clipboard Support:** `Ctrl+C` (Copy), `Ctrl+X` (Cut), `Ctrl+V` (Paste) for system clipboard (macOS/Windows).
|
|
17
|
+
- **File I/O:** Loads from and saves to the filesystem.
|
|
18
|
+
- **Search:** `Ctrl+W` to find text.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
```bash
|
|
22
|
+
npm install cliedit
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
The package exports an `async` function `openEditor` that returns a `Promise`. The promise resolves when the user quits the editor.
|
|
28
|
+
```javascript
|
|
29
|
+
import { openEditor } from 'cliedit';
|
|
30
|
+
import path from 'path';
|
|
31
|
+
|
|
32
|
+
async function getCommitMessage() {
|
|
33
|
+
const tempFile = path.resolve(process.cwd(), 'COMMIT_MSG.txt');
|
|
34
|
+
console.log('Opening editor for commit message...');
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const result = await openEditor(tempFile);
|
|
38
|
+
|
|
39
|
+
// Give the terminal a moment to restore
|
|
40
|
+
await new Promise(res => setTimeout(res, 50));
|
|
41
|
+
|
|
42
|
+
if (result.saved) {
|
|
43
|
+
console.log('Message saved!');
|
|
44
|
+
console.log('---------------------');
|
|
45
|
+
console.log(result.content);
|
|
46
|
+
console.log('---------------------');
|
|
47
|
+
} else {
|
|
48
|
+
console.log('Editor quit without saving.');
|
|
49
|
+
}
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error('Editor failed to start:', err);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getCommitMessage();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Public API
|
|
59
|
+
|
|
60
|
+
`openEditor(filepath: string)`
|
|
61
|
+
|
|
62
|
+
Opens the editor for the specified file. If the file doesn't exist, it will be created upon saving.
|
|
63
|
+
- **Returns:** `Promise<{ saved: boolean; content: string }>`
|
|
64
|
+
* `saved`: `true` if the user saved (Ctrl+S), `false` otherwise (Ctrl+Q).
|
|
65
|
+
* `content`: The final content of the file as a string.
|
|
66
|
+
|
|
67
|
+
`CliEditor`
|
|
68
|
+
|
|
69
|
+
The main editor class. You can import this directly if you need to extend or instantiate the editor with custom logic.
|
|
70
|
+
```javascript
|
|
71
|
+
import { CliEditor } from 'cliedit';
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Types
|
|
75
|
+
|
|
76
|
+
Key types are also exported for convenience:
|
|
77
|
+
```javascript
|
|
78
|
+
import type {
|
|
79
|
+
DocumentState,
|
|
80
|
+
VisualRow,
|
|
81
|
+
EditorMode,
|
|
82
|
+
NormalizedRange,
|
|
83
|
+
} from 'cliedit';
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Acknowledgements
|
|
87
|
+
|
|
88
|
+
Please see the [ACKNOWLEDGEMENTS.md](ACKNOWLEDGEMENTS.md) file for important copyright information regarding the vendored `keypress` component.
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
88
92
|
[MIT](LICENSE)
|
package/dist/editor.d.ts
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import { HistoryManager } from './history.js';
|
|
2
2
|
import { VisualRow, EditorMode } from './types.js';
|
|
3
|
-
declare module 'keypress' {
|
|
4
|
-
interface KeypressEvent {
|
|
5
|
-
name?: string;
|
|
6
|
-
ctrl: boolean;
|
|
7
|
-
meta: boolean;
|
|
8
|
-
shift: boolean;
|
|
9
|
-
sequence: string;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
3
|
import { editingMethods } from './editor.editing.js';
|
|
13
4
|
import { clipboardMethods } from './editor.clipboard.js';
|
|
14
5
|
import { navigationMethods } from './editor.navigation.js';
|
package/dist/editor.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
// Cập nhật 1: Import từ ./vendor/keypress.js
|
|
2
|
+
import keypress from './vendor/keypress.js';
|
|
2
3
|
import { ANSI } from './constants.js';
|
|
3
4
|
import { HistoryManager } from './history.js';
|
|
5
|
+
// Block `declare module 'keypress'` đã bị xóa
|
|
4
6
|
// Import all functional modules
|
|
5
7
|
import { editingMethods } from './editor.editing.js';
|
|
6
8
|
import { clipboardMethods } from './editor.clipboard.js';
|
package/dist/editor.keys.d.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
import type { KeypressEvent } from 'keypress';
|
|
2
|
-
declare module 'keypress' {
|
|
3
|
-
interface KeypressEvent {
|
|
4
|
-
name?: string;
|
|
5
|
-
ctrl: boolean;
|
|
6
|
-
meta: boolean;
|
|
7
|
-
shift: boolean;
|
|
8
|
-
sequence: string;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
1
|
+
import type { KeypressEvent } from './vendor/keypress.js';
|
|
11
2
|
export type TKeyHandlingMethods = {
|
|
12
3
|
handleKeypressEvent: (ch: string, key: KeypressEvent) => void;
|
|
13
4
|
handleEditKeys: (key: string) => boolean;
|
package/dist/editor.keys.js
CHANGED
|
@@ -8,19 +8,23 @@ function handleKeypressEvent(ch, key) {
|
|
|
8
8
|
if (this.isExiting) {
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
let keyName = undefined;
|
|
12
|
+
let edited = false;
|
|
13
|
+
// --- 1. Xử lý trường hợp key là null/undefined (Ký tự in được) ---
|
|
12
14
|
if (!key) {
|
|
13
|
-
if (ch && ch >= ' ' && ch <= '~') {
|
|
14
|
-
this.
|
|
15
|
-
|
|
15
|
+
if (ch && ch.length === 1 && ch >= ' ' && ch <= '~') {
|
|
16
|
+
edited = this.handleEditKeys(ch);
|
|
17
|
+
if (edited) {
|
|
18
|
+
this.saveState();
|
|
19
|
+
this.recalculateVisualRows(); // Phải tính toán lại sau khi gõ
|
|
20
|
+
}
|
|
16
21
|
this.render();
|
|
22
|
+
return;
|
|
17
23
|
}
|
|
18
24
|
return;
|
|
19
25
|
}
|
|
20
|
-
// ---
|
|
21
|
-
|
|
22
|
-
let edited = false;
|
|
23
|
-
// 1. Map Control sequences (Ctrl+Arrow for selection)
|
|
26
|
+
// --- 2. Từ đây, 'key' object là đảm bảo có (phím đặc biệt hoặc Ctrl/Meta) ---
|
|
27
|
+
// 2.1. Ánh xạ Control sequences (Ctrl+Arrow cho selection)
|
|
24
28
|
if (key.ctrl) {
|
|
25
29
|
if (key.name === 'up')
|
|
26
30
|
keyName = KEYS.CTRL_ARROW_UP;
|
|
@@ -31,10 +35,10 @@ function handleKeypressEvent(ch, key) {
|
|
|
31
35
|
else if (key.name === 'right')
|
|
32
36
|
keyName = KEYS.CTRL_ARROW_RIGHT;
|
|
33
37
|
else
|
|
34
|
-
keyName = key.sequence;
|
|
38
|
+
keyName = key.sequence;
|
|
35
39
|
}
|
|
36
40
|
else {
|
|
37
|
-
// 2.
|
|
41
|
+
// 2.2. Ánh xạ phím tiêu chuẩn (Arrow, Home, End, Enter, Tab)
|
|
38
42
|
if (key.name === 'up')
|
|
39
43
|
keyName = KEYS.ARROW_UP;
|
|
40
44
|
else if (key.name === 'down')
|
|
@@ -60,55 +64,41 @@ function handleKeypressEvent(ch, key) {
|
|
|
60
64
|
else if (key.name === 'tab')
|
|
61
65
|
keyName = KEYS.TAB;
|
|
62
66
|
else
|
|
63
|
-
keyName = key.sequence;
|
|
67
|
+
keyName = key.sequence;
|
|
64
68
|
}
|
|
65
|
-
// 3.
|
|
66
|
-
// This was the source of the "no typing" bug.
|
|
67
|
-
// We must check for characters *before* routing to handleEditKeys.
|
|
68
|
-
if (keyName && keyName.length === 1 && keyName >= ' ' && keyName <= '~' && !key.ctrl && !key.meta) {
|
|
69
|
-
this.handleCharacterKey(keyName);
|
|
70
|
-
this.recalculateVisualRows();
|
|
71
|
-
this.render();
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
// 4. Mode Routing (If it's not a character, it's a command)
|
|
69
|
+
// --- 3. Định tuyến theo Mode ---
|
|
75
70
|
if (this.mode === 'search') {
|
|
76
71
|
this.handleSearchKeys(keyName || ch);
|
|
77
72
|
}
|
|
78
73
|
else {
|
|
79
|
-
//
|
|
74
|
+
// 4. Xử lý phím lựa chọn (Ctrl+Arrow) - Navigation
|
|
80
75
|
switch (keyName) {
|
|
81
76
|
case KEYS.CTRL_ARROW_UP:
|
|
82
|
-
this.startOrUpdateSelection();
|
|
83
|
-
this.moveCursorVisually(-1);
|
|
84
|
-
this.render();
|
|
85
|
-
return;
|
|
86
77
|
case KEYS.CTRL_ARROW_DOWN:
|
|
87
|
-
this.startOrUpdateSelection();
|
|
88
|
-
this.moveCursorVisually(1);
|
|
89
|
-
this.render();
|
|
90
|
-
return;
|
|
91
78
|
case KEYS.CTRL_ARROW_LEFT:
|
|
92
|
-
this.startOrUpdateSelection();
|
|
93
|
-
this.moveCursorLogically(-1);
|
|
94
|
-
this.render();
|
|
95
|
-
return;
|
|
96
79
|
case KEYS.CTRL_ARROW_RIGHT:
|
|
97
80
|
this.startOrUpdateSelection();
|
|
98
|
-
|
|
81
|
+
if (keyName === KEYS.CTRL_ARROW_UP)
|
|
82
|
+
this.moveCursorVisually(-1);
|
|
83
|
+
else if (keyName === KEYS.CTRL_ARROW_DOWN)
|
|
84
|
+
this.moveCursorVisually(1);
|
|
85
|
+
else if (keyName === KEYS.CTRL_ARROW_LEFT)
|
|
86
|
+
this.moveCursorLogically(-1);
|
|
87
|
+
else if (keyName === KEYS.CTRL_ARROW_RIGHT)
|
|
88
|
+
this.moveCursorLogically(1);
|
|
99
89
|
this.render();
|
|
100
90
|
return;
|
|
101
91
|
}
|
|
102
|
-
//
|
|
92
|
+
// 5. Xử lý tất cả các phím lệnh/chỉnh sửa khác
|
|
103
93
|
edited = this.handleEditKeys(keyName || ch);
|
|
104
94
|
}
|
|
105
|
-
//
|
|
95
|
+
// 6. Cập nhật Trạng thái và Render
|
|
106
96
|
if (edited) {
|
|
107
|
-
this.saveState();
|
|
108
|
-
this.recalculateVisualRows();
|
|
97
|
+
this.saveState(); // <-- Chỉ gọi khi gõ phím, xóa, v.v.
|
|
98
|
+
this.recalculateVisualRows(); // Tính toán lại layout
|
|
109
99
|
}
|
|
110
100
|
if (!this.isExiting) {
|
|
111
|
-
this.render();
|
|
101
|
+
this.render(); // Render cuối cùng (với visual rows đã được cập nhật nếu cần)
|
|
112
102
|
}
|
|
113
103
|
}
|
|
114
104
|
/**
|
|
@@ -116,10 +106,6 @@ function handleKeypressEvent(ch, key) {
|
|
|
116
106
|
* Returns true if content was modified.
|
|
117
107
|
*/
|
|
118
108
|
function handleEditKeys(key) {
|
|
119
|
-
// (FIXED) Removed the guard clause that was blocking typing.
|
|
120
|
-
// if (key.length === 1 && key >= ' ' && key <= '~') {
|
|
121
|
-
// return false;
|
|
122
|
-
// }
|
|
123
109
|
// Cancel selection on normal navigation
|
|
124
110
|
const isNavigation = [
|
|
125
111
|
KEYS.ARROW_UP, KEYS.ARROW_DOWN, KEYS.ARROW_LEFT, KEYS.ARROW_RIGHT,
|
|
@@ -131,7 +117,6 @@ function handleEditKeys(key) {
|
|
|
131
117
|
this.setStatusMessage(this.DEFAULT_STATUS, 0);
|
|
132
118
|
}
|
|
133
119
|
}
|
|
134
|
-
// Commands that return Promises must be wrapped in a sync call here
|
|
135
120
|
switch (key) {
|
|
136
121
|
// --- Exit / Save ---
|
|
137
122
|
case KEYS.CTRL_Q:
|
|
@@ -143,7 +128,7 @@ function handleEditKeys(key) {
|
|
|
143
128
|
case KEYS.CTRL_C:
|
|
144
129
|
this.handleCopy();
|
|
145
130
|
return false;
|
|
146
|
-
// --- Navigation ---
|
|
131
|
+
// --- Navigation (Non-Selection) ---
|
|
147
132
|
case KEYS.ARROW_UP:
|
|
148
133
|
this.moveCursorVisually(-1);
|
|
149
134
|
return false;
|
|
@@ -194,12 +179,16 @@ function handleEditKeys(key) {
|
|
|
194
179
|
case KEYS.CTRL_G:
|
|
195
180
|
this.findNext();
|
|
196
181
|
return false;
|
|
182
|
+
// ***** SỬA LỖI VISUAL *****
|
|
183
|
+
// Sau khi undo/redo, chúng ta PHẢI tính toán lại visual rows
|
|
197
184
|
case KEYS.CTRL_Z:
|
|
198
185
|
this.undo();
|
|
199
|
-
|
|
186
|
+
this.recalculateVisualRows(); // <-- THÊM DÒNG NÀY
|
|
187
|
+
return false;
|
|
200
188
|
case KEYS.CTRL_Y:
|
|
201
189
|
this.redo();
|
|
202
|
-
|
|
190
|
+
this.recalculateVisualRows(); // <-- THÊM DÒNG NÀY
|
|
191
|
+
return false;
|
|
203
192
|
// --- Clipboard ---
|
|
204
193
|
case KEYS.CTRL_K: // Cut Line (Traditional)
|
|
205
194
|
this.cutLine();
|
|
@@ -208,12 +197,17 @@ function handleEditKeys(key) {
|
|
|
208
197
|
this.pasteLine();
|
|
209
198
|
return true;
|
|
210
199
|
case KEYS.CTRL_X: // Cut Selection
|
|
211
|
-
this.cutSelection();
|
|
200
|
+
this.cutSelection();
|
|
212
201
|
return true;
|
|
213
202
|
case KEYS.CTRL_V: // Paste Selection
|
|
214
203
|
this.pasteSelection();
|
|
215
204
|
return true;
|
|
205
|
+
// Xử lý Ký tự in được
|
|
216
206
|
default:
|
|
207
|
+
if (key.length === 1 && key >= ' ' && key <= '~') {
|
|
208
|
+
this.handleCharacterKey(key);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
217
211
|
return false;
|
|
218
212
|
}
|
|
219
213
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Định nghĩa giao diện cho một sự kiện keypress.
|
|
3
|
+
* Được điều chỉnh từ file editor.ts.
|
|
4
|
+
*/
|
|
5
|
+
export interface KeypressEvent {
|
|
6
|
+
name?: string;
|
|
7
|
+
ctrl: boolean;
|
|
8
|
+
meta: boolean;
|
|
9
|
+
shift: boolean;
|
|
10
|
+
sequence: string;
|
|
11
|
+
code?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Hàm chính, chấp nhận một Readable Stream và làm cho nó
|
|
15
|
+
* phát ra sự kiện "keypress".
|
|
16
|
+
*/
|
|
17
|
+
export default function keypress(stream: NodeJS.ReadStream): void;
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
// src/vendor/keypress.ts
|
|
2
|
+
// Đây là phiên bản "vendored" của thư viện 'keypress' (0.2.1)
|
|
3
|
+
// được chuyển đổi sang TypeScript và loại bỏ hỗ trợ chuột
|
|
4
|
+
// để tích hợp trực tiếp vào cliedit.
|
|
5
|
+
import { EventEmitter } from 'events';
|
|
6
|
+
import { StringDecoder } from 'string_decoder';
|
|
7
|
+
/**
|
|
8
|
+
* Hàm polyfill cho `EventEmitter.listenerCount()`, để tương thích ngược.
|
|
9
|
+
*/
|
|
10
|
+
let listenerCount = EventEmitter.listenerCount;
|
|
11
|
+
if (!listenerCount) {
|
|
12
|
+
listenerCount = function (emitter, event) {
|
|
13
|
+
return emitter.listeners(event).length;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Regexes dùng để phân tích escape code của ansi
|
|
18
|
+
*/
|
|
19
|
+
const metaKeyCodeRe = /^(?:\x1b)([a-zA-Z0-9])$/;
|
|
20
|
+
const functionKeyCodeRe = /^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/;
|
|
21
|
+
/**
|
|
22
|
+
* Hàm chính, chấp nhận một Readable Stream và làm cho nó
|
|
23
|
+
* phát ra sự kiện "keypress".
|
|
24
|
+
*/
|
|
25
|
+
export default function keypress(stream) {
|
|
26
|
+
if (isEmittingKeypress(stream))
|
|
27
|
+
return;
|
|
28
|
+
// Gắn decoder vào stream để theo dõi
|
|
29
|
+
stream._keypressDecoder = new StringDecoder('utf8');
|
|
30
|
+
function onData(b) {
|
|
31
|
+
if (listenerCount(stream, 'keypress') > 0) {
|
|
32
|
+
const r = stream._keypressDecoder.write(b);
|
|
33
|
+
if (r)
|
|
34
|
+
emitKey(stream, r);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// Không ai đang nghe, gỡ bỏ listener
|
|
38
|
+
stream.removeListener('data', onData);
|
|
39
|
+
stream.on('newListener', onNewListener);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function onNewListener(event) {
|
|
43
|
+
if (event === 'keypress') {
|
|
44
|
+
stream.on('data', onData);
|
|
45
|
+
stream.removeListener('newListener', onNewListener);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (listenerCount(stream, 'keypress') > 0) {
|
|
49
|
+
stream.on('data', onData);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
stream.on('newListener', onNewListener);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Kiểm tra xem stream đã phát ra sự kiện "keypress" hay chưa.
|
|
57
|
+
*/
|
|
58
|
+
function isEmittingKeypress(stream) {
|
|
59
|
+
let rtn = !!stream._keypressDecoder;
|
|
60
|
+
if (!rtn) {
|
|
61
|
+
// XXX: Đối với các phiên bản node cũ, chúng ta muốn xóa các
|
|
62
|
+
// listener "data" và "newListener" hiện có vì chúng sẽ không
|
|
63
|
+
// bao gồm các phần mở rộng của module này (như "mousepress" đã bị loại bỏ).
|
|
64
|
+
stream.listeners('data').slice(0).forEach(function (l) {
|
|
65
|
+
if (l.name === 'onData' && /emitKey/.test(l.toString())) {
|
|
66
|
+
// FIX TS2769: Ép kiểu 'l' thành kiểu listener hợp lệ
|
|
67
|
+
stream.removeListener('data', l);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
stream.listeners('newListener').slice(0).forEach(function (l) {
|
|
71
|
+
if (l.name === 'onNewListener' && /keypress/.test(l.toString())) {
|
|
72
|
+
// FIX TS2769: Ép kiểu 'l' thành kiểu listener hợp lệ
|
|
73
|
+
stream.removeListener('newListener', l);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return rtn;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Phần code bên dưới được lấy từ module `readline.js` của node-core
|
|
81
|
+
* và đã được chuyển đổi sang TypeScript.
|
|
82
|
+
*/
|
|
83
|
+
function emitKey(stream, s) {
|
|
84
|
+
let ch;
|
|
85
|
+
const key = {
|
|
86
|
+
name: undefined,
|
|
87
|
+
ctrl: false,
|
|
88
|
+
meta: false,
|
|
89
|
+
shift: false,
|
|
90
|
+
sequence: s,
|
|
91
|
+
};
|
|
92
|
+
let parts;
|
|
93
|
+
// Cảnh báo: Block `Buffer.isBuffer(s)` đã bị loại bỏ.
|
|
94
|
+
// Lý do: `onData` luôn gọi `emitKey` với một string (kết quả từ StringDecoder).
|
|
95
|
+
// Block đệ quy (paste) cũng gọi với string.
|
|
96
|
+
// Vì vậy, `s` luôn là string.
|
|
97
|
+
if (s === '\r') {
|
|
98
|
+
// carriage return
|
|
99
|
+
key.name = 'return';
|
|
100
|
+
}
|
|
101
|
+
else if (s === '\n') {
|
|
102
|
+
// enter, đáng lẽ phải là linefeed
|
|
103
|
+
key.name = 'enter';
|
|
104
|
+
}
|
|
105
|
+
else if (s === '\t') {
|
|
106
|
+
// tab
|
|
107
|
+
key.name = 'tab';
|
|
108
|
+
}
|
|
109
|
+
else if (s === '\b' ||
|
|
110
|
+
s === '\x7f' ||
|
|
111
|
+
s === '\x1b\x7f' ||
|
|
112
|
+
s === '\x1b\b') {
|
|
113
|
+
// backspace hoặc ctrl+h
|
|
114
|
+
key.name = 'backspace';
|
|
115
|
+
key.meta = s.charAt(0) === '\x1b';
|
|
116
|
+
}
|
|
117
|
+
else if (s === '\x1b' || s === '\x1b\x1b') {
|
|
118
|
+
// escape key
|
|
119
|
+
key.name = 'escape';
|
|
120
|
+
key.meta = s.length === 2;
|
|
121
|
+
}
|
|
122
|
+
else if (s === ' ' || s === '\x1b ') {
|
|
123
|
+
key.name = 'space';
|
|
124
|
+
key.meta = s.length === 2;
|
|
125
|
+
}
|
|
126
|
+
else if (s <= '\x1a') {
|
|
127
|
+
// ctrl+letter
|
|
128
|
+
key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
|
|
129
|
+
key.ctrl = true;
|
|
130
|
+
}
|
|
131
|
+
else if (s.length === 1 && s >= 'a' && s <= 'z') {
|
|
132
|
+
// lowercase letter
|
|
133
|
+
key.name = s;
|
|
134
|
+
}
|
|
135
|
+
else if (s.length === 1 && s >= 'A' && s <= 'Z') {
|
|
136
|
+
// shift+letter
|
|
137
|
+
key.name = s.toLowerCase();
|
|
138
|
+
key.shift = true;
|
|
139
|
+
}
|
|
140
|
+
else if ((parts = metaKeyCodeRe.exec(s))) {
|
|
141
|
+
// meta+character key
|
|
142
|
+
key.name = parts[1].toLowerCase();
|
|
143
|
+
key.meta = true;
|
|
144
|
+
key.shift = /^[A-Z]$/.test(parts[1]);
|
|
145
|
+
}
|
|
146
|
+
else if ((parts = functionKeyCodeRe.exec(s))) {
|
|
147
|
+
// ansi escape sequence
|
|
148
|
+
// Lắp ráp lại key code, bỏ qua \x1b đứng đầu,
|
|
149
|
+
// bitflag của phím bổ trợ và bất kỳ chuỗi "1;" vô nghĩa nào
|
|
150
|
+
const code = (parts[1] || '') +
|
|
151
|
+
(parts[2] || '') +
|
|
152
|
+
(parts[4] || '') +
|
|
153
|
+
(parts[6] || '');
|
|
154
|
+
// FIX TS2362: Chuyển đổi (parts[...]) sang number bằng parseInt
|
|
155
|
+
const modifier = parseInt(parts[3] || parts[5] || '1', 10) - 1;
|
|
156
|
+
// Phân tích phím bổ trợ
|
|
157
|
+
key.ctrl = !!(modifier & 4);
|
|
158
|
+
key.meta = !!(modifier & 10);
|
|
159
|
+
key.shift = !!(modifier & 1);
|
|
160
|
+
key.code = code;
|
|
161
|
+
// Phân tích chính phím đó
|
|
162
|
+
switch (code) {
|
|
163
|
+
/* xterm/gnome ESC O letter */
|
|
164
|
+
case 'OP':
|
|
165
|
+
key.name = 'f1';
|
|
166
|
+
break;
|
|
167
|
+
case 'OQ':
|
|
168
|
+
key.name = 'f2';
|
|
169
|
+
break;
|
|
170
|
+
case 'OR':
|
|
171
|
+
key.name = 'f3';
|
|
172
|
+
break;
|
|
173
|
+
case 'OS':
|
|
174
|
+
key.name = 'f4';
|
|
175
|
+
break;
|
|
176
|
+
/* xterm/rxvt ESC [ number ~ */
|
|
177
|
+
case '[11~':
|
|
178
|
+
key.name = 'f1';
|
|
179
|
+
break;
|
|
180
|
+
case '[12~':
|
|
181
|
+
key.name = 'f2';
|
|
182
|
+
break;
|
|
183
|
+
case '[13~':
|
|
184
|
+
key.name = 'f3';
|
|
185
|
+
break;
|
|
186
|
+
case '[14~':
|
|
187
|
+
key.name = 'f4';
|
|
188
|
+
break;
|
|
189
|
+
/* from Cygwin and used in libuv */
|
|
190
|
+
case '[[A':
|
|
191
|
+
key.name = 'f1';
|
|
192
|
+
break;
|
|
193
|
+
case '[[B':
|
|
194
|
+
key.name = 'f2';
|
|
195
|
+
break;
|
|
196
|
+
case '[[C':
|
|
197
|
+
key.name = 'f3';
|
|
198
|
+
break;
|
|
199
|
+
case '[[D':
|
|
200
|
+
key.name = 'f4';
|
|
201
|
+
break;
|
|
202
|
+
case '[[E':
|
|
203
|
+
key.name = 'f5';
|
|
204
|
+
break;
|
|
205
|
+
/* common */
|
|
206
|
+
case '[15~':
|
|
207
|
+
key.name = 'f5';
|
|
208
|
+
break;
|
|
209
|
+
case '[17~':
|
|
210
|
+
key.name = 'f6';
|
|
211
|
+
break;
|
|
212
|
+
case '[18~':
|
|
213
|
+
key.name = 'f7';
|
|
214
|
+
break;
|
|
215
|
+
case '[19~':
|
|
216
|
+
key.name = 'f8';
|
|
217
|
+
break;
|
|
218
|
+
case '[20~':
|
|
219
|
+
key.name = 'f9';
|
|
220
|
+
break;
|
|
221
|
+
case '[21~':
|
|
222
|
+
key.name = 'f10';
|
|
223
|
+
break;
|
|
224
|
+
case '[23~':
|
|
225
|
+
key.name = 'f11';
|
|
226
|
+
break;
|
|
227
|
+
case '[24~':
|
|
228
|
+
key.name = 'f12';
|
|
229
|
+
break;
|
|
230
|
+
/* xterm ESC [ letter */
|
|
231
|
+
case '[A':
|
|
232
|
+
key.name = 'up';
|
|
233
|
+
break;
|
|
234
|
+
case '[B':
|
|
235
|
+
key.name = 'down';
|
|
236
|
+
break;
|
|
237
|
+
case '[C':
|
|
238
|
+
key.name = 'right';
|
|
239
|
+
break;
|
|
240
|
+
case '[D':
|
|
241
|
+
key.name = 'left';
|
|
242
|
+
break;
|
|
243
|
+
case '[E':
|
|
244
|
+
key.name = 'clear';
|
|
245
|
+
break;
|
|
246
|
+
case '[F':
|
|
247
|
+
key.name = 'end';
|
|
248
|
+
break;
|
|
249
|
+
case '[H':
|
|
250
|
+
key.name = 'home';
|
|
251
|
+
break;
|
|
252
|
+
/* xterm/gnome ESC O letter */
|
|
253
|
+
case 'OA':
|
|
254
|
+
key.name = 'up';
|
|
255
|
+
break;
|
|
256
|
+
case 'OB':
|
|
257
|
+
key.name = 'down';
|
|
258
|
+
break;
|
|
259
|
+
case 'OC':
|
|
260
|
+
key.name = 'right';
|
|
261
|
+
break;
|
|
262
|
+
case 'OD':
|
|
263
|
+
key.name = 'left';
|
|
264
|
+
break;
|
|
265
|
+
case 'OE':
|
|
266
|
+
key.name = 'clear';
|
|
267
|
+
break;
|
|
268
|
+
case 'OF':
|
|
269
|
+
key.name = 'end';
|
|
270
|
+
break;
|
|
271
|
+
case 'OH':
|
|
272
|
+
key.name = 'home';
|
|
273
|
+
break;
|
|
274
|
+
/* xterm/rxvt ESC [ number ~ */
|
|
275
|
+
case '[1~':
|
|
276
|
+
key.name = 'home';
|
|
277
|
+
break;
|
|
278
|
+
case '[2~':
|
|
279
|
+
key.name = 'insert';
|
|
280
|
+
break;
|
|
281
|
+
case '[3~':
|
|
282
|
+
key.name = 'delete';
|
|
283
|
+
break;
|
|
284
|
+
case '[4~':
|
|
285
|
+
key.name = 'end';
|
|
286
|
+
break;
|
|
287
|
+
case '[5~':
|
|
288
|
+
key.name = 'pageup';
|
|
289
|
+
break;
|
|
290
|
+
case '[6~':
|
|
291
|
+
key.name = 'pagedown';
|
|
292
|
+
break;
|
|
293
|
+
/* putty */
|
|
294
|
+
case '[[5~':
|
|
295
|
+
key.name = 'pageup';
|
|
296
|
+
break;
|
|
297
|
+
case '[[6~':
|
|
298
|
+
key.name = 'pagedown';
|
|
299
|
+
break;
|
|
300
|
+
/* rxvt */
|
|
301
|
+
case '[7~':
|
|
302
|
+
key.name = 'home';
|
|
303
|
+
break;
|
|
304
|
+
case '[8~':
|
|
305
|
+
key.name = 'end';
|
|
306
|
+
break;
|
|
307
|
+
/* rxvt keys with modifiers */
|
|
308
|
+
case '[a':
|
|
309
|
+
key.name = 'up';
|
|
310
|
+
key.shift = true;
|
|
311
|
+
break;
|
|
312
|
+
case '[b':
|
|
313
|
+
key.name = 'down';
|
|
314
|
+
key.shift = true;
|
|
315
|
+
break;
|
|
316
|
+
case '[c':
|
|
317
|
+
key.name = 'right';
|
|
318
|
+
key.shift = true;
|
|
319
|
+
break;
|
|
320
|
+
case '[d':
|
|
321
|
+
key.name = 'left';
|
|
322
|
+
key.shift = true;
|
|
323
|
+
break;
|
|
324
|
+
case '[e':
|
|
325
|
+
key.name = 'clear';
|
|
326
|
+
key.shift = true;
|
|
327
|
+
break;
|
|
328
|
+
case '[2$':
|
|
329
|
+
key.name = 'insert';
|
|
330
|
+
key.shift = true;
|
|
331
|
+
break;
|
|
332
|
+
case '[3$':
|
|
333
|
+
key.name = 'delete';
|
|
334
|
+
key.shift = true;
|
|
335
|
+
break;
|
|
336
|
+
case '[5$':
|
|
337
|
+
key.name = 'pageup';
|
|
338
|
+
key.shift = true;
|
|
339
|
+
break;
|
|
340
|
+
case '[6$':
|
|
341
|
+
key.name = 'pagedown';
|
|
342
|
+
key.shift = true;
|
|
343
|
+
break;
|
|
344
|
+
case '[7$':
|
|
345
|
+
key.name = 'home';
|
|
346
|
+
key.shift = true;
|
|
347
|
+
break;
|
|
348
|
+
case '[8$':
|
|
349
|
+
key.name = 'end';
|
|
350
|
+
key.shift = true;
|
|
351
|
+
break;
|
|
352
|
+
case 'Oa':
|
|
353
|
+
key.name = 'up';
|
|
354
|
+
key.ctrl = true;
|
|
355
|
+
break;
|
|
356
|
+
case 'Ob':
|
|
357
|
+
key.name = 'down';
|
|
358
|
+
key.ctrl = true;
|
|
359
|
+
break;
|
|
360
|
+
case 'Oc':
|
|
361
|
+
key.name = 'right';
|
|
362
|
+
key.ctrl = true;
|
|
363
|
+
break;
|
|
364
|
+
case 'Od':
|
|
365
|
+
key.name = 'left';
|
|
366
|
+
key.ctrl = true;
|
|
367
|
+
break;
|
|
368
|
+
case 'Oe':
|
|
369
|
+
key.name = 'clear';
|
|
370
|
+
key.ctrl = true;
|
|
371
|
+
break;
|
|
372
|
+
case '[2^':
|
|
373
|
+
key.name = 'insert';
|
|
374
|
+
key.ctrl = true;
|
|
375
|
+
break;
|
|
376
|
+
case '[3^':
|
|
377
|
+
key.name = 'delete';
|
|
378
|
+
key.ctrl = true;
|
|
379
|
+
break;
|
|
380
|
+
case '[5^':
|
|
381
|
+
key.name = 'pageup';
|
|
382
|
+
key.ctrl = true;
|
|
383
|
+
break;
|
|
384
|
+
case '[6^':
|
|
385
|
+
key.name = 'pagedown';
|
|
386
|
+
key.ctrl = true;
|
|
387
|
+
break;
|
|
388
|
+
case '[7^':
|
|
389
|
+
key.name = 'home';
|
|
390
|
+
key.ctrl = true;
|
|
391
|
+
break;
|
|
392
|
+
case '[8^':
|
|
393
|
+
key.name = 'end';
|
|
394
|
+
key.ctrl = true;
|
|
395
|
+
break;
|
|
396
|
+
/* misc. */
|
|
397
|
+
case '[Z':
|
|
398
|
+
key.name = 'tab';
|
|
399
|
+
key.shift = true;
|
|
400
|
+
break;
|
|
401
|
+
default:
|
|
402
|
+
key.name = 'undefined';
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
else if (s.length > 1 && s[0] !== '\x1b') {
|
|
407
|
+
// Nhận được một chuỗi ký tự dài hơn một.
|
|
408
|
+
// Có thể là paste, vì nó không phải là control sequence.
|
|
409
|
+
for (const c of s) {
|
|
410
|
+
emitKey(stream, c);
|
|
411
|
+
}
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
// XXX: code phân tích "mouse" đã bị XÓA theo yêu cầu.
|
|
415
|
+
// Không phát ra key nếu không tìm thấy tên
|
|
416
|
+
if (key.name === undefined) {
|
|
417
|
+
return; // key = undefined;
|
|
418
|
+
}
|
|
419
|
+
if (s.length === 1) {
|
|
420
|
+
ch = s;
|
|
421
|
+
}
|
|
422
|
+
if (key || ch) {
|
|
423
|
+
stream.emit('keypress', ch, key);
|
|
424
|
+
}
|
|
425
|
+
}
|
package/package.json
CHANGED
|
@@ -1,44 +1,43 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "cliedit",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "A lightweight, raw-mode terminal editor utility for Node.js CLI applications, with line wrapping and undo/redo support.",
|
|
5
|
-
"repository": "https://github.com/CodeTease/cliedit",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"main": "dist/index.js",
|
|
8
|
-
"types": "dist/index.d.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": {
|
|
11
|
-
"import": "./dist/index.js",
|
|
12
|
-
"types": "./dist/index.d.ts"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist",
|
|
17
|
-
"LICENSE",
|
|
18
|
-
"README.md"
|
|
19
|
-
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"build": "tsc",
|
|
22
|
-
"prepublishOnly": "npm run build",
|
|
23
|
-
"demo": "npm run build && cross-env NODE_ENV=development node dist/demo.js",
|
|
24
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
25
|
-
},
|
|
26
|
-
"keywords": [
|
|
27
|
-
"cli",
|
|
28
|
-
"editor",
|
|
29
|
-
"terminal",
|
|
30
|
-
"tui",
|
|
31
|
-
"raw-mode",
|
|
32
|
-
"typescript"
|
|
33
|
-
],
|
|
34
|
-
"author": "CodeTease",
|
|
35
|
-
"license": "MIT",
|
|
36
|
-
"devDependencies": {
|
|
37
|
-
"@types/node": "^20.12.12",
|
|
38
|
-
"cross-env": "^7.0.3",
|
|
39
|
-
"typescript": "^5.4.5"
|
|
40
|
-
},
|
|
41
|
-
"dependencies": {
|
|
42
|
-
|
|
43
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "cliedit",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "A lightweight, raw-mode terminal editor utility for Node.js CLI applications, with line wrapping and undo/redo support.",
|
|
5
|
+
"repository": "https://github.com/CodeTease/cliedit",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"prepublishOnly": "npm run build",
|
|
23
|
+
"demo": "npm run build && cross-env NODE_ENV=development node dist/demo.js",
|
|
24
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"cli",
|
|
28
|
+
"editor",
|
|
29
|
+
"terminal",
|
|
30
|
+
"tui",
|
|
31
|
+
"raw-mode",
|
|
32
|
+
"typescript"
|
|
33
|
+
],
|
|
34
|
+
"author": "CodeTease",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^20.12.12",
|
|
38
|
+
"cross-env": "^7.0.3",
|
|
39
|
+
"typescript": "^5.4.5"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
}
|
|
44
43
|
}
|