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.keys.js
CHANGED
|
@@ -8,19 +8,34 @@ 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
|
+
if (this.mode === 'search_find' || this.mode === 'search_replace') {
|
|
17
|
+
this.handleSearchKeys(ch);
|
|
18
|
+
}
|
|
19
|
+
else if (this.mode === 'goto_line') {
|
|
20
|
+
this.handleGoToLineKeys(ch);
|
|
21
|
+
}
|
|
22
|
+
else if (this.mode === 'edit') {
|
|
23
|
+
edited = this.handleEditKeys(ch);
|
|
24
|
+
if (edited) {
|
|
25
|
+
this.saveState();
|
|
26
|
+
this.recalculateVisualRows(); // Phải tính toán lại sau khi gõ
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else if (this.mode === 'search_confirm') {
|
|
30
|
+
this.handleSearchConfirmKeys(ch);
|
|
31
|
+
}
|
|
16
32
|
this.render();
|
|
33
|
+
return;
|
|
17
34
|
}
|
|
18
35
|
return;
|
|
19
36
|
}
|
|
20
|
-
// ---
|
|
21
|
-
|
|
22
|
-
let edited = false;
|
|
23
|
-
// 1. Map Control sequences (Ctrl+Arrow for selection)
|
|
37
|
+
// --- 2. Từ đây, 'key' object là đảm bảo có (phím đặc biệt hoặc Ctrl/Meta) ---
|
|
38
|
+
// 2.1. Ánh xạ Control sequences (Ctrl+Arrow cho selection)
|
|
24
39
|
if (key.ctrl) {
|
|
25
40
|
if (key.name === 'up')
|
|
26
41
|
keyName = KEYS.CTRL_ARROW_UP;
|
|
@@ -31,10 +46,10 @@ function handleKeypressEvent(ch, key) {
|
|
|
31
46
|
else if (key.name === 'right')
|
|
32
47
|
keyName = KEYS.CTRL_ARROW_RIGHT;
|
|
33
48
|
else
|
|
34
|
-
keyName = key.sequence;
|
|
49
|
+
keyName = key.sequence;
|
|
35
50
|
}
|
|
36
51
|
else {
|
|
37
|
-
// 2.
|
|
52
|
+
// 2.2. Ánh xạ phím tiêu chuẩn (Arrow, Home, End, Enter, Tab)
|
|
38
53
|
if (key.name === 'up')
|
|
39
54
|
keyName = KEYS.ARROW_UP;
|
|
40
55
|
else if (key.name === 'down')
|
|
@@ -60,55 +75,47 @@ function handleKeypressEvent(ch, key) {
|
|
|
60
75
|
else if (key.name === 'tab')
|
|
61
76
|
keyName = KEYS.TAB;
|
|
62
77
|
else
|
|
63
|
-
keyName = key.sequence;
|
|
78
|
+
keyName = key.sequence;
|
|
64
79
|
}
|
|
65
|
-
// 3.
|
|
66
|
-
|
|
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)
|
|
75
|
-
if (this.mode === 'search') {
|
|
80
|
+
// --- 3. Định tuyến theo Mode ---
|
|
81
|
+
if (this.mode === 'search_find' || this.mode === 'search_replace') {
|
|
76
82
|
this.handleSearchKeys(keyName || ch);
|
|
77
83
|
}
|
|
84
|
+
else if (this.mode === 'search_confirm') {
|
|
85
|
+
this.handleSearchConfirmKeys(keyName || ch);
|
|
86
|
+
}
|
|
87
|
+
else if (this.mode === 'goto_line') {
|
|
88
|
+
this.handleGoToLineKeys(keyName || ch);
|
|
89
|
+
}
|
|
78
90
|
else {
|
|
79
|
-
//
|
|
91
|
+
// 4. Xử lý phím lựa chọn (Ctrl+Arrow) - Navigation
|
|
80
92
|
switch (keyName) {
|
|
81
93
|
case KEYS.CTRL_ARROW_UP:
|
|
82
|
-
this.startOrUpdateSelection();
|
|
83
|
-
this.moveCursorVisually(-1);
|
|
84
|
-
this.render();
|
|
85
|
-
return;
|
|
86
94
|
case KEYS.CTRL_ARROW_DOWN:
|
|
87
|
-
this.startOrUpdateSelection();
|
|
88
|
-
this.moveCursorVisually(1);
|
|
89
|
-
this.render();
|
|
90
|
-
return;
|
|
91
95
|
case KEYS.CTRL_ARROW_LEFT:
|
|
92
|
-
this.startOrUpdateSelection();
|
|
93
|
-
this.moveCursorLogically(-1);
|
|
94
|
-
this.render();
|
|
95
|
-
return;
|
|
96
96
|
case KEYS.CTRL_ARROW_RIGHT:
|
|
97
97
|
this.startOrUpdateSelection();
|
|
98
|
-
|
|
98
|
+
if (keyName === KEYS.CTRL_ARROW_UP)
|
|
99
|
+
this.moveCursorVisually(-1);
|
|
100
|
+
else if (keyName === KEYS.CTRL_ARROW_DOWN)
|
|
101
|
+
this.moveCursorVisually(1);
|
|
102
|
+
else if (keyName === KEYS.CTRL_ARROW_LEFT)
|
|
103
|
+
this.moveCursorLogically(-1);
|
|
104
|
+
else if (keyName === KEYS.CTRL_ARROW_RIGHT)
|
|
105
|
+
this.moveCursorLogically(1);
|
|
99
106
|
this.render();
|
|
100
107
|
return;
|
|
101
108
|
}
|
|
102
|
-
//
|
|
109
|
+
// 5. Xử lý tất cả các phím lệnh/chỉnh sửa khác
|
|
103
110
|
edited = this.handleEditKeys(keyName || ch);
|
|
104
111
|
}
|
|
105
|
-
//
|
|
112
|
+
// 6. Cập nhật Trạng thái và Render
|
|
106
113
|
if (edited) {
|
|
107
|
-
this.saveState();
|
|
108
|
-
this.recalculateVisualRows();
|
|
114
|
+
this.saveState(); // <-- Chỉ gọi khi gõ phím, xóa, v.v.
|
|
115
|
+
this.recalculateVisualRows(); // Tính toán lại layout
|
|
109
116
|
}
|
|
110
117
|
if (!this.isExiting) {
|
|
111
|
-
this.render();
|
|
118
|
+
this.render(); // Render cuối cùng (với visual rows đã được cập nhật nếu cần)
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
121
|
/**
|
|
@@ -116,10 +123,6 @@ function handleKeypressEvent(ch, key) {
|
|
|
116
123
|
* Returns true if content was modified.
|
|
117
124
|
*/
|
|
118
125
|
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
126
|
// Cancel selection on normal navigation
|
|
124
127
|
const isNavigation = [
|
|
125
128
|
KEYS.ARROW_UP, KEYS.ARROW_DOWN, KEYS.ARROW_LEFT, KEYS.ARROW_RIGHT,
|
|
@@ -131,7 +134,6 @@ function handleEditKeys(key) {
|
|
|
131
134
|
this.setStatusMessage(this.DEFAULT_STATUS, 0);
|
|
132
135
|
}
|
|
133
136
|
}
|
|
134
|
-
// Commands that return Promises must be wrapped in a sync call here
|
|
135
137
|
switch (key) {
|
|
136
138
|
// --- Exit / Save ---
|
|
137
139
|
case KEYS.CTRL_Q:
|
|
@@ -143,7 +145,7 @@ function handleEditKeys(key) {
|
|
|
143
145
|
case KEYS.CTRL_C:
|
|
144
146
|
this.handleCopy();
|
|
145
147
|
return false;
|
|
146
|
-
// --- Navigation ---
|
|
148
|
+
// --- Navigation (Non-Selection) ---
|
|
147
149
|
case KEYS.ARROW_UP:
|
|
148
150
|
this.moveCursorVisually(-1);
|
|
149
151
|
return false;
|
|
@@ -189,17 +191,27 @@ function handleEditKeys(key) {
|
|
|
189
191
|
return true;
|
|
190
192
|
// --- Search & History ---
|
|
191
193
|
case KEYS.CTRL_W:
|
|
192
|
-
this.
|
|
194
|
+
this.enterFindMode();
|
|
195
|
+
return false;
|
|
196
|
+
case KEYS.CTRL_R:
|
|
197
|
+
this.enterReplaceMode();
|
|
198
|
+
return false;
|
|
199
|
+
case KEYS.CTRL_L:
|
|
200
|
+
this.enterGoToLineMode();
|
|
193
201
|
return false;
|
|
194
202
|
case KEYS.CTRL_G:
|
|
195
203
|
this.findNext();
|
|
196
204
|
return false;
|
|
205
|
+
// ***** SỬA LỖI VISUAL *****
|
|
206
|
+
// Sau khi undo/redo, chúng ta PHẢI tính toán lại visual rows
|
|
197
207
|
case KEYS.CTRL_Z:
|
|
198
208
|
this.undo();
|
|
199
|
-
|
|
209
|
+
this.recalculateVisualRows(); // <-- THÊM DÒNG NÀY
|
|
210
|
+
return false;
|
|
200
211
|
case KEYS.CTRL_Y:
|
|
201
212
|
this.redo();
|
|
202
|
-
|
|
213
|
+
this.recalculateVisualRows(); // <-- THÊM DÒNG NÀY
|
|
214
|
+
return false;
|
|
203
215
|
// --- Clipboard ---
|
|
204
216
|
case KEYS.CTRL_K: // Cut Line (Traditional)
|
|
205
217
|
this.cutLine();
|
|
@@ -208,12 +220,17 @@ function handleEditKeys(key) {
|
|
|
208
220
|
this.pasteLine();
|
|
209
221
|
return true;
|
|
210
222
|
case KEYS.CTRL_X: // Cut Selection
|
|
211
|
-
this.cutSelection();
|
|
223
|
+
this.cutSelection();
|
|
212
224
|
return true;
|
|
213
225
|
case KEYS.CTRL_V: // Paste Selection
|
|
214
226
|
this.pasteSelection();
|
|
215
227
|
return true;
|
|
228
|
+
// Xử lý Ký tự in được
|
|
216
229
|
default:
|
|
230
|
+
if (key.length === 1 && key >= ' ' && key <= '~') {
|
|
231
|
+
this.handleCharacterKey(key);
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
217
234
|
return false;
|
|
218
235
|
}
|
|
219
236
|
}
|
|
@@ -274,28 +291,130 @@ async function handleSave() {
|
|
|
274
291
|
}
|
|
275
292
|
}
|
|
276
293
|
/**
|
|
277
|
-
* Handles Search Mode input keys.
|
|
294
|
+
* Handles Search Mode input keys (for 'search_find' and 'search_replace').
|
|
278
295
|
*/
|
|
279
296
|
function handleSearchKeys(key) {
|
|
297
|
+
const cancelSearch = () => {
|
|
298
|
+
this.mode = 'edit';
|
|
299
|
+
this.searchQuery = '';
|
|
300
|
+
this.replaceQuery = null;
|
|
301
|
+
this.searchResults = [];
|
|
302
|
+
this.searchResultIndex = -1;
|
|
303
|
+
this.setStatusMessage('Cancelled');
|
|
304
|
+
};
|
|
280
305
|
switch (key) {
|
|
281
306
|
case KEYS.ENTER:
|
|
282
|
-
this.
|
|
283
|
-
|
|
307
|
+
if (this.mode === 'search_find') {
|
|
308
|
+
if (this.replaceQuery === null) {
|
|
309
|
+
// Find-Only Flow: Execute search and find first
|
|
310
|
+
this.executeSearch();
|
|
311
|
+
this.mode = 'edit';
|
|
312
|
+
this.findNext();
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
// Replace Flow: Transition to get replace string
|
|
316
|
+
this.mode = 'search_replace';
|
|
317
|
+
this.setStatusMessage('Replace with: ');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
else if (this.mode === 'search_replace') {
|
|
321
|
+
// Replace Flow: We have both strings, execute and find first
|
|
322
|
+
this.executeSearch();
|
|
323
|
+
this.mode = 'edit';
|
|
324
|
+
this.findNext();
|
|
325
|
+
}
|
|
326
|
+
break;
|
|
327
|
+
case KEYS.ESCAPE:
|
|
328
|
+
case KEYS.CTRL_C:
|
|
329
|
+
case KEYS.CTRL_Q:
|
|
330
|
+
cancelSearch();
|
|
331
|
+
break;
|
|
332
|
+
case KEYS.BACKSPACE:
|
|
333
|
+
if (this.mode === 'search_find') {
|
|
334
|
+
this.searchQuery = this.searchQuery.slice(0, -1);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
this.replaceQuery = this.replaceQuery.slice(0, -1);
|
|
338
|
+
}
|
|
339
|
+
break;
|
|
340
|
+
default:
|
|
341
|
+
if (key.length === 1 && key >= ' ' && key <= '~') {
|
|
342
|
+
if (this.mode === 'search_find') {
|
|
343
|
+
this.searchQuery += key;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
this.replaceQuery += key;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Update status bar message live (if not cancelling)
|
|
351
|
+
if (this.mode === 'search_find') {
|
|
352
|
+
this.setStatusMessage((this.replaceQuery === null ? 'Find: ' : 'Find: ') + this.searchQuery);
|
|
353
|
+
}
|
|
354
|
+
else if (this.mode === 'search_replace') {
|
|
355
|
+
this.setStatusMessage('Replace with: ' + this.replaceQuery);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Handles keypresses during the (y/n/a/q) confirmation step.
|
|
360
|
+
*/
|
|
361
|
+
function handleSearchConfirmKeys(key) {
|
|
362
|
+
switch (key.toLowerCase()) {
|
|
363
|
+
case 'y': // Yes
|
|
364
|
+
this.replaceCurrentAndFindNext();
|
|
365
|
+
break;
|
|
366
|
+
case 'n': // No
|
|
284
367
|
this.findNext();
|
|
285
368
|
break;
|
|
369
|
+
case 'a': // All
|
|
370
|
+
this.replaceAll();
|
|
371
|
+
break;
|
|
372
|
+
case 'q': // Quit
|
|
286
373
|
case KEYS.ESCAPE:
|
|
287
374
|
case KEYS.CTRL_C:
|
|
288
375
|
case KEYS.CTRL_Q:
|
|
289
376
|
this.mode = 'edit';
|
|
290
|
-
this.
|
|
291
|
-
this.
|
|
377
|
+
this.searchResults = [];
|
|
378
|
+
this.searchResultIndex = -1;
|
|
379
|
+
this.setStatusMessage('Replace cancelled');
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Handles keypresses during the 'Go to Line' prompt.
|
|
385
|
+
*/
|
|
386
|
+
function handleGoToLineKeys(key) {
|
|
387
|
+
const cancel = () => {
|
|
388
|
+
this.mode = 'edit';
|
|
389
|
+
this.goToLineQuery = '';
|
|
390
|
+
this.setStatusMessage('Cancelled');
|
|
391
|
+
};
|
|
392
|
+
switch (key) {
|
|
393
|
+
case KEYS.ENTER:
|
|
394
|
+
const lineNumber = parseInt(this.goToLineQuery, 10);
|
|
395
|
+
if (!isNaN(lineNumber) && lineNumber > 0) {
|
|
396
|
+
this.jumpToLine(lineNumber);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
this.mode = 'edit';
|
|
400
|
+
this.setStatusMessage('Invalid line number');
|
|
401
|
+
}
|
|
402
|
+
this.goToLineQuery = '';
|
|
403
|
+
break;
|
|
404
|
+
case KEYS.ESCAPE:
|
|
405
|
+
case KEYS.CTRL_C:
|
|
406
|
+
case KEYS.CTRL_Q:
|
|
407
|
+
cancel();
|
|
292
408
|
break;
|
|
293
409
|
case KEYS.BACKSPACE:
|
|
294
|
-
this.
|
|
410
|
+
this.goToLineQuery = this.goToLineQuery.slice(0, -1);
|
|
411
|
+
this.setStatusMessage('Go to Line: ' + this.goToLineQuery);
|
|
295
412
|
break;
|
|
296
413
|
default:
|
|
297
|
-
|
|
298
|
-
|
|
414
|
+
// Only accept digits
|
|
415
|
+
if (key.length === 1 && key >= '0' && key <= '9') {
|
|
416
|
+
this.goToLineQuery += key;
|
|
417
|
+
this.setStatusMessage('Go to Line: ' + this.goToLineQuery);
|
|
299
418
|
}
|
|
300
419
|
}
|
|
301
420
|
}
|
|
@@ -303,6 +422,8 @@ export const keyHandlingMethods = {
|
|
|
303
422
|
handleKeypressEvent,
|
|
304
423
|
handleEditKeys,
|
|
305
424
|
handleSearchKeys,
|
|
425
|
+
handleSearchConfirmKeys,
|
|
426
|
+
handleGoToLineKeys,
|
|
306
427
|
handleCtrlQ,
|
|
307
428
|
handleCopy,
|
|
308
429
|
handleCharacterKey,
|
|
@@ -30,6 +30,14 @@ declare function adjustCursorPosition(this: CliEditor): void;
|
|
|
30
30
|
* Scrolls the viewport to keep the cursor visible.
|
|
31
31
|
*/
|
|
32
32
|
declare function scroll(this: CliEditor): void;
|
|
33
|
+
/**
|
|
34
|
+
* Jumps the cursor to a specific line number (1-based).
|
|
35
|
+
*/
|
|
36
|
+
declare function jumpToLine(this: CliEditor, lineNumber: number): void;
|
|
37
|
+
/**
|
|
38
|
+
* Enters Go To Line mode.
|
|
39
|
+
*/
|
|
40
|
+
declare function enterGoToLineMode(this: CliEditor): void;
|
|
33
41
|
export declare const navigationMethods: {
|
|
34
42
|
findCurrentVisualRowIndex: typeof findCurrentVisualRowIndex;
|
|
35
43
|
moveCursorLogically: typeof moveCursorLogically;
|
|
@@ -38,5 +46,7 @@ export declare const navigationMethods: {
|
|
|
38
46
|
findVisualRowEnd: typeof findVisualRowEnd;
|
|
39
47
|
adjustCursorPosition: typeof adjustCursorPosition;
|
|
40
48
|
scroll: typeof scroll;
|
|
49
|
+
jumpToLine: typeof jumpToLine;
|
|
50
|
+
enterGoToLineMode: typeof enterGoToLineMode;
|
|
41
51
|
};
|
|
42
52
|
export {};
|
|
@@ -121,6 +121,28 @@ function scroll() {
|
|
|
121
121
|
this.rowOffset = currentVisualRow - this.screenRows + 1;
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Jumps the cursor to a specific line number (1-based).
|
|
126
|
+
*/
|
|
127
|
+
function jumpToLine(lineNumber) {
|
|
128
|
+
const targetY = lineNumber - 1; // Convert 1-based to 0-based index
|
|
129
|
+
// Clamp targetY to valid range
|
|
130
|
+
this.cursorY = Math.max(0, Math.min(targetY, this.lines.length - 1));
|
|
131
|
+
this.cursorX = 0; // Move to start of line
|
|
132
|
+
// Adjust scroll
|
|
133
|
+
const visualRowIndex = this.findCurrentVisualRowIndex();
|
|
134
|
+
this.rowOffset = Math.max(0, visualRowIndex - Math.floor(this.screenRows / 2));
|
|
135
|
+
this.mode = 'edit';
|
|
136
|
+
this.setStatusMessage(`Jumped to line ${lineNumber}`, 1000);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Enters Go To Line mode.
|
|
140
|
+
*/
|
|
141
|
+
function enterGoToLineMode() {
|
|
142
|
+
this.mode = 'goto_line';
|
|
143
|
+
this.goToLineQuery = '';
|
|
144
|
+
this.setStatusMessage('Go to Line (ESC to cancel): ');
|
|
145
|
+
}
|
|
124
146
|
export const navigationMethods = {
|
|
125
147
|
findCurrentVisualRowIndex,
|
|
126
148
|
moveCursorLogically,
|
|
@@ -129,4 +151,6 @@ export const navigationMethods = {
|
|
|
129
151
|
findVisualRowEnd,
|
|
130
152
|
adjustCursorPosition,
|
|
131
153
|
scroll,
|
|
154
|
+
jumpToLine,
|
|
155
|
+
enterGoToLineMode,
|
|
132
156
|
};
|
package/dist/editor.rendering.js
CHANGED
|
@@ -65,6 +65,11 @@ function render() {
|
|
|
65
65
|
const logicalY = row.logicalY;
|
|
66
66
|
const isCursorPosition = (visualRowIndex === currentVisualRowIndex && i === cursorVisualX);
|
|
67
67
|
const isSelected = selectionRange && this.isPositionInSelection(logicalY, logicalX, selectionRange);
|
|
68
|
+
// Highlight search result under cursor
|
|
69
|
+
const isSearchResult = (this.searchResultIndex !== -1 &&
|
|
70
|
+
this.searchResults[this.searchResultIndex]?.y === logicalY &&
|
|
71
|
+
logicalX >= this.searchResults[this.searchResultIndex]?.x &&
|
|
72
|
+
logicalX < (this.searchResults[this.searchResultIndex]?.x + this.searchQuery.length));
|
|
68
73
|
if (isSelected) {
|
|
69
74
|
buffer += ANSI.INVERT_COLORS + char + ANSI.RESET_COLORS;
|
|
70
75
|
}
|
|
@@ -72,6 +77,10 @@ function render() {
|
|
|
72
77
|
// Cursor is a single inverted character if not already covered by selection
|
|
73
78
|
buffer += ANSI.INVERT_COLORS + char + ANSI.RESET_COLORS;
|
|
74
79
|
}
|
|
80
|
+
else if (isSearchResult) {
|
|
81
|
+
// Highlight search result
|
|
82
|
+
buffer += ANSI.INVERT_COLORS + char + ANSI.RESET_COLORS;
|
|
83
|
+
}
|
|
75
84
|
else {
|
|
76
85
|
buffer += char;
|
|
77
86
|
}
|
|
@@ -123,24 +132,38 @@ function renderStatusBar() {
|
|
|
123
132
|
let status = '';
|
|
124
133
|
const contentWidth = this.screenCols;
|
|
125
134
|
// --- Line 1: Mode, File Status, Position ---
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
switch (this.mode) {
|
|
136
|
+
case 'search_find':
|
|
137
|
+
status = (this.replaceQuery === null ? 'Find: ' : 'Find: ') + this.searchQuery;
|
|
138
|
+
break;
|
|
139
|
+
case 'search_replace':
|
|
140
|
+
status = 'Replace with: ' + this.replaceQuery;
|
|
141
|
+
break;
|
|
142
|
+
case 'goto_line':
|
|
143
|
+
status = 'Go to Line: ' + this.goToLineQuery;
|
|
144
|
+
break;
|
|
145
|
+
case 'search_confirm':
|
|
146
|
+
// The (y/n/a/q) prompt is set via setStatusMessage
|
|
147
|
+
status = this.statusMessage;
|
|
148
|
+
break;
|
|
149
|
+
case 'edit':
|
|
150
|
+
default:
|
|
151
|
+
const visualRowIndex = this.findCurrentVisualRowIndex();
|
|
152
|
+
const visualRow = this.visualRows[visualRowIndex];
|
|
153
|
+
const visualX = visualRow ? (this.cursorX - visualRow.logicalXStart) : 0;
|
|
154
|
+
const fileStatus = this.isDirty ? `* ${this.filepath}` : this.filepath;
|
|
155
|
+
const pos = `Ln ${this.cursorY + 1}, Col ${this.cursorX + 1} (View: ${visualRowIndex + 1},${visualX + 1})`;
|
|
156
|
+
const statusLeft = `[${fileStatus}]`.padEnd(Math.floor(contentWidth * 0.5));
|
|
157
|
+
const statusRight = pos.padStart(Math.floor(contentWidth * 0.5));
|
|
158
|
+
status = statusLeft + statusRight;
|
|
159
|
+
break;
|
|
138
160
|
}
|
|
139
161
|
status = status.padEnd(contentWidth);
|
|
140
162
|
let buffer = `${ANSI.INVERT_COLORS}${status}${ANSI.RESET_COLORS}`;
|
|
141
163
|
// --- Line 2: Message/Help line ---
|
|
142
164
|
buffer += `\x1b[${this.screenRows + this.screenStartRow + 1};1H`;
|
|
143
|
-
|
|
165
|
+
// Show prompt message if in search mode, otherwise show default help
|
|
166
|
+
const message = (this.mode === 'edit' ? this.DEFAULT_STATUS : this.statusMessage).padEnd(contentWidth);
|
|
144
167
|
buffer += `${message}${ANSI.CLEAR_LINE}`;
|
|
145
168
|
return buffer;
|
|
146
169
|
}
|
package/dist/editor.search.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { CliEditor } from './editor.js';
|
|
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
|
-
declare function
|
|
8
|
+
declare function enterFindMode(this: CliEditor): void;
|
|
9
|
+
/**
|
|
10
|
+
* Enters Replace mode (starting with the "Find" prompt).
|
|
11
|
+
*/
|
|
12
|
+
declare function enterReplaceMode(this: CliEditor): void;
|
|
9
13
|
/**
|
|
10
14
|
* Executes the search and populates results.
|
|
11
15
|
*/
|
|
@@ -14,6 +18,14 @@ declare function executeSearch(this: CliEditor): void;
|
|
|
14
18
|
* Jumps to the next search result.
|
|
15
19
|
*/
|
|
16
20
|
declare function findNext(this: CliEditor): void;
|
|
21
|
+
/**
|
|
22
|
+
* Replaces the current highlighted search result and finds the next one.
|
|
23
|
+
*/
|
|
24
|
+
declare function replaceCurrentAndFindNext(this: CliEditor): void;
|
|
25
|
+
/**
|
|
26
|
+
* Replaces all occurrences of the search query.
|
|
27
|
+
*/
|
|
28
|
+
declare function replaceAll(this: CliEditor): void;
|
|
17
29
|
/**
|
|
18
30
|
* Moves cursor and adjusts scroll offset to make the result visible.
|
|
19
31
|
*/
|
|
@@ -22,9 +34,12 @@ declare function jumpToResult(this: CliEditor, result: {
|
|
|
22
34
|
x: number;
|
|
23
35
|
}): void;
|
|
24
36
|
export declare const searchMethods: {
|
|
25
|
-
|
|
37
|
+
enterFindMode: typeof enterFindMode;
|
|
38
|
+
enterReplaceMode: typeof enterReplaceMode;
|
|
26
39
|
executeSearch: typeof executeSearch;
|
|
27
40
|
findNext: typeof findNext;
|
|
41
|
+
replaceCurrentAndFindNext: typeof replaceCurrentAndFindNext;
|
|
42
|
+
replaceAll: typeof replaceAll;
|
|
28
43
|
jumpToResult: typeof jumpToResult;
|
|
29
44
|
};
|
|
30
45
|
export {};
|