cliedit 0.3.5 → 0.4.0

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/README.md CHANGED
@@ -19,7 +19,10 @@ It includes line wrapping, visual navigation, smart auto-indentation, undo/redo,
19
19
  - **Search & Replace:** `Ctrl+W` to find text, `Ctrl+R` to find and replace interactively.
20
20
  - **Go to Line:** `Ctrl+L` to quickly jump to a specific line number.
21
21
  - **Smart Auto-Indentation:** Automatically preserves indentation level when pressing Enter.
22
+ - **Block Indentation:** Use `Tab` / `Shift+Tab` to indent or outdent selected blocks of text.
22
23
  - **Smart Navigation:** `Alt + Left/Right` to jump by words, `Ctrl + M` to jump between matching brackets.
24
+ - **Line Moving:** `Alt + Up/Down` to move the current line or selection up and down.
25
+ - **Line Duplication:** `Ctrl+D` to duplicate the current line or selection.
23
26
  - **Piping Support:** Works with standard Unix pipes (e.g. `cat file.txt | cliedit`).
24
27
  - **Crash Recovery:** Automatically saves changes to a hidden swap file (e.g. `.filename.swp`) to prevent data loss.
25
28
 
@@ -50,4 +50,8 @@ export declare const KEYS: {
50
50
  PAGE_UP: string;
51
51
  PAGE_DOWN: string;
52
52
  TAB: string;
53
+ SHIFT_TAB: string;
54
+ ALT_UP: string;
55
+ ALT_DOWN: string;
56
+ CTRL_D: string;
53
57
  };
package/dist/constants.js CHANGED
@@ -56,4 +56,8 @@ export const KEYS = {
56
56
  PAGE_UP: 'pageup',
57
57
  PAGE_DOWN: 'pagedown',
58
58
  TAB: '\t',
59
+ SHIFT_TAB: 'SHIFT_TAB', // Internal representation
60
+ ALT_UP: 'ALT_UP', // Internal representation
61
+ ALT_DOWN: 'ALT_DOWN', // Internal representation
62
+ CTRL_D: '\x04',
59
63
  };
@@ -37,6 +37,23 @@ declare function deleteForward(this: CliEditor): void;
37
37
  * @param closeChar The corresponding closing character (e.g., ')', ']', '}').
38
38
  */
39
39
  declare function handleAutoPair(this: CliEditor, openChar: string, closeChar: string): void;
40
+ /**
41
+ * Indents the selected lines (Block Indentation).
42
+ */
43
+ declare function indentSelection(this: CliEditor): void;
44
+ /**
45
+ * Outdents the selected lines (Block Outdent).
46
+ */
47
+ declare function outdentSelection(this: CliEditor): void;
48
+ /**
49
+ * Moves the current line or selection up or down.
50
+ * @param direction -1 for Up, 1 for Down
51
+ */
52
+ declare function moveLines(this: CliEditor, direction: -1 | 1): void;
53
+ /**
54
+ * Duplicates the current line or selection.
55
+ */
56
+ declare function duplicateLineOrSelection(this: CliEditor): void;
40
57
  export declare const editingMethods: {
41
58
  insertContentAtCursor: typeof insertContentAtCursor;
42
59
  insertCharacter: typeof insertCharacter;
@@ -45,5 +62,9 @@ export declare const editingMethods: {
45
62
  deleteBackward: typeof deleteBackward;
46
63
  deleteForward: typeof deleteForward;
47
64
  handleAutoPair: typeof handleAutoPair;
65
+ indentSelection: typeof indentSelection;
66
+ outdentSelection: typeof outdentSelection;
67
+ moveLines: typeof moveLines;
68
+ duplicateLineOrSelection: typeof duplicateLineOrSelection;
48
69
  };
49
70
  export {};
@@ -147,6 +147,129 @@ function handleAutoPair(openChar, closeChar) {
147
147
  }
148
148
  this.setDirty();
149
149
  }
