@compilr-dev/cli 0.8.6 → 0.8.8
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/dist/commands-v2/handlers/settings.js +22 -3
- package/dist/commands-v2/types.d.ts +10 -0
- package/dist/compilr-diff-companion.vsix +0 -0
- package/dist/index.js +68 -1
- package/dist/repl-v2.d.ts +19 -1
- package/dist/repl-v2.js +37 -0
- package/dist/ui/overlay-manager.js +10 -2
- package/dist/utils/model-tiers.js +14 -9
- package/package.json +3 -3
- package/dist/episodes/recorder.d.ts +0 -51
- package/dist/episodes/recorder.js +0 -195
- package/dist/episodes/significant-work.d.ts +0 -21
- package/dist/episodes/significant-work.js +0 -56
- package/dist/episodes/store.d.ts +0 -38
- package/dist/episodes/store.js +0 -199
- package/dist/episodes/types.d.ts +0 -35
- package/dist/episodes/types.js +0 -6
- package/dist/episodes/work-at-risk.d.ts +0 -12
- package/dist/episodes/work-at-risk.js +0 -38
- package/dist/episodes/work-summary-anchor.d.ts +0 -23
- package/dist/episodes/work-summary-anchor.js +0 -73
- package/dist/tools/consult.d.ts +0 -14
- package/dist/tools/consult.js +0 -73
- package/dist/tools/delegate-background.d.ts +0 -27
- package/dist/tools/delegate-background.js +0 -115
- package/dist/tools/delegate.d.ts +0 -28
- package/dist/tools/delegate.js +0 -113
- package/dist/tools/handoff.d.ts +0 -17
- package/dist/tools/handoff.js +0 -79
- package/dist/tools/meta-tools.d.ts +0 -31
- package/dist/tools/meta-tools.js +0 -78
- package/dist/ui/overlay/impl/skill-editor-overlay.d.ts +0 -56
- package/dist/ui/overlay/impl/skill-editor-overlay.js +0 -493
|
@@ -1,493 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Skill Editor Overlay
|
|
3
|
-
*
|
|
4
|
-
* Fullscreen raw markdown editor for SKILL.md files.
|
|
5
|
-
* Uses alternate screen buffer (same pattern as DocumentDetailOverlayV2).
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Raw markdown editing with syntax highlighting
|
|
9
|
-
* - Line numbers, cursor, scroll
|
|
10
|
-
* - Ctrl+S saves to disk + validates + shows warnings
|
|
11
|
-
* - Dirty check with save prompt on exit
|
|
12
|
-
*/
|
|
13
|
-
import { promises as fs } from 'node:fs';
|
|
14
|
-
import chalk from 'chalk';
|
|
15
|
-
import { ttyWrite } from '../../terminal.js';
|
|
16
|
-
import { BaseOverlayV2 } from '../../base/overlay-base-v2.js';
|
|
17
|
-
import { renderBorder, isCtrlC, isUpArrow, isDownArrow, isLeftArrow, isRightArrow, isHome, isEnd, isPageUp, isPageDown, isBackspace, isDelete, isEnter, isTab, isEscape, getPrintableChar, isPrintable, } from '../../base/index.js';
|
|
18
|
-
import * as terminal from '../../terminal.js';
|
|
19
|
-
import { getCurrentTheme } from '../../../themes/index.js';
|
|
20
|
-
import { parseSkillMarkdown } from '@compilr-dev/sdk';
|
|
21
|
-
// =============================================================================
|
|
22
|
-
// Alternate Screen
|
|
23
|
-
// =============================================================================
|
|
24
|
-
let inAlternateScreen = false;
|
|
25
|
-
function enterAlternateScreen() {
|
|
26
|
-
if (inAlternateScreen)
|
|
27
|
-
return;
|
|
28
|
-
ttyWrite('\x1b[?1049h');
|
|
29
|
-
ttyWrite('\x1b[H');
|
|
30
|
-
inAlternateScreen = true;
|
|
31
|
-
}
|
|
32
|
-
function exitAlternateScreen() {
|
|
33
|
-
if (!inAlternateScreen)
|
|
34
|
-
return;
|
|
35
|
-
ttyWrite('\x1b[?1049l');
|
|
36
|
-
inAlternateScreen = false;
|
|
37
|
-
}
|
|
38
|
-
// =============================================================================
|
|
39
|
-
// Key helpers
|
|
40
|
-
// =============================================================================
|
|
41
|
-
function isCtrlS(data) {
|
|
42
|
-
return data.length === 1 && data[0] === 0x13;
|
|
43
|
-
}
|
|
44
|
-
// =============================================================================
|
|
45
|
-
// Syntax highlighting (simplified — YAML frontmatter + markdown)
|
|
46
|
-
// =============================================================================
|
|
47
|
-
function highlightLine(line, inFrontmatter) {
|
|
48
|
-
const theme = getCurrentTheme();
|
|
49
|
-
const primary = chalk.hex(theme.colors.primary);
|
|
50
|
-
const secondary = chalk.hex(theme.colors.secondary || theme.colors.primary);
|
|
51
|
-
const muted = chalk.hex(theme.colors.muted);
|
|
52
|
-
if (!line.trim())
|
|
53
|
-
return line;
|
|
54
|
-
// Frontmatter delimiter
|
|
55
|
-
if (line === '---')
|
|
56
|
-
return muted(line);
|
|
57
|
-
// Inside frontmatter — YAML highlighting
|
|
58
|
-
if (inFrontmatter) {
|
|
59
|
-
// Comment
|
|
60
|
-
if (line.trimStart().startsWith('#'))
|
|
61
|
-
return muted(line);
|
|
62
|
-
// Key: value
|
|
63
|
-
const kvMatch = line.match(/^(\s*)([\w-]+)(\s*:\s*)(.*)/);
|
|
64
|
-
if (kvMatch) {
|
|
65
|
-
const [, indent, key, colon, value] = kvMatch;
|
|
66
|
-
return indent + primary(key) + muted(colon) + secondary(value);
|
|
67
|
-
}
|
|
68
|
-
// List item
|
|
69
|
-
if (line.trimStart().startsWith('-'))
|
|
70
|
-
return secondary(line);
|
|
71
|
-
return secondary(line);
|
|
72
|
-
}
|
|
73
|
-
// Markdown highlighting
|
|
74
|
-
const headerMatch = line.match(/^(#{1,6})\s(.*)$/);
|
|
75
|
-
if (headerMatch)
|
|
76
|
-
return primary(headerMatch[1]) + ' ' + primary.bold(headerMatch[2]);
|
|
77
|
-
if (line.match(/^([-*_])\1{2,}\s*$/))
|
|
78
|
-
return muted(line);
|
|
79
|
-
const ulMatch = line.match(/^(\s*)([-*+])\s+(.*)/);
|
|
80
|
-
if (ulMatch)
|
|
81
|
-
return ulMatch[1] + secondary(ulMatch[2]) + ' ' + ulMatch[3];
|
|
82
|
-
const olMatch = line.match(/^(\s*)(\d+\.)\s+(.*)/);
|
|
83
|
-
if (olMatch)
|
|
84
|
-
return olMatch[1] + secondary(olMatch[2]) + ' ' + olMatch[3];
|
|
85
|
-
const bqMatch = line.match(/^(>\s*)(.*)/);
|
|
86
|
-
if (bqMatch)
|
|
87
|
-
return muted(bqMatch[1]) + muted.italic(bqMatch[2]);
|
|
88
|
-
return line;
|
|
89
|
-
}
|
|
90
|
-
// =============================================================================
|
|
91
|
-
// Skill Editor Overlay
|
|
92
|
-
// =============================================================================
|
|
93
|
-
export class SkillEditorOverlay extends BaseOverlayV2 {
|
|
94
|
-
type = 'inline';
|
|
95
|
-
id = 'skill-editor-overlay';
|
|
96
|
-
usesAlternateScreen = true;
|
|
97
|
-
filePath;
|
|
98
|
-
skillName;
|
|
99
|
-
constructor(filePath, skillName, content) {
|
|
100
|
-
super({
|
|
101
|
-
rawLines: content.split('\n'),
|
|
102
|
-
cursorLine: 0,
|
|
103
|
-
cursorColumn: 0,
|
|
104
|
-
scrollOffset: 0,
|
|
105
|
-
isDirty: false,
|
|
106
|
-
originalContent: content,
|
|
107
|
-
showSavePrompt: false,
|
|
108
|
-
statusMessage: null,
|
|
109
|
-
statusType: null,
|
|
110
|
-
});
|
|
111
|
-
this.filePath = filePath;
|
|
112
|
-
this.skillName = skillName;
|
|
113
|
-
}
|
|
114
|
-
onMount() {
|
|
115
|
-
enterAlternateScreen();
|
|
116
|
-
}
|
|
117
|
-
onUnmount() {
|
|
118
|
-
exitAlternateScreen();
|
|
119
|
-
}
|
|
120
|
-
// ===========================================================================
|
|
121
|
-
// Rendering
|
|
122
|
-
// ===========================================================================
|
|
123
|
-
renderContent(context) {
|
|
124
|
-
const s = context.styles;
|
|
125
|
-
const cols = context.width;
|
|
126
|
-
const rows = context.height;
|
|
127
|
-
const border = renderBorder(cols, s);
|
|
128
|
-
const lines = [];
|
|
129
|
-
// Header
|
|
130
|
-
const editIndicator = this.state.isDirty ? s.warning('[EDIT*]') : s.primary('[EDIT]');
|
|
131
|
-
lines.push(border);
|
|
132
|
-
lines.push(` ${s.primaryBold('SKILL')}${s.muted(' │ ')}${chalk.bold(this.skillName)} ${editIndicator}`);
|
|
133
|
-
lines.push(border);
|
|
134
|
-
// Content area
|
|
135
|
-
const headerLines = 3;
|
|
136
|
-
const footerLines = 3;
|
|
137
|
-
const contentHeight = rows - headerLines - footerLines;
|
|
138
|
-
this.ensureCursorVisible(contentHeight);
|
|
139
|
-
const totalLines = this.state.rawLines.length;
|
|
140
|
-
const lineNumWidth = String(totalLines).length + 1;
|
|
141
|
-
// Determine frontmatter state for each visible line
|
|
142
|
-
let inFrontmatter = false;
|
|
143
|
-
let fmCount = 0;
|
|
144
|
-
for (let i = 0; i <= Math.min(this.state.scrollOffset + contentHeight, totalLines - 1); i++) {
|
|
145
|
-
if (this.state.rawLines[i] === '---') {
|
|
146
|
-
fmCount++;
|
|
147
|
-
if (fmCount === 1)
|
|
148
|
-
inFrontmatter = true;
|
|
149
|
-
else if (fmCount === 2)
|
|
150
|
-
inFrontmatter = false;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
// Reset and track frontmatter state for rendering
|
|
154
|
-
inFrontmatter = false;
|
|
155
|
-
fmCount = 0;
|
|
156
|
-
for (let i = 0; i < contentHeight; i++) {
|
|
157
|
-
const lineIndex = this.state.scrollOffset + i;
|
|
158
|
-
if (lineIndex < totalLines) {
|
|
159
|
-
const lineContent = this.state.rawLines[lineIndex];
|
|
160
|
-
const lineNum = String(lineIndex + 1).padStart(lineNumWidth);
|
|
161
|
-
const isCursorLine = lineIndex === this.state.cursorLine;
|
|
162
|
-
// Track frontmatter state
|
|
163
|
-
if (lineContent === '---') {
|
|
164
|
-
fmCount++;
|
|
165
|
-
if (fmCount === 1)
|
|
166
|
-
inFrontmatter = true;
|
|
167
|
-
else if (fmCount === 2)
|
|
168
|
-
inFrontmatter = false;
|
|
169
|
-
}
|
|
170
|
-
if (isCursorLine) {
|
|
171
|
-
const rendered = this.renderCursorLine(lineContent, this.state.cursorColumn, cols - lineNumWidth - 2);
|
|
172
|
-
lines.push(s.muted(`${lineNum}│`) + rendered);
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
const maxLen = cols - lineNumWidth - 2;
|
|
176
|
-
const truncated = lineContent.length > maxLen;
|
|
177
|
-
const display = truncated ? lineContent.slice(0, maxLen - 1) : lineContent;
|
|
178
|
-
const highlighted = highlightLine(display, inFrontmatter && fmCount < 2);
|
|
179
|
-
lines.push(s.muted(`${lineNum}│`) + highlighted + (truncated ? s.muted('…') : ''));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
const lineNum = ' '.repeat(lineNumWidth);
|
|
184
|
-
lines.push(s.muted(`${lineNum}│`) + s.muted('~'));
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
// Footer
|
|
188
|
-
lines.push(border);
|
|
189
|
-
if (this.state.showSavePrompt) {
|
|
190
|
-
lines.push(s.warning(' Unsaved changes. Save? ') + s.primary('(y)') + 'es / ' + s.primary('(n)') + 'o / ' + s.primary('(c)') + 'ancel');
|
|
191
|
-
}
|
|
192
|
-
else if (this.state.statusMessage) {
|
|
193
|
-
const styleFn = this.state.statusType === 'success' ? s.success
|
|
194
|
-
: this.state.statusType === 'warning' ? s.warning
|
|
195
|
-
: this.state.statusType === 'error' ? s.warning
|
|
196
|
-
: s.muted;
|
|
197
|
-
lines.push(styleFn(` ${this.state.statusMessage}`));
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
const cursorPos = `Ln ${String(this.state.cursorLine + 1)}, Col ${String(this.state.cursorColumn + 1)}`;
|
|
201
|
-
const mod = this.state.isDirty ? s.warning('[modified]') : '';
|
|
202
|
-
lines.push(s.primaryBold(' ── INSERT ── ') + s.muted(cursorPos) + ' ' + mod + s.muted(' Ctrl+S Save · Esc Exit'));
|
|
203
|
-
}
|
|
204
|
-
lines.push(border);
|
|
205
|
-
return lines;
|
|
206
|
-
}
|
|
207
|
-
renderCursorLine(line, cursorCol, maxLen) {
|
|
208
|
-
const cursorStyle = chalk.inverse;
|
|
209
|
-
const effectiveCursorCol = Math.min(cursorCol, line.length);
|
|
210
|
-
let viewStart = 0;
|
|
211
|
-
if (effectiveCursorCol >= maxLen - 5) {
|
|
212
|
-
viewStart = effectiveCursorCol - maxLen + 10;
|
|
213
|
-
}
|
|
214
|
-
viewStart = Math.max(0, viewStart);
|
|
215
|
-
const viewEnd = viewStart + maxLen;
|
|
216
|
-
const visibleLine = line.slice(viewStart, viewEnd);
|
|
217
|
-
const cursorPosInView = effectiveCursorCol - viewStart;
|
|
218
|
-
let result = '';
|
|
219
|
-
for (let i = 0; i < visibleLine.length; i++) {
|
|
220
|
-
result += i === cursorPosInView ? cursorStyle(visibleLine[i]) : visibleLine[i];
|
|
221
|
-
}
|
|
222
|
-
if (cursorPosInView >= visibleLine.length) {
|
|
223
|
-
result += cursorStyle(' ');
|
|
224
|
-
}
|
|
225
|
-
const theme = getCurrentTheme();
|
|
226
|
-
if (viewStart > 0) {
|
|
227
|
-
result = chalk.hex(theme.colors.muted)('…') + result.slice(1);
|
|
228
|
-
}
|
|
229
|
-
if (line.length > viewEnd) {
|
|
230
|
-
result = result.slice(0, -1) + chalk.hex(theme.colors.muted)('…');
|
|
231
|
-
}
|
|
232
|
-
return result;
|
|
233
|
-
}
|
|
234
|
-
ensureCursorVisible(contentHeight) {
|
|
235
|
-
if (this.state.cursorLine < this.state.scrollOffset) {
|
|
236
|
-
this.state.scrollOffset = this.state.cursorLine;
|
|
237
|
-
}
|
|
238
|
-
if (this.state.cursorLine >= this.state.scrollOffset + contentHeight) {
|
|
239
|
-
this.state.scrollOffset = this.state.cursorLine - contentHeight + 1;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
// ===========================================================================
|
|
243
|
-
// Key Handling
|
|
244
|
-
// ===========================================================================
|
|
245
|
-
handleKey(key) {
|
|
246
|
-
const data = key.raw;
|
|
247
|
-
// Clear status message on any keypress
|
|
248
|
-
if (this.state.statusMessage && !isCtrlS(data)) {
|
|
249
|
-
this.state.statusMessage = null;
|
|
250
|
-
this.state.statusType = null;
|
|
251
|
-
}
|
|
252
|
-
// Save prompt
|
|
253
|
-
if (this.state.showSavePrompt) {
|
|
254
|
-
return this.handleSavePrompt(data);
|
|
255
|
-
}
|
|
256
|
-
// Ctrl+C — force close
|
|
257
|
-
if (isCtrlC(data)) {
|
|
258
|
-
return { type: 'close', result: { saved: false } };
|
|
259
|
-
}
|
|
260
|
-
// Ctrl+S — save
|
|
261
|
-
if (isCtrlS(data)) {
|
|
262
|
-
void this.save();
|
|
263
|
-
return { type: 'render' };
|
|
264
|
-
}
|
|
265
|
-
// Esc — exit (with dirty check)
|
|
266
|
-
if (isEscape(data)) {
|
|
267
|
-
if (this.state.isDirty) {
|
|
268
|
-
this.state.showSavePrompt = true;
|
|
269
|
-
return { type: 'render' };
|
|
270
|
-
}
|
|
271
|
-
return { type: 'close', result: { saved: false } };
|
|
272
|
-
}
|
|
273
|
-
// Navigation
|
|
274
|
-
if (isUpArrow(data)) {
|
|
275
|
-
this.moveCursorUp();
|
|
276
|
-
return { type: 'render' };
|
|
277
|
-
}
|
|
278
|
-
if (isDownArrow(data)) {
|
|
279
|
-
this.moveCursorDown();
|
|
280
|
-
return { type: 'render' };
|
|
281
|
-
}
|
|
282
|
-
if (isLeftArrow(data)) {
|
|
283
|
-
this.moveCursorLeft();
|
|
284
|
-
return { type: 'render' };
|
|
285
|
-
}
|
|
286
|
-
if (isRightArrow(data)) {
|
|
287
|
-
this.moveCursorRight();
|
|
288
|
-
return { type: 'render' };
|
|
289
|
-
}
|
|
290
|
-
if (isHome(data)) {
|
|
291
|
-
this.state.cursorColumn = 0;
|
|
292
|
-
return { type: 'render' };
|
|
293
|
-
}
|
|
294
|
-
if (isEnd(data)) {
|
|
295
|
-
this.state.cursorColumn = this.currentLine().length;
|
|
296
|
-
return { type: 'render' };
|
|
297
|
-
}
|
|
298
|
-
if (isPageUp(data)) {
|
|
299
|
-
this.pageUp();
|
|
300
|
-
return { type: 'render' };
|
|
301
|
-
}
|
|
302
|
-
if (isPageDown(data)) {
|
|
303
|
-
this.pageDown();
|
|
304
|
-
return { type: 'render' };
|
|
305
|
-
}
|
|
306
|
-
// Editing
|
|
307
|
-
if (isEnter(data)) {
|
|
308
|
-
this.insertNewline();
|
|
309
|
-
return { type: 'render' };
|
|
310
|
-
}
|
|
311
|
-
if (isBackspace(data)) {
|
|
312
|
-
this.handleBackspace();
|
|
313
|
-
return { type: 'render' };
|
|
314
|
-
}
|
|
315
|
-
if (isDelete(data)) {
|
|
316
|
-
this.handleDelete();
|
|
317
|
-
return { type: 'render' };
|
|
318
|
-
}
|
|
319
|
-
if (isTab(data)) {
|
|
320
|
-
this.insertText(' ');
|
|
321
|
-
return { type: 'render' };
|
|
322
|
-
}
|
|
323
|
-
// Printable character
|
|
324
|
-
if (isPrintable(data)) {
|
|
325
|
-
const ch = getPrintableChar(data);
|
|
326
|
-
if (ch) {
|
|
327
|
-
this.insertText(ch);
|
|
328
|
-
return { type: 'render' };
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
return { type: 'none' };
|
|
332
|
-
}
|
|
333
|
-
// ===========================================================================
|
|
334
|
-
// Save prompt
|
|
335
|
-
// ===========================================================================
|
|
336
|
-
handleSavePrompt(data) {
|
|
337
|
-
if (isPrintable(data)) {
|
|
338
|
-
const ch = getPrintableChar(data) ?? '';
|
|
339
|
-
if (ch === 'y') {
|
|
340
|
-
void this.save();
|
|
341
|
-
this.state.showSavePrompt = false;
|
|
342
|
-
return { type: 'close', result: { saved: true } };
|
|
343
|
-
}
|
|
344
|
-
if (ch === 'n') {
|
|
345
|
-
this.state.showSavePrompt = false;
|
|
346
|
-
return { type: 'close', result: { saved: false } };
|
|
347
|
-
}
|
|
348
|
-
if (ch === 'c') {
|
|
349
|
-
this.state.showSavePrompt = false;
|
|
350
|
-
return { type: 'render' };
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
if (isEscape(data)) {
|
|
354
|
-
this.state.showSavePrompt = false;
|
|
355
|
-
return { type: 'render' };
|
|
356
|
-
}
|
|
357
|
-
return { type: 'none' };
|
|
358
|
-
}
|
|
359
|
-
// ===========================================================================
|
|
360
|
-
// Save + Validate
|
|
361
|
-
// ===========================================================================
|
|
362
|
-
async save() {
|
|
363
|
-
const content = this.state.rawLines.join('\n');
|
|
364
|
-
await fs.writeFile(this.filePath, content);
|
|
365
|
-
this.state.isDirty = false;
|
|
366
|
-
this.state.originalContent = content;
|
|
367
|
-
// Validate
|
|
368
|
-
const parsed = parseSkillMarkdown(content);
|
|
369
|
-
const warnings = [];
|
|
370
|
-
if (!parsed) {
|
|
371
|
-
warnings.push('Invalid frontmatter (required: name, description)');
|
|
372
|
-
}
|
|
373
|
-
else {
|
|
374
|
-
if (parsed.description.length < 60) {
|
|
375
|
-
warnings.push(`Description is ${String(parsed.description.length)} chars (recommend 60+)`);
|
|
376
|
-
}
|
|
377
|
-
if (!parsed.prompt || parsed.prompt.trim().length === 0) {
|
|
378
|
-
warnings.push('Body is empty');
|
|
379
|
-
}
|
|
380
|
-
if (parsed.name !== this.skillName) {
|
|
381
|
-
warnings.push(`Frontmatter name '${parsed.name}' doesn't match folder '${this.skillName}'`);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
if (warnings.length > 0) {
|
|
385
|
-
this.state.statusMessage = `⚠ Saved with ${String(warnings.length)} warning${warnings.length > 1 ? 's' : ''}: ${warnings.join('; ')}`;
|
|
386
|
-
this.state.statusType = 'warning';
|
|
387
|
-
}
|
|
388
|
-
else {
|
|
389
|
-
this.state.statusMessage = '✓ Saved';
|
|
390
|
-
this.state.statusType = 'success';
|
|
391
|
-
}
|
|
392
|
-
this.requestRender();
|
|
393
|
-
}
|
|
394
|
-
// ===========================================================================
|
|
395
|
-
// Cursor movement
|
|
396
|
-
// ===========================================================================
|
|
397
|
-
currentLine() {
|
|
398
|
-
return this.state.rawLines[this.state.cursorLine] ?? '';
|
|
399
|
-
}
|
|
400
|
-
moveCursorUp() {
|
|
401
|
-
if (this.state.cursorLine > 0) {
|
|
402
|
-
this.state.cursorLine--;
|
|
403
|
-
this.state.cursorColumn = Math.min(this.state.cursorColumn, this.currentLine().length);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
moveCursorDown() {
|
|
407
|
-
if (this.state.cursorLine < this.state.rawLines.length - 1) {
|
|
408
|
-
this.state.cursorLine++;
|
|
409
|
-
this.state.cursorColumn = Math.min(this.state.cursorColumn, this.currentLine().length);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
moveCursorLeft() {
|
|
413
|
-
if (this.state.cursorColumn > 0) {
|
|
414
|
-
this.state.cursorColumn--;
|
|
415
|
-
}
|
|
416
|
-
else if (this.state.cursorLine > 0) {
|
|
417
|
-
this.state.cursorLine--;
|
|
418
|
-
this.state.cursorColumn = this.currentLine().length;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
moveCursorRight() {
|
|
422
|
-
if (this.state.cursorColumn < this.currentLine().length) {
|
|
423
|
-
this.state.cursorColumn++;
|
|
424
|
-
}
|
|
425
|
-
else if (this.state.cursorLine < this.state.rawLines.length - 1) {
|
|
426
|
-
this.state.cursorLine++;
|
|
427
|
-
this.state.cursorColumn = 0;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
pageUp() {
|
|
431
|
-
const pageSize = Math.max(1, terminal.getTerminalHeight() - 10);
|
|
432
|
-
this.state.cursorLine = Math.max(0, this.state.cursorLine - pageSize);
|
|
433
|
-
this.state.cursorColumn = Math.min(this.state.cursorColumn, this.currentLine().length);
|
|
434
|
-
}
|
|
435
|
-
pageDown() {
|
|
436
|
-
const pageSize = Math.max(1, terminal.getTerminalHeight() - 10);
|
|
437
|
-
this.state.cursorLine = Math.min(this.state.rawLines.length - 1, this.state.cursorLine + pageSize);
|
|
438
|
-
this.state.cursorColumn = Math.min(this.state.cursorColumn, this.currentLine().length);
|
|
439
|
-
}
|
|
440
|
-
// ===========================================================================
|
|
441
|
-
// Text editing
|
|
442
|
-
// ===========================================================================
|
|
443
|
-
insertText(text) {
|
|
444
|
-
const line = this.currentLine();
|
|
445
|
-
const col = this.state.cursorColumn;
|
|
446
|
-
this.state.rawLines[this.state.cursorLine] = line.slice(0, col) + text + line.slice(col);
|
|
447
|
-
this.state.cursorColumn += text.length;
|
|
448
|
-
this.state.isDirty = true;
|
|
449
|
-
}
|
|
450
|
-
insertNewline() {
|
|
451
|
-
const line = this.currentLine();
|
|
452
|
-
const col = this.state.cursorColumn;
|
|
453
|
-
const before = line.slice(0, col);
|
|
454
|
-
const after = line.slice(col);
|
|
455
|
-
this.state.rawLines[this.state.cursorLine] = before;
|
|
456
|
-
this.state.rawLines.splice(this.state.cursorLine + 1, 0, after);
|
|
457
|
-
this.state.cursorLine++;
|
|
458
|
-
this.state.cursorColumn = 0;
|
|
459
|
-
this.state.isDirty = true;
|
|
460
|
-
}
|
|
461
|
-
handleBackspace() {
|
|
462
|
-
if (this.state.cursorColumn > 0) {
|
|
463
|
-
const line = this.currentLine();
|
|
464
|
-
this.state.rawLines[this.state.cursorLine] =
|
|
465
|
-
line.slice(0, this.state.cursorColumn - 1) + line.slice(this.state.cursorColumn);
|
|
466
|
-
this.state.cursorColumn--;
|
|
467
|
-
this.state.isDirty = true;
|
|
468
|
-
}
|
|
469
|
-
else if (this.state.cursorLine > 0) {
|
|
470
|
-
const currentLine = this.currentLine();
|
|
471
|
-
const prevLine = this.state.rawLines[this.state.cursorLine - 1];
|
|
472
|
-
this.state.cursorColumn = prevLine.length;
|
|
473
|
-
this.state.rawLines[this.state.cursorLine - 1] = prevLine + currentLine;
|
|
474
|
-
this.state.rawLines.splice(this.state.cursorLine, 1);
|
|
475
|
-
this.state.cursorLine--;
|
|
476
|
-
this.state.isDirty = true;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
handleDelete() {
|
|
480
|
-
const line = this.currentLine();
|
|
481
|
-
if (this.state.cursorColumn < line.length) {
|
|
482
|
-
this.state.rawLines[this.state.cursorLine] =
|
|
483
|
-
line.slice(0, this.state.cursorColumn) + line.slice(this.state.cursorColumn + 1);
|
|
484
|
-
this.state.isDirty = true;
|
|
485
|
-
}
|
|
486
|
-
else if (this.state.cursorLine < this.state.rawLines.length - 1) {
|
|
487
|
-
const nextLine = this.state.rawLines[this.state.cursorLine + 1];
|
|
488
|
-
this.state.rawLines[this.state.cursorLine] = line + nextLine;
|
|
489
|
-
this.state.rawLines.splice(this.state.cursorLine + 1, 1);
|
|
490
|
-
this.state.isDirty = true;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
}
|