@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.
Files changed (330) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +251 -0
  3. package/dist/components/agents/AgentActivityPanel.svelte +565 -0
  4. package/dist/components/agents/AgentActivityPanel.svelte.d.ts +24 -0
  5. package/dist/components/agents/AgentAvatar.svelte +417 -0
  6. package/dist/components/agents/AgentAvatar.svelte.d.ts +23 -0
  7. package/dist/components/agents/AgentCursor.svelte +224 -0
  8. package/dist/components/agents/AgentCursor.svelte.d.ts +35 -0
  9. package/dist/components/agents/AgentPresenceBar.svelte +261 -0
  10. package/dist/components/agents/AgentPresenceBar.svelte.d.ts +20 -0
  11. package/dist/components/agents/index.d.ts +4 -0
  12. package/dist/components/agents/index.js +5 -0
  13. package/dist/components/ai/AIConversationList.svelte +524 -0
  14. package/dist/components/ai/AIConversationList.svelte.d.ts +17 -0
  15. package/dist/components/ai/AIEditPreview.svelte +132 -0
  16. package/dist/components/ai/AIEditPreview.svelte.d.ts +8 -0
  17. package/dist/components/ai/AIInlineEdit.svelte +155 -0
  18. package/dist/components/ai/AIInlineEdit.svelte.d.ts +10 -0
  19. package/dist/components/ai/AIMessage.svelte +239 -0
  20. package/dist/components/ai/AIMessage.svelte.d.ts +13 -0
  21. package/dist/components/ai/AIMessageActions.svelte +176 -0
  22. package/dist/components/ai/AIMessageActions.svelte.d.ts +12 -0
  23. package/dist/components/ai/AIMessageContent.svelte +355 -0
  24. package/dist/components/ai/AIMessageContent.svelte.d.ts +7 -0
  25. package/dist/components/ai/AIPanel.svelte +561 -0
  26. package/dist/components/ai/AIPanel.svelte.d.ts +7 -0
  27. package/dist/components/ai/AISuggestionWidget.svelte +132 -0
  28. package/dist/components/ai/AISuggestionWidget.svelte.d.ts +10 -0
  29. package/dist/components/ai/AIToolCallDisplay.svelte +317 -0
  30. package/dist/components/ai/AIToolCallDisplay.svelte.d.ts +12 -0
  31. package/dist/components/ai/index.d.ts +9 -0
  32. package/dist/components/ai/index.js +10 -0
  33. package/dist/components/core/Avatar.svelte +110 -0
  34. package/dist/components/core/Avatar.svelte.d.ts +12 -0
  35. package/dist/components/core/Badge.svelte +98 -0
  36. package/dist/components/core/Badge.svelte.d.ts +11 -0
  37. package/dist/components/core/Button.svelte +175 -0
  38. package/dist/components/core/Button.svelte.d.ts +18 -0
  39. package/dist/components/core/ConnectionStatus.svelte +294 -0
  40. package/dist/components/core/ConnectionStatus.svelte.d.ts +20 -0
  41. package/dist/components/core/ContextMenu.svelte +176 -0
  42. package/dist/components/core/ContextMenu.svelte.d.ts +19 -0
  43. package/dist/components/core/ErrorBoundary.svelte +277 -0
  44. package/dist/components/core/ErrorBoundary.svelte.d.ts +23 -0
  45. package/dist/components/core/Icon.svelte +107 -0
  46. package/dist/components/core/Icon.svelte.d.ts +8 -0
  47. package/dist/components/core/Input.svelte +138 -0
  48. package/dist/components/core/Input.svelte.d.ts +20 -0
  49. package/dist/components/core/Kbd.svelte +34 -0
  50. package/dist/components/core/Kbd.svelte.d.ts +7 -0
  51. package/dist/components/core/ResizeHandle.svelte +200 -0
  52. package/dist/components/core/ResizeHandle.svelte.d.ts +23 -0
  53. package/dist/components/core/Spinner.svelte +35 -0
  54. package/dist/components/core/Spinner.svelte.d.ts +7 -0
  55. package/dist/components/core/Textarea.svelte +112 -0
  56. package/dist/components/core/Textarea.svelte.d.ts +18 -0
  57. package/dist/components/core/Tooltip.svelte +103 -0
  58. package/dist/components/core/Tooltip.svelte.d.ts +11 -0
  59. package/dist/components/core/index.d.ts +13 -0
  60. package/dist/components/core/index.js +14 -0
  61. package/dist/components/editor/AIFocusLayer.svelte +430 -0
  62. package/dist/components/editor/AIFocusLayer.svelte.d.ts +32 -0
  63. package/dist/components/editor/Breadcrumbs.svelte +435 -0
  64. package/dist/components/editor/Breadcrumbs.svelte.d.ts +33 -0
  65. package/dist/components/editor/BreakpointLayer.svelte +642 -0
  66. package/dist/components/editor/BreakpointLayer.svelte.d.ts +20 -0
  67. package/dist/components/editor/CognitiveLoadMeter.svelte +324 -0
  68. package/dist/components/editor/CognitiveLoadMeter.svelte.d.ts +18 -0
  69. package/dist/components/editor/CollaborativeEditor.svelte +218 -0
  70. package/dist/components/editor/CollaborativeEditor.svelte.d.ts +32 -0
  71. package/dist/components/editor/CommandPalette.svelte +434 -0
  72. package/dist/components/editor/CommandPalette.svelte.d.ts +11 -0
  73. package/dist/components/editor/ComplexityLayer.svelte +293 -0
  74. package/dist/components/editor/ComplexityLayer.svelte.d.ts +23 -0
  75. package/dist/components/editor/ConflictZoneLayer.svelte +441 -0
  76. package/dist/components/editor/ConflictZoneLayer.svelte.d.ts +25 -0
  77. package/dist/components/editor/ContextLens.svelte +262 -0
  78. package/dist/components/editor/ContextLens.svelte.d.ts +27 -0
  79. package/dist/components/editor/CustomEditor.svelte +1242 -0
  80. package/dist/components/editor/CustomEditor.svelte.d.ts +37 -0
  81. package/dist/components/editor/DebugConsole.svelte +646 -0
  82. package/dist/components/editor/DebugConsole.svelte.d.ts +41 -0
  83. package/dist/components/editor/EchoCursorLayer.svelte +363 -0
  84. package/dist/components/editor/EchoCursorLayer.svelte.d.ts +24 -0
  85. package/dist/components/editor/Editor.svelte +61 -0
  86. package/dist/components/editor/Editor.svelte.d.ts +22 -0
  87. package/dist/components/editor/EditorGutter.svelte +119 -0
  88. package/dist/components/editor/EditorGutter.svelte.d.ts +19 -0
  89. package/dist/components/editor/EditorLines.svelte +182 -0
  90. package/dist/components/editor/EditorLines.svelte.d.ts +43 -0
  91. package/dist/components/editor/EditorPane.svelte +134 -0
  92. package/dist/components/editor/EditorPane.svelte.d.ts +9 -0
  93. package/dist/components/editor/EditorSelections.svelte +186 -0
  94. package/dist/components/editor/EditorSelections.svelte.d.ts +25 -0
  95. package/dist/components/editor/EditorTabs.svelte +170 -0
  96. package/dist/components/editor/EditorTabs.svelte.d.ts +12 -0
  97. package/dist/components/editor/FileExplorer.svelte +811 -0
  98. package/dist/components/editor/FileExplorer.svelte.d.ts +67 -0
  99. package/dist/components/editor/FileIcon.svelte +110 -0
  100. package/dist/components/editor/FileIcon.svelte.d.ts +10 -0
  101. package/dist/components/editor/FindReplace.svelte +448 -0
  102. package/dist/components/editor/FindReplace.svelte.d.ts +40 -0
  103. package/dist/components/editor/GhostBracketLayer.svelte +391 -0
  104. package/dist/components/editor/GhostBracketLayer.svelte.d.ts +24 -0
  105. package/dist/components/editor/GitBlameLayer.svelte +436 -0
  106. package/dist/components/editor/GitBlameLayer.svelte.d.ts +18 -0
  107. package/dist/components/editor/InlineDiagnosticsLayer.svelte +540 -0
  108. package/dist/components/editor/InlineDiagnosticsLayer.svelte.d.ts +35 -0
  109. package/dist/components/editor/InlineDiffLayer.svelte +337 -0
  110. package/dist/components/editor/InlineDiffLayer.svelte.d.ts +31 -0
  111. package/dist/components/editor/MinimalEditor.svelte +75 -0
  112. package/dist/components/editor/MinimalEditor.svelte.d.ts +6 -0
  113. package/dist/components/editor/MinimalEditor2.svelte +84 -0
  114. package/dist/components/editor/MinimalEditor2.svelte.d.ts +6 -0
  115. package/dist/components/editor/Minimap.svelte +327 -0
  116. package/dist/components/editor/Minimap.svelte.d.ts +34 -0
  117. package/dist/components/editor/PluginPreviewSandbox.svelte +793 -0
  118. package/dist/components/editor/PluginPreviewSandbox.svelte.d.ts +49 -0
  119. package/dist/components/editor/ProblemsPanel.svelte +628 -0
  120. package/dist/components/editor/ProblemsPanel.svelte.d.ts +25 -0
  121. package/dist/components/editor/QuickActionsMenu.svelte +403 -0
  122. package/dist/components/editor/QuickActionsMenu.svelte.d.ts +18 -0
  123. package/dist/components/editor/SnippetPalette.svelte +530 -0
  124. package/dist/components/editor/SnippetPalette.svelte.d.ts +16 -0
  125. package/dist/components/editor/StructureMap.svelte +431 -0
  126. package/dist/components/editor/StructureMap.svelte.d.ts +37 -0
  127. package/dist/components/editor/SymbolOutline.svelte +722 -0
  128. package/dist/components/editor/SymbolOutline.svelte.d.ts +44 -0
  129. package/dist/components/editor/TimelineScrubber.svelte +470 -0
  130. package/dist/components/editor/TimelineScrubber.svelte.d.ts +40 -0
  131. package/dist/components/editor/TokenRenderer.svelte +69 -0
  132. package/dist/components/editor/TokenRenderer.svelte.d.ts +15 -0
  133. package/dist/components/editor/constants.d.ts +32 -0
  134. package/dist/components/editor/constants.js +36 -0
  135. package/dist/components/editor/core/ai-awareness.d.ts +176 -0
  136. package/dist/components/editor/core/ai-awareness.js +210 -0
  137. package/dist/components/editor/core/bracket-healer.d.ts +189 -0
  138. package/dist/components/editor/core/bracket-healer.js +406 -0
  139. package/dist/components/editor/core/breakpoints.d.ts +203 -0
  140. package/dist/components/editor/core/breakpoints.js +414 -0
  141. package/dist/components/editor/core/commands.d.ts +108 -0
  142. package/dist/components/editor/core/commands.js +246 -0
  143. package/dist/components/editor/core/complexity-analyzer.d.ts +123 -0
  144. package/dist/components/editor/core/complexity-analyzer.js +376 -0
  145. package/dist/components/editor/core/conflict-predictor.d.ts +135 -0
  146. package/dist/components/editor/core/conflict-predictor.js +316 -0
  147. package/dist/components/editor/core/crdt-binding.d.ts +118 -0
  148. package/dist/components/editor/core/crdt-binding.js +286 -0
  149. package/dist/components/editor/core/diagnostics.d.ts +210 -0
  150. package/dist/components/editor/core/diagnostics.js +335 -0
  151. package/dist/components/editor/core/echo-cursor.d.ts +201 -0
  152. package/dist/components/editor/core/echo-cursor.js +267 -0
  153. package/dist/components/editor/core/folding.d.ts +124 -0
  154. package/dist/components/editor/core/folding.js +672 -0
  155. package/dist/components/editor/core/ghost-pair.d.ts +122 -0
  156. package/dist/components/editor/core/ghost-pair.js +221 -0
  157. package/dist/components/editor/core/git-blame.d.ts +170 -0
  158. package/dist/components/editor/core/git-blame.js +324 -0
  159. package/dist/components/editor/core/index.d.ts +26 -0
  160. package/dist/components/editor/core/index.js +24 -0
  161. package/dist/components/editor/core/keybindings.d.ts +79 -0
  162. package/dist/components/editor/core/keybindings.js +357 -0
  163. package/dist/components/editor/core/multi-cursor.d.ts +196 -0
  164. package/dist/components/editor/core/multi-cursor.js +521 -0
  165. package/dist/components/editor/core/navigation.d.ts +107 -0
  166. package/dist/components/editor/core/navigation.js +408 -0
  167. package/dist/components/editor/core/quick-actions.d.ts +189 -0
  168. package/dist/components/editor/core/quick-actions.js +427 -0
  169. package/dist/components/editor/core/search.d.ts +88 -0
  170. package/dist/components/editor/core/search.js +192 -0
  171. package/dist/components/editor/core/semantic-analyzer.d.ts +77 -0
  172. package/dist/components/editor/core/semantic-analyzer.js +424 -0
  173. package/dist/components/editor/core/snippet-manager.d.ts +202 -0
  174. package/dist/components/editor/core/snippet-manager.js +565 -0
  175. package/dist/components/editor/core/state.d.ts +367 -0
  176. package/dist/components/editor/core/state.js +900 -0
  177. package/dist/components/editor/core/timeline.d.ts +204 -0
  178. package/dist/components/editor/core/timeline.js +349 -0
  179. package/dist/components/editor/editor-find.d.ts +56 -0
  180. package/dist/components/editor/editor-find.js +148 -0
  181. package/dist/components/editor/editor-input.d.ts +77 -0
  182. package/dist/components/editor/editor-input.js +445 -0
  183. package/dist/components/editor/editor-multicursor.d.ts +21 -0
  184. package/dist/components/editor/editor-multicursor.js +196 -0
  185. package/dist/components/editor/editor-scroll.d.ts +14 -0
  186. package/dist/components/editor/editor-scroll.js +34 -0
  187. package/dist/components/editor/index.d.ts +15 -0
  188. package/dist/components/editor/index.js +21 -0
  189. package/dist/components/editor/languages.d.ts +62 -0
  190. package/dist/components/editor/languages.js +285 -0
  191. package/dist/components/editor/theme.d.ts +88 -0
  192. package/dist/components/editor/theme.js +139 -0
  193. package/dist/components/editor/tokenizer/base.d.ts +40 -0
  194. package/dist/components/editor/tokenizer/base.js +203 -0
  195. package/dist/components/editor/tokenizer/index.d.ts +56 -0
  196. package/dist/components/editor/tokenizer/index.js +215 -0
  197. package/dist/components/editor/tokenizer/languages/css.d.ts +17 -0
  198. package/dist/components/editor/tokenizer/languages/css.js +194 -0
  199. package/dist/components/editor/tokenizer/languages/go.d.ts +17 -0
  200. package/dist/components/editor/tokenizer/languages/go.js +220 -0
  201. package/dist/components/editor/tokenizer/languages/html.d.ts +24 -0
  202. package/dist/components/editor/tokenizer/languages/html.js +145 -0
  203. package/dist/components/editor/tokenizer/languages/javascript.d.ts +56 -0
  204. package/dist/components/editor/tokenizer/languages/javascript.js +452 -0
  205. package/dist/components/editor/tokenizer/languages/json.d.ts +12 -0
  206. package/dist/components/editor/tokenizer/languages/json.js +91 -0
  207. package/dist/components/editor/tokenizer/languages/markdown.d.ts +16 -0
  208. package/dist/components/editor/tokenizer/languages/markdown.js +156 -0
  209. package/dist/components/editor/tokenizer/languages/python.d.ts +20 -0
  210. package/dist/components/editor/tokenizer/languages/python.js +227 -0
  211. package/dist/components/editor/tokenizer/languages/svelte.d.ts +40 -0
  212. package/dist/components/editor/tokenizer/languages/svelte.js +326 -0
  213. package/dist/components/editor/tokenizer/types.d.ts +86 -0
  214. package/dist/components/editor/tokenizer/types.js +4 -0
  215. package/dist/components/layout/IDELayout.svelte +274 -0
  216. package/dist/components/layout/IDELayout.svelte.d.ts +29 -0
  217. package/dist/components/layout/StatusBar.svelte +511 -0
  218. package/dist/components/layout/StatusBar.svelte.d.ts +47 -0
  219. package/dist/components/layout/index.d.ts +2 -0
  220. package/dist/components/layout/index.js +3 -0
  221. package/dist/components/lsp/AutocompleteWidget.svelte +364 -0
  222. package/dist/components/lsp/AutocompleteWidget.svelte.d.ts +33 -0
  223. package/dist/components/lsp/DiagnosticMarker.svelte +166 -0
  224. package/dist/components/lsp/DiagnosticMarker.svelte.d.ts +19 -0
  225. package/dist/components/lsp/DiagnosticsPanel.svelte +388 -0
  226. package/dist/components/lsp/DiagnosticsPanel.svelte.d.ts +21 -0
  227. package/dist/components/lsp/HoverTooltip.svelte +274 -0
  228. package/dist/components/lsp/HoverTooltip.svelte.d.ts +24 -0
  229. package/dist/components/lsp/LSPEditor.svelte +486 -0
  230. package/dist/components/lsp/LSPEditor.svelte.d.ts +39 -0
  231. package/dist/components/lsp/SignatureHelpWidget.svelte +216 -0
  232. package/dist/components/lsp/SignatureHelpWidget.svelte.d.ts +22 -0
  233. package/dist/components/lsp/index.d.ts +6 -0
  234. package/dist/components/lsp/index.js +7 -0
  235. package/dist/components/plugins/PluginCard.svelte +153 -0
  236. package/dist/components/plugins/PluginCard.svelte.d.ts +19 -0
  237. package/dist/components/plugins/PluginPanel.svelte +280 -0
  238. package/dist/components/plugins/PluginPanel.svelte.d.ts +8 -0
  239. package/dist/components/plugins/PluginProposalForm.svelte +250 -0
  240. package/dist/components/plugins/PluginProposalForm.svelte.d.ts +6 -0
  241. package/dist/components/plugins/PluginStatusBadge.svelte +14 -0
  242. package/dist/components/plugins/PluginStatusBadge.svelte.d.ts +8 -0
  243. package/dist/components/plugins/index.d.ts +4 -0
  244. package/dist/components/plugins/index.js +5 -0
  245. package/dist/components/vfs/LockConflictDialog.svelte +705 -0
  246. package/dist/components/vfs/LockConflictDialog.svelte.d.ts +21 -0
  247. package/dist/components/vfs/LockIndicator.svelte +194 -0
  248. package/dist/components/vfs/LockIndicator.svelte.d.ts +29 -0
  249. package/dist/components/vfs/LockOverlay.svelte +344 -0
  250. package/dist/components/vfs/LockOverlay.svelte.d.ts +17 -0
  251. package/dist/components/vfs/VersionConflictDialog.svelte +549 -0
  252. package/dist/components/vfs/VersionConflictDialog.svelte.d.ts +24 -0
  253. package/dist/components/vfs/index.d.ts +4 -0
  254. package/dist/components/vfs/index.js +5 -0
  255. package/dist/crdt/awareness.d.ts +42 -0
  256. package/dist/crdt/awareness.js +109 -0
  257. package/dist/crdt/document.d.ts +101 -0
  258. package/dist/crdt/document.js +187 -0
  259. package/dist/crdt/index.d.ts +9 -0
  260. package/dist/crdt/index.js +8 -0
  261. package/dist/crdt/provider.d.ts +85 -0
  262. package/dist/crdt/provider.js +150 -0
  263. package/dist/crdt/types.d.ts +61 -0
  264. package/dist/crdt/types.js +4 -0
  265. package/dist/crdt/undo.d.ts +34 -0
  266. package/dist/crdt/undo.js +70 -0
  267. package/dist/index.d.ts +277 -0
  268. package/dist/index.js +280 -0
  269. package/dist/plugins/index.d.ts +103 -0
  270. package/dist/plugins/index.js +153 -0
  271. package/dist/services/error-handling.d.ts +95 -0
  272. package/dist/services/error-handling.js +413 -0
  273. package/dist/services/ide-integration.d.ts +83 -0
  274. package/dist/services/ide-integration.js +367 -0
  275. package/dist/services/lsp-client.d.ts +69 -0
  276. package/dist/services/lsp-client.js +667 -0
  277. package/dist/services/mock-ai.d.ts +37 -0
  278. package/dist/services/mock-ai.js +318 -0
  279. package/dist/services/optimistic.d.ts +141 -0
  280. package/dist/services/optimistic.js +367 -0
  281. package/dist/services/vfs-client.d.ts +81 -0
  282. package/dist/services/vfs-client.js +348 -0
  283. package/dist/stores/agents.svelte.d.ts +85 -0
  284. package/dist/stores/agents.svelte.js +459 -0
  285. package/dist/stores/ai-persistence.svelte.d.ts +76 -0
  286. package/dist/stores/ai-persistence.svelte.js +334 -0
  287. package/dist/stores/ai.svelte.d.ts +140 -0
  288. package/dist/stores/ai.svelte.js +383 -0
  289. package/dist/stores/collaboration.svelte.d.ts +164 -0
  290. package/dist/stores/collaboration.svelte.js +334 -0
  291. package/dist/stores/editor.svelte.d.ts +131 -0
  292. package/dist/stores/editor.svelte.js +250 -0
  293. package/dist/stores/index.d.ts +10 -0
  294. package/dist/stores/index.js +29 -0
  295. package/dist/stores/layout.svelte.d.ts +171 -0
  296. package/dist/stores/layout.svelte.js +351 -0
  297. package/dist/stores/plugin.svelte.d.ts +121 -0
  298. package/dist/stores/plugin.svelte.js +410 -0
  299. package/dist/stores/vfs.svelte.d.ts +123 -0
  300. package/dist/stores/vfs.svelte.js +680 -0
  301. package/dist/styles/theme.css +623 -0
  302. package/dist/types/agents.d.ts +127 -0
  303. package/dist/types/agents.js +5 -0
  304. package/dist/types/ai.d.ts +137 -0
  305. package/dist/types/ai.js +4 -0
  306. package/dist/types/crdt.d.ts +222 -0
  307. package/dist/types/crdt.js +5 -0
  308. package/dist/types/editor.d.ts +52 -0
  309. package/dist/types/editor.js +18 -0
  310. package/dist/types/events.d.ts +133 -0
  311. package/dist/types/events.js +4 -0
  312. package/dist/types/filesystem.d.ts +77 -0
  313. package/dist/types/filesystem.js +4 -0
  314. package/dist/types/index.d.ts +9 -0
  315. package/dist/types/index.js +12 -0
  316. package/dist/types/lsp.d.ts +691 -0
  317. package/dist/types/lsp.js +108 -0
  318. package/dist/types/plugin.d.ts +239 -0
  319. package/dist/types/plugin.js +5 -0
  320. package/dist/types/vfs.d.ts +191 -0
  321. package/dist/types/vfs.js +18 -0
  322. package/dist/utils/format.d.ts +55 -0
  323. package/dist/utils/format.js +152 -0
  324. package/dist/utils/index.d.ts +3 -0
  325. package/dist/utils/index.js +4 -0
  326. package/dist/utils/keybindings.d.ts +33 -0
  327. package/dist/utils/keybindings.js +171 -0
  328. package/dist/utils/language.d.ts +27 -0
  329. package/dist/utils/language.js +222 -0
  330. 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;