cliedit 0.1.0 → 0.1.2
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/ACKNOWLEDGEMENTS.md +21 -0
- package/LICENSE +21 -21
- package/README.md +91 -87
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/editor.clipboard.js +8 -2
- package/dist/editor.d.ts +3 -10
- package/dist/editor.editing.d.ts +1 -0
- package/dist/editor.editing.js +7 -2
- package/dist/editor.js +6 -2
- package/dist/editor.keys.d.ts +3 -10
- package/dist/editor.keys.js +180 -59
- package/dist/editor.navigation.d.ts +10 -0
- package/dist/editor.navigation.js +24 -0
- package/dist/editor.rendering.js +36 -13
- package/dist/editor.search.d.ts +19 -4
- package/dist/editor.search.js +120 -9
- package/dist/types.d.ts +1 -1
- package/dist/vendor/keypress.d.ts +17 -0
- package/dist/vendor/keypress.js +435 -0
- package/package.json +43 -43
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Acknowledgements & Copyright (ACKNOWLEDGEMENTS)
|
|
2
|
+
|
|
3
|
+
`cliedit` is an open-source project that utilizes components (or modified/converted versions thereof) from other open-source projects. We express our deep gratitude to the communities and authors who provided the foundation for these components.
|
|
4
|
+
|
|
5
|
+
All components listed below are included as vendored code (integrated directly into the project source code) and have been adapted to fit **cliedit**'s TypeScript architecture and remove unnecessary features (e.g., mouse support).
|
|
6
|
+
|
|
7
|
+
## Vendored Components List
|
|
8
|
+
|
|
9
|
+
**Original Project Name: `keypress`**
|
|
10
|
+
|
|
11
|
+
**Description & Origin:** Provides raw key event parsing logic for the TTY environment. The source code was converted from the original JavaScript version and optimized for Node.js.
|
|
12
|
+
|
|
13
|
+
**Repository:** https://github.com/TooTallNate/keypress
|
|
14
|
+
|
|
15
|
+
**License:** MIT
|
|
16
|
+
|
|
17
|
+
**Copyright Notes:**
|
|
18
|
+
|
|
19
|
+
- `keypress` was originally authored by **Nathan Rajlich** (tootallnate.net).
|
|
20
|
+
- Copyright (c) 2012 Nathan Rajlich.
|
|
21
|
+
- The module is based on the keypress logic found within the Node.js Core `readline` module.
|
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/constants.d.ts
CHANGED
package/dist/constants.js
CHANGED
|
@@ -22,7 +22,9 @@ export const KEYS = {
|
|
|
22
22
|
CTRL_Q: '\x11', // Quit
|
|
23
23
|
CTRL_S: '\x13', // Save
|
|
24
24
|
CTRL_W: '\x17', // Find (Where is)
|
|
25
|
+
CTRL_R: '\x12', // Replace
|
|
25
26
|
CTRL_G: '\x07', // Go to next
|
|
27
|
+
CTRL_L: '\x0c', // Go to Line (L)
|
|
26
28
|
CTRL_Z: '\x1a', // Undo
|
|
27
29
|
CTRL_Y: '\x19', // Redo
|
|
28
30
|
CTRL_K: '\x0b', // Cut/Kill line
|
package/dist/editor.clipboard.js
CHANGED
|
@@ -15,8 +15,11 @@ function setClipboard(text) {
|
|
|
15
15
|
case 'win32':
|
|
16
16
|
command = 'clip';
|
|
17
17
|
break;
|
|
18
|
+
case 'linux': // <--- THÊM HỖ TRỢ LINUX
|
|
19
|
+
command = 'xclip -selection clipboard';
|
|
20
|
+
break;
|
|
18
21
|
default:
|
|
19
|
-
this.setStatusMessage('Clipboard
|
|
22
|
+
this.setStatusMessage('Clipboard not supported on this platform');
|
|
20
23
|
return resolve();
|
|
21
24
|
}
|
|
22
25
|
const process = exec(command, (error) => {
|
|
@@ -43,8 +46,11 @@ function getClipboard() {
|
|
|
43
46
|
case 'win32':
|
|
44
47
|
command = 'powershell -command "Get-Clipboard"';
|
|
45
48
|
break;
|
|
49
|
+
case 'linux': // <--- THÊM HỖ TRỢ LINUX
|
|
50
|
+
command = 'xclip -selection clipboard -o'; // -o (hoặc -out) để đọc
|
|
51
|
+
break;
|
|
46
52
|
default:
|
|
47
|
-
this.setStatusMessage('Clipboard
|
|
53
|
+
this.setStatusMessage('Clipboard not supported on this platform');
|
|
48
54
|
return resolve('');
|
|
49
55
|
}
|
|
50
56
|
exec(command, (error, stdout) => {
|
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';
|
|
@@ -51,8 +42,10 @@ export declare class CliEditor {
|
|
|
51
42
|
statusTimeout: NodeJS.Timeout | null;
|
|
52
43
|
isMessageCustom: boolean;
|
|
53
44
|
quitConfirm: boolean;
|
|
54
|
-
readonly DEFAULT_STATUS = "HELP: Ctrl+S = Save
|
|
45
|
+
readonly DEFAULT_STATUS = "HELP: Ctrl+S = Save | Ctrl+Q = Quit | Ctrl+W = Find | Ctrl+R = Replace | Ctrl+L = Go to Line";
|
|
55
46
|
searchQuery: string;
|
|
47
|
+
replaceQuery: string | null;
|
|
48
|
+
goToLineQuery: string;
|
|
56
49
|
searchResults: {
|
|
57
50
|
y: number;
|
|
58
51
|
x: number;
|
package/dist/editor.editing.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ declare function insertCharacter(this: CliEditor, char: string): void;
|
|
|
18
18
|
declare function insertSoftTab(this: CliEditor): void;
|
|
19
19
|
/**
|
|
20
20
|
* Inserts a new line, splitting the current line at the cursor position.
|
|
21
|
+
* Implements auto-indent.
|
|
21
22
|
*/
|
|
22
23
|
declare function insertNewLine(this: CliEditor): void;
|
|
23
24
|
/**
|
package/dist/editor.editing.js
CHANGED
|
@@ -51,14 +51,19 @@ function insertSoftTab() {
|
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
53
|
* Inserts a new line, splitting the current line at the cursor position.
|
|
54
|
+
* Implements auto-indent.
|
|
54
55
|
*/
|
|
55
56
|
function insertNewLine() {
|
|
56
57
|
const line = this.lines[this.cursorY] || '';
|
|
58
|
+
// Find indentation of the current line
|
|
59
|
+
const match = line.match(/^(\s*)/);
|
|
60
|
+
const indent = match ? match[1] : '';
|
|
57
61
|
const remainder = line.slice(this.cursorX);
|
|
58
62
|
this.lines[this.cursorY] = line.slice(0, this.cursorX);
|
|
59
|
-
|
|
63
|
+
// Add new line with the same indentation + remainder
|
|
64
|
+
this.lines.splice(this.cursorY + 1, 0, indent + remainder);
|
|
60
65
|
this.cursorY++;
|
|
61
|
-
this.cursorX =
|
|
66
|
+
this.cursorX = indent.length; // Move cursor to end of indent
|
|
62
67
|
this.setDirty();
|
|
63
68
|
}
|
|
64
69
|
/**
|
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';
|
|
@@ -11,7 +13,7 @@ import { historyMethods } from './editor.history.js';
|
|
|
11
13
|
import { ioMethods } from './editor.io.js';
|
|
12
14
|
import { keyHandlingMethods } from './editor.keys.js';
|
|
13
15
|
import { selectionMethods } from './editor.selection.js';
|
|
14
|
-
const DEFAULT_STATUS = 'HELP: Ctrl+S = Save
|
|
16
|
+
const DEFAULT_STATUS = 'HELP: Ctrl+S = Save | Ctrl+Q = Quit | Ctrl+W = Find | Ctrl+R = Replace | Ctrl+L = Go to Line';
|
|
15
17
|
/**
|
|
16
18
|
* Main editor class managing application state, TTY interaction, and rendering.
|
|
17
19
|
*/
|
|
@@ -34,6 +36,8 @@ export class CliEditor {
|
|
|
34
36
|
this.quitConfirm = false;
|
|
35
37
|
this.DEFAULT_STATUS = DEFAULT_STATUS;
|
|
36
38
|
this.searchQuery = '';
|
|
39
|
+
this.replaceQuery = null; // null = Find mode, string = Replace mode
|
|
40
|
+
this.goToLineQuery = ''; // For Go to Line prompt
|
|
37
41
|
this.searchResults = [];
|
|
38
42
|
this.searchResultIndex = -1;
|
|
39
43
|
this.isCleanedUp = false;
|
package/dist/editor.keys.d.ts
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
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;
|
|
14
5
|
handleSearchKeys: (key: string) => void;
|
|
6
|
+
handleSearchConfirmKeys: (key: string) => void;
|
|
7
|
+
handleGoToLineKeys: (key: string) => void;
|
|
15
8
|
handleCtrlQ: () => void;
|
|
16
9
|
handleCopy: () => Promise<void>;
|
|
17
10
|
handleCharacterKey: (ch: string) => void;
|