@reliverse/rempts 1.7.44 → 1.7.46

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.
Files changed (134) hide show
  1. package/README.md +1 -1
  2. package/dist-npm/bin/mod.d.mts +2333 -0
  3. package/dist-npm/bin/mod.mjs +8310 -0
  4. package/package.json +82 -42
  5. package/bin/libs/animate/animate-mod.d.ts +0 -14
  6. package/bin/libs/animate/animate-mod.js +0 -62
  7. package/bin/libs/anykey/anykey-mod.d.ts +0 -12
  8. package/bin/libs/anykey/anykey-mod.js +0 -125
  9. package/bin/libs/cancel/cancel.d.ts +0 -45
  10. package/bin/libs/cancel/cancel.js +0 -72
  11. package/bin/libs/confirm/confirm-alias.d.ts +0 -2
  12. package/bin/libs/confirm/confirm-alias.js +0 -2
  13. package/bin/libs/confirm/confirm-mod.d.ts +0 -5
  14. package/bin/libs/confirm/confirm-mod.js +0 -179
  15. package/bin/libs/date/date.d.ts +0 -2
  16. package/bin/libs/date/date.js +0 -236
  17. package/bin/libs/editor/editor-mod.d.ts +0 -25
  18. package/bin/libs/editor/editor-mod.js +0 -897
  19. package/bin/libs/figures/figures-mod.d.ts +0 -461
  20. package/bin/libs/figures/figures-mod.js +0 -285
  21. package/bin/libs/group/group-mod.d.ts +0 -33
  22. package/bin/libs/group/group-mod.js +0 -93
  23. package/bin/libs/input/input-alias.d.ts +0 -5
  24. package/bin/libs/input/input-alias.js +0 -4
  25. package/bin/libs/input/input-mod.d.ts +0 -16
  26. package/bin/libs/input/input-mod.js +0 -372
  27. package/bin/libs/intro/intro-alias.d.ts +0 -2
  28. package/bin/libs/intro/intro-alias.js +0 -3
  29. package/bin/libs/intro/intro-mod.d.ts +0 -20
  30. package/bin/libs/intro/intro-mod.js +0 -81
  31. package/bin/libs/launcher/command-runner.d.ts +0 -18
  32. package/bin/libs/launcher/command-runner.js +0 -79
  33. package/bin/libs/launcher/command-typed.d.ts +0 -16
  34. package/bin/libs/launcher/command-typed.js +0 -60
  35. package/bin/libs/launcher/launcher-alias.d.ts +0 -2
  36. package/bin/libs/launcher/launcher-alias.js +0 -2
  37. package/bin/libs/launcher/launcher-mod.d.ts +0 -162
  38. package/bin/libs/launcher/launcher-mod.js +0 -1222
  39. package/bin/libs/launcher/launcher-types.d.ts +0 -159
  40. package/bin/libs/launcher/launcher-types.js +0 -0
  41. package/bin/libs/launcher/trpc-orpc-support/completions.d.ts +0 -4
  42. package/bin/libs/launcher/trpc-orpc-support/completions.js +0 -45
  43. package/bin/libs/launcher/trpc-orpc-support/errors.d.ts +0 -11
  44. package/bin/libs/launcher/trpc-orpc-support/errors.js +0 -10
  45. package/bin/libs/launcher/trpc-orpc-support/index.d.ts +0 -34
  46. package/bin/libs/launcher/trpc-orpc-support/index.js +0 -641
  47. package/bin/libs/launcher/trpc-orpc-support/json-schema.d.ts +0 -17
  48. package/bin/libs/launcher/trpc-orpc-support/json-schema.js +0 -168
  49. package/bin/libs/launcher/trpc-orpc-support/json.d.ts +0 -44
  50. package/bin/libs/launcher/trpc-orpc-support/json.js +0 -41
  51. package/bin/libs/launcher/trpc-orpc-support/logging.d.ts +0 -11
  52. package/bin/libs/launcher/trpc-orpc-support/logging.js +0 -26
  53. package/bin/libs/launcher/trpc-orpc-support/parse-procedure.d.ts +0 -2
  54. package/bin/libs/launcher/trpc-orpc-support/parse-procedure.js +0 -486
  55. package/bin/libs/launcher/trpc-orpc-support/prompts.d.ts +0 -18
  56. package/bin/libs/launcher/trpc-orpc-support/prompts.js +0 -534
  57. package/bin/libs/launcher/trpc-orpc-support/standard-schema/contract.d.ts +0 -53
  58. package/bin/libs/launcher/trpc-orpc-support/standard-schema/contract.js +0 -0
  59. package/bin/libs/launcher/trpc-orpc-support/standard-schema/errors.d.ts +0 -9
  60. package/bin/libs/launcher/trpc-orpc-support/standard-schema/errors.js +0 -47
  61. package/bin/libs/launcher/trpc-orpc-support/standard-schema/utils.d.ts +0 -3
  62. package/bin/libs/launcher/trpc-orpc-support/standard-schema/utils.js +0 -6
  63. package/bin/libs/launcher/trpc-orpc-support/trpc-compat.d.ts +0 -71
  64. package/bin/libs/launcher/trpc-orpc-support/trpc-compat.js +0 -11
  65. package/bin/libs/launcher/trpc-orpc-support/types.d.ts +0 -276
  66. package/bin/libs/launcher/trpc-orpc-support/types.js +0 -0
  67. package/bin/libs/launcher/trpc-orpc-support/util.d.ts +0 -9
  68. package/bin/libs/launcher/trpc-orpc-support/util.js +0 -9
  69. package/bin/libs/log/log-alias.d.ts +0 -1
  70. package/bin/libs/log/log-alias.js +0 -2
  71. package/bin/libs/msg-fmt/colors.d.ts +0 -30
  72. package/bin/libs/msg-fmt/colors.js +0 -42
  73. package/bin/libs/msg-fmt/logger.d.ts +0 -17
  74. package/bin/libs/msg-fmt/logger.js +0 -103
  75. package/bin/libs/msg-fmt/mapping.d.ts +0 -3
  76. package/bin/libs/msg-fmt/mapping.js +0 -49
  77. package/bin/libs/msg-fmt/messages.d.ts +0 -35
  78. package/bin/libs/msg-fmt/messages.js +0 -319
  79. package/bin/libs/msg-fmt/terminal.d.ts +0 -15
  80. package/bin/libs/msg-fmt/terminal.js +0 -60
  81. package/bin/libs/msg-fmt/variants.d.ts +0 -11
  82. package/bin/libs/msg-fmt/variants.js +0 -52
  83. package/bin/libs/multiselect/multiselect-alias.d.ts +0 -2
  84. package/bin/libs/multiselect/multiselect-alias.js +0 -2
  85. package/bin/libs/multiselect/multiselect-prompt.d.ts +0 -2
  86. package/bin/libs/multiselect/multiselect-prompt.js +0 -348
  87. package/bin/libs/next-steps/next-steps.d.ts +0 -14
  88. package/bin/libs/next-steps/next-steps.js +0 -24
  89. package/bin/libs/number/number-mod.d.ts +0 -28
  90. package/bin/libs/number/number-mod.js +0 -194
  91. package/bin/libs/outro/outro-alias.d.ts +0 -2
  92. package/bin/libs/outro/outro-alias.js +0 -3
  93. package/bin/libs/outro/outro-mod.d.ts +0 -8
  94. package/bin/libs/outro/outro-mod.js +0 -55
  95. package/bin/libs/results/results.d.ts +0 -7
  96. package/bin/libs/results/results.js +0 -27
  97. package/bin/libs/select/nummultiselect-prompt.d.ts +0 -6
  98. package/bin/libs/select/nummultiselect-prompt.js +0 -105
  99. package/bin/libs/select/numselect-prompt.d.ts +0 -7
  100. package/bin/libs/select/numselect-prompt.js +0 -115
  101. package/bin/libs/select/select-alias.d.ts +0 -9
  102. package/bin/libs/select/select-alias.js +0 -9
  103. package/bin/libs/select/select-prompt.d.ts +0 -5
  104. package/bin/libs/select/select-prompt.js +0 -314
  105. package/bin/libs/select/toggle-prompt.d.ts +0 -5
  106. package/bin/libs/select/toggle-prompt.js +0 -209
  107. package/bin/libs/spinner/spinner-alias.d.ts +0 -1
  108. package/bin/libs/spinner/spinner-alias.js +0 -2
  109. package/bin/libs/spinner/spinner-mod.d.ts +0 -106
  110. package/bin/libs/spinner/spinner-mod.js +0 -265
  111. package/bin/libs/task/progress.d.ts +0 -2
  112. package/bin/libs/task/progress.js +0 -57
  113. package/bin/libs/task/task-spin.d.ts +0 -15
  114. package/bin/libs/task/task-spin.js +0 -110
  115. package/bin/libs/utils/colorize.d.ts +0 -2
  116. package/bin/libs/utils/colorize.js +0 -135
  117. package/bin/libs/utils/errors.d.ts +0 -1
  118. package/bin/libs/utils/errors.js +0 -17
  119. package/bin/libs/utils/prevent.d.ts +0 -8
  120. package/bin/libs/utils/prevent.js +0 -65
  121. package/bin/libs/utils/prompt-end.d.ts +0 -8
  122. package/bin/libs/utils/prompt-end.js +0 -34
  123. package/bin/libs/utils/stream-text.d.ts +0 -18
  124. package/bin/libs/utils/stream-text.js +0 -136
  125. package/bin/libs/utils/system.d.ts +0 -6
  126. package/bin/libs/utils/system.js +0 -7
  127. package/bin/libs/utils/validate.d.ts +0 -21
  128. package/bin/libs/utils/validate.js +0 -17
  129. package/bin/libs/visual/visual-mod.d.ts +0 -6
  130. package/bin/libs/visual/visual-mod.js +0 -13
  131. package/bin/mod.d.ts +0 -69
  132. package/bin/mod.js +0 -159
  133. package/bin/types.d.ts +0 -371
  134. package/bin/types.js +0 -0
