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
package/dist/editor.search.js
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
// src/editor.search.ts
|
|
2
2
|
/**
|
|
3
|
-
* Methods related to Find/Search functionality.
|
|
3
|
+
* Methods related to Find/Search/Replace functionality.
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
|
-
* Enters
|
|
6
|
+
* Enters Find mode.
|
|
7
7
|
*/
|
|
8
|
-
function
|
|
9
|
-
this.mode = '
|
|
8
|
+
function enterFindMode() {
|
|
9
|
+
this.mode = 'search_find';
|
|
10
10
|
this.searchQuery = '';
|
|
11
|
+
this.replaceQuery = null; // Mark as Find-Only
|
|
11
12
|
this.searchResults = [];
|
|
12
13
|
this.searchResultIndex = -1;
|
|
13
|
-
this.setStatusMessage('
|
|
14
|
+
this.setStatusMessage('Find (ESC to cancel): ');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Enters Replace mode (starting with the "Find" prompt).
|
|
18
|
+
*/
|
|
19
|
+
function enterReplaceMode() {
|
|
20
|
+
this.mode = 'search_find';
|
|
21
|
+
this.searchQuery = '';
|
|
22
|
+
this.replaceQuery = ''; // Mark as Replace flow
|
|
23
|
+
this.searchResults = [];
|
|
24
|
+
this.searchResultIndex = -1;
|
|
25
|
+
this.setStatusMessage('Find (for Replace): ');
|
|
14
26
|
}
|
|
15
27
|
/**
|
|
16
28
|
* Executes the search and populates results.
|
|
@@ -27,18 +39,114 @@ function executeSearch() {
|
|
|
27
39
|
}
|
|
28
40
|
}
|
|
29
41
|
this.searchResultIndex = -1;
|
|
30
|
-
this.
|
|
42
|
+
if (this.replaceQuery === null) { // Find-only flow
|
|
43
|
+
this.setStatusMessage(`Found ${this.searchResults.length} results for "${this.searchQuery}"`);
|
|
44
|
+
}
|
|
31
45
|
}
|
|
32
46
|
/**
|
|
33
47
|
* Jumps to the next search result.
|
|
34
48
|
*/
|
|
35
49
|
function findNext() {
|
|
50
|
+
if (this.searchQuery === '') {
|
|
51
|
+
this.enterFindMode();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// Execute search if results are not yet populated
|
|
55
|
+
if (this.searchResults.length === 0 && this.searchResultIndex === -1) {
|
|
56
|
+
this.executeSearch();
|
|
57
|
+
}
|
|
36
58
|
if (this.searchResults.length === 0) {
|
|
37
|
-
this.setStatusMessage('No
|
|
59
|
+
this.setStatusMessage('No results found');
|
|
60
|
+
this.mode = 'edit';
|
|
38
61
|
return;
|
|
39
62
|
}
|
|
40
|
-
this.searchResultIndex
|
|
63
|
+
this.searchResultIndex++;
|
|
64
|
+
if (this.searchResultIndex >= this.searchResults.length) {
|
|
65
|
+
this.setStatusMessage('End of file reached. Starting from top.');
|
|
66
|
+
this.searchResultIndex = 0;
|
|
67
|
+
}
|
|
68
|
+
const result = this.searchResults[this.searchResultIndex];
|
|
69
|
+
this.jumpToResult(result);
|
|
70
|
+
if (this.replaceQuery !== null) {
|
|
71
|
+
// Replace flow: Enter confirmation step
|
|
72
|
+
this.mode = 'search_confirm';
|
|
73
|
+
this.setStatusMessage(`Replace "${this.searchQuery}"? (y/n/a/q)`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Find-only flow: Go back to edit
|
|
77
|
+
this.mode = 'edit';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Replaces the current highlighted search result and finds the next one.
|
|
82
|
+
*/
|
|
83
|
+
function replaceCurrentAndFindNext() {
|
|
84
|
+
if (this.searchResultIndex === -1 || !this.searchResults[this.searchResultIndex]) {
|
|
85
|
+
this.findNext();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const result = this.searchResults[this.searchResultIndex];
|
|
89
|
+
const line = this.lines[result.y];
|
|
90
|
+
const before = line.substring(0, result.x);
|
|
91
|
+
const after = line.substring(result.x + this.searchQuery.length);
|
|
92
|
+
// Use replaceQuery (it's guaranteed to be a string here, not null)
|
|
93
|
+
this.lines[result.y] = before + this.replaceQuery + after;
|
|
94
|
+
this.setDirty();
|
|
95
|
+
// Store current position to find the *next* match after this one
|
|
96
|
+
const replacedResultY = result.y;
|
|
97
|
+
const replacedResultX = result.x;
|
|
98
|
+
// We MUST re-execute search as all indices may have changed
|
|
99
|
+
this.executeSearch();
|
|
100
|
+
this.recalculateVisualRows();
|
|
101
|
+
// Find the next result *after* the one we just replaced
|
|
102
|
+
let nextIndex = -1;
|
|
103
|
+
for (let i = 0; i < this.searchResults.length; i++) {
|
|
104
|
+
const res = this.searchResults[i];
|
|
105
|
+
if (res.y > replacedResultY || (res.y === replacedResultY && res.x > replacedResultX)) {
|
|
106
|
+
nextIndex = i;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (nextIndex === -1) {
|
|
111
|
+
this.setStatusMessage('No more results');
|
|
112
|
+
this.mode = 'edit';
|
|
113
|
+
this.searchResultIndex = -1; // Reset search
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Found the next one
|
|
117
|
+
this.searchResultIndex = nextIndex;
|
|
41
118
|
this.jumpToResult(this.searchResults[this.searchResultIndex]);
|
|
119
|
+
this.mode = 'search_confirm'; // Stay in confirm mode
|
|
120
|
+
this.setStatusMessage(`Replace "${this.searchQuery}"? (y/n/a/q)`);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Replaces all occurrences of the search query.
|
|
124
|
+
*/
|
|
125
|
+
function replaceAll() {
|
|
126
|
+
if (this.searchResults.length === 0) {
|
|
127
|
+
this.executeSearch();
|
|
128
|
+
}
|
|
129
|
+
if (this.searchResults.length === 0) {
|
|
130
|
+
this.setStatusMessage('No results found');
|
|
131
|
+
this.mode = 'edit';
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
let count = 0;
|
|
135
|
+
// Iterate backwards to ensure indices remain valid during replacement
|
|
136
|
+
for (let i = this.searchResults.length - 1; i >= 0; i--) {
|
|
137
|
+
const result = this.searchResults[i];
|
|
138
|
+
const line = this.lines[result.y];
|
|
139
|
+
const before = line.substring(0, result.x);
|
|
140
|
+
const after = line.substring(result.x + this.searchQuery.length);
|
|
141
|
+
this.lines[result.y] = before + this.replaceQuery + after;
|
|
142
|
+
count++;
|
|
143
|
+
}
|
|
144
|
+
this.setDirty();
|
|
145
|
+
this.recalculateVisualRows();
|
|
146
|
+
this.mode = 'edit';
|
|
147
|
+
this.searchResults = [];
|
|
148
|
+
this.searchResultIndex = -1;
|
|
149
|
+
this.setStatusMessage(`Replaced ${count} occurrences.`);
|
|
42
150
|
}
|
|
43
151
|
/**
|
|
44
152
|
* Moves cursor and adjusts scroll offset to make the result visible.
|
|
@@ -51,8 +159,11 @@ function jumpToResult(result) {
|
|
|
51
159
|
this.rowOffset = Math.max(0, visualRowIndex - Math.floor(this.screenRows / 2));
|
|
52
160
|
}
|
|
53
161
|
export const searchMethods = {
|
|
54
|
-
|
|
162
|
+
enterFindMode,
|
|
163
|
+
enterReplaceMode,
|
|
55
164
|
executeSearch,
|
|
56
165
|
findNext,
|
|
166
|
+
replaceCurrentAndFindNext,
|
|
167
|
+
replaceAll,
|
|
57
168
|
jumpToResult,
|
|
58
169
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -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,435 @@
|
|
|
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
|
+
// ***** START BUG FIX *****
|
|
146
|
+
// The original library failed to handle any standard printable
|
|
147
|
+
// characters (numbers, symbols) that weren't a-z or A-Z.
|
|
148
|
+
}
|
|
149
|
+
else if (s.length === 1 && s >= ' ' && s <= '~') {
|
|
150
|
+
// Standard printable character (digits, symbols, etc.)
|
|
151
|
+
key.name = s;
|
|
152
|
+
// We can infer shift status for common symbols
|
|
153
|
+
key.shift = '!@#$%^&*()_+{}|:"<>?~'.includes(s);
|
|
154
|
+
// ***** END BUG FIX *****
|
|
155
|
+
}
|
|
156
|
+
else if ((parts = functionKeyCodeRe.exec(s))) {
|
|
157
|
+
// ansi escape sequence
|
|
158
|
+
// Lắp ráp lại key code, bỏ qua \x1b đứng đầu,
|
|
159
|
+
// bitflag của phím bổ trợ và bất kỳ chuỗi "1;" vô nghĩa nào
|
|
160
|
+
const code = (parts[1] || '') +
|
|
161
|
+
(parts[2] || '') +
|
|
162
|
+
(parts[4] || '') +
|
|
163
|
+
(parts[6] || '');
|
|
164
|
+
// FIX TS2362: Chuyển đổi (parts[...]) sang number bằng parseInt
|
|
165
|
+
const modifier = parseInt(parts[3] || parts[5] || '1', 10) - 1;
|
|
166
|
+
// Phân tích phím bổ trợ
|
|
167
|
+
key.ctrl = !!(modifier & 4);
|
|
168
|
+
key.meta = !!(modifier & 10);
|
|
169
|
+
key.shift = !!(modifier & 1);
|
|
170
|
+
key.code = code;
|
|
171
|
+
// Phân tích chính phím đó
|
|
172
|
+
switch (code) {
|
|
173
|
+
/* xterm/gnome ESC O letter */
|
|
174
|
+
case 'OP':
|
|
175
|
+
key.name = 'f1';
|
|
176
|
+
break;
|
|
177
|
+
case 'OQ':
|
|
178
|
+
key.name = 'f2';
|
|
179
|
+
break;
|
|
180
|
+
case 'OR':
|
|
181
|
+
key.name = 'f3';
|
|
182
|
+
break;
|
|
183
|
+
case 'OS':
|
|
184
|
+
key.name = 'f4';
|
|
185
|
+
break;
|
|
186
|
+
/* xterm/rxvt ESC [ number ~ */
|
|
187
|
+
case '[11~':
|
|
188
|
+
key.name = 'f1';
|
|
189
|
+
break;
|
|
190
|
+
case '[12~':
|
|
191
|
+
key.name = 'f2';
|
|
192
|
+
break;
|
|
193
|
+
case '[13~':
|
|
194
|
+
key.name = 'f3';
|
|
195
|
+
break;
|
|
196
|
+
case '[14~':
|
|
197
|
+
key.name = 'f4';
|
|
198
|
+
break;
|
|
199
|
+
/* from Cygwin and used in libuv */
|
|
200
|
+
case '[[A':
|
|
201
|
+
key.name = 'f1';
|
|
202
|
+
break;
|
|
203
|
+
case '[[B':
|
|
204
|
+
key.name = 'f2';
|
|
205
|
+
break;
|
|
206
|
+
case '[[C':
|
|
207
|
+
key.name = 'f3';
|
|
208
|
+
break;
|
|
209
|
+
case '[[D':
|
|
210
|
+
key.name = 'f4';
|
|
211
|
+
break;
|
|
212
|
+
case '[[E':
|
|
213
|
+
key.name = 'f5';
|
|
214
|
+
break;
|
|
215
|
+
/* common */
|
|
216
|
+
case '[15~':
|
|
217
|
+
key.name = 'f5';
|
|
218
|
+
break;
|
|
219
|
+
case '[17~':
|
|
220
|
+
key.name = 'f6';
|
|
221
|
+
break;
|
|
222
|
+
case '[18~':
|
|
223
|
+
key.name = 'f7';
|
|
224
|
+
break;
|
|
225
|
+
case '[19~':
|
|
226
|
+
key.name = 'f8';
|
|
227
|
+
break;
|
|
228
|
+
case '[20~':
|
|
229
|
+
key.name = 'f9';
|
|
230
|
+
break;
|
|
231
|
+
case '[21~':
|
|
232
|
+
key.name = 'f10';
|
|
233
|
+
break;
|
|
234
|
+
case '[23~':
|
|
235
|
+
key.name = 'f11';
|
|
236
|
+
break;
|
|
237
|
+
case '[24~':
|
|
238
|
+
key.name = 'f12';
|
|
239
|
+
break;
|
|
240
|
+
/* xterm ESC [ letter */
|
|
241
|
+
case '[A':
|
|
242
|
+
key.name = 'up';
|
|
243
|
+
break;
|
|
244
|
+
case '[B':
|
|
245
|
+
key.name = 'down';
|
|
246
|
+
break;
|
|
247
|
+
case '[C':
|
|
248
|
+
key.name = 'right';
|
|
249
|
+
break;
|
|
250
|
+
case '[D':
|
|
251
|
+
key.name = 'left';
|
|
252
|
+
break;
|
|
253
|
+
case '[E':
|
|
254
|
+
key.name = 'clear';
|
|
255
|
+
break;
|
|
256
|
+
case '[F':
|
|
257
|
+
key.name = 'end';
|
|
258
|
+
break;
|
|
259
|
+
case '[H':
|
|
260
|
+
key.name = 'home';
|
|
261
|
+
break;
|
|
262
|
+
/* xterm/gnome ESC O letter */
|
|
263
|
+
case 'OA':
|
|
264
|
+
key.name = 'up';
|
|
265
|
+
break;
|
|
266
|
+
case 'OB':
|
|
267
|
+
key.name = 'down';
|
|
268
|
+
break;
|
|
269
|
+
case 'OC':
|
|
270
|
+
key.name = 'right';
|
|
271
|
+
break;
|
|
272
|
+
case 'OD':
|
|
273
|
+
key.name = 'left';
|
|
274
|
+
break;
|
|
275
|
+
case 'OE':
|
|
276
|
+
key.name = 'clear';
|
|
277
|
+
break;
|
|
278
|
+
case 'OF':
|
|
279
|
+
key.name = 'end';
|
|
280
|
+
break;
|
|
281
|
+
case 'OH':
|
|
282
|
+
key.name = 'home';
|
|
283
|
+
break;
|
|
284
|
+
/* xterm/rxvt ESC [ number ~ */
|
|
285
|
+
case '[1~':
|
|
286
|
+
key.name = 'home';
|
|
287
|
+
break;
|
|
288
|
+
case '[2~':
|
|
289
|
+
key.name = 'insert';
|
|
290
|
+
break;
|
|
291
|
+
case '[3~':
|
|
292
|
+
key.name = 'delete';
|
|
293
|
+
break;
|
|
294
|
+
case '[4~':
|
|
295
|
+
key.name = 'end';
|
|
296
|
+
break;
|
|
297
|
+
case '[5~':
|
|
298
|
+
key.name = 'pageup';
|
|
299
|
+
break;
|
|
300
|
+
case '[6~':
|
|
301
|
+
key.name = 'pagedown';
|
|
302
|
+
break;
|
|
303
|
+
/* putty */
|
|
304
|
+
case '[[5~':
|
|
305
|
+
key.name = 'pageup';
|
|
306
|
+
break;
|
|
307
|
+
case '[[6~':
|
|
308
|
+
key.name = 'pagedown';
|
|
309
|
+
break;
|
|
310
|
+
/* rxvt */
|
|
311
|
+
case '[7~':
|
|
312
|
+
key.name = 'home';
|
|
313
|
+
break;
|
|
314
|
+
case '[8~':
|
|
315
|
+
key.name = 'end';
|
|
316
|
+
break;
|
|
317
|
+
/* rxvt keys with modifiers */
|
|
318
|
+
case '[a':
|
|
319
|
+
key.name = 'up';
|
|
320
|
+
key.shift = true;
|
|
321
|
+
break;
|
|
322
|
+
case '[b':
|
|
323
|
+
key.name = 'down';
|
|
324
|
+
key.shift = true;
|
|
325
|
+
break;
|
|
326
|
+
case '[c':
|
|
327
|
+
key.name = 'right';
|
|
328
|
+
key.shift = true;
|
|
329
|
+
break;
|
|
330
|
+
case '[d':
|
|
331
|
+
key.name = 'left';
|
|
332
|
+
key.shift = true;
|
|
333
|
+
break;
|
|
334
|
+
case '[e':
|
|
335
|
+
key.name = 'clear';
|
|
336
|
+
key.shift = true;
|
|
337
|
+
break;
|
|
338
|
+
case '[2$':
|
|
339
|
+
key.name = 'insert';
|
|
340
|
+
key.shift = true;
|
|
341
|
+
break;
|
|
342
|
+
case '[3$':
|
|
343
|
+
key.name = 'delete';
|
|
344
|
+
key.shift = true;
|
|
345
|
+
break;
|
|
346
|
+
case '[5$':
|
|
347
|
+
key.name = 'pageup';
|
|
348
|
+
key.shift = true;
|
|
349
|
+
break;
|
|
350
|
+
case '[6$':
|
|
351
|
+
key.name = 'pagedown';
|
|
352
|
+
key.shift = true;
|
|
353
|
+
break;
|
|
354
|
+
case '[7$':
|
|
355
|
+
key.name = 'home';
|
|
356
|
+
key.shift = true;
|
|
357
|
+
break;
|
|
358
|
+
case '[8$':
|
|
359
|
+
key.name = 'end';
|
|
360
|
+
key.shift = true;
|
|
361
|
+
break;
|
|
362
|
+
case 'Oa':
|
|
363
|
+
key.name = 'up';
|
|
364
|
+
key.ctrl = true;
|
|
365
|
+
break;
|
|
366
|
+
case 'Ob':
|
|
367
|
+
key.name = 'down';
|
|
368
|
+
key.ctrl = true;
|
|
369
|
+
break;
|
|
370
|
+
case 'Oc':
|
|
371
|
+
key.name = 'right';
|
|
372
|
+
key.ctrl = true;
|
|
373
|
+
break;
|
|
374
|
+
case 'Od':
|
|
375
|
+
key.name = 'left';
|
|
376
|
+
key.ctrl = true;
|
|
377
|
+
break;
|
|
378
|
+
case 'Oe':
|
|
379
|
+
key.name = 'clear';
|
|
380
|
+
key.ctrl = true;
|
|
381
|
+
break;
|
|
382
|
+
case '[2^':
|
|
383
|
+
key.name = 'insert';
|
|
384
|
+
key.ctrl = true;
|
|
385
|
+
break;
|
|
386
|
+
case '[3^':
|
|
387
|
+
key.name = 'delete';
|
|
388
|
+
key.ctrl = true;
|
|
389
|
+
break;
|
|
390
|
+
case '[5^':
|
|
391
|
+
key.name = 'pageup';
|
|
392
|
+
key.ctrl = true;
|
|
393
|
+
break;
|
|
394
|
+
case '[6^':
|
|
395
|
+
key.name = 'pagedown';
|
|
396
|
+
key.ctrl = true;
|
|
397
|
+
break;
|
|
398
|
+
case '[7^':
|
|
399
|
+
key.name = 'home';
|
|
400
|
+
key.ctrl = true;
|
|
401
|
+
break;
|
|
402
|
+
case '[8^':
|
|
403
|
+
key.name = 'end';
|
|
404
|
+
key.ctrl = true;
|
|
405
|
+
break;
|
|
406
|
+
/* misc. */
|
|
407
|
+
case '[Z':
|
|
408
|
+
key.name = 'tab';
|
|
409
|
+
key.shift = true;
|
|
410
|
+
break;
|
|
411
|
+
default:
|
|
412
|
+
key.name = 'undefined';
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
else if (s.length > 1 && s[0] !== '\x1b') {
|
|
417
|
+
// Nhận được một chuỗi ký tự dài hơn một.
|
|
418
|
+
// Có thể là paste, vì nó không phải là control sequence.
|
|
419
|
+
for (const c of s) {
|
|
420
|
+
emitKey(stream, c);
|
|
421
|
+
}
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
// XXX: code phân tích "mouse" đã bị XÓA theo yêu cầu.
|
|
425
|
+
// Không phát ra key nếu không tìm thấy tên
|
|
426
|
+
if (key.name === undefined) {
|
|
427
|
+
return; // key = undefined;
|
|
428
|
+
}
|
|
429
|
+
if (s.length === 1) {
|
|
430
|
+
ch = s;
|
|
431
|
+
}
|
|
432
|
+
if (key || ch) {
|
|
433
|
+
stream.emit('keypress', ch, key);
|
|
434
|
+
}
|
|
435
|
+
}
|