@nocturnium/svelte-ide 1.1.1 → 1.2.1
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/components/editor/CognitiveLoadMeter.svelte +27 -0
- package/dist/components/editor/ComplexityHeatLayer.svelte +157 -0
- package/dist/components/editor/ComplexityHeatLayer.svelte.d.ts +24 -0
- package/dist/components/editor/ComplexityLayer.svelte +325 -109
- package/dist/components/editor/ComplexityLayer.svelte.d.ts +13 -0
- package/dist/components/editor/ConflictZoneLayer.svelte +22 -15
- package/dist/components/editor/CustomEditor.svelte +80 -1
- package/dist/components/editor/CustomEditor.svelte.d.ts +3 -1
- package/dist/components/editor/EchoCursorLayer.svelte +60 -0
- package/dist/components/editor/PluginPreviewSandbox.svelte +43 -9
- package/dist/components/editor/PluginPreviewSandbox.svelte.d.ts +4 -4
- package/dist/components/editor/core/complexity-analyzer.d.ts +31 -0
- package/dist/components/editor/core/complexity-analyzer.js +479 -29
- package/dist/components/editor/core/conflict-predictor.d.ts +32 -0
- package/dist/components/editor/core/conflict-predictor.js +55 -0
- package/dist/components/editor/core/crdt-binding.d.ts +4 -0
- package/dist/components/editor/core/crdt-binding.js +34 -9
- package/dist/components/editor/core/echo-cursor.d.ts +18 -1
- package/dist/components/editor/core/echo-cursor.js +117 -6
- package/dist/components/editor/core/extract-function.d.ts +27 -0
- package/dist/components/editor/core/extract-function.js +865 -0
- package/dist/components/editor/core/index.d.ts +1 -0
- package/dist/components/editor/core/index.js +1 -0
- package/dist/components/editor/core/state.d.ts +38 -5
- package/dist/components/editor/core/state.js +175 -98
- package/dist/components/editor/core/timeline.js +6 -1
- package/dist/components/editor/editor-find.js +15 -3
- package/dist/components/editor/theme.d.ts +8 -0
- package/dist/components/editor/theme.js +52 -0
- package/dist/services/lsp-client.d.ts +3 -0
- package/dist/services/lsp-client.js +86 -14
- package/dist/styles/theme.css +4 -1
- package/package.json +1 -1
|
@@ -51,6 +51,10 @@ export declare class CRDTBinding {
|
|
|
51
51
|
* Uses Yjs relative positions for accurate cursor preservation during concurrent edits
|
|
52
52
|
*/
|
|
53
53
|
private handleCRDTChange;
|
|
54
|
+
/**
|
|
55
|
+
* Apply a Y.Text delta to the editor using bounded index-based edit paths.
|
|
56
|
+
*/
|
|
57
|
+
private applyCRDTDelta;
|
|
54
58
|
/**
|
|
55
59
|
* Convert position to index using provided lines (for atomic operations)
|
|
56
60
|
*/
|
|
@@ -67,7 +67,7 @@ export class CRDTBinding {
|
|
|
67
67
|
* Handle changes from CRDT
|
|
68
68
|
* Uses Yjs relative positions for accurate cursor preservation during concurrent edits
|
|
69
69
|
*/
|
|
70
|
-
handleCRDTChange(
|
|
70
|
+
handleCRDTChange(event) {
|
|
71
71
|
this.isUpdating = true;
|
|
72
72
|
try {
|
|
73
73
|
// Capture current selection as relative positions BEFORE applying changes
|
|
@@ -78,22 +78,20 @@ export class CRDTBinding {
|
|
|
78
78
|
// Create Yjs relative positions (these track position relative to surrounding content)
|
|
79
79
|
const anchorRelPos = Y.createRelativePositionFromTypeIndex(this.text, anchorIndex);
|
|
80
80
|
const headRelPos = Y.createRelativePositionFromTypeIndex(this.text, headIndex);
|
|
81
|
-
// Get new content
|
|
82
|
-
const content = this.text.toString();
|
|
83
81
|
// Resolve relative positions back to absolute positions
|
|
84
82
|
// This correctly handles insertions/deletions around the cursor
|
|
85
83
|
const anchorAbsPos = Y.createAbsolutePositionFromRelativePosition(anchorRelPos, this.doc);
|
|
86
84
|
const headAbsPos = Y.createAbsolutePositionFromRelativePosition(headRelPos, this.doc);
|
|
87
85
|
// Convert back to editor positions with bounds checking
|
|
88
|
-
const maxIndex =
|
|
86
|
+
const maxIndex = this.text.length;
|
|
89
87
|
const newAnchorIndex = anchorAbsPos ? Math.min(anchorAbsPos.index, maxIndex) : 0;
|
|
90
88
|
const newHeadIndex = headAbsPos ? Math.min(headAbsPos.index, maxIndex) : 0;
|
|
91
|
-
const newAnchor = this.indexToPosition(newAnchorIndex);
|
|
92
|
-
const newHead = this.indexToPosition(newHeadIndex);
|
|
93
89
|
// Apply content and selection atomically without triggering intermediate events
|
|
94
90
|
// This prevents race conditions where selection listeners modify state during update
|
|
95
91
|
this.editorState.runWithoutNotifications(() => {
|
|
96
|
-
this.
|
|
92
|
+
this.applyCRDTDelta(event.delta);
|
|
93
|
+
const newAnchor = this.indexToPosition(newAnchorIndex);
|
|
94
|
+
const newHead = this.indexToPosition(newHeadIndex);
|
|
97
95
|
this.editorState.setSelection(newAnchor, newHead);
|
|
98
96
|
});
|
|
99
97
|
}
|
|
@@ -101,6 +99,28 @@ export class CRDTBinding {
|
|
|
101
99
|
this.isUpdating = false;
|
|
102
100
|
}
|
|
103
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Apply a Y.Text delta to the editor using bounded index-based edit paths.
|
|
104
|
+
*/
|
|
105
|
+
applyCRDTDelta(delta) {
|
|
106
|
+
let index = 0;
|
|
107
|
+
for (const op of delta) {
|
|
108
|
+
if (op.retain) {
|
|
109
|
+
index += op.retain;
|
|
110
|
+
}
|
|
111
|
+
if (op.insert !== undefined) {
|
|
112
|
+
const insertedText = String(op.insert);
|
|
113
|
+
const position = this.indexToPosition(index);
|
|
114
|
+
this.editorState.insertAt(position, insertedText);
|
|
115
|
+
index += insertedText.length;
|
|
116
|
+
}
|
|
117
|
+
if (op.delete) {
|
|
118
|
+
const from = this.indexToPosition(index);
|
|
119
|
+
const to = this.indexToPosition(index + op.delete);
|
|
120
|
+
this.editorState.deleteRange(from, to);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
104
124
|
/**
|
|
105
125
|
* Convert position to index using provided lines (for atomic operations)
|
|
106
126
|
*/
|
|
@@ -153,8 +173,13 @@ export class CRDTBinding {
|
|
|
153
173
|
if (currentLength > 0) {
|
|
154
174
|
this.text.delete(0, currentLength);
|
|
155
175
|
}
|
|
156
|
-
|
|
157
|
-
|
|
176
|
+
// Source the full content from the editor itself, not from
|
|
177
|
+
// event.text: undo/redo emit a `replace` with no `text`, so
|
|
178
|
+
// trusting event.text here would empty the shared doc. getContent()
|
|
179
|
+
// is the authoritative post-change content for any replace emitter.
|
|
180
|
+
const fullContent = this.editorState.getContent();
|
|
181
|
+
if (fullContent) {
|
|
182
|
+
this.text.insert(0, fullContent);
|
|
158
183
|
}
|
|
159
184
|
break;
|
|
160
185
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* keystrokes with a configurable delay. Type in one place, and edits
|
|
6
6
|
* propagate to echo locations with visible animation.
|
|
7
7
|
*/
|
|
8
|
-
import type
|
|
8
|
+
import { EditorState, type Position } from './state';
|
|
9
9
|
/**
|
|
10
10
|
* Keystroke event for replay
|
|
11
11
|
*/
|
|
@@ -24,6 +24,8 @@ export interface KeystrokeEvent {
|
|
|
24
24
|
direction?: 'forward' | 'backward';
|
|
25
25
|
/** Count for delete */
|
|
26
26
|
count?: number;
|
|
27
|
+
/** Removed text for delete replay */
|
|
28
|
+
removed?: string;
|
|
27
29
|
/** New position for move */
|
|
28
30
|
position?: Position;
|
|
29
31
|
};
|
|
@@ -107,7 +109,10 @@ export declare class EchoCursorManager {
|
|
|
107
109
|
private echoCursors;
|
|
108
110
|
private keystrokeBuffer;
|
|
109
111
|
private replayTimers;
|
|
112
|
+
private echoMirrors;
|
|
110
113
|
private listeners;
|
|
114
|
+
private detachEditorState;
|
|
115
|
+
private attachedEditorState;
|
|
111
116
|
private enabled;
|
|
112
117
|
private colorIndex;
|
|
113
118
|
constructor(config?: Partial<EchoCursorConfig>);
|
|
@@ -127,6 +132,10 @@ export declare class EchoCursorManager {
|
|
|
127
132
|
* Check if echo mode is enabled
|
|
128
133
|
*/
|
|
129
134
|
isEnabled(): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Attach echo recording to a real editor state.
|
|
137
|
+
*/
|
|
138
|
+
attach(editorState: EditorState): () => void;
|
|
130
139
|
/**
|
|
131
140
|
* Add an echo cursor at a position
|
|
132
141
|
*/
|
|
@@ -170,6 +179,10 @@ export declare class EchoCursorManager {
|
|
|
170
179
|
* Get echo cursor by ID
|
|
171
180
|
*/
|
|
172
181
|
getEchoCursor(id: string): EchoCursor | undefined;
|
|
182
|
+
/**
|
|
183
|
+
* Get the real mirrored buffer content for an echo cursor.
|
|
184
|
+
*/
|
|
185
|
+
getEchoContent(id: string): string;
|
|
173
186
|
/**
|
|
174
187
|
* Get keystroke buffer
|
|
175
188
|
*/
|
|
@@ -186,6 +199,10 @@ export declare class EchoCursorManager {
|
|
|
186
199
|
* Schedule keystroke replay for an echo cursor
|
|
187
200
|
*/
|
|
188
201
|
private scheduleReplay;
|
|
202
|
+
private recordChangeEvent;
|
|
203
|
+
private getReplayText;
|
|
204
|
+
private positionAfterText;
|
|
205
|
+
private positionBeforeText;
|
|
189
206
|
/**
|
|
190
207
|
* Emit event to listeners
|
|
191
208
|
*/
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* keystrokes with a configurable delay. Type in one place, and edits
|
|
6
6
|
* propagate to echo locations with visible animation.
|
|
7
7
|
*/
|
|
8
|
+
import { EditorState } from './state';
|
|
8
9
|
const DEFAULT_CONFIG = {
|
|
9
10
|
defaultDelay: 150,
|
|
10
11
|
maxEchoCursors: 10,
|
|
@@ -30,7 +31,10 @@ export class EchoCursorManager {
|
|
|
30
31
|
echoCursors = new Map();
|
|
31
32
|
keystrokeBuffer = [];
|
|
32
33
|
replayTimers = new Map();
|
|
34
|
+
echoMirrors = new Map();
|
|
33
35
|
listeners = new Set();
|
|
36
|
+
detachEditorState = null;
|
|
37
|
+
attachedEditorState = null;
|
|
34
38
|
enabled = false;
|
|
35
39
|
colorIndex = 0;
|
|
36
40
|
constructor(config = {}) {
|
|
@@ -69,6 +73,21 @@ export class EchoCursorManager {
|
|
|
69
73
|
isEnabled() {
|
|
70
74
|
return this.enabled;
|
|
71
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Attach echo recording to a real editor state.
|
|
78
|
+
*/
|
|
79
|
+
attach(editorState) {
|
|
80
|
+
this.detachEditorState?.();
|
|
81
|
+
this.attachedEditorState = editorState;
|
|
82
|
+
this.detachEditorState = editorState.onContentChange((event) => this.recordChangeEvent(event));
|
|
83
|
+
return () => {
|
|
84
|
+
if (this.attachedEditorState === editorState) {
|
|
85
|
+
this.detachEditorState?.();
|
|
86
|
+
this.detachEditorState = null;
|
|
87
|
+
this.attachedEditorState = null;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
72
91
|
/**
|
|
73
92
|
* Add an echo cursor at a position
|
|
74
93
|
*/
|
|
@@ -92,6 +111,9 @@ export class EchoCursorManager {
|
|
|
92
111
|
};
|
|
93
112
|
this.echoCursors.set(id, cursor);
|
|
94
113
|
this.replayTimers.set(id, []);
|
|
114
|
+
this.echoMirrors.set(id, new EditorState({
|
|
115
|
+
content: this.attachedEditorState?.getContent() ?? ''
|
|
116
|
+
}));
|
|
95
117
|
this.emit({ type: 'echo-added', cursor });
|
|
96
118
|
return cursor;
|
|
97
119
|
}
|
|
@@ -108,6 +130,7 @@ export class EchoCursorManager {
|
|
|
108
130
|
clearTimeout(timer);
|
|
109
131
|
}
|
|
110
132
|
this.replayTimers.delete(id);
|
|
133
|
+
this.echoMirrors.delete(id);
|
|
111
134
|
this.echoCursors.delete(id);
|
|
112
135
|
this.emit({ type: 'echo-removed', cursorId: id });
|
|
113
136
|
return true;
|
|
@@ -192,6 +215,12 @@ export class EchoCursorManager {
|
|
|
192
215
|
getEchoCursor(id) {
|
|
193
216
|
return this.echoCursors.get(id);
|
|
194
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Get the real mirrored buffer content for an echo cursor.
|
|
220
|
+
*/
|
|
221
|
+
getEchoContent(id) {
|
|
222
|
+
return this.echoMirrors.get(id)?.getContent() ?? '';
|
|
223
|
+
}
|
|
195
224
|
/**
|
|
196
225
|
* Get keystroke buffer
|
|
197
226
|
*/
|
|
@@ -221,19 +250,101 @@ export class EchoCursorManager {
|
|
|
221
250
|
if (!cursor.active)
|
|
222
251
|
return;
|
|
223
252
|
const timer = setTimeout(() => {
|
|
253
|
+
const mirror = this.echoMirrors.get(cursor.id);
|
|
254
|
+
if (!mirror)
|
|
255
|
+
return;
|
|
224
256
|
cursor.isReplaying = true;
|
|
225
257
|
this.emit({ type: 'replay-started', cursorId: cursor.id, keystroke });
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
258
|
+
if (keystroke.type === 'insert' || keystroke.type === 'newline' || keystroke.type === 'tab') {
|
|
259
|
+
const text = this.getReplayText(keystroke);
|
|
260
|
+
if (text) {
|
|
261
|
+
mirror.insertAt(cursor.position, text);
|
|
262
|
+
this.updateEchoPosition(cursor.id, mirror.cursor);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else if (keystroke.type === 'delete') {
|
|
266
|
+
const removed = keystroke.data.removed ?? ''.padStart(keystroke.data.count ?? 1, ' ');
|
|
267
|
+
const from = keystroke.data.direction === 'forward'
|
|
268
|
+
? cursor.position
|
|
269
|
+
: this.positionBeforeText(mirror, cursor.position, removed);
|
|
270
|
+
const to = keystroke.data.direction === 'forward'
|
|
271
|
+
? this.positionAfterText(mirror, cursor.position, removed)
|
|
272
|
+
: cursor.position;
|
|
273
|
+
mirror.deleteRange(from, to);
|
|
274
|
+
this.updateEchoPosition(cursor.id, mirror.cursor);
|
|
275
|
+
}
|
|
276
|
+
cursor.isReplaying = false;
|
|
277
|
+
cursor.replayIndex++;
|
|
278
|
+
this.emit({ type: 'replay-completed', cursorId: cursor.id, keystroke });
|
|
232
279
|
}, cursor.delayMs);
|
|
233
280
|
const timers = this.replayTimers.get(cursor.id) || [];
|
|
234
281
|
timers.push(timer);
|
|
235
282
|
this.replayTimers.set(cursor.id, timers);
|
|
236
283
|
}
|
|
284
|
+
recordChangeEvent(event) {
|
|
285
|
+
if (event.type === 'insert' && event.text) {
|
|
286
|
+
this.recordInsert(event.text);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (event.type === 'delete' && event.removed) {
|
|
290
|
+
this.recordKeystroke({
|
|
291
|
+
type: 'delete',
|
|
292
|
+
data: {
|
|
293
|
+
direction: 'backward',
|
|
294
|
+
count: event.removed.length,
|
|
295
|
+
removed: event.removed
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (event.type === 'replace') {
|
|
301
|
+
if (event.removed) {
|
|
302
|
+
this.recordKeystroke({
|
|
303
|
+
type: 'delete',
|
|
304
|
+
data: {
|
|
305
|
+
direction: 'backward',
|
|
306
|
+
count: event.removed.length,
|
|
307
|
+
removed: event.removed
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
if (event.text) {
|
|
312
|
+
this.recordInsert(event.text);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
getReplayText(keystroke) {
|
|
317
|
+
if (keystroke.type === 'newline')
|
|
318
|
+
return '\n';
|
|
319
|
+
if (keystroke.type === 'tab')
|
|
320
|
+
return '\t';
|
|
321
|
+
return keystroke.data.text ?? '';
|
|
322
|
+
}
|
|
323
|
+
positionAfterText(mirror, position, text) {
|
|
324
|
+
const lines = text.split('\n');
|
|
325
|
+
if (lines.length === 1) {
|
|
326
|
+
return { line: position.line, column: position.column + text.length };
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
line: Math.min(position.line + lines.length - 1, mirror.lineCount - 1),
|
|
330
|
+
column: lines[lines.length - 1].length
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
positionBeforeText(mirror, position, text) {
|
|
334
|
+
const lines = text.split('\n');
|
|
335
|
+
if (lines.length === 1) {
|
|
336
|
+
return {
|
|
337
|
+
line: position.line,
|
|
338
|
+
column: Math.max(0, position.column - text.length)
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
const line = Math.max(0, position.line - lines.length + 1);
|
|
342
|
+
const lineText = mirror.getLine(line)?.text ?? '';
|
|
343
|
+
return {
|
|
344
|
+
line,
|
|
345
|
+
column: Math.max(0, lineText.length - lines[0].length)
|
|
346
|
+
};
|
|
347
|
+
}
|
|
237
348
|
/**
|
|
238
349
|
* Emit event to listeners
|
|
239
350
|
*/
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type ExtractPlan = {
|
|
2
|
+
ok: true;
|
|
3
|
+
functionText: string;
|
|
4
|
+
callText: string;
|
|
5
|
+
params: string[];
|
|
6
|
+
returns: string[];
|
|
7
|
+
insertAfterLine: number;
|
|
8
|
+
};
|
|
9
|
+
export type ExtractRefusal = {
|
|
10
|
+
ok: false;
|
|
11
|
+
reason: string;
|
|
12
|
+
};
|
|
13
|
+
type ExtractInput = {
|
|
14
|
+
lines: readonly {
|
|
15
|
+
text: string;
|
|
16
|
+
}[];
|
|
17
|
+
language: string;
|
|
18
|
+
region: {
|
|
19
|
+
startLine: number;
|
|
20
|
+
endLine: number;
|
|
21
|
+
type?: string;
|
|
22
|
+
};
|
|
23
|
+
blockStart?: number;
|
|
24
|
+
blockEnd?: number;
|
|
25
|
+
};
|
|
26
|
+
export declare function planExtractFunction(input: ExtractInput): ExtractPlan | ExtractRefusal;
|
|
27
|
+
export {};
|