@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,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editor find/replace logic extracted from CustomEditor.svelte
|
|
3
|
+
*
|
|
4
|
+
* Factory function that creates all find/replace state and operations,
|
|
5
|
+
* accepting callbacks for things it cannot own (editor state access, DOM operations).
|
|
6
|
+
*/
|
|
7
|
+
import { findAllMatches, findMatchIndexAtOrAfter, getNextMatchIndex, getPrevMatchIndex, replaceMatch, replaceAllMatches } from './core';
|
|
8
|
+
export function createEditorFind(deps) {
|
|
9
|
+
let query = '';
|
|
10
|
+
let replace = '';
|
|
11
|
+
let caseSensitive = false;
|
|
12
|
+
let useRegex = false;
|
|
13
|
+
let matches = [];
|
|
14
|
+
let currentIndex = -1;
|
|
15
|
+
function updateMatches() {
|
|
16
|
+
const lines = deps.getLines();
|
|
17
|
+
if (!lines || !query) {
|
|
18
|
+
matches = [];
|
|
19
|
+
currentIndex = -1;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
matches = findAllMatches(lines, query, {
|
|
23
|
+
caseSensitive,
|
|
24
|
+
useRegex
|
|
25
|
+
});
|
|
26
|
+
if (matches.length > 0) {
|
|
27
|
+
currentIndex = findMatchIndexAtOrAfter(matches, deps.getSelection().head);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
currentIndex = -1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function setQuery(q) {
|
|
34
|
+
query = q;
|
|
35
|
+
updateMatches();
|
|
36
|
+
}
|
|
37
|
+
function setReplaceText(text) {
|
|
38
|
+
replace = text;
|
|
39
|
+
}
|
|
40
|
+
function setCaseSensitive(value) {
|
|
41
|
+
caseSensitive = value;
|
|
42
|
+
updateMatches();
|
|
43
|
+
}
|
|
44
|
+
function setUseRegex(value) {
|
|
45
|
+
useRegex = value;
|
|
46
|
+
updateMatches();
|
|
47
|
+
}
|
|
48
|
+
function goToMatch(index) {
|
|
49
|
+
if (index < 0 || index >= matches.length)
|
|
50
|
+
return;
|
|
51
|
+
const match = matches[index];
|
|
52
|
+
deps.setSelection(match.start, match.end);
|
|
53
|
+
deps.scrollCursorIntoView();
|
|
54
|
+
deps.focusEditor();
|
|
55
|
+
}
|
|
56
|
+
function findNext() {
|
|
57
|
+
if (matches.length === 0)
|
|
58
|
+
return;
|
|
59
|
+
currentIndex = getNextMatchIndex(matches, currentIndex, true);
|
|
60
|
+
goToMatch(currentIndex);
|
|
61
|
+
}
|
|
62
|
+
function findPrev() {
|
|
63
|
+
if (matches.length === 0)
|
|
64
|
+
return;
|
|
65
|
+
currentIndex = getPrevMatchIndex(matches, currentIndex, true);
|
|
66
|
+
goToMatch(currentIndex);
|
|
67
|
+
}
|
|
68
|
+
function doReplace() {
|
|
69
|
+
if (deps.isReadonly() || matches.length === 0 || currentIndex < 0)
|
|
70
|
+
return;
|
|
71
|
+
const match = matches[currentIndex];
|
|
72
|
+
const lines = deps.getLines();
|
|
73
|
+
const result = replaceMatch(lines, match, replace);
|
|
74
|
+
deps.setContent(result.newLines.join('\n'));
|
|
75
|
+
deps.setCursor(result.cursorPosition);
|
|
76
|
+
updateMatches();
|
|
77
|
+
if (matches.length > 0) {
|
|
78
|
+
currentIndex = Math.min(currentIndex, matches.length - 1);
|
|
79
|
+
goToMatch(currentIndex);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function doReplaceAll() {
|
|
83
|
+
if (deps.isReadonly() || matches.length === 0)
|
|
84
|
+
return;
|
|
85
|
+
const lines = deps.getLines();
|
|
86
|
+
const newLines = replaceAllMatches(lines, matches, replace);
|
|
87
|
+
deps.setContent(newLines.join('\n'));
|
|
88
|
+
updateMatches();
|
|
89
|
+
}
|
|
90
|
+
function computeMatchRects(viewport, measurements) {
|
|
91
|
+
if (matches.length === 0)
|
|
92
|
+
return [];
|
|
93
|
+
const { scrollTop, viewportHeight } = viewport;
|
|
94
|
+
const { lineHeight, charWidth, gutterWidth, contentPadding } = measurements;
|
|
95
|
+
const firstVisibleLine = Math.max(0, Math.floor(scrollTop / lineHeight) - 1);
|
|
96
|
+
const lineCount = deps.getLineCount();
|
|
97
|
+
const lastVisibleLine = Math.min(lineCount - 1, Math.ceil((scrollTop + viewportHeight) / lineHeight) + 1);
|
|
98
|
+
const rects = [];
|
|
99
|
+
for (let i = 0; i < matches.length; i++) {
|
|
100
|
+
const match = matches[i];
|
|
101
|
+
if (match.line < firstVisibleLine || match.line > lastVisibleLine)
|
|
102
|
+
continue;
|
|
103
|
+
const width = (match.endColumn - match.startColumn) * charWidth;
|
|
104
|
+
if (width <= 0)
|
|
105
|
+
continue;
|
|
106
|
+
rects.push({
|
|
107
|
+
top: match.line * lineHeight,
|
|
108
|
+
left: gutterWidth + contentPadding + match.startColumn * charWidth,
|
|
109
|
+
width,
|
|
110
|
+
height: lineHeight,
|
|
111
|
+
isCurrent: i === currentIndex
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return rects;
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
// Getters for reactive reads
|
|
118
|
+
get query() {
|
|
119
|
+
return query;
|
|
120
|
+
},
|
|
121
|
+
get replaceText() {
|
|
122
|
+
return replace;
|
|
123
|
+
},
|
|
124
|
+
get caseSensitive() {
|
|
125
|
+
return caseSensitive;
|
|
126
|
+
},
|
|
127
|
+
get useRegex() {
|
|
128
|
+
return useRegex;
|
|
129
|
+
},
|
|
130
|
+
get matches() {
|
|
131
|
+
return matches;
|
|
132
|
+
},
|
|
133
|
+
get currentIndex() {
|
|
134
|
+
return currentIndex;
|
|
135
|
+
},
|
|
136
|
+
// Setters / actions
|
|
137
|
+
setQuery,
|
|
138
|
+
setReplaceText,
|
|
139
|
+
setCaseSensitive,
|
|
140
|
+
setUseRegex,
|
|
141
|
+
updateMatches,
|
|
142
|
+
findNext,
|
|
143
|
+
findPrev,
|
|
144
|
+
replace: doReplace,
|
|
145
|
+
replaceAll: doReplaceAll,
|
|
146
|
+
computeMatchRects
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editor input handling — keyboard, mouse, clipboard, IME, focus, and measurement.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from CustomEditor.svelte to keep the component focused on rendering.
|
|
5
|
+
* All handlers are created via a factory that receives dependency callbacks so the
|
|
6
|
+
* module remains framework-agnostic and testable.
|
|
7
|
+
*/
|
|
8
|
+
import type { EditorState, Selection, Cursor } from './core';
|
|
9
|
+
import type { createNavigation, createKeyboardHandler, FoldManager } from './core';
|
|
10
|
+
export interface EditorInputDeps {
|
|
11
|
+
getEditorState: () => EditorState;
|
|
12
|
+
getNavigation: () => ReturnType<typeof createNavigation>;
|
|
13
|
+
getKeyboardHandler: () => ReturnType<typeof createKeyboardHandler>;
|
|
14
|
+
getFoldManager: () => FoldManager;
|
|
15
|
+
getEditorContent: () => HTMLDivElement | null;
|
|
16
|
+
getHiddenInput: () => HTMLTextAreaElement | null;
|
|
17
|
+
getMeasureSpan: () => HTMLSpanElement | null;
|
|
18
|
+
isReadonly: () => boolean;
|
|
19
|
+
isMultiCursor: () => boolean;
|
|
20
|
+
isFolding: () => boolean;
|
|
21
|
+
hasMultipleCursors: () => boolean;
|
|
22
|
+
getSelection: () => Selection;
|
|
23
|
+
getCursors: () => readonly Cursor[];
|
|
24
|
+
getScrollPosition: () => {
|
|
25
|
+
scrollTop: number;
|
|
26
|
+
scrollLeft: number;
|
|
27
|
+
};
|
|
28
|
+
getMeasurements: () => {
|
|
29
|
+
charWidth: number;
|
|
30
|
+
lineHeight: number;
|
|
31
|
+
gutterWidth: number;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Editor tab size (columns per tab stop). Optional for backwards
|
|
35
|
+
* compatibility; when omitted, DEFAULT_TAB_SIZE is used. Needed so that
|
|
36
|
+
* mouse hit-testing expands tabs to the correct visual width.
|
|
37
|
+
*/
|
|
38
|
+
getTabSize?: () => number;
|
|
39
|
+
onOpenFind: (withReplace: boolean) => void;
|
|
40
|
+
onCloseFind: () => void;
|
|
41
|
+
onFindNext: () => void;
|
|
42
|
+
onFindPrev: () => void;
|
|
43
|
+
onFoldCurrent: () => void;
|
|
44
|
+
onUnfoldCurrent: () => void;
|
|
45
|
+
onFoldAll: () => void;
|
|
46
|
+
onUnfoldAll: () => void;
|
|
47
|
+
onSelectNextOccurrence: () => void;
|
|
48
|
+
onSelectAllOccurrences: () => void;
|
|
49
|
+
onShowCommandPalette: () => void;
|
|
50
|
+
onSave: (() => void) | undefined;
|
|
51
|
+
announce: (message: string) => void;
|
|
52
|
+
resetCursorBlink: () => void;
|
|
53
|
+
stopCursorBlink: () => void;
|
|
54
|
+
setCharWidth: (w: number) => void;
|
|
55
|
+
setLineHeight: (h: number) => void;
|
|
56
|
+
setScrollTop: (v: number) => void;
|
|
57
|
+
setScrollLeft: (v: number) => void;
|
|
58
|
+
setCursorVisible: (v: boolean) => void;
|
|
59
|
+
isShowFindReplace: () => boolean;
|
|
60
|
+
getSearchQuery: () => string;
|
|
61
|
+
getSearchMatchCount: () => number;
|
|
62
|
+
}
|
|
63
|
+
export declare function createEditorInput(deps: EditorInputDeps): {
|
|
64
|
+
handleMouseDown: (e: MouseEvent) => void;
|
|
65
|
+
handleKeyDown: (e: KeyboardEvent) => void;
|
|
66
|
+
handleInput: (e: Event) => void;
|
|
67
|
+
handlePaste: (e: ClipboardEvent) => void;
|
|
68
|
+
handleCopy: (e: ClipboardEvent) => void;
|
|
69
|
+
handleCut: (e: ClipboardEvent) => void;
|
|
70
|
+
handleScroll: (e: Event) => void;
|
|
71
|
+
handleFocus: () => void;
|
|
72
|
+
handleBlur: () => void;
|
|
73
|
+
handleCompositionStart: () => void;
|
|
74
|
+
handleCompositionEnd: (e: CompositionEvent) => void;
|
|
75
|
+
measureCharacter: () => void;
|
|
76
|
+
cleanupGlobalListeners: () => void;
|
|
77
|
+
};
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editor input handling — keyboard, mouse, clipboard, IME, focus, and measurement.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from CustomEditor.svelte to keep the component focused on rendering.
|
|
5
|
+
* All handlers are created via a factory that receives dependency callbacks so the
|
|
6
|
+
* module remains framework-agnostic and testable.
|
|
7
|
+
*/
|
|
8
|
+
import { selectNextOccurrence, selectAllOccurrences } from './editor-multicursor';
|
|
9
|
+
import { CONTENT_PADDING, DEFAULT_FONT_SIZE, LINE_HEIGHT_MULTIPLIER } from './constants';
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Factory
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
/** Fallback tab size used when deps.getTabSize is not provided. */
|
|
14
|
+
const DEFAULT_TAB_SIZE = 4;
|
|
15
|
+
export function createEditorInput(deps) {
|
|
16
|
+
// ---- internal mutable state ----
|
|
17
|
+
let isMouseDown = false;
|
|
18
|
+
let isDragging = false;
|
|
19
|
+
let isComposing = false;
|
|
20
|
+
let boundMouseMove = null;
|
|
21
|
+
let boundMouseUp = null;
|
|
22
|
+
/**
|
|
23
|
+
* Map a horizontal pixel offset (relative to the start of the text content,
|
|
24
|
+
* scroll already applied) to a character column on a line, expanding tabs to
|
|
25
|
+
* the next tab stop. A tab occupies `tabSize - (visualCol % tabSize)` columns
|
|
26
|
+
* worth of width. We pick the column whose character boundary is nearest the
|
|
27
|
+
* target x by tracking the visual width consumed character-by-character.
|
|
28
|
+
*
|
|
29
|
+
* Perfect proportional measurement (emoji, CJK wide glyphs) is not attempted;
|
|
30
|
+
* tabs are the dominant source of mis-clicks and are handled exactly.
|
|
31
|
+
*/
|
|
32
|
+
function columnFromX(lineText, x, charWidth, tabSize) {
|
|
33
|
+
if (charWidth <= 0)
|
|
34
|
+
return 0;
|
|
35
|
+
if (x <= 0)
|
|
36
|
+
return 0;
|
|
37
|
+
let visualCol = 0; // visual column in "cells" (each cell == charWidth px)
|
|
38
|
+
for (let i = 0; i < lineText.length; i++) {
|
|
39
|
+
const cellsForChar = lineText[i] === '\t'
|
|
40
|
+
? tabSize - (visualCol % tabSize)
|
|
41
|
+
: 1;
|
|
42
|
+
const startPx = visualCol * charWidth;
|
|
43
|
+
const widthPx = cellsForChar * charWidth;
|
|
44
|
+
// If x falls within the first half of this character's width, the
|
|
45
|
+
// click belongs to column i; otherwise it moves past it.
|
|
46
|
+
if (x < startPx + widthPx / 2) {
|
|
47
|
+
return i;
|
|
48
|
+
}
|
|
49
|
+
visualCol += cellsForChar;
|
|
50
|
+
}
|
|
51
|
+
return lineText.length;
|
|
52
|
+
}
|
|
53
|
+
// ------------------------------------------------------------------
|
|
54
|
+
// Measurement
|
|
55
|
+
// ------------------------------------------------------------------
|
|
56
|
+
function measureCharacter() {
|
|
57
|
+
const measureSpan = deps.getMeasureSpan();
|
|
58
|
+
if (!measureSpan)
|
|
59
|
+
return;
|
|
60
|
+
measureSpan.textContent = 'M';
|
|
61
|
+
const rect = measureSpan.getBoundingClientRect();
|
|
62
|
+
deps.setCharWidth(rect.width || 8);
|
|
63
|
+
// Use computed line-height from CSS, not character bounding box height
|
|
64
|
+
const computedStyle = window.getComputedStyle(measureSpan);
|
|
65
|
+
const computedLineHeight = parseFloat(computedStyle.lineHeight);
|
|
66
|
+
if (isNaN(computedLineHeight)) {
|
|
67
|
+
const fontSize = parseFloat(computedStyle.fontSize) || DEFAULT_FONT_SIZE;
|
|
68
|
+
deps.setLineHeight(Math.ceil(fontSize * LINE_HEIGHT_MULTIPLIER));
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
deps.setLineHeight(Math.ceil(computedLineHeight));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// ------------------------------------------------------------------
|
|
75
|
+
// Mouse helpers
|
|
76
|
+
// ------------------------------------------------------------------
|
|
77
|
+
function getPositionFromMouse(e) {
|
|
78
|
+
const editorContent = deps.getEditorContent();
|
|
79
|
+
if (!editorContent)
|
|
80
|
+
return { line: 0, column: 0 };
|
|
81
|
+
const { charWidth, lineHeight, gutterWidth } = deps.getMeasurements();
|
|
82
|
+
const { scrollTop, scrollLeft } = deps.getScrollPosition();
|
|
83
|
+
const rect = editorContent.getBoundingClientRect();
|
|
84
|
+
const x = e.clientX - rect.left - gutterWidth - CONTENT_PADDING;
|
|
85
|
+
const y = e.clientY - rect.top;
|
|
86
|
+
const editorState = deps.getEditorState();
|
|
87
|
+
const line = Math.max(0, Math.min(Math.floor((y + scrollTop) / lineHeight), editorState.lineCount - 1));
|
|
88
|
+
const lineContent = editorState.getLine(line);
|
|
89
|
+
const lineText = lineContent?.text ?? '';
|
|
90
|
+
const tabSize = deps.getTabSize?.() ?? DEFAULT_TAB_SIZE;
|
|
91
|
+
// Map the pixel offset to a column, expanding tabs to tab stops so that
|
|
92
|
+
// clicks on lines containing tabs land on the intended character.
|
|
93
|
+
const column = columnFromX(lineText, x + scrollLeft, charWidth, tabSize);
|
|
94
|
+
return { line, column };
|
|
95
|
+
}
|
|
96
|
+
// ------------------------------------------------------------------
|
|
97
|
+
// Mouse handlers
|
|
98
|
+
// ------------------------------------------------------------------
|
|
99
|
+
function handleMouseDown(e) {
|
|
100
|
+
if (e.button !== 0)
|
|
101
|
+
return;
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
deps.getHiddenInput()?.focus();
|
|
104
|
+
isMouseDown = true;
|
|
105
|
+
isDragging = false;
|
|
106
|
+
// Attach global listeners only when drag starts (per-instance scoping)
|
|
107
|
+
if (!boundMouseMove) {
|
|
108
|
+
boundMouseMove = (ev) => handleMouseMove(ev);
|
|
109
|
+
boundMouseUp = () => handleMouseUp();
|
|
110
|
+
window.addEventListener('mousemove', boundMouseMove);
|
|
111
|
+
window.addEventListener('mouseup', boundMouseUp);
|
|
112
|
+
}
|
|
113
|
+
const pos = getPositionFromMouse(e);
|
|
114
|
+
const editorState = deps.getEditorState();
|
|
115
|
+
const navigation = deps.getNavigation();
|
|
116
|
+
if (e.shiftKey) {
|
|
117
|
+
editorState.extendSelection(pos);
|
|
118
|
+
}
|
|
119
|
+
else if (e.altKey && deps.isMultiCursor() && !e.shiftKey) {
|
|
120
|
+
// Alt+Click - add a new cursor
|
|
121
|
+
editorState.addCursor(pos);
|
|
122
|
+
}
|
|
123
|
+
else if (e.detail === 2) {
|
|
124
|
+
// Double click - select word
|
|
125
|
+
editorState.setCursor(pos);
|
|
126
|
+
navigation.selectWord();
|
|
127
|
+
}
|
|
128
|
+
else if (e.detail === 3) {
|
|
129
|
+
// Triple click - select line
|
|
130
|
+
editorState.setCursor(pos);
|
|
131
|
+
navigation.selectLine();
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
editorState.setCursor(pos);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function handleMouseMove(e) {
|
|
138
|
+
if (!isMouseDown)
|
|
139
|
+
return;
|
|
140
|
+
isDragging = true;
|
|
141
|
+
const pos = getPositionFromMouse(e);
|
|
142
|
+
deps.getEditorState().extendSelection(pos);
|
|
143
|
+
}
|
|
144
|
+
function handleMouseUp() {
|
|
145
|
+
isMouseDown = false;
|
|
146
|
+
isDragging = false;
|
|
147
|
+
// Remove global listeners when drag ends
|
|
148
|
+
if (boundMouseMove) {
|
|
149
|
+
window.removeEventListener('mousemove', boundMouseMove);
|
|
150
|
+
boundMouseMove = null;
|
|
151
|
+
}
|
|
152
|
+
if (boundMouseUp) {
|
|
153
|
+
window.removeEventListener('mouseup', boundMouseUp);
|
|
154
|
+
boundMouseUp = null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function cleanupGlobalListeners() {
|
|
158
|
+
if (boundMouseMove) {
|
|
159
|
+
window.removeEventListener('mousemove', boundMouseMove);
|
|
160
|
+
boundMouseMove = null;
|
|
161
|
+
}
|
|
162
|
+
if (boundMouseUp) {
|
|
163
|
+
window.removeEventListener('mouseup', boundMouseUp);
|
|
164
|
+
boundMouseUp = null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// ------------------------------------------------------------------
|
|
168
|
+
// Keyboard
|
|
169
|
+
// ------------------------------------------------------------------
|
|
170
|
+
function handleKeyDown(e) {
|
|
171
|
+
// Don't process keyboard shortcuts during IME composition
|
|
172
|
+
if (isComposing)
|
|
173
|
+
return;
|
|
174
|
+
const isMod = e.ctrlKey || e.metaKey;
|
|
175
|
+
const editorState = deps.getEditorState();
|
|
176
|
+
// Find/Replace shortcuts
|
|
177
|
+
if (isMod && e.key === 'f') {
|
|
178
|
+
e.preventDefault();
|
|
179
|
+
deps.onOpenFind(false);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (isMod && e.key === 'h') {
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
deps.onOpenFind(true);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// Command Palette (Ctrl+Shift+P or Cmd+Shift+P)
|
|
188
|
+
if (isMod && e.shiftKey && e.key.toLowerCase() === 'p') {
|
|
189
|
+
e.preventDefault();
|
|
190
|
+
deps.onShowCommandPalette();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (e.key === 'F3' || (isMod && e.key === 'g')) {
|
|
194
|
+
e.preventDefault();
|
|
195
|
+
if (deps.isShowFindReplace() && deps.getSearchMatchCount() > 0) {
|
|
196
|
+
if (e.shiftKey) {
|
|
197
|
+
deps.onFindPrev();
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
deps.onFindNext();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else if (deps.getSearchQuery()) {
|
|
204
|
+
// Re-open find with last query
|
|
205
|
+
deps.onOpenFind(false);
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (e.key === 'Escape' && deps.isShowFindReplace()) {
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
deps.onCloseFind();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// Folding shortcuts (Ctrl+Shift+[ and Ctrl+Shift+])
|
|
215
|
+
if (deps.isFolding() && isMod && e.shiftKey) {
|
|
216
|
+
if (e.key === '[' || e.code === 'BracketLeft') {
|
|
217
|
+
e.preventDefault();
|
|
218
|
+
deps.onFoldCurrent();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (e.key === ']' || e.code === 'BracketRight') {
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
deps.onUnfoldCurrent();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Ctrl+Alt+[ for fold all, Ctrl+Alt+] for unfold all
|
|
228
|
+
if (deps.isFolding() && isMod && e.altKey && !e.shiftKey) {
|
|
229
|
+
if (e.key === '[' || e.code === 'BracketLeft') {
|
|
230
|
+
e.preventDefault();
|
|
231
|
+
deps.onFoldAll();
|
|
232
|
+
deps.announce('Collapsed all foldable regions');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (e.key === ']' || e.code === 'BracketRight') {
|
|
236
|
+
e.preventDefault();
|
|
237
|
+
deps.onUnfoldAll();
|
|
238
|
+
deps.announce('Expanded all foldable regions');
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Multi-cursor shortcuts
|
|
243
|
+
if (deps.isMultiCursor()) {
|
|
244
|
+
// Escape to clear secondary cursors
|
|
245
|
+
if (e.key === 'Escape' && deps.hasMultipleCursors()) {
|
|
246
|
+
e.preventDefault();
|
|
247
|
+
editorState.clearSecondaryCursors();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
// Ctrl+Alt+Up/Down to add cursors above/below
|
|
251
|
+
if (isMod && e.altKey && !e.shiftKey) {
|
|
252
|
+
if (e.key === 'ArrowUp') {
|
|
253
|
+
e.preventDefault();
|
|
254
|
+
editorState.addCursorAbove();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (e.key === 'ArrowDown') {
|
|
258
|
+
e.preventDefault();
|
|
259
|
+
editorState.addCursorBelow();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Ctrl+U to remove last cursor
|
|
264
|
+
if (isMod && e.key === 'u' && !e.shiftKey && !e.altKey) {
|
|
265
|
+
e.preventDefault();
|
|
266
|
+
editorState.removeLastCursor();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
// Ctrl+D to select next occurrence
|
|
270
|
+
if (isMod && e.key === 'd' && !e.shiftKey && !e.altKey) {
|
|
271
|
+
e.preventDefault();
|
|
272
|
+
selectNextOccurrence(editorState);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
// Ctrl+Shift+L to select all occurrences
|
|
276
|
+
if (isMod && e.shiftKey && e.key === 'l') {
|
|
277
|
+
e.preventDefault();
|
|
278
|
+
selectAllOccurrences(editorState);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
// Ctrl+S to save
|
|
282
|
+
if (isMod && e.key === 's') {
|
|
283
|
+
e.preventDefault();
|
|
284
|
+
if (deps.onSave) {
|
|
285
|
+
deps.onSave();
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Let keyboard handler process it
|
|
291
|
+
const handled = deps.getKeyboardHandler().handleKeyDown(e, deps.isReadonly());
|
|
292
|
+
if (!handled && !deps.isReadonly() && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
293
|
+
// Regular character input - handled by input event
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// ------------------------------------------------------------------
|
|
297
|
+
// Text input / IME
|
|
298
|
+
// ------------------------------------------------------------------
|
|
299
|
+
function handleInput(e) {
|
|
300
|
+
if (deps.isReadonly())
|
|
301
|
+
return;
|
|
302
|
+
// Don't process input during IME composition - wait for compositionend
|
|
303
|
+
if (isComposing)
|
|
304
|
+
return;
|
|
305
|
+
const target = e.target;
|
|
306
|
+
const inputText = target.value;
|
|
307
|
+
if (inputText) {
|
|
308
|
+
deps.getEditorState().insert(inputText);
|
|
309
|
+
target.value = '';
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function handleCompositionStart() {
|
|
313
|
+
isComposing = true;
|
|
314
|
+
}
|
|
315
|
+
function handleCompositionEnd(e) {
|
|
316
|
+
isComposing = false;
|
|
317
|
+
if (deps.isReadonly())
|
|
318
|
+
return;
|
|
319
|
+
// Insert the composed text
|
|
320
|
+
const composedText = e.data;
|
|
321
|
+
if (composedText) {
|
|
322
|
+
deps.getEditorState().insert(composedText);
|
|
323
|
+
}
|
|
324
|
+
// Clear the hidden input
|
|
325
|
+
const hiddenInput = deps.getHiddenInput();
|
|
326
|
+
if (hiddenInput) {
|
|
327
|
+
hiddenInput.value = '';
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// ------------------------------------------------------------------
|
|
331
|
+
// Clipboard
|
|
332
|
+
// ------------------------------------------------------------------
|
|
333
|
+
function handlePaste(e) {
|
|
334
|
+
if (deps.isReadonly())
|
|
335
|
+
return;
|
|
336
|
+
e.preventDefault();
|
|
337
|
+
const text = e.clipboardData?.getData('text/plain');
|
|
338
|
+
if (text) {
|
|
339
|
+
deps.getEditorState().insert(text);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function handleCopy(e) {
|
|
343
|
+
e.preventDefault();
|
|
344
|
+
const editorState = deps.getEditorState();
|
|
345
|
+
const selection = deps.getSelection();
|
|
346
|
+
const cursors = deps.getCursors();
|
|
347
|
+
// Multi-cursor: if any cursor has a selection, copy from all cursors
|
|
348
|
+
if (editorState.hasAnySelection) {
|
|
349
|
+
const text = editorState.getSelectedTextFromAllCursors();
|
|
350
|
+
e.clipboardData?.setData('text/plain', text);
|
|
351
|
+
}
|
|
352
|
+
else if (deps.hasMultipleCursors()) {
|
|
353
|
+
// Multiple cursors with no selection: copy line at each cursor
|
|
354
|
+
const lines = cursors.map(c => editorState.getLine(c.selection.head.line)?.text ?? '');
|
|
355
|
+
e.clipboardData?.setData('text/plain', lines.join('\n') + '\n');
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
// Single cursor with no selection: copy the entire current line
|
|
359
|
+
const line = editorState.getLine(selection.head.line);
|
|
360
|
+
const lineText = (line?.text ?? '') + '\n';
|
|
361
|
+
e.clipboardData?.setData('text/plain', lineText);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function handleCut(e) {
|
|
365
|
+
if (deps.isReadonly())
|
|
366
|
+
return;
|
|
367
|
+
e.preventDefault();
|
|
368
|
+
const editorState = deps.getEditorState();
|
|
369
|
+
const selection = deps.getSelection();
|
|
370
|
+
const cursors = deps.getCursors();
|
|
371
|
+
// Multi-cursor: if any cursor has a selection, cut from all cursors
|
|
372
|
+
if (editorState.hasAnySelection) {
|
|
373
|
+
const text = editorState.getSelectedTextFromAllCursors();
|
|
374
|
+
e.clipboardData?.setData('text/plain', text);
|
|
375
|
+
editorState.deleteSelection();
|
|
376
|
+
}
|
|
377
|
+
else if (deps.hasMultipleCursors()) {
|
|
378
|
+
// Multiple cursors with no selection: cut line at each cursor
|
|
379
|
+
const lines = cursors.map(c => editorState.getLine(c.selection.head.line)?.text ?? '');
|
|
380
|
+
e.clipboardData?.setData('text/plain', lines.join('\n') + '\n');
|
|
381
|
+
// Select and delete each line (in reverse order to maintain positions)
|
|
382
|
+
const sortedCursors = [...cursors].sort((a, b) => b.selection.head.line - a.selection.head.line);
|
|
383
|
+
for (const cursor of sortedCursors) {
|
|
384
|
+
const lineNum = cursor.selection.head.line;
|
|
385
|
+
const line = editorState.getLine(lineNum);
|
|
386
|
+
const lineStart = { line: lineNum, column: 0 };
|
|
387
|
+
const lineEnd = lineNum < editorState.lineCount - 1
|
|
388
|
+
? { line: lineNum + 1, column: 0 }
|
|
389
|
+
: { line: lineNum, column: line?.text.length ?? 0 };
|
|
390
|
+
editorState.cursorManager.setSelection(cursor.id, lineStart, lineEnd);
|
|
391
|
+
}
|
|
392
|
+
editorState.deleteSelection();
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
// Single cursor with no selection: cut the entire current line
|
|
396
|
+
const lineNum = selection.head.line;
|
|
397
|
+
const line = editorState.getLine(lineNum);
|
|
398
|
+
const lineText = (line?.text ?? '') + '\n';
|
|
399
|
+
e.clipboardData?.setData('text/plain', lineText);
|
|
400
|
+
// Delete the entire line
|
|
401
|
+
const lineStart = { line: lineNum, column: 0 };
|
|
402
|
+
const lineEnd = lineNum < editorState.lineCount - 1
|
|
403
|
+
? { line: lineNum + 1, column: 0 }
|
|
404
|
+
: { line: lineNum, column: line?.text.length ?? 0 };
|
|
405
|
+
editorState.setSelection(lineStart, lineEnd);
|
|
406
|
+
editorState.deleteSelection();
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
// ------------------------------------------------------------------
|
|
410
|
+
// Scroll
|
|
411
|
+
// ------------------------------------------------------------------
|
|
412
|
+
function handleScroll(e) {
|
|
413
|
+
const target = e.target;
|
|
414
|
+
deps.setScrollTop(target.scrollTop);
|
|
415
|
+
deps.setScrollLeft(target.scrollLeft);
|
|
416
|
+
}
|
|
417
|
+
// ------------------------------------------------------------------
|
|
418
|
+
// Focus
|
|
419
|
+
// ------------------------------------------------------------------
|
|
420
|
+
function handleFocus() {
|
|
421
|
+
deps.resetCursorBlink();
|
|
422
|
+
}
|
|
423
|
+
function handleBlur() {
|
|
424
|
+
deps.stopCursorBlink();
|
|
425
|
+
deps.setCursorVisible(false);
|
|
426
|
+
}
|
|
427
|
+
// ------------------------------------------------------------------
|
|
428
|
+
// Public API
|
|
429
|
+
// ------------------------------------------------------------------
|
|
430
|
+
return {
|
|
431
|
+
handleMouseDown,
|
|
432
|
+
handleKeyDown,
|
|
433
|
+
handleInput,
|
|
434
|
+
handlePaste,
|
|
435
|
+
handleCopy,
|
|
436
|
+
handleCut,
|
|
437
|
+
handleScroll,
|
|
438
|
+
handleFocus,
|
|
439
|
+
handleBlur,
|
|
440
|
+
handleCompositionStart,
|
|
441
|
+
handleCompositionEnd,
|
|
442
|
+
measureCharacter,
|
|
443
|
+
cleanupGlobalListeners
|
|
444
|
+
};
|
|
445
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-cursor selection helpers extracted from CustomEditor.
|
|
3
|
+
* Pure functions that operate on EditorState.
|
|
4
|
+
*/
|
|
5
|
+
import type { EditorState, Position } from './core';
|
|
6
|
+
/**
|
|
7
|
+
* Get word at cursor position
|
|
8
|
+
*/
|
|
9
|
+
export declare function getWordAtPosition(state: EditorState, pos: Position): {
|
|
10
|
+
text: string;
|
|
11
|
+
start: Position;
|
|
12
|
+
end: Position;
|
|
13
|
+
} | null;
|
|
14
|
+
/**
|
|
15
|
+
* Select next occurrence of current word/selection (Ctrl+D)
|
|
16
|
+
*/
|
|
17
|
+
export declare function selectNextOccurrence(state: EditorState): void;
|
|
18
|
+
/**
|
|
19
|
+
* Select all occurrences of current word/selection (Ctrl+Shift+L)
|
|
20
|
+
*/
|
|
21
|
+
export declare function selectAllOccurrences(state: EditorState): void;
|