150
+ /**
151
+ * Indents the selected lines (Block Indentation).
152
+ */
153
+ function indentSelection() {
154
+ this.saveState(); // Save state before modification for Undo
155
+ const selection = this.getNormalizedSelection();
156
+ if (!selection)
157
+ return;
158
+ for (let i = selection.start.y; i <= selection.end.y; i++) {
159
+ const line = this.lines[i];
160
+ this.lines[i] = ' '.repeat(this.tabSize) + line;
161
+ }
162
+ // Adjust selection anchors
163
+ if (this.selectionAnchor) {
164
+ this.selectionAnchor.x += this.tabSize;
165
+ this.cursorX += this.tabSize;
166
+ }
167
+ this.setDirty();
168
+ this.invalidateSyntaxCache();
169
+ this.recalculateVisualRows();
170
+ }
171
+ /**
172
+ * Outdents the selected lines (Block Outdent).
173
+ */
174
+ function outdentSelection() {
175
+ this.saveState(); // Save state before modification for Undo
176
+ // If no selection, try to outdent current line
177
+ let startY = this.cursorY;
178
+ let endY = this.cursorY;
179
+ if (this.selectionAnchor) {
180
+ const selection = this.getNormalizedSelection();
181
+ if (selection) {
182
+ startY = selection.start.y;
183
+ endY = selection.end.y;
184
+ }
185
+ }
186
+ let changed = false;
187
+ for (let i = startY; i <= endY; i++) {
188
+ const line = this.lines[i];
189
+ // Remove up to tabSize spaces
190
+ const match = line.match(/^(\s+)/);
191
+ if (match) {
192
+ const spaces = match[1].length;
193
+ const toRemove = Math.min(spaces, this.tabSize);
194
+ this.lines[i] = line.slice(toRemove);
195
+ changed = true;
196
+ }
197
+ }
198
+ if (changed) {
199
+ if (this.selectionAnchor) {
200
+ // Approximation: shift anchor and cursor left
201
+ this.selectionAnchor.x = Math.max(0, this.selectionAnchor.x - this.tabSize);
202
+ this.cursorX = Math.max(0, this.cursorX - this.tabSize);
203
+ }
204
+ else {
205
+ this.cursorX = Math.max(0, this.cursorX - this.tabSize);
206
+ }
207
+ this.setDirty();
208
+ this.invalidateSyntaxCache();
209
+ this.recalculateVisualRows();
210
+ }
211
+ }
212
+ /**
213
+ * Moves the current line or selection up or down.
214
+ * @param direction -1 for Up, 1 for Down
215
+ */
216
+ function moveLines(direction) {
217
+ this.saveState(); // Save state before modification for Undo
218
+ let startY = this.cursorY;
219
+ let endY = this.cursorY;
220
+ if (this.selectionAnchor) {
221
+ const selection = this.getNormalizedSelection();
222
+ if (selection) {
223
+ startY = selection.start.y;
224
+ endY = selection.end.y;
225
+ }
226
+ }
227
+ // Boundary checks
228
+ if (direction === -1 && startY === 0)
229
+ return; // Top
230
+ if (direction === 1 && endY >= this.lines.length - 1)
231
+ return; // Bottom
232
+ // Extract lines to move
233
+ const count = endY - startY + 1;
234
+ const linesToMove = this.lines.splice(startY, count);
235
+ // Insert at new position
236
+ const newStart = startY + direction;
237
+ this.lines.splice(newStart, 0, ...linesToMove);
238
+ // Update selection/cursor
239
+ this.cursorY += direction;
240
+ if (this.selectionAnchor) {
241
+ this.selectionAnchor.y += direction;
242
+ }
243
+ this.setDirty();
244
+ this.recalculateVisualRows();
245
+ }
246
+ /**
247
+ * Duplicates the current line or selection.
248
+ */
249
+ function duplicateLineOrSelection() {
250
+ this.saveState(); // Save state before modification for Undo
251
+ if (this.selectionAnchor) {
252
+ const selection = this.getNormalizedSelection();
253
+ if (!selection)
254
+ return;
255
+ const text = this.getSelectedText();
256
+ // We need to move cursor to end of selection.
257
+ // Normalized selection end:
258
+ this.cursorX = selection.end.x;
259
+ this.cursorY = selection.end.y;
260
+ const contentLines = text.split('\n');
261
+ this.insertContentAtCursor(contentLines);
262
+ }
263
+ else {
264
+ // Single line duplication
265
+ const line = this.lines[this.cursorY];
266
+ this.lines.splice(this.cursorY + 1, 0, line);
267
+ this.cursorY++; // Move down to the new line
268
+ // CursorX stays same? Usually yes.
269
+ }
270
+ this.setDirty();
271
+ this.recalculateVisualRows();
272
+ }
150
273
  export const editingMethods = {
151
274
  insertContentAtCursor,
152
275
  insertCharacter,
@@ -155,4 +278,8 @@ export const editingMethods = {
155
278
  deleteBackward,
156
279
  deleteForward,
157
280
  handleAutoPair,
281
+ indentSelection,
282
+ outdentSelection,
283
+ moveLines,
284
+ duplicateLineOrSelection,
158
285
  };
@@ -80,11 +80,15 @@ function handleKeypressEvent(ch, key) {
80
80
  else if (key.name === 'return')
81
81
  keyName = KEYS.ENTER;
82
82
  else if (key.name === 'tab')
83
- keyName = KEYS.TAB;
83
+ keyName = key.shift ? KEYS.SHIFT_TAB : KEYS.TAB;
84
84
  else if (key.meta && key.name === 'left')
85
85
  keyName = 'ALT_LEFT';
86
86
  else if (key.meta && key.name === 'right')
87
87
  keyName = 'ALT_RIGHT';
88
+ else if (key.meta && key.name === 'up')
89
+ keyName = KEYS.ALT_UP;
90
+ else if (key.meta && key.name === 'down')
91
+ keyName = KEYS.ALT_DOWN;
88
92
  // Handle Mouse Scroll events explicitly
89
93
  else if (key.name === 'scrollup')
90
94
  keyName = 'SCROLL_UP';
@@ -277,8 +281,30 @@ function handleEditKeys(key) {
277
281
  return true;
278
282
  case KEYS.TAB:
279
283
  this.clearSearchResults();
280
- this.insertSoftTab();
281
- return true;
284
+ if (this.selectionAnchor) {
285
+ this.indentSelection();
286
+ return false; // Manually saved state
287
+ }
288
+ else {
289
+ this.insertSoftTab();
290
+ return true;
291
+ }
292
+ case KEYS.SHIFT_TAB:
293
+ this.clearSearchResults();
294
+ this.outdentSelection();
295
+ return false; // Manually saved state
296
+ case KEYS.ALT_UP:
297
+ this.clearSearchResults();
298
+ this.moveLines(-1);
299
+ return false; // Manually saved state
300
+ case KEYS.ALT_DOWN:
301
+ this.clearSearchResults();
302
+ this.moveLines(1);
303
+ return false; // Manually saved state
304
+ case KEYS.CTRL_D:
305
+ this.clearSearchResults();
306
+ this.duplicateLineOrSelection();
307
+ return false; // Manually saved state
282
308
  // --- Search & History ---
283
309
  case KEYS.CTRL_W:
284
310
  this.enterFindMode();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cliedit",
3
- "version": "0.3.5",
4
- "description": "A lightweight, raw-mode terminal editor utility for Node.js CLI applications, with line wrapping and undo/redo support.",
3
+ "version": "0.4.0",
4
+ "description": "A zero-dependency, embeddable TUI text editor for Node.js CLI applications.",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/CodeTease/cliedit.git"