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