@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.
Files changed (33) hide show
  1. package/dist/components/editor/CognitiveLoadMeter.svelte +27 -0
  2. package/dist/components/editor/ComplexityHeatLayer.svelte +157 -0
  3. package/dist/components/editor/ComplexityHeatLayer.svelte.d.ts +24 -0
  4. package/dist/components/editor/ComplexityLayer.svelte +325 -109
  5. package/dist/components/editor/ComplexityLayer.svelte.d.ts +13 -0
  6. package/dist/components/editor/ConflictZoneLayer.svelte +22 -15
  7. package/dist/components/editor/CustomEditor.svelte +80 -1
  8. package/dist/components/editor/CustomEditor.svelte.d.ts +3 -1
  9. package/dist/components/editor/EchoCursorLayer.svelte +60 -0
  10. package/dist/components/editor/PluginPreviewSandbox.svelte +43 -9
  11. package/dist/components/editor/PluginPreviewSandbox.svelte.d.ts +4 -4
  12. package/dist/components/editor/core/complexity-analyzer.d.ts +31 -0
  13. package/dist/components/editor/core/complexity-analyzer.js +479 -29
  14. package/dist/components/editor/core/conflict-predictor.d.ts +32 -0
  15. package/dist/components/editor/core/conflict-predictor.js +55 -0
  16. package/dist/components/editor/core/crdt-binding.d.ts +4 -0
  17. package/dist/components/editor/core/crdt-binding.js +34 -9
  18. package/dist/components/editor/core/echo-cursor.d.ts +18 -1
  19. package/dist/components/editor/core/echo-cursor.js +117 -6
  20. package/dist/components/editor/core/extract-function.d.ts +27 -0
  21. package/dist/components/editor/core/extract-function.js +865 -0
  22. package/dist/components/editor/core/index.d.ts +1 -0
  23. package/dist/components/editor/core/index.js +1 -0
  24. package/dist/components/editor/core/state.d.ts +38 -5
  25. package/dist/components/editor/core/state.js +175 -98
  26. package/dist/components/editor/core/timeline.js +6 -1
  27. package/dist/components/editor/editor-find.js +15 -3
  28. package/dist/components/editor/theme.d.ts +8 -0
  29. package/dist/components/editor/theme.js +52 -0
  30. package/dist/services/lsp-client.d.ts +3 -0
  31. package/dist/services/lsp-client.js +86 -14
  32. package/dist/styles/theme.css +4 -1
  33. 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(_event) {
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 = content.length;
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.editorState.setContent(content);
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
- if (event.text) {
157
- this.text.insert(0, event.text);
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 { Position } from './state';
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
- // Simulate replay completion after a short animation time
227
- setTimeout(() => {
228
- cursor.isReplaying = false;
229
- cursor.replayIndex++;
230
- this.emit({ type: 'replay-completed', cursorId: cursor.id, keystroke });
231
- }, 50);
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 {};