@@ -1,897 +0,0 @@
1
- import path from "@reliverse/pathkit";
2
- import { re } from "@reliverse/relico";
3
- import fs from "@reliverse/relifso";
4
- import { relinka } from "@reliverse/relinka";
5
- import { loadConfig } from "c12";
6
- import termkit from "terminal-kit";
7
- const { terminal: term } = termkit;
8
- let state = {
9
- lines: [""],
10
- // Document content as an array of strings
11
- cursorX: 0,
12
- // Horizontal cursor position (0-based index within the line string)
13
- cursorY: 0,
14
- // Vertical cursor position (0-based line index)
15
- topLine: 0,
16
- // Index of the top visible line in the viewport
17
- leftCol: 0,
18
- // Index of the leftmost visible column (for horizontal scroll - basic impl)
19
- filename: null,
20
- // Current file path
21
- originalContent: "",
22
- // Content when the file was opened/saved last
23
- modified: false,
24
- // Has the file been modified?
25
- statusMessage: "",
26
- // Message to display in the status bar
27
- lastSearchTerm: "",
28
- editorConfig: {},
29
- // Loaded configuration
30
- hooks: {},
31
- // Callbacks { onSave, onExit }
32
- options: {
33
- // Default options before resolution
34
- allowSaveAs: true,
35
- allowOpen: true,
36
- autoCloseOnSave: false,
37
- returnContentOnSave: false
38
- },
39
- clipboard: [],
40
- // Simple line-based clipboard
41
- isRunning: true,
42
- // Flag to control the main loop
43
- theme: {
44
- // Default Light Theme
45
- text: (str) => str,
46
- statusBarBg: (str) => re.bgBrown(str),
47
- statusBarText: (str) => re.white(str),
48
- highlight: (str) => re.bgYellow(re.black(str)),
49
- // For search results, etc.
50
- lineNumber: (str) => re.blue(str)
51
- },
52
- syntaxHighlightToggle: false,
53
- // Toggled state for syntax highlighting
54
- exitResolver: null,
55
- exitRejecter: null
56
- };
57
- async function loadEditorConfig(cwd = process.cwd(), overrides = {}) {
58
- const { config } = await loadConfig({
59
- name: "minedit",
60
- cwd,
61
- defaults: {
62
- // Low priority defaults
63
- syntaxHighlighting: false,
64
- theme: "auto",
65
- defaultAllowSaveAs: true,
66
- defaultAllowOpen: true,
67
- defaultAutoCloseOnSave: false,
68
- defaultReturnContentOnSave: false
69
- },
70
- // user provided overrides during programmatic call have higher priority
71
- overrides
72
- });
73
- return config || {};
74
- }
75
- function setupTheme(configTheme) {
76
- let mode = configTheme;
77
- if (mode === "auto") {
78
- const termBg = process.env.COLORFGBG;
79
- mode = termBg && termBg.split(";")[1] === "0" ? "dark" : "light";
80
- if (!termBg) mode = "light";
81
- }
82
- if (mode === "dark") {
83
- state.theme = {
84
- text: (str) => re.white(str),
85
- statusBarBg: (str) => re.bgWhite(str),
86
- statusBarText: (str) => re.black(str),
87
- highlight: (str) => re.bgYellow(re.black(str)),
88
- lineNumber: (str) => re.blue(str)
89
- };
90
- term.bgColor("black").color("white");
91
- } else {
92
- state.theme = {
93
- text: (str) => re.black(str),
94
- statusBarBg: (str) => re.bgBrown(str),
95
- statusBarText: (str) => re.white(str),
96
- highlight: (str) => re.bgCyan(re.black(str)),
97
- lineNumber: (str) => re.gray(str)
98
- };
99
- term.bgColor("white").color("black");
100
- }
101
- term.styleReset();
102
- }
103
- function clamp(value, min, max) {
104
- return Math.max(min, Math.min(value, max));
105
- }
106
- function getCurrentLine() {
107
- return state.lines[state.cursorY] || "";
108
- }
109
- function updateModifiedStatus() {
110
- const currentContent = state.lines.join("\n");
111
- state.modified = currentContent !== state.originalContent;
112
- }
113
- function renderStatusBar() {
114
- const { width } = term;
115
- const filename = state.filename ? path.basename(state.filename) : "[No Name]";
116
- const modifiedIndicator = state.modified ? "*" : "";
117
- const position = `L: ${state.cursorY + 1} C: ${state.cursorX + 1}`;
118
- const fileInfo = `${filename}${modifiedIndicator} - ${state.lines.length} lines`;
119
- const leftPart = ` ${fileInfo} `;
120
- const rightPart = ` ${position} `;
121
- let hints = " Ctrl+S:Save | Ctrl+A:SaveAs | Ctrl+O:Open | Ctrl+X:Save&Exit | Ctrl+C:Exit | Ctrl+F:Find ";
122
- if (!state.options.allowSaveAs) {
123
- hints = hints.replace("Ctrl+A:SaveAs | ", "");
124
- }
125
- if (!state.options.allowOpen) {
126
- hints = hints.replace("Ctrl+O:Open | ", "");
127
- }
128
- const remainingWidth = width - leftPart.length - rightPart.length;
129
- const hintPadding = Math.max(0, remainingWidth - hints.length);
130
- const middlePart = hints + " ".repeat(hintPadding);
131
- const statusBar = leftPart + middlePart + rightPart;
132
- term.moveTo(1, term.height - 1);
133
- term(
134
- state.theme.statusBarBg(state.theme.statusBarText(statusBar.padEnd(width)))
135
- );
136
- }
137
- function renderMessageBar() {
138
- term.moveTo(1, term.height);
139
- term.eraseLine();
140
- if (state.statusMessage) {
141
- term(state.theme.highlight(state.statusMessage.slice(0, term.width)));
142
- setTimeout(() => {
143
- if (state.isRunning) {
144
- state.statusMessage = "";
145
- render();
146
- }
147
- }, 3e3);
148
- }
149
- }
150
- function applySyntaxHighlighting(line) {
151
- if (!state.editorConfig.syntaxHighlighting || !state.syntaxHighlightToggle) {
152
- return state.theme.text(line);
153
- }
154
- let highlightedLine = line;
155
- highlightedLine = highlightedLine.replace(
156
- /(\/\/.*)/g,
157
- (match) => re.green(match)
158
- );
159
- highlightedLine = highlightedLine.replace(
160
- /(['"`].*?['"`])/g,
161
- (match) => re.magenta(match)
162
- );
163
- highlightedLine = highlightedLine.replace(
164
- /\b(const|let|var|function|return|if|else|for|while|import|export|from|default|async|await|new|this)\b/g,
165
- (match) => re.blue(match)
166
- );
167
- highlightedLine = highlightedLine.replace(
168
- /(\d+)/g,
169
- (match) => re.cyan(match)
170
- );
171
- return state.theme.text(highlightedLine);
172
- }
173
- function renderEditor() {
174
- const { height, width } = term;
175
- const editorHeight = height - 2;
176
- if (state.cursorY < state.topLine) {
177
- state.topLine = state.cursorY;
178
- } else if (state.cursorY >= state.topLine + editorHeight) {
179
- state.topLine = state.cursorY - editorHeight + 1;
180
- }
181
- const displayWidth = width - 4;
182
- if (state.cursorX < state.leftCol) {
183
- state.leftCol = state.cursorX;
184
- } else if (state.cursorX >= state.leftCol + displayWidth) {
185
- state.leftCol = state.cursorX - displayWidth + 1;
186
- }
187
- state.leftCol = Math.max(0, state.leftCol);
188
- for (let y = 0; y < editorHeight; y++) {
189
- const fileLineIndex = state.topLine + y;
190
- term.moveTo(1, y + 1);
191
- if (fileLineIndex < state.lines.length) {
192
- const lineNum = String(fileLineIndex + 1).padStart(3);
193
- term(state.theme.lineNumber(`${lineNum} `));
194
- const line = state.lines[fileLineIndex];
195
- const displayLine = line?.substring(
196
- state.leftCol,
197
- state.leftCol + displayWidth
198
- );
199
- const highlightedDisplayLine = applySyntaxHighlighting(displayLine ?? "");
200
- term(highlightedDisplayLine);
201
- term.eraseLineAfter();
202
- } else {
203
- term.eraseLine();
204
- }
205
- }
206
- }
207
- function render() {
208
- if (!state.isRunning) return;
209
- term.hideCursor();
210
- term.clear();
211
- renderEditor();
212
- renderStatusBar();
213
- renderMessageBar();
214
- const screenX = state.cursorX - state.leftCol + 4 + 1;
215
- const screenY = state.cursorY - state.topLine + 1;
216
- term.moveTo(
217
- clamp(screenX, 5, term.width),
218
- clamp(screenY, 1, term.height - 2)
219
- );
220
- term.restoreCursor();
221
- }
222
- function insertChar(char) {
223
- const line = getCurrentLine();
224
- const newLine = line.slice(0, state.cursorX) + char + line.slice(state.cursorX);
225
- state.lines[state.cursorY] = newLine;
226
- state.cursorX++;
227
- updateModifiedStatus();
228
- }
229
- function deleteCharBackward() {
230
- if (state.cursorX > 0) {
231
- const line = getCurrentLine();
232
- const newLine = line.slice(0, state.cursorX - 1) + line.slice(state.cursorX);
233
- state.lines[state.cursorY] = newLine;
234
- state.cursorX--;
235
- updateModifiedStatus();
236
- } else if (state.cursorY > 0) {
237
- const currentLine = state.lines.splice(state.cursorY, 1)[0];
238
- state.cursorY--;
239
- const prevLine = state.lines[state.cursorY];
240
- state.cursorX = prevLine?.length ?? 0;
241
- state.lines[state.cursorY] = prevLine + (currentLine ?? "");
242
- updateModifiedStatus();
243
- }
244
- }
245
- function deleteCharForward() {
246
- const line = getCurrentLine();
247
- if (state.cursorX < line.length) {
248
- const newLine = line.slice(0, state.cursorX) + line.slice(state.cursorX + 1);
249
- state.lines[state.cursorY] = newLine;
250
- updateModifiedStatus();
251
- } else if (state.cursorY < state.lines.length - 1) {
252
- const nextLine = state.lines.splice(state.cursorY + 1, 1)[0];
253
- state.lines[state.cursorY] = line + nextLine;
254
- updateModifiedStatus();
255
- }
256
- }
257
- function insertNewline() {
258
- const line = getCurrentLine();
259
- const beforeCursor = line.slice(0, state.cursorX);
260
- const afterCursor = line.slice(state.cursorX);
261
- state.lines[state.cursorY] = beforeCursor;
262
- state.lines.splice(state.cursorY + 1, 0, afterCursor);
263
- state.cursorY++;
264
- state.cursorX = 0;
265
- state.leftCol = 0;
266
- updateModifiedStatus();
267
- }
268
- function copyLine() {
269
- if (state.lines.length > 0) {
270
- state.clipboard = [getCurrentLine()];
271
- state.statusMessage = "Line copied";
272
- }
273
- }
274
- function cutLine() {
275
- if (state.lines.length > 0) {
276
- state.clipboard = state.lines.splice(state.cursorY, 1);
277
- if (state.lines.length === 0) {
278
- state.lines.push("");
279
- }
280
- state.cursorY = clamp(state.cursorY, 0, state.lines.length - 1);
281
- state.cursorX = 0;
282
- updateModifiedStatus();
283
- state.statusMessage = "Line cut";
284
- }
285
- }
286
- function pasteLine() {
287
- if (state.clipboard.length > 0) {
288
- state.lines.splice(state.cursorY + 1, 0, ...state.clipboard);
289
- state.cursorY += state.clipboard.length;
290
- state.cursorX = 0;
291
- updateModifiedStatus();
292
- state.statusMessage = `${state.clipboard.length} line(s) pasted`;
293
- }
294
- }
295
- function moveCursor(dx, dy) {
296
- state.cursorY = clamp(state.cursorY + dy, 0, state.lines.length - 1);
297
- const currentLineLength = getCurrentLine().length;
298
- state.cursorX = clamp(state.cursorX + dx, 0, currentLineLength);
299
- if (dy !== 0) {
300
- state.cursorX = clamp(state.cursorX, 0, getCurrentLine().length);
301
- }
302
- }
303
- function pageMove(direction) {
304
- const { height } = term;
305
- const editorHeight = height - 2;
306
- const step = direction * (editorHeight - 1);
307
- state.cursorY = clamp(state.cursorY + step, 0, state.lines.length - 1);
308
- state.topLine = clamp(
309
- state.topLine + step,
310
- 0,
311
- Math.max(0, state.lines.length - editorHeight)
312
- );
313
- if (state.cursorY < state.topLine) {
314
- state.topLine = state.cursorY;
315
- } else if (state.cursorY >= state.topLine + editorHeight) {
316
- state.topLine = state.cursorY - editorHeight + 1;
317
- }
318
- state.cursorX = clamp(state.cursorX, 0, getCurrentLine().length);
319
- }
320
- function jumpToLineEdge(pos) {
321
- if (pos === "start") {
322
- state.cursorX = 0;
323
- } else if (pos === "end") {
324
- state.cursorX = getCurrentLine().length;
325
- }
326
- state.leftCol = 0;
327
- }
328
- function jumpToDocumentEdge(pos) {
329
- if (pos === "start") {
330
- state.cursorY = 0;
331
- state.cursorX = 0;
332
- state.topLine = 0;
333
- } else if (pos === "end") {
334
- state.cursorY = state.lines.length - 1;
335
- state.cursorX = getCurrentLine().length;
336
- const { height } = term;
337
- const editorHeight = height - 2;
338
- state.topLine = Math.max(0, state.lines.length - editorHeight);
339
- }
340
- state.leftCol = 0;
341
- }
342
- async function promptForFilename(promptMessage = "File path: ") {
343
- renderStatusBar();
344
- renderMessageBar();
345
- term.moveTo(1, term.height);
346
- term.eraseLine();
347
- term(promptMessage);
348
- try {
349
- const input = await term.inputField({ echo: true }).promise;
350
- term.moveTo(1, term.height).eraseLine();
351
- return input ? input.trim() : null;
352
- } catch (_error) {
353
- term.moveTo(1, term.height).eraseLine();
354
- state.statusMessage = "Cancelled";
355
- render();
356
- return null;
357
- }
358
- }
359
- async function confirmAction(promptMessage = "Are you sure? (y/N)") {
360
- renderStatusBar();
361
- renderMessageBar();
362
- term.moveTo(1, term.height);
363
- term.eraseLine();
364
- term(`${promptMessage} `);
365
- try {
366
- const confirm = await term.yesOrNo({
367
- yes: ["y", "Y"],
368
- no: ["n", "N", "ENTER"]
369
- }).promise;
370
- term.moveTo(1, term.height).eraseLine();
371
- return confirm ?? false;
372
- } catch (_error) {
373
- term.moveTo(1, term.height).eraseLine();
374
- state.statusMessage = "Cancelled";
375
- render();
376
- return false;
377
- }
378
- }
379
- async function saveFile() {
380
- if (!state.filename) {
381
- return saveAsFile();
382
- }
383
- let contentToSave = state.lines.join("\n");
384
- let proceed = true;
385
- if (state.hooks?.onSave) {
386
- try {
387
- const hookResult = await state.hooks.onSave(
388
- contentToSave,
389
- state.filename
390
- );
391
- if (hookResult === false) {
392
- state.statusMessage = "Save prevented by hook.";
393
- proceed = false;
394
- } else if (typeof hookResult === "string") {
395
- contentToSave = hookResult;
396
- state.lines = contentToSave.split("\n");
397
- state.statusMessage = "Content modified by pre-save hook.";
398
- updateModifiedStatus();
399
- }
400
- } catch (error) {
401
- const errorMessage = error instanceof Error ? error.message : String(error);
402
- state.statusMessage = `Error in onSave hook: ${errorMessage}`;
403
- relinka("error", "onSave Hook Error:", error);
404
- proceed = false;
405
- }
406
- }
407
- if (!proceed) {
408
- render();
409
- return false;
410
- }
411
- const returnContent = state.options.returnContentOnSave;
412
- if (returnContent) {
413
- state.originalContent = contentToSave;
414
- state.modified = false;
415
- state.statusMessage = `Content prepared. ${state.filename || ""}`;
416
- render();
417
- if (state.options.autoCloseOnSave) {
418
- await cleanupAndExit(true, contentToSave);
419
- }
420
- return true;
421
- }
422
- try {
423
- await fs.writeFile(state.filename, contentToSave);
424
- state.originalContent = contentToSave;
425
- state.modified = false;
426
- state.statusMessage = `Saved to ${state.filename}`;
427
- render();
428
- if (state.options.autoCloseOnSave) {
429
- await cleanupAndExit(true);
430
- }
431
- return true;
432
- } catch (error) {
433
- const errorMessage = error instanceof Error ? error.message : String(error);
434
- state.statusMessage = `Error saving file: ${errorMessage}`;
435
- relinka("error", "Save Error:", error);
436
- render();
437
- return false;
438
- }
439
- }
440
- async function saveAsFile() {
441
- if (!state.options.allowSaveAs) {
442
- state.statusMessage = "Save As is disabled in this mode.";
443
- render();
444
- return false;
445
- }
446
- const newFilename = await promptForFilename("Save As: ");
447
- if (newFilename) {
448
- const cwd = state.options.cwd || process.cwd();
449
- state.filename = path.resolve(cwd, newFilename);
450
- return saveFile();
451
- }
452
- state.statusMessage = "Save As cancelled.";
453
- render();
454
- return false;
455
- }
456
- async function openFilePrompt() {
457
- if (!state.options.allowOpen) {
458
- state.statusMessage = "Opening files is disabled in this mode.";
459
- render();
460
- return;
461
- }
462
- if (state.modified) {
463
- const shouldSave = await confirmAction(
464
- `Save changes to ${state.filename || "current file"}? (Y/n)`
465
- );
466
- if (shouldSave) {
467
- const saved = await saveFile();
468
- if (!saved) {
469
- state.statusMessage = "Save failed or cancelled. Open cancelled.";
470
- render();
471
- return;
472
- }
473
- }
474
- }
475
- const fileToOpen = await promptForFilename("Open file: ");
476
- if (fileToOpen) {
477
- await loadFile(fileToOpen);
478
- } else {
479
- state.statusMessage = "Open cancelled.";
480
- render();
481
- }
482
- }
483
- async function loadFile(filePath) {
484
- try {
485
- const cwd = state.options.cwd || process.cwd();
486
- const absolutePath = path.resolve(cwd, filePath);
487
- let content = "";
488
- try {
489
- content = await fs.readFile(absolutePath, "utf-8");
490
- state.statusMessage = `Opened ${absolutePath}`;
491
- } catch (error) {
492
- if (error && error.code === "ENOENT") {
493
- content = "";
494
- state.statusMessage = `New file: ${absolutePath}`;
495
- } else {
496
- throw error;
497
- }
498
- }
499
- state.lines = content.split("\n");
500
- if (state.lines.length === 0 || state.lines.length === 1 && state.lines[0] === "" && content.length > 0) {
501
- if (state.lines.length === 0) state.lines = [""];
502
- }
503
- state.filename = absolutePath;
504
- state.originalContent = content;
505
- state.modified = false;
506
- state.cursorX = 0;
507
- state.cursorY = 0;
508
- state.topLine = 0;
509
- state.leftCol = 0;
510
- render();
511
- } catch (error) {
512
- const errorMessage = error instanceof Error ? error.message : String(error);
513
- state.statusMessage = `Error opening file: ${errorMessage}`;
514
- relinka("error", "Open Error:", error);
515
- render();
516
- }
517
- }
518
- async function findText() {
519
- const searchTerm = await promptForFilename(
520
- `Find (Leave empty to cancel, Prev: ${state.lastSearchTerm}): `
521
- );
522
- if (searchTerm === null) {
523
- state.statusMessage = "Find cancelled.";
524
- render();
525
- return;
526
- }
527
- if (!searchTerm && !state.lastSearchTerm) {
528
- state.statusMessage = "No search term provided.";
529
- render();
530
- return;
531
- }
532
- const termToUse = searchTerm || state.lastSearchTerm;
533
- if (!termToUse) return;
534
- state.lastSearchTerm = termToUse;
535
- state.statusMessage = `Searching for: ${termToUse}`;
536
- let found = false;
537
- for (let y = state.cursorY; y < state.lines.length; y++) {
538
- const line = state.lines[y];
539
- const startIdx = y === state.cursorY ? state.cursorX + 1 : 0;
540
- const matchIndex = line?.indexOf(termToUse, startIdx) ?? -1;
541
- if (matchIndex !== -1) {
542
- state.cursorY = y;
543
- state.cursorX = matchIndex;
544
- found = true;
545
- break;
546
- }
547
- }
548
- if (!found) {
549
- state.statusMessage = `Search wrapped: ${termToUse}`;
550
- for (let y = 0; y <= state.cursorY; y++) {
551
- const line = state.lines[y];
552
- const endIdx = y === state.cursorY ? state.cursorX + 1 : line?.length ?? 0;
553
- const matchIndex = line?.substring(0, endIdx).indexOf(termToUse) ?? -1;
554
- if (matchIndex !== -1) {
555
- state.cursorY = y;
556
- state.cursorX = matchIndex;
557
- found = true;
558
- break;
559
- }
560
- }
561
- }
562
- if (found) {
563
- state.statusMessage = `Found: ${termToUse} at L:${state.cursorY + 1}, C:${state.cursorX + 1}`;
564
- } else {
565
- state.statusMessage = `Not found: ${termToUse}`;
566
- }
567
- render();
568
- }
569
- async function handleInput(key, _matches, data) {
570
- let handled = true;
571
- if (data.isCharacter) {
572
- insertChar(key);
573
- } else {
574
- switch (key) {
575
- // --- Hotkeys ---
576
- case "CTRL_S":
577
- await saveFile();
578
- break;
579
- case "CTRL_A":
580
- if (state.options.allowSaveAs) {
581
- await saveAsFile();
582
- } else {
583
- state.statusMessage = "Save As disabled.";
584
- }
585
- break;
586
- case "CTRL_O":
587
- if (state.options.allowOpen) {
588
- await openFilePrompt();
589
- } else {
590
- state.statusMessage = "Open disabled.";
591
- }
592
- break;
593
- case "CTRL_X":
594
- if (state.modified) {
595
- const saved = await saveFile();
596
- if (saved) {
597
- if (!state.options.autoCloseOnSave) {
598
- await cleanupAndExit(true);
599
- }
600
- } else if (!state.filename && !saved) {
601
- state.statusMessage = "Save failed or cancelled. Exit cancelled.";
602
- } else if (state.filename && !saved) {
603
- state.statusMessage = "Save failed. Exit cancelled.";
604
- }
605
- } else {
606
- await cleanupAndExit(false);
607
- }
608
- break;
609
- case "CTRL_C":
610
- if (state.modified) {
611
- const confirm = await confirmAction("Discard changes? (y/N)");
612
- if (confirm) {
613
- await cleanupAndExit(false);
614
- } else {
615
- state.statusMessage = "Exit cancelled.";
616
- }
617
- } else {
618
- await cleanupAndExit(false);
619
- }
620
- break;
621
- case "CTRL_F":
622
- await findText();
623
- break;
624
- case "CTRL_K":
625
- cutLine();
626
- break;
627
- case "CTRL_U":
628
- pasteLine();
629
- break;
630
- case "ALT_C":
631
- // Simple Copy Line (Alternative binding)
632
- case "CTRL_INSERT":
633
- copyLine();
634
- break;
635
- case "SHIFT_INSERT":
636
- pasteLine();
637
- break;
638
- case "CTRL_T": {
639
- state.syntaxHighlightToggle = !state.syntaxHighlightToggle;
640
- state.statusMessage = `Syntax Highlighting: ${state.syntaxHighlightToggle ? "ON" : "OFF"}`;
641
- break;
642
- }
643
- // --- Navigation ---
644
- case "UP":
645
- moveCursor(0, -1);
646
- break;
647
- case "DOWN":
648
- moveCursor(0, 1);
649
- break;
650
- case "LEFT":
651
- moveCursor(-1, 0);
652
- break;
653
- case "RIGHT":
654
- moveCursor(1, 0);
655
- break;
656
- case "PAGE_UP":
657
- pageMove(-1);
658
- break;
659
- case "PAGE_DOWN":
660
- pageMove(1);
661
- break;
662
- case "HOME":
663
- jumpToLineEdge("start");
664
- break;
665
- case "END":
666
- jumpToLineEdge("end");
667
- break;
668
- case "CTRL_HOME":
669
- jumpToDocumentEdge("start");
670
- break;
671
- case "CTRL_END":
672
- jumpToDocumentEdge("end");
673
- break;
674
- // --- Editing ---
675
- case "BACKSPACE":
676
- deleteCharBackward();
677
- break;
678
- case "DELETE":
679
- deleteCharForward();
680
- break;
681
- case "ENTER":
682
- insertNewline();
683
- break;
684
- case "TAB":
685
- {
686
- const tabWidth = 4;
687
- for (let i = 0; i < tabWidth; i++) insertChar(" ");
688
- }
689
- break;
690
- default:
691
- handled = false;
692
- break;
693
- }
694
- }
695
- if (handled && state.isRunning) {
696
- updateModifiedStatus();
697
- render();
698
- }
699
- }
700
- async function cleanupAndExit(saved = false, content = null) {
701
- if (!state.isRunning) return;
702
- state.isRunning = false;
703
- const exitContent = content ?? (saved ? state.lines.join("\n") : null);
704
- if (state.hooks?.onExit) {
705
- try {
706
- await state.hooks.onExit(exitContent, saved, state.filename);
707
- } catch (error) {
708
- relinka("error", "onExit Hook Error:", error);
709
- }
710
- }
711
- term.off("key", handleInputWrapper);
712
- term.off("resize", handleResize);
713
- term.hideCursor(false);
714
- term.grabInput(false);
715
- term.fullscreen(false);
716
- term.styleReset();
717
- term.clear();
718
- if (state.exitResolver) {
719
- state.exitResolver({
720
- saved,
721
- content: exitContent,
722
- filename: state.filename
723
- });
724
- state.exitResolver = null;
725
- state.exitRejecter = null;
726
- } else {
727
- process.exit(0);
728
- }
729
- }
730
- function handleResize(_width, _height) {
731
- if (state.isRunning) {
732
- render();
733
- }
734
- }
735
- let isHandlingInput = false;
736
- async function handleInputWrapper(key, matches, data) {
737
- if (isHandlingInput || !state.isRunning) return;
738
- isHandlingInput = true;
739
- try {
740
- await handleInput(key, matches, data);
741
- } catch (error) {
742
- relinka("error", "Input Handling Error:", error);
743
- const errorMessage = error instanceof Error ? error.message : String(error);
744
- state.statusMessage = `Error: ${errorMessage}`;
745
- render();
746
- } finally {
747
- isHandlingInput = false;
748
- }
749
- }
750
- async function initializeEditorState(options) {
751
- state = {
752
- // Keep defaults for theme, clipboard, potentially others?
753
- ...state,
754
- // Start with previous state defaults for theme etc.
755
- lines: [""],
756
- cursorX: 0,
757
- cursorY: 0,
758
- topLine: 0,
759
- leftCol: 0,
760
- filename: options.filename || null,
761
- originalContent: "",
762
- modified: false,
763
- statusMessage: "",
764
- lastSearchTerm: "",
765
- editorConfig: {},
766
- // Will be loaded/merged below
767
- hooks: {
768
- onSave: options.onSave,
769
- onExit: options.onExit
770
- },
771
- options: {
772
- // Will be resolved below
773
- filename: options.filename,
774
- initialContent: options.initialContent,
775
- configOverrides: options.configOverrides || {},
776
- allowSaveAs: true,
777
- // Default, will be overridden
778
- allowOpen: true,
779
- // Default, will be overridden
780
- autoCloseOnSave: false,
781
- // Default, will be overridden
782
- returnContentOnSave: false,
783
- // Default, will be overridden
784
- mode: options.mode || "normal",
785
- cwd: options.cwd || process.cwd()
786
- },
787
- clipboard: [],
788
- isRunning: true,
789
- // Reset running state
790
- exitResolver: null,
791
- // Will be set by Promise executor
792
- exitRejecter: null,
793
- // Will be set by Promise executor
794
- syntaxHighlightToggle: false
795
- // Reset toggle state
796
- };
797
- try {
798
- const loadedConfig = await loadEditorConfig(
799
- state.options.cwd,
800
- options.configOverrides
801
- );
802
- state.editorConfig = loadedConfig;
803
- state.options.allowSaveAs = options.allowSaveAs ?? state.editorConfig.defaultAllowSaveAs ?? true;
804
- state.options.allowOpen = options.allowOpen ?? state.editorConfig.defaultAllowOpen ?? true;
805
- state.options.autoCloseOnSave = options.autoCloseOnSave ?? state.editorConfig.defaultAutoCloseOnSave ?? false;
806
- state.options.returnContentOnSave = options.returnContentOnSave ?? state.editorConfig.defaultReturnContentOnSave ?? false;
807
- state.syntaxHighlightToggle = state.editorConfig.syntaxHighlighting ?? false;
808
- state.theme = {
809
- // Reset theme based on config
810
- text: (str) => str,
811
- statusBarBg: (str) => re.bgBrown(str),
812
- statusBarText: (str) => re.white(str),
813
- highlight: (str) => re.bgYellow(re.black(str)),
814
- lineNumber: (str) => re.blue(str)
815
- };
816
- setupTheme(state.editorConfig.theme);
817
- } catch (error) {
818
- const message = error instanceof Error ? error.message : String(error);
819
- relinka("error", "Error loading configuration:", message, error);
820
- term.red("Failed to load configuration. Using defaults.\n");
821
- state.options.allowSaveAs = options.allowSaveAs ?? true;
822
- state.options.allowOpen = options.allowOpen ?? true;
823
- state.options.autoCloseOnSave = options.autoCloseOnSave ?? false;
824
- state.options.returnContentOnSave = options.returnContentOnSave ?? false;
825
- state.syntaxHighlightToggle = false;
826
- setupTheme("light");
827
- }
828
- if (options.initialContent !== void 0 && options.initialContent !== null) {
829
- const content = String(options.initialContent);
830
- state.lines = content.split("\n");
831
- if (state.lines.length === 0) state.lines = [""];
832
- state.originalContent = content;
833
- state.modified = false;
834
- state.filename = options.filename || null;
835
- state.statusMessage = options.filename ? `Editing ${path.basename(options.filename)}` : "Editing new buffer";
836
- } else if (options.filename) {
837
- await loadFile(options.filename);
838
- } else {
839
- state.lines = [""];
840
- state.originalContent = "";
841
- state.modified = false;
842
- state.filename = null;
843
- state.statusMessage = "New file. Ctrl+S to save.";
844
- }
845
- }
846
- export async function startEditor(options = {}) {
847
- await initializeEditorState(options);
848
- return new Promise((resolve, reject) => {
849
- state.exitResolver = resolve;
850
- state.exitRejecter = reject;
851
- try {
852
- term.fullscreen(true);
853
- term.grabInput(true);
854
- term.on("key", handleInputWrapper);
855
- term.on("resize", handleResize);
856
- render();
857
- } catch (termError) {
858
- state.isRunning = false;
859
- reject(
860
- new Error(
861
- `Terminal initialization failed: ${termError instanceof Error ? termError.message : String(termError)}`
862
- )
863
- );
864
- try {
865
- term.grabInput(false);
866
- term.fullscreen(false);
867
- term.styleReset();
868
- term.clear();
869
- } catch (_cleanupErr) {
870
- }
871
- }
872
- });
873
- }
874
- const isDirectRun = (() => {
875
- try {
876
- const scriptPath = fs.realpathSync(process.argv[1] || "");
877
- const modulePath = fs.realpathSync(import.meta.filename);
878
- return scriptPath === modulePath;
879
- } catch (_e) {
880
- return false;
881
- }
882
- })();
883
- if (isDirectRun) {
884
- const fileArg = process.argv[2];
885
- startEditor({ filename: fileArg }).then((_result) => {
886
- }).catch((error) => {
887
- try {
888
- term.grabInput(false);
889
- term.fullscreen(false);
890
- term.styleReset();
891
- term.clear();
892
- } catch (_cleanupErr) {
893
- }
894
- relinka("error", "\nAn unexpected error occurred:", error);
895
- process.exit(1);
896
- });
897
- }