@nocturnium/svelte-ide 1.0.0-rc.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/LICENSE +21 -0
- package/README.md +251 -0
- package/dist/components/agents/AgentActivityPanel.svelte +565 -0
- package/dist/components/agents/AgentActivityPanel.svelte.d.ts +24 -0
- package/dist/components/agents/AgentAvatar.svelte +417 -0
- package/dist/components/agents/AgentAvatar.svelte.d.ts +23 -0
- package/dist/components/agents/AgentCursor.svelte +224 -0
- package/dist/components/agents/AgentCursor.svelte.d.ts +35 -0
- package/dist/components/agents/AgentPresenceBar.svelte +261 -0
- package/dist/components/agents/AgentPresenceBar.svelte.d.ts +20 -0
- package/dist/components/agents/index.d.ts +4 -0
- package/dist/components/agents/index.js +5 -0
- package/dist/components/ai/AIConversationList.svelte +524 -0
- package/dist/components/ai/AIConversationList.svelte.d.ts +17 -0
- package/dist/components/ai/AIEditPreview.svelte +132 -0
- package/dist/components/ai/AIEditPreview.svelte.d.ts +8 -0
- package/dist/components/ai/AIInlineEdit.svelte +155 -0
- package/dist/components/ai/AIInlineEdit.svelte.d.ts +10 -0
- package/dist/components/ai/AIMessage.svelte +239 -0
- package/dist/components/ai/AIMessage.svelte.d.ts +13 -0
- package/dist/components/ai/AIMessageActions.svelte +176 -0
- package/dist/components/ai/AIMessageActions.svelte.d.ts +12 -0
- package/dist/components/ai/AIMessageContent.svelte +355 -0
- package/dist/components/ai/AIMessageContent.svelte.d.ts +7 -0
- package/dist/components/ai/AIPanel.svelte +561 -0
- package/dist/components/ai/AIPanel.svelte.d.ts +7 -0
- package/dist/components/ai/AISuggestionWidget.svelte +132 -0
- package/dist/components/ai/AISuggestionWidget.svelte.d.ts +10 -0
- package/dist/components/ai/AIToolCallDisplay.svelte +317 -0
- package/dist/components/ai/AIToolCallDisplay.svelte.d.ts +12 -0
- package/dist/components/ai/index.d.ts +9 -0
- package/dist/components/ai/index.js +10 -0
- package/dist/components/core/Avatar.svelte +110 -0
- package/dist/components/core/Avatar.svelte.d.ts +12 -0
- package/dist/components/core/Badge.svelte +98 -0
- package/dist/components/core/Badge.svelte.d.ts +11 -0
- package/dist/components/core/Button.svelte +175 -0
- package/dist/components/core/Button.svelte.d.ts +18 -0
- package/dist/components/core/ConnectionStatus.svelte +294 -0
- package/dist/components/core/ConnectionStatus.svelte.d.ts +20 -0
- package/dist/components/core/ContextMenu.svelte +176 -0
- package/dist/components/core/ContextMenu.svelte.d.ts +19 -0
- package/dist/components/core/ErrorBoundary.svelte +277 -0
- package/dist/components/core/ErrorBoundary.svelte.d.ts +23 -0
- package/dist/components/core/Icon.svelte +107 -0
- package/dist/components/core/Icon.svelte.d.ts +8 -0
- package/dist/components/core/Input.svelte +138 -0
- package/dist/components/core/Input.svelte.d.ts +20 -0
- package/dist/components/core/Kbd.svelte +34 -0
- package/dist/components/core/Kbd.svelte.d.ts +7 -0
- package/dist/components/core/ResizeHandle.svelte +200 -0
- package/dist/components/core/ResizeHandle.svelte.d.ts +23 -0
- package/dist/components/core/Spinner.svelte +35 -0
- package/dist/components/core/Spinner.svelte.d.ts +7 -0
- package/dist/components/core/Textarea.svelte +112 -0
- package/dist/components/core/Textarea.svelte.d.ts +18 -0
- package/dist/components/core/Tooltip.svelte +103 -0
- package/dist/components/core/Tooltip.svelte.d.ts +11 -0
- package/dist/components/core/index.d.ts +13 -0
- package/dist/components/core/index.js +14 -0
- package/dist/components/editor/AIFocusLayer.svelte +430 -0
- package/dist/components/editor/AIFocusLayer.svelte.d.ts +32 -0
- package/dist/components/editor/Breadcrumbs.svelte +435 -0
- package/dist/components/editor/Breadcrumbs.svelte.d.ts +33 -0
- package/dist/components/editor/BreakpointLayer.svelte +642 -0
- package/dist/components/editor/BreakpointLayer.svelte.d.ts +20 -0
- package/dist/components/editor/CognitiveLoadMeter.svelte +324 -0
- package/dist/components/editor/CognitiveLoadMeter.svelte.d.ts +18 -0
- package/dist/components/editor/CollaborativeEditor.svelte +218 -0
- package/dist/components/editor/CollaborativeEditor.svelte.d.ts +32 -0
- package/dist/components/editor/CommandPalette.svelte +434 -0
- package/dist/components/editor/CommandPalette.svelte.d.ts +11 -0
- package/dist/components/editor/ComplexityLayer.svelte +293 -0
- package/dist/components/editor/ComplexityLayer.svelte.d.ts +23 -0
- package/dist/components/editor/ConflictZoneLayer.svelte +441 -0
- package/dist/components/editor/ConflictZoneLayer.svelte.d.ts +25 -0
- package/dist/components/editor/ContextLens.svelte +262 -0
- package/dist/components/editor/ContextLens.svelte.d.ts +27 -0
- package/dist/components/editor/CustomEditor.svelte +1242 -0
- package/dist/components/editor/CustomEditor.svelte.d.ts +37 -0
- package/dist/components/editor/DebugConsole.svelte +646 -0
- package/dist/components/editor/DebugConsole.svelte.d.ts +41 -0
- package/dist/components/editor/EchoCursorLayer.svelte +363 -0
- package/dist/components/editor/EchoCursorLayer.svelte.d.ts +24 -0
- package/dist/components/editor/Editor.svelte +61 -0
- package/dist/components/editor/Editor.svelte.d.ts +22 -0
- package/dist/components/editor/EditorGutter.svelte +119 -0
- package/dist/components/editor/EditorGutter.svelte.d.ts +19 -0
- package/dist/components/editor/EditorLines.svelte +182 -0
- package/dist/components/editor/EditorLines.svelte.d.ts +43 -0
- package/dist/components/editor/EditorPane.svelte +134 -0
- package/dist/components/editor/EditorPane.svelte.d.ts +9 -0
- package/dist/components/editor/EditorSelections.svelte +186 -0
- package/dist/components/editor/EditorSelections.svelte.d.ts +25 -0
- package/dist/components/editor/EditorTabs.svelte +170 -0
- package/dist/components/editor/EditorTabs.svelte.d.ts +12 -0
- package/dist/components/editor/FileExplorer.svelte +811 -0
- package/dist/components/editor/FileExplorer.svelte.d.ts +67 -0
- package/dist/components/editor/FileIcon.svelte +110 -0
- package/dist/components/editor/FileIcon.svelte.d.ts +10 -0
- package/dist/components/editor/FindReplace.svelte +448 -0
- package/dist/components/editor/FindReplace.svelte.d.ts +40 -0
- package/dist/components/editor/GhostBracketLayer.svelte +391 -0
- package/dist/components/editor/GhostBracketLayer.svelte.d.ts +24 -0
- package/dist/components/editor/GitBlameLayer.svelte +436 -0
- package/dist/components/editor/GitBlameLayer.svelte.d.ts +18 -0
- package/dist/components/editor/InlineDiagnosticsLayer.svelte +540 -0
- package/dist/components/editor/InlineDiagnosticsLayer.svelte.d.ts +35 -0
- package/dist/components/editor/InlineDiffLayer.svelte +337 -0
- package/dist/components/editor/InlineDiffLayer.svelte.d.ts +31 -0
- package/dist/components/editor/MinimalEditor.svelte +75 -0
- package/dist/components/editor/MinimalEditor.svelte.d.ts +6 -0
- package/dist/components/editor/MinimalEditor2.svelte +84 -0
- package/dist/components/editor/MinimalEditor2.svelte.d.ts +6 -0
- package/dist/components/editor/Minimap.svelte +327 -0
- package/dist/components/editor/Minimap.svelte.d.ts +34 -0
- package/dist/components/editor/PluginPreviewSandbox.svelte +793 -0
- package/dist/components/editor/PluginPreviewSandbox.svelte.d.ts +49 -0
- package/dist/components/editor/ProblemsPanel.svelte +628 -0
- package/dist/components/editor/ProblemsPanel.svelte.d.ts +25 -0
- package/dist/components/editor/QuickActionsMenu.svelte +403 -0
- package/dist/components/editor/QuickActionsMenu.svelte.d.ts +18 -0
- package/dist/components/editor/SnippetPalette.svelte +530 -0
- package/dist/components/editor/SnippetPalette.svelte.d.ts +16 -0
- package/dist/components/editor/StructureMap.svelte +431 -0
- package/dist/components/editor/StructureMap.svelte.d.ts +37 -0
- package/dist/components/editor/SymbolOutline.svelte +722 -0
- package/dist/components/editor/SymbolOutline.svelte.d.ts +44 -0
- package/dist/components/editor/TimelineScrubber.svelte +470 -0
- package/dist/components/editor/TimelineScrubber.svelte.d.ts +40 -0
- package/dist/components/editor/TokenRenderer.svelte +69 -0
- package/dist/components/editor/TokenRenderer.svelte.d.ts +15 -0
- package/dist/components/editor/constants.d.ts +32 -0
- package/dist/components/editor/constants.js +36 -0
- package/dist/components/editor/core/ai-awareness.d.ts +176 -0
- package/dist/components/editor/core/ai-awareness.js +210 -0
- package/dist/components/editor/core/bracket-healer.d.ts +189 -0
- package/dist/components/editor/core/bracket-healer.js +406 -0
- package/dist/components/editor/core/breakpoints.d.ts +203 -0
- package/dist/components/editor/core/breakpoints.js +414 -0
- package/dist/components/editor/core/commands.d.ts +108 -0
- package/dist/components/editor/core/commands.js +246 -0
- package/dist/components/editor/core/complexity-analyzer.d.ts +123 -0
- package/dist/components/editor/core/complexity-analyzer.js +376 -0
- package/dist/components/editor/core/conflict-predictor.d.ts +135 -0
- package/dist/components/editor/core/conflict-predictor.js +316 -0
- package/dist/components/editor/core/crdt-binding.d.ts +118 -0
- package/dist/components/editor/core/crdt-binding.js +286 -0
- package/dist/components/editor/core/diagnostics.d.ts +210 -0
- package/dist/components/editor/core/diagnostics.js +335 -0
- package/dist/components/editor/core/echo-cursor.d.ts +201 -0
- package/dist/components/editor/core/echo-cursor.js +267 -0
- package/dist/components/editor/core/folding.d.ts +124 -0
- package/dist/components/editor/core/folding.js +672 -0
- package/dist/components/editor/core/ghost-pair.d.ts +122 -0
- package/dist/components/editor/core/ghost-pair.js +221 -0
- package/dist/components/editor/core/git-blame.d.ts +170 -0
- package/dist/components/editor/core/git-blame.js +324 -0
- package/dist/components/editor/core/index.d.ts +26 -0
- package/dist/components/editor/core/index.js +24 -0
- package/dist/components/editor/core/keybindings.d.ts +79 -0
- package/dist/components/editor/core/keybindings.js +357 -0
- package/dist/components/editor/core/multi-cursor.d.ts +196 -0
- package/dist/components/editor/core/multi-cursor.js +521 -0
- package/dist/components/editor/core/navigation.d.ts +107 -0
- package/dist/components/editor/core/navigation.js +408 -0
- package/dist/components/editor/core/quick-actions.d.ts +189 -0
- package/dist/components/editor/core/quick-actions.js +427 -0
- package/dist/components/editor/core/search.d.ts +88 -0
- package/dist/components/editor/core/search.js +192 -0
- package/dist/components/editor/core/semantic-analyzer.d.ts +77 -0
- package/dist/components/editor/core/semantic-analyzer.js +424 -0
- package/dist/components/editor/core/snippet-manager.d.ts +202 -0
- package/dist/components/editor/core/snippet-manager.js +565 -0
- package/dist/components/editor/core/state.d.ts +367 -0
- package/dist/components/editor/core/state.js +900 -0
- package/dist/components/editor/core/timeline.d.ts +204 -0
- package/dist/components/editor/core/timeline.js +349 -0
- package/dist/components/editor/editor-find.d.ts +56 -0
- package/dist/components/editor/editor-find.js +148 -0
- package/dist/components/editor/editor-input.d.ts +77 -0
- package/dist/components/editor/editor-input.js +445 -0
- package/dist/components/editor/editor-multicursor.d.ts +21 -0
- package/dist/components/editor/editor-multicursor.js +196 -0
- package/dist/components/editor/editor-scroll.d.ts +14 -0
- package/dist/components/editor/editor-scroll.js +34 -0
- package/dist/components/editor/index.d.ts +15 -0
- package/dist/components/editor/index.js +21 -0
- package/dist/components/editor/languages.d.ts +62 -0
- package/dist/components/editor/languages.js +285 -0
- package/dist/components/editor/theme.d.ts +88 -0
- package/dist/components/editor/theme.js +139 -0
- package/dist/components/editor/tokenizer/base.d.ts +40 -0
- package/dist/components/editor/tokenizer/base.js +203 -0
- package/dist/components/editor/tokenizer/index.d.ts +56 -0
- package/dist/components/editor/tokenizer/index.js +215 -0
- package/dist/components/editor/tokenizer/languages/css.d.ts +17 -0
- package/dist/components/editor/tokenizer/languages/css.js +194 -0
- package/dist/components/editor/tokenizer/languages/go.d.ts +17 -0
- package/dist/components/editor/tokenizer/languages/go.js +220 -0
- package/dist/components/editor/tokenizer/languages/html.d.ts +24 -0
- package/dist/components/editor/tokenizer/languages/html.js +145 -0
- package/dist/components/editor/tokenizer/languages/javascript.d.ts +56 -0
- package/dist/components/editor/tokenizer/languages/javascript.js +452 -0
- package/dist/components/editor/tokenizer/languages/json.d.ts +12 -0
- package/dist/components/editor/tokenizer/languages/json.js +91 -0
- package/dist/components/editor/tokenizer/languages/markdown.d.ts +16 -0
- package/dist/components/editor/tokenizer/languages/markdown.js +156 -0
- package/dist/components/editor/tokenizer/languages/python.d.ts +20 -0
- package/dist/components/editor/tokenizer/languages/python.js +227 -0
- package/dist/components/editor/tokenizer/languages/svelte.d.ts +40 -0
- package/dist/components/editor/tokenizer/languages/svelte.js +326 -0
- package/dist/components/editor/tokenizer/types.d.ts +86 -0
- package/dist/components/editor/tokenizer/types.js +4 -0
- package/dist/components/layout/IDELayout.svelte +274 -0
- package/dist/components/layout/IDELayout.svelte.d.ts +29 -0
- package/dist/components/layout/StatusBar.svelte +511 -0
- package/dist/components/layout/StatusBar.svelte.d.ts +47 -0
- package/dist/components/layout/index.d.ts +2 -0
- package/dist/components/layout/index.js +3 -0
- package/dist/components/lsp/AutocompleteWidget.svelte +364 -0
- package/dist/components/lsp/AutocompleteWidget.svelte.d.ts +33 -0
- package/dist/components/lsp/DiagnosticMarker.svelte +166 -0
- package/dist/components/lsp/DiagnosticMarker.svelte.d.ts +19 -0
- package/dist/components/lsp/DiagnosticsPanel.svelte +388 -0
- package/dist/components/lsp/DiagnosticsPanel.svelte.d.ts +21 -0
- package/dist/components/lsp/HoverTooltip.svelte +274 -0
- package/dist/components/lsp/HoverTooltip.svelte.d.ts +24 -0
- package/dist/components/lsp/LSPEditor.svelte +486 -0
- package/dist/components/lsp/LSPEditor.svelte.d.ts +39 -0
- package/dist/components/lsp/SignatureHelpWidget.svelte +216 -0
- package/dist/components/lsp/SignatureHelpWidget.svelte.d.ts +22 -0
- package/dist/components/lsp/index.d.ts +6 -0
- package/dist/components/lsp/index.js +7 -0
- package/dist/components/plugins/PluginCard.svelte +153 -0
- package/dist/components/plugins/PluginCard.svelte.d.ts +19 -0
- package/dist/components/plugins/PluginPanel.svelte +280 -0
- package/dist/components/plugins/PluginPanel.svelte.d.ts +8 -0
- package/dist/components/plugins/PluginProposalForm.svelte +250 -0
- package/dist/components/plugins/PluginProposalForm.svelte.d.ts +6 -0
- package/dist/components/plugins/PluginStatusBadge.svelte +14 -0
- package/dist/components/plugins/PluginStatusBadge.svelte.d.ts +8 -0
- package/dist/components/plugins/index.d.ts +4 -0
- package/dist/components/plugins/index.js +5 -0
- package/dist/components/vfs/LockConflictDialog.svelte +705 -0
- package/dist/components/vfs/LockConflictDialog.svelte.d.ts +21 -0
- package/dist/components/vfs/LockIndicator.svelte +194 -0
- package/dist/components/vfs/LockIndicator.svelte.d.ts +29 -0
- package/dist/components/vfs/LockOverlay.svelte +344 -0
- package/dist/components/vfs/LockOverlay.svelte.d.ts +17 -0
- package/dist/components/vfs/VersionConflictDialog.svelte +549 -0
- package/dist/components/vfs/VersionConflictDialog.svelte.d.ts +24 -0
- package/dist/components/vfs/index.d.ts +4 -0
- package/dist/components/vfs/index.js +5 -0
- package/dist/crdt/awareness.d.ts +42 -0
- package/dist/crdt/awareness.js +109 -0
- package/dist/crdt/document.d.ts +101 -0
- package/dist/crdt/document.js +187 -0
- package/dist/crdt/index.d.ts +9 -0
- package/dist/crdt/index.js +8 -0
- package/dist/crdt/provider.d.ts +85 -0
- package/dist/crdt/provider.js +150 -0
- package/dist/crdt/types.d.ts +61 -0
- package/dist/crdt/types.js +4 -0
- package/dist/crdt/undo.d.ts +34 -0
- package/dist/crdt/undo.js +70 -0
- package/dist/index.d.ts +277 -0
- package/dist/index.js +280 -0
- package/dist/plugins/index.d.ts +103 -0
- package/dist/plugins/index.js +153 -0
- package/dist/services/error-handling.d.ts +95 -0
- package/dist/services/error-handling.js +413 -0
- package/dist/services/ide-integration.d.ts +83 -0
- package/dist/services/ide-integration.js +367 -0
- package/dist/services/lsp-client.d.ts +69 -0
- package/dist/services/lsp-client.js +667 -0
- package/dist/services/mock-ai.d.ts +37 -0
- package/dist/services/mock-ai.js +318 -0
- package/dist/services/optimistic.d.ts +141 -0
- package/dist/services/optimistic.js +367 -0
- package/dist/services/vfs-client.d.ts +81 -0
- package/dist/services/vfs-client.js +348 -0
- package/dist/stores/agents.svelte.d.ts +85 -0
- package/dist/stores/agents.svelte.js +459 -0
- package/dist/stores/ai-persistence.svelte.d.ts +76 -0
- package/dist/stores/ai-persistence.svelte.js +334 -0
- package/dist/stores/ai.svelte.d.ts +140 -0
- package/dist/stores/ai.svelte.js +383 -0
- package/dist/stores/collaboration.svelte.d.ts +164 -0
- package/dist/stores/collaboration.svelte.js +334 -0
- package/dist/stores/editor.svelte.d.ts +131 -0
- package/dist/stores/editor.svelte.js +250 -0
- package/dist/stores/index.d.ts +10 -0
- package/dist/stores/index.js +29 -0
- package/dist/stores/layout.svelte.d.ts +171 -0
- package/dist/stores/layout.svelte.js +351 -0
- package/dist/stores/plugin.svelte.d.ts +121 -0
- package/dist/stores/plugin.svelte.js +410 -0
- package/dist/stores/vfs.svelte.d.ts +123 -0
- package/dist/stores/vfs.svelte.js +680 -0
- package/dist/styles/theme.css +623 -0
- package/dist/types/agents.d.ts +127 -0
- package/dist/types/agents.js +5 -0
- package/dist/types/ai.d.ts +137 -0
- package/dist/types/ai.js +4 -0
- package/dist/types/crdt.d.ts +222 -0
- package/dist/types/crdt.js +5 -0
- package/dist/types/editor.d.ts +52 -0
- package/dist/types/editor.js +18 -0
- package/dist/types/events.d.ts +133 -0
- package/dist/types/events.js +4 -0
- package/dist/types/filesystem.d.ts +77 -0
- package/dist/types/filesystem.js +4 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.js +12 -0
- package/dist/types/lsp.d.ts +691 -0
- package/dist/types/lsp.js +108 -0
- package/dist/types/plugin.d.ts +239 -0
- package/dist/types/plugin.js +5 -0
- package/dist/types/vfs.d.ts +191 -0
- package/dist/types/vfs.js +18 -0
- package/dist/utils/format.d.ts +55 -0
- package/dist/utils/format.js +152 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/keybindings.d.ts +33 -0
- package/dist/utils/keybindings.js +171 -0
- package/dist/utils/language.d.ts +27 -0
- package/dist/utils/language.js +222 -0
- package/package.json +178 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collaboration store using Svelte 5 runes
|
|
3
|
+
* Manages CRDT-based real-time collaboration
|
|
4
|
+
*
|
|
5
|
+
* Note: Svelte 5 modules cannot directly export $derived values.
|
|
6
|
+
* We use getter functions to expose reactive derived state.
|
|
7
|
+
*/
|
|
8
|
+
// Reactive state
|
|
9
|
+
let state = $state({
|
|
10
|
+
config: null,
|
|
11
|
+
status: 'disconnected',
|
|
12
|
+
error: null,
|
|
13
|
+
synced: false,
|
|
14
|
+
users: [],
|
|
15
|
+
cursors: new Map(),
|
|
16
|
+
awareness: new Map(),
|
|
17
|
+
aiSessions: [],
|
|
18
|
+
pendingChanges: [],
|
|
19
|
+
snapshots: [],
|
|
20
|
+
localUser: null
|
|
21
|
+
});
|
|
22
|
+
// Event handlers
|
|
23
|
+
const eventHandlers = new Set();
|
|
24
|
+
// Getter functions for derived values (Svelte 5 module-safe)
|
|
25
|
+
export function getConfig() {
|
|
26
|
+
return state.config;
|
|
27
|
+
}
|
|
28
|
+
export function getStatus() {
|
|
29
|
+
return state.status;
|
|
30
|
+
}
|
|
31
|
+
export function getError() {
|
|
32
|
+
return state.error;
|
|
33
|
+
}
|
|
34
|
+
export function getSynced() {
|
|
35
|
+
return state.synced;
|
|
36
|
+
}
|
|
37
|
+
export function getUsers() {
|
|
38
|
+
return state.users;
|
|
39
|
+
}
|
|
40
|
+
export function getCursors() {
|
|
41
|
+
return Array.from(state.cursors.values());
|
|
42
|
+
}
|
|
43
|
+
export function getAwareness() {
|
|
44
|
+
return Array.from(state.awareness.values());
|
|
45
|
+
}
|
|
46
|
+
export function getAISessions() {
|
|
47
|
+
return state.aiSessions;
|
|
48
|
+
}
|
|
49
|
+
export function getActiveAISessions() {
|
|
50
|
+
return state.aiSessions.filter((s) => s.status === 'active');
|
|
51
|
+
}
|
|
52
|
+
export function getPendingChanges() {
|
|
53
|
+
return state.pendingChanges;
|
|
54
|
+
}
|
|
55
|
+
export function getSnapshots() {
|
|
56
|
+
return state.snapshots;
|
|
57
|
+
}
|
|
58
|
+
export function getLocalUser() {
|
|
59
|
+
return state.localUser;
|
|
60
|
+
}
|
|
61
|
+
export function getIsConnected() {
|
|
62
|
+
return state.status === 'connected';
|
|
63
|
+
}
|
|
64
|
+
export function getOtherUsers() {
|
|
65
|
+
return state.users.filter((u) => u.id !== state.localUser?.id);
|
|
66
|
+
}
|
|
67
|
+
// Legacy aliases for backward compatibility
|
|
68
|
+
export const config = { get current() { return getConfig(); } };
|
|
69
|
+
export const status = { get current() { return getStatus(); } };
|
|
70
|
+
export const error = { get current() { return getError(); } };
|
|
71
|
+
export const synced = { get current() { return getSynced(); } };
|
|
72
|
+
export const users = { get current() { return getUsers(); } };
|
|
73
|
+
export const cursors = { get current() { return getCursors(); } };
|
|
74
|
+
export const awareness = { get current() { return getAwareness(); } };
|
|
75
|
+
export const aiSessions = { get current() { return getAISessions(); } };
|
|
76
|
+
export const activeAISessions = { get current() { return getActiveAISessions(); } };
|
|
77
|
+
export const pendingChanges = { get current() { return getPendingChanges(); } };
|
|
78
|
+
export const snapshots = { get current() { return getSnapshots(); } };
|
|
79
|
+
export const localUser = { get current() { return getLocalUser(); } };
|
|
80
|
+
export const isConnected = { get current() { return getIsConnected(); } };
|
|
81
|
+
export const otherUsers = { get current() { return getOtherUsers(); } };
|
|
82
|
+
// Collaboration colors for cursors
|
|
83
|
+
const CURSOR_COLORS = [
|
|
84
|
+
'var(--ide-collab-cursor-1)',
|
|
85
|
+
'var(--ide-collab-cursor-2)',
|
|
86
|
+
'var(--ide-collab-cursor-3)',
|
|
87
|
+
'var(--ide-collab-cursor-4)',
|
|
88
|
+
'var(--ide-collab-cursor-5)'
|
|
89
|
+
];
|
|
90
|
+
/**
|
|
91
|
+
* Initialize collaboration with config
|
|
92
|
+
*/
|
|
93
|
+
export function initialize(collabConfig) {
|
|
94
|
+
state.config = collabConfig;
|
|
95
|
+
state.localUser = collabConfig.user;
|
|
96
|
+
state.status = 'connecting';
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Set connection status
|
|
100
|
+
*/
|
|
101
|
+
export function setStatus(newStatus, errorMsg) {
|
|
102
|
+
state.status = newStatus;
|
|
103
|
+
state.error = errorMsg ?? null;
|
|
104
|
+
if (newStatus === 'connected') {
|
|
105
|
+
emitEvent({ type: 'connected', users: state.users });
|
|
106
|
+
}
|
|
107
|
+
else if (newStatus === 'disconnected') {
|
|
108
|
+
emitEvent({ type: 'disconnected', reason: errorMsg });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Set synced state
|
|
113
|
+
*/
|
|
114
|
+
export function setSynced(syncedState) {
|
|
115
|
+
state.synced = syncedState;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Add a user to the session
|
|
119
|
+
*/
|
|
120
|
+
export function addUser(user) {
|
|
121
|
+
// Assign a color if not provided
|
|
122
|
+
if (!user.color) {
|
|
123
|
+
const colorIndex = state.users.length % CURSOR_COLORS.length;
|
|
124
|
+
user = { ...user, color: CURSOR_COLORS[colorIndex] };
|
|
125
|
+
}
|
|
126
|
+
if (!state.users.some((u) => u.id === user.id)) {
|
|
127
|
+
state.users = [...state.users, user];
|
|
128
|
+
emitEvent({ type: 'user_joined', user });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Remove a user from the session
|
|
133
|
+
*/
|
|
134
|
+
export function removeUser(userId) {
|
|
135
|
+
state.users = state.users.filter((u) => u.id !== userId);
|
|
136
|
+
state.cursors.delete(userId);
|
|
137
|
+
state.awareness.delete(userId);
|
|
138
|
+
emitEvent({ type: 'user_left', userId });
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Update cursor position for a user
|
|
142
|
+
*/
|
|
143
|
+
export function updateCursor(cursor) {
|
|
144
|
+
state.cursors.set(cursor.userId, cursor);
|
|
145
|
+
emitEvent({ type: 'cursor_moved', cursor });
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Update awareness for a user
|
|
149
|
+
*/
|
|
150
|
+
export function updateAwareness(awarenessData) {
|
|
151
|
+
state.awareness.set(awarenessData.userId, awarenessData);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Update local user's cursor
|
|
155
|
+
*/
|
|
156
|
+
export function setLocalCursor(position, selection) {
|
|
157
|
+
if (!state.localUser)
|
|
158
|
+
return;
|
|
159
|
+
const cursor = {
|
|
160
|
+
userId: state.localUser.id,
|
|
161
|
+
user: state.localUser,
|
|
162
|
+
position,
|
|
163
|
+
selection,
|
|
164
|
+
lastActivity: new Date()
|
|
165
|
+
};
|
|
166
|
+
state.cursors.set(state.localUser.id, cursor);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Update local user's awareness
|
|
170
|
+
*/
|
|
171
|
+
export function setLocalAwareness(updates) {
|
|
172
|
+
if (!state.localUser)
|
|
173
|
+
return;
|
|
174
|
+
const current = state.awareness.get(state.localUser.id);
|
|
175
|
+
const awarenessData = {
|
|
176
|
+
...current,
|
|
177
|
+
userId: state.localUser.id,
|
|
178
|
+
user: state.localUser,
|
|
179
|
+
state: 'active',
|
|
180
|
+
...updates
|
|
181
|
+
};
|
|
182
|
+
state.awareness.set(state.localUser.id, awarenessData);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Start an AI collaboration session
|
|
186
|
+
*/
|
|
187
|
+
export function startAISession(documentId, aiUser) {
|
|
188
|
+
const id = crypto.randomUUID();
|
|
189
|
+
const session = {
|
|
190
|
+
id,
|
|
191
|
+
documentId,
|
|
192
|
+
status: 'pending',
|
|
193
|
+
aiUser: { ...aiUser, isAI: true, color: 'var(--ide-collab-ai)' },
|
|
194
|
+
humanUsers: state.users.filter((u) => !u.isAI),
|
|
195
|
+
startedAt: new Date(),
|
|
196
|
+
updatedAt: new Date()
|
|
197
|
+
};
|
|
198
|
+
state.aiSessions = [...state.aiSessions, session];
|
|
199
|
+
// Add AI as a user
|
|
200
|
+
addUser(session.aiUser);
|
|
201
|
+
emitEvent({ type: 'ai_edit_started', session });
|
|
202
|
+
return id;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Update AI session status
|
|
206
|
+
*/
|
|
207
|
+
export function updateAISession(sessionId, updates) {
|
|
208
|
+
state.aiSessions = state.aiSessions.map((s) => s.id === sessionId ? { ...s, ...updates, updatedAt: new Date() } : s);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Set current task for AI session
|
|
212
|
+
*/
|
|
213
|
+
export function setAITask(sessionId, task) {
|
|
214
|
+
updateAISession(sessionId, { currentTask: task, status: 'active' });
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Propose a change from AI
|
|
218
|
+
*/
|
|
219
|
+
export function proposeAIChange(sessionId, change) {
|
|
220
|
+
const id = crypto.randomUUID();
|
|
221
|
+
const proposedChange = {
|
|
222
|
+
...change,
|
|
223
|
+
id,
|
|
224
|
+
sessionId,
|
|
225
|
+
status: 'pending'
|
|
226
|
+
};
|
|
227
|
+
state.pendingChanges = [...state.pendingChanges, proposedChange];
|
|
228
|
+
emitEvent({ type: 'ai_edit_proposed', change: proposedChange });
|
|
229
|
+
return id;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Review an AI proposed change
|
|
233
|
+
*/
|
|
234
|
+
export function reviewAIChange(changeId, approved, reviewerId) {
|
|
235
|
+
state.pendingChanges = state.pendingChanges.map((c) => c.id === changeId
|
|
236
|
+
? {
|
|
237
|
+
...c,
|
|
238
|
+
status: approved ? 'approved' : 'rejected',
|
|
239
|
+
reviewedBy: reviewerId,
|
|
240
|
+
reviewedAt: new Date()
|
|
241
|
+
}
|
|
242
|
+
: c);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Complete an AI session
|
|
246
|
+
*/
|
|
247
|
+
export function completeAISession(sessionId) {
|
|
248
|
+
const session = state.aiSessions.find((s) => s.id === sessionId);
|
|
249
|
+
if (!session)
|
|
250
|
+
return;
|
|
251
|
+
updateAISession(sessionId, { status: 'completed' });
|
|
252
|
+
// Remove AI user
|
|
253
|
+
removeUser(session.aiUser.id);
|
|
254
|
+
// Clear pending changes for this session
|
|
255
|
+
state.pendingChanges = state.pendingChanges.filter((c) => c.sessionId !== sessionId);
|
|
256
|
+
emitEvent({ type: 'ai_edit_completed', sessionId });
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Cancel an AI session
|
|
260
|
+
*/
|
|
261
|
+
export function cancelAISession(sessionId) {
|
|
262
|
+
const session = state.aiSessions.find((s) => s.id === sessionId);
|
|
263
|
+
if (!session)
|
|
264
|
+
return;
|
|
265
|
+
updateAISession(sessionId, { status: 'cancelled' });
|
|
266
|
+
removeUser(session.aiUser.id);
|
|
267
|
+
// Reject all pending changes for this session
|
|
268
|
+
state.pendingChanges = state.pendingChanges.map((c) => c.sessionId === sessionId ? { ...c, status: 'rejected' } : c);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Create a document snapshot
|
|
272
|
+
*/
|
|
273
|
+
export function createSnapshot(documentId, content, reason = 'manual') {
|
|
274
|
+
const id = crypto.randomUUID();
|
|
275
|
+
const snapshot = {
|
|
276
|
+
id,
|
|
277
|
+
documentId,
|
|
278
|
+
content,
|
|
279
|
+
version: state.snapshots.filter((s) => s.documentId === documentId).length + 1,
|
|
280
|
+
createdAt: new Date(),
|
|
281
|
+
createdBy: state.localUser,
|
|
282
|
+
reason
|
|
283
|
+
};
|
|
284
|
+
state.snapshots = [...state.snapshots, snapshot];
|
|
285
|
+
emitEvent({ type: 'snapshot_created', snapshot });
|
|
286
|
+
return id;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Get snapshots for a document
|
|
290
|
+
*/
|
|
291
|
+
export function getDocumentSnapshots(documentId) {
|
|
292
|
+
return state.snapshots.filter((s) => s.documentId === documentId);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Subscribe to collaboration events
|
|
296
|
+
*/
|
|
297
|
+
export function onEvent(handler) {
|
|
298
|
+
eventHandlers.add(handler);
|
|
299
|
+
return () => eventHandlers.delete(handler);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Emit a collaboration event
|
|
303
|
+
*/
|
|
304
|
+
function emitEvent(event) {
|
|
305
|
+
eventHandlers.forEach((handler) => handler(event));
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Disconnect and cleanup
|
|
309
|
+
*/
|
|
310
|
+
export function disconnect() {
|
|
311
|
+
state.status = 'disconnected';
|
|
312
|
+
state.users = [];
|
|
313
|
+
state.cursors.clear();
|
|
314
|
+
state.awareness.clear();
|
|
315
|
+
state.synced = false;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Reset collaboration state
|
|
319
|
+
*/
|
|
320
|
+
export function reset() {
|
|
321
|
+
disconnect();
|
|
322
|
+
state.config = null;
|
|
323
|
+
state.localUser = null;
|
|
324
|
+
state.aiSessions = [];
|
|
325
|
+
state.pendingChanges = [];
|
|
326
|
+
state.snapshots = [];
|
|
327
|
+
state.error = null;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Get user color by index
|
|
331
|
+
*/
|
|
332
|
+
export function getUserColor(index) {
|
|
333
|
+
return CURSOR_COLORS[index % CURSOR_COLORS.length];
|
|
334
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editor store using Svelte 5 runes
|
|
3
|
+
* Manages tabs, active file, and editor preferences
|
|
4
|
+
*
|
|
5
|
+
* Note: Svelte 5 modules cannot directly export $derived values.
|
|
6
|
+
* We use getter functions to expose reactive derived state.
|
|
7
|
+
*/
|
|
8
|
+
import type { EditorTab, EditorPreferences, SplitMode, CursorPosition } from '../types';
|
|
9
|
+
export declare function getTabs(): EditorTab[];
|
|
10
|
+
export declare function getActiveTabId(): string | null;
|
|
11
|
+
export declare function getActiveTab(): EditorTab | null;
|
|
12
|
+
export declare function getSplitMode(): SplitMode;
|
|
13
|
+
export declare function getPreferences(): EditorPreferences;
|
|
14
|
+
export declare function getRecentFiles(): string[];
|
|
15
|
+
export declare function getLoading(): boolean;
|
|
16
|
+
export declare function getError(): string | null;
|
|
17
|
+
export declare function getDirtyTabs(): EditorTab[];
|
|
18
|
+
export declare function getHasDirtyTabs(): boolean;
|
|
19
|
+
export declare const tabs: {
|
|
20
|
+
readonly current: EditorTab[];
|
|
21
|
+
};
|
|
22
|
+
export declare const activeTabId: {
|
|
23
|
+
readonly current: string | null;
|
|
24
|
+
};
|
|
25
|
+
export declare const activeTab: {
|
|
26
|
+
readonly current: EditorTab | null;
|
|
27
|
+
};
|
|
28
|
+
export declare const splitMode: {
|
|
29
|
+
readonly current: SplitMode;
|
|
30
|
+
};
|
|
31
|
+
export declare const preferences: {
|
|
32
|
+
readonly current: EditorPreferences;
|
|
33
|
+
};
|
|
34
|
+
export declare const recentFiles: {
|
|
35
|
+
readonly current: string[];
|
|
36
|
+
};
|
|
37
|
+
export declare const loading: {
|
|
38
|
+
readonly current: boolean;
|
|
39
|
+
};
|
|
40
|
+
export declare const error: {
|
|
41
|
+
readonly current: string | null;
|
|
42
|
+
};
|
|
43
|
+
export declare const dirtyTabs: {
|
|
44
|
+
readonly current: EditorTab[];
|
|
45
|
+
};
|
|
46
|
+
export declare const hasDirtyTabs: {
|
|
47
|
+
readonly current: boolean;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Open a file in a new tab or focus existing tab
|
|
51
|
+
*/
|
|
52
|
+
export declare function openFile(path: string, content: string, options?: {
|
|
53
|
+
language?: string;
|
|
54
|
+
focus?: boolean;
|
|
55
|
+
}): string;
|
|
56
|
+
/**
|
|
57
|
+
* Close a tab by ID
|
|
58
|
+
*/
|
|
59
|
+
export declare function closeTab(tabId: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Force close a tab, ignoring dirty state
|
|
62
|
+
*/
|
|
63
|
+
export declare function forceCloseTab(tabId: string): void;
|
|
64
|
+
/**
|
|
65
|
+
* Close all tabs
|
|
66
|
+
*/
|
|
67
|
+
export declare function closeAllTabs(force?: boolean): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Close other tabs (keep the specified one)
|
|
70
|
+
*/
|
|
71
|
+
export declare function closeOtherTabs(keepTabId: string, force?: boolean): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Set the active tab
|
|
74
|
+
*/
|
|
75
|
+
export declare function setActiveTab(tabId: string): void;
|
|
76
|
+
/**
|
|
77
|
+
* Update content for a tab
|
|
78
|
+
*/
|
|
79
|
+
export declare function updateContent(tabId: string, content: string): void;
|
|
80
|
+
/**
|
|
81
|
+
* Mark a tab as saved
|
|
82
|
+
*/
|
|
83
|
+
export declare function markSaved(tabId: string, newContent?: string): void;
|
|
84
|
+
/**
|
|
85
|
+
* Update cursor position for a tab
|
|
86
|
+
*/
|
|
87
|
+
export declare function updateCursor(tabId: string, position: CursorPosition): void;
|
|
88
|
+
/**
|
|
89
|
+
* Mark a tab as being edited by AI
|
|
90
|
+
*/
|
|
91
|
+
export declare function setAIEditing(tabId: string, editing: boolean): void;
|
|
92
|
+
/**
|
|
93
|
+
* Reorder tabs
|
|
94
|
+
*/
|
|
95
|
+
export declare function reorderTabs(fromIndex: number, toIndex: number): void;
|
|
96
|
+
/**
|
|
97
|
+
* Set split mode
|
|
98
|
+
*/
|
|
99
|
+
export declare function setSplitMode(mode: SplitMode): void;
|
|
100
|
+
/**
|
|
101
|
+
* Update editor preferences
|
|
102
|
+
*/
|
|
103
|
+
export declare function updatePreferences(updates: Partial<EditorPreferences>): void;
|
|
104
|
+
/**
|
|
105
|
+
* Reset preferences to defaults
|
|
106
|
+
*/
|
|
107
|
+
export declare function resetPreferences(): void;
|
|
108
|
+
/**
|
|
109
|
+
* Get a tab by ID
|
|
110
|
+
*/
|
|
111
|
+
export declare function getTab(tabId: string): EditorTab | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* Get a tab by path
|
|
114
|
+
*/
|
|
115
|
+
export declare function getTabByPath(path: string): EditorTab | undefined;
|
|
116
|
+
/**
|
|
117
|
+
* Navigate to next tab
|
|
118
|
+
*/
|
|
119
|
+
export declare function nextTab(): void;
|
|
120
|
+
/**
|
|
121
|
+
* Navigate to previous tab
|
|
122
|
+
*/
|
|
123
|
+
export declare function prevTab(): void;
|
|
124
|
+
/**
|
|
125
|
+
* Set loading state
|
|
126
|
+
*/
|
|
127
|
+
export declare function setLoading(loadingState: boolean): void;
|
|
128
|
+
/**
|
|
129
|
+
* Set error state
|
|
130
|
+
*/
|
|
131
|
+
export declare function setError(errorState: string | null): void;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editor store using Svelte 5 runes
|
|
3
|
+
* Manages tabs, active file, and editor preferences
|
|
4
|
+
*
|
|
5
|
+
* Note: Svelte 5 modules cannot directly export $derived values.
|
|
6
|
+
* We use getter functions to expose reactive derived state.
|
|
7
|
+
*/
|
|
8
|
+
import { DEFAULT_EDITOR_PREFERENCES } from '../types';
|
|
9
|
+
import { detectLanguage } from '../utils/language';
|
|
10
|
+
// Reactive state using $state rune
|
|
11
|
+
let state = $state({
|
|
12
|
+
tabs: [],
|
|
13
|
+
activeTabId: null,
|
|
14
|
+
splitMode: 'none',
|
|
15
|
+
preferences: { ...DEFAULT_EDITOR_PREFERENCES },
|
|
16
|
+
recentFiles: [],
|
|
17
|
+
loading: false,
|
|
18
|
+
error: null
|
|
19
|
+
});
|
|
20
|
+
// Getter functions for derived values (Svelte 5 module-safe)
|
|
21
|
+
export function getTabs() {
|
|
22
|
+
return state.tabs;
|
|
23
|
+
}
|
|
24
|
+
export function getActiveTabId() {
|
|
25
|
+
return state.activeTabId;
|
|
26
|
+
}
|
|
27
|
+
export function getActiveTab() {
|
|
28
|
+
return state.tabs.find((t) => t.id === state.activeTabId) ?? null;
|
|
29
|
+
}
|
|
30
|
+
export function getSplitMode() {
|
|
31
|
+
return state.splitMode;
|
|
32
|
+
}
|
|
33
|
+
export function getPreferences() {
|
|
34
|
+
return state.preferences;
|
|
35
|
+
}
|
|
36
|
+
export function getRecentFiles() {
|
|
37
|
+
return state.recentFiles;
|
|
38
|
+
}
|
|
39
|
+
export function getLoading() {
|
|
40
|
+
return state.loading;
|
|
41
|
+
}
|
|
42
|
+
export function getError() {
|
|
43
|
+
return state.error;
|
|
44
|
+
}
|
|
45
|
+
export function getDirtyTabs() {
|
|
46
|
+
return state.tabs.filter((t) => t.isDirty);
|
|
47
|
+
}
|
|
48
|
+
export function getHasDirtyTabs() {
|
|
49
|
+
return state.tabs.some((t) => t.isDirty);
|
|
50
|
+
}
|
|
51
|
+
// Legacy aliases for backward compatibility
|
|
52
|
+
export const tabs = { get current() { return getTabs(); } };
|
|
53
|
+
export const activeTabId = { get current() { return getActiveTabId(); } };
|
|
54
|
+
export const activeTab = { get current() { return getActiveTab(); } };
|
|
55
|
+
export const splitMode = { get current() { return getSplitMode(); } };
|
|
56
|
+
export const preferences = { get current() { return getPreferences(); } };
|
|
57
|
+
export const recentFiles = { get current() { return getRecentFiles(); } };
|
|
58
|
+
export const loading = { get current() { return getLoading(); } };
|
|
59
|
+
export const error = { get current() { return getError(); } };
|
|
60
|
+
export const dirtyTabs = { get current() { return getDirtyTabs(); } };
|
|
61
|
+
export const hasDirtyTabs = { get current() { return getHasDirtyTabs(); } };
|
|
62
|
+
/**
|
|
63
|
+
* Open a file in a new tab or focus existing tab
|
|
64
|
+
*/
|
|
65
|
+
export function openFile(path, content, options) {
|
|
66
|
+
const existing = state.tabs.find((t) => t.path === path);
|
|
67
|
+
if (existing) {
|
|
68
|
+
if (options?.focus !== false) {
|
|
69
|
+
state.activeTabId = existing.id;
|
|
70
|
+
}
|
|
71
|
+
return existing.id;
|
|
72
|
+
}
|
|
73
|
+
const name = path.split('/').pop() ?? path;
|
|
74
|
+
const language = options?.language ?? detectLanguage(name);
|
|
75
|
+
const id = crypto.randomUUID();
|
|
76
|
+
const tab = {
|
|
77
|
+
id,
|
|
78
|
+
path,
|
|
79
|
+
name,
|
|
80
|
+
content,
|
|
81
|
+
language,
|
|
82
|
+
isDirty: false,
|
|
83
|
+
cursorPosition: { line: 1, column: 1 }
|
|
84
|
+
};
|
|
85
|
+
state.tabs = [...state.tabs, tab];
|
|
86
|
+
if (options?.focus !== false) {
|
|
87
|
+
state.activeTabId = id;
|
|
88
|
+
}
|
|
89
|
+
// Update recent files
|
|
90
|
+
state.recentFiles = [path, ...state.recentFiles.filter((f) => f !== path)].slice(0, 20);
|
|
91
|
+
return id;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Close a tab by ID
|
|
95
|
+
*/
|
|
96
|
+
export function closeTab(tabId) {
|
|
97
|
+
const index = state.tabs.findIndex((t) => t.id === tabId);
|
|
98
|
+
if (index === -1)
|
|
99
|
+
return false;
|
|
100
|
+
const tab = state.tabs[index];
|
|
101
|
+
if (tab.isDirty) {
|
|
102
|
+
// Could prompt for save here - returning false indicates unsaved changes
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
state.tabs = state.tabs.filter((t) => t.id !== tabId);
|
|
106
|
+
// Update active tab if we closed the active one
|
|
107
|
+
if (state.activeTabId === tabId) {
|
|
108
|
+
const newIndex = Math.min(index, state.tabs.length - 1);
|
|
109
|
+
state.activeTabId = state.tabs[newIndex]?.id ?? null;
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Force close a tab, ignoring dirty state
|
|
115
|
+
*/
|
|
116
|
+
export function forceCloseTab(tabId) {
|
|
117
|
+
const index = state.tabs.findIndex((t) => t.id === tabId);
|
|
118
|
+
if (index === -1)
|
|
119
|
+
return;
|
|
120
|
+
state.tabs = state.tabs.filter((t) => t.id !== tabId);
|
|
121
|
+
if (state.activeTabId === tabId) {
|
|
122
|
+
const newIndex = Math.min(index, state.tabs.length - 1);
|
|
123
|
+
state.activeTabId = state.tabs[newIndex]?.id ?? null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Close all tabs
|
|
128
|
+
*/
|
|
129
|
+
export function closeAllTabs(force = false) {
|
|
130
|
+
if (!force && state.tabs.some((t) => t.isDirty)) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
state.tabs = [];
|
|
134
|
+
state.activeTabId = null;
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Close other tabs (keep the specified one)
|
|
139
|
+
*/
|
|
140
|
+
export function closeOtherTabs(keepTabId, force = false) {
|
|
141
|
+
if (!force && state.tabs.some((t) => t.id !== keepTabId && t.isDirty)) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
state.tabs = state.tabs.filter((t) => t.id === keepTabId);
|
|
145
|
+
state.activeTabId = keepTabId;
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Set the active tab
|
|
150
|
+
*/
|
|
151
|
+
export function setActiveTab(tabId) {
|
|
152
|
+
if (state.tabs.some((t) => t.id === tabId)) {
|
|
153
|
+
state.activeTabId = tabId;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Update content for a tab
|
|
158
|
+
*/
|
|
159
|
+
export function updateContent(tabId, content) {
|
|
160
|
+
state.tabs = state.tabs.map((t) => t.id === tabId ? { ...t, content, isDirty: t.content !== content || t.isDirty } : t);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Mark a tab as saved
|
|
164
|
+
*/
|
|
165
|
+
export function markSaved(tabId, newContent) {
|
|
166
|
+
state.tabs = state.tabs.map((t) => t.id === tabId ? { ...t, isDirty: false, content: newContent ?? t.content } : t);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Update cursor position for a tab
|
|
170
|
+
*/
|
|
171
|
+
export function updateCursor(tabId, position) {
|
|
172
|
+
state.tabs = state.tabs.map((t) => (t.id === tabId ? { ...t, cursorPosition: position } : t));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Mark a tab as being edited by AI
|
|
176
|
+
*/
|
|
177
|
+
export function setAIEditing(tabId, editing) {
|
|
178
|
+
state.tabs = state.tabs.map((t) => (t.id === tabId ? { ...t, aiEditing: editing } : t));
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Reorder tabs
|
|
182
|
+
*/
|
|
183
|
+
export function reorderTabs(fromIndex, toIndex) {
|
|
184
|
+
const newTabs = [...state.tabs];
|
|
185
|
+
const [removed] = newTabs.splice(fromIndex, 1);
|
|
186
|
+
newTabs.splice(toIndex, 0, removed);
|
|
187
|
+
state.tabs = newTabs;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Set split mode
|
|
191
|
+
*/
|
|
192
|
+
export function setSplitMode(mode) {
|
|
193
|
+
state.splitMode = mode;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Update editor preferences
|
|
197
|
+
*/
|
|
198
|
+
export function updatePreferences(updates) {
|
|
199
|
+
state.preferences = { ...state.preferences, ...updates };
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Reset preferences to defaults
|
|
203
|
+
*/
|
|
204
|
+
export function resetPreferences() {
|
|
205
|
+
state.preferences = { ...DEFAULT_EDITOR_PREFERENCES };
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get a tab by ID
|
|
209
|
+
*/
|
|
210
|
+
export function getTab(tabId) {
|
|
211
|
+
return state.tabs.find((t) => t.id === tabId);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get a tab by path
|
|
215
|
+
*/
|
|
216
|
+
export function getTabByPath(path) {
|
|
217
|
+
return state.tabs.find((t) => t.path === path);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Navigate to next tab
|
|
221
|
+
*/
|
|
222
|
+
export function nextTab() {
|
|
223
|
+
if (state.tabs.length === 0)
|
|
224
|
+
return;
|
|
225
|
+
const currentIndex = state.tabs.findIndex((t) => t.id === state.activeTabId);
|
|
226
|
+
const nextIndex = (currentIndex + 1) % state.tabs.length;
|
|
227
|
+
state.activeTabId = state.tabs[nextIndex].id;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Navigate to previous tab
|
|
231
|
+
*/
|
|
232
|
+
export function prevTab() {
|
|
233
|
+
if (state.tabs.length === 0)
|
|
234
|
+
return;
|
|
235
|
+
const currentIndex = state.tabs.findIndex((t) => t.id === state.activeTabId);
|
|
236
|
+
const prevIndex = (currentIndex - 1 + state.tabs.length) % state.tabs.length;
|
|
237
|
+
state.activeTabId = state.tabs[prevIndex].id;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Set loading state
|
|
241
|
+
*/
|
|
242
|
+
export function setLoading(loadingState) {
|
|
243
|
+
state.loading = loadingState;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Set error state
|
|
247
|
+
*/
|
|
248
|
+
export function setError(errorState) {
|
|
249
|
+
state.error = errorState;
|
|
250
|
+
}
|