@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,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML/XML tokenizer
|
|
3
|
+
*/
|
|
4
|
+
import { createToken } from '../base';
|
|
5
|
+
export class HTMLTokenizer {
|
|
6
|
+
language;
|
|
7
|
+
isXML;
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.isXML = options.xml ?? false;
|
|
10
|
+
this.language = this.isXML ? 'xml' : 'html';
|
|
11
|
+
}
|
|
12
|
+
getInitialState() {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
tokenizeLine(line, lineNumber, prevState) {
|
|
16
|
+
const tokens = [];
|
|
17
|
+
let pos = 0;
|
|
18
|
+
const state = { ...prevState };
|
|
19
|
+
// Handle multi-line comment continuation
|
|
20
|
+
if (state.inBlockComment) {
|
|
21
|
+
const endIdx = line.indexOf('-->');
|
|
22
|
+
if (endIdx !== -1) {
|
|
23
|
+
tokens.push(createToken('comment.block', line.slice(0, endIdx + 3), 0));
|
|
24
|
+
pos = endIdx + 3;
|
|
25
|
+
state.inBlockComment = false;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
tokens.push(createToken('comment.block', line, 0));
|
|
29
|
+
return { lineNumber, tokens, text: line, state };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
while (pos < line.length) {
|
|
33
|
+
const remaining = line.slice(pos);
|
|
34
|
+
const token = this.getNextToken(remaining, pos, state, line);
|
|
35
|
+
if (token) {
|
|
36
|
+
tokens.push(token);
|
|
37
|
+
pos = token.end;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
tokens.push(createToken('text', remaining[0], pos));
|
|
41
|
+
pos += 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (tokens.length === 0) {
|
|
45
|
+
tokens.push(createToken('text', '', 0));
|
|
46
|
+
}
|
|
47
|
+
return { lineNumber, tokens, text: line, state };
|
|
48
|
+
}
|
|
49
|
+
getNextToken(text, pos, state, fullLine) {
|
|
50
|
+
// Whitespace
|
|
51
|
+
const wsMatch = text.match(/^[ \t]+/);
|
|
52
|
+
if (wsMatch) {
|
|
53
|
+
return createToken('text', wsMatch[0], pos);
|
|
54
|
+
}
|
|
55
|
+
// Comments
|
|
56
|
+
if (text.startsWith('<!--')) {
|
|
57
|
+
const endIdx = text.indexOf('-->', 4);
|
|
58
|
+
if (endIdx !== -1) {
|
|
59
|
+
return createToken('comment.block', text.slice(0, endIdx + 3), pos);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
state.inBlockComment = true;
|
|
63
|
+
return createToken('comment.block', text, pos);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// DOCTYPE
|
|
67
|
+
if (text.match(/^<!DOCTYPE/i)) {
|
|
68
|
+
const endIdx = text.indexOf('>');
|
|
69
|
+
if (endIdx !== -1) {
|
|
70
|
+
return createToken('keyword', text.slice(0, endIdx + 1), pos);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// CDATA (XML)
|
|
74
|
+
if (this.isXML && text.startsWith('<![CDATA[')) {
|
|
75
|
+
const endIdx = text.indexOf(']]>');
|
|
76
|
+
if (endIdx !== -1) {
|
|
77
|
+
return createToken('string', text.slice(0, endIdx + 3), pos);
|
|
78
|
+
}
|
|
79
|
+
return createToken('string', text, pos);
|
|
80
|
+
}
|
|
81
|
+
// Processing instructions (XML)
|
|
82
|
+
if (this.isXML && text.startsWith('<?')) {
|
|
83
|
+
const endIdx = text.indexOf('?>');
|
|
84
|
+
if (endIdx !== -1) {
|
|
85
|
+
return createToken('keyword', text.slice(0, endIdx + 2), pos);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Closing tag
|
|
89
|
+
const closingTagMatch = text.match(/^<\/([a-zA-Z][a-zA-Z0-9:-]*)>/);
|
|
90
|
+
if (closingTagMatch) {
|
|
91
|
+
const tagName = closingTagMatch[1].toLowerCase();
|
|
92
|
+
if (tagName === 'script')
|
|
93
|
+
state.inScript = false;
|
|
94
|
+
if (tagName === 'style')
|
|
95
|
+
state.inStyle = false;
|
|
96
|
+
return createToken('tag', closingTagMatch[0], pos);
|
|
97
|
+
}
|
|
98
|
+
// Opening tag
|
|
99
|
+
const openingTagMatch = text.match(/^<([a-zA-Z][a-zA-Z0-9:-]*)(?:\s|\/?>|$)/);
|
|
100
|
+
if (openingTagMatch) {
|
|
101
|
+
return this.tokenizeTag(text, pos, state);
|
|
102
|
+
}
|
|
103
|
+
// Entity references
|
|
104
|
+
const entityMatch = text.match(/^&(?:#[0-9]+|#x[0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*);/);
|
|
105
|
+
if (entityMatch) {
|
|
106
|
+
return createToken('constant.builtin', entityMatch[0], pos);
|
|
107
|
+
}
|
|
108
|
+
// Text content
|
|
109
|
+
const textMatch = text.match(/^[^<&]+/);
|
|
110
|
+
if (textMatch) {
|
|
111
|
+
return createToken('text', textMatch[0], pos);
|
|
112
|
+
}
|
|
113
|
+
return createToken('text', text[0], pos);
|
|
114
|
+
}
|
|
115
|
+
tokenizeTag(text, pos, state) {
|
|
116
|
+
// Match entire tag with attributes
|
|
117
|
+
const selfClosingMatch = text.match(/^<([a-zA-Z][a-zA-Z0-9:-]*)([^>]*?)\/>/);
|
|
118
|
+
const openingMatch = text.match(/^<([a-zA-Z][a-zA-Z0-9:-]*)([^>]*)>/);
|
|
119
|
+
if (selfClosingMatch) {
|
|
120
|
+
return createToken('tag', selfClosingMatch[0], pos);
|
|
121
|
+
}
|
|
122
|
+
if (openingMatch) {
|
|
123
|
+
const tagName = openingMatch[1].toLowerCase();
|
|
124
|
+
if (tagName === 'script')
|
|
125
|
+
state.inScript = true;
|
|
126
|
+
if (tagName === 'style')
|
|
127
|
+
state.inStyle = true;
|
|
128
|
+
return createToken('tag', openingMatch[0], pos);
|
|
129
|
+
}
|
|
130
|
+
// Partial tag - just match the tag name part
|
|
131
|
+
const partialMatch = text.match(/^<([a-zA-Z][a-zA-Z0-9:-]*)/);
|
|
132
|
+
if (partialMatch) {
|
|
133
|
+
state.inTag = true;
|
|
134
|
+
state.tagName = partialMatch[1];
|
|
135
|
+
return createToken('tag.name', partialMatch[0], pos);
|
|
136
|
+
}
|
|
137
|
+
return createToken('tag.punctuation', '<', pos);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export function createHTMLTokenizer() {
|
|
141
|
+
return new HTMLTokenizer();
|
|
142
|
+
}
|
|
143
|
+
export function createXMLTokenizer() {
|
|
144
|
+
return new HTMLTokenizer({ xml: true });
|
|
145
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JavaScript/TypeScript tokenizer
|
|
3
|
+
*/
|
|
4
|
+
import type { TokenizedLine, TokenizerState } from '../types';
|
|
5
|
+
interface JSTokenizerState extends TokenizerState {
|
|
6
|
+
templateDepth?: number;
|
|
7
|
+
jsxDepth?: number;
|
|
8
|
+
/** Track if regex is valid in current context (for division vs regex ambiguity) */
|
|
9
|
+
expectExpression?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Last significant token text, used for regex/division disambiguation.
|
|
12
|
+
* Stored per-line in the threaded state (not on the tokenizer instance) so a
|
|
13
|
+
* single cached tokenizer instance can be safely shared across multiple
|
|
14
|
+
* EditorStates / split views without corrupting each other's context.
|
|
15
|
+
*/
|
|
16
|
+
lastToken?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare class JavaScriptTokenizer {
|
|
19
|
+
language: string;
|
|
20
|
+
private isTypeScript;
|
|
21
|
+
private isJSX;
|
|
22
|
+
constructor(options?: {
|
|
23
|
+
typescript?: boolean;
|
|
24
|
+
jsx?: boolean;
|
|
25
|
+
});
|
|
26
|
+
getInitialState(): JSTokenizerState;
|
|
27
|
+
tokenizeLine(line: string, lineNumber: number, prevState?: JSTokenizerState): TokenizedLine;
|
|
28
|
+
private getNextToken;
|
|
29
|
+
private classifyIdentifier;
|
|
30
|
+
/**
|
|
31
|
+
* Check if a regex literal can start in the current context
|
|
32
|
+
* This disambiguates between `/` as division vs regex start.
|
|
33
|
+
* Reads the last significant token from the threaded per-line state.
|
|
34
|
+
*/
|
|
35
|
+
private canStartRegex;
|
|
36
|
+
/**
|
|
37
|
+
* Update the last token for context tracking.
|
|
38
|
+
* Stores onto the threaded per-line state so that a shared tokenizer
|
|
39
|
+
* instance does not leak context across documents.
|
|
40
|
+
*/
|
|
41
|
+
private updateLastToken;
|
|
42
|
+
/**
|
|
43
|
+
* Try to match a regex literal using iterative parsing (avoids regex DoS)
|
|
44
|
+
* Returns the matched regex string or null if not a valid regex
|
|
45
|
+
*/
|
|
46
|
+
private tryMatchRegex;
|
|
47
|
+
private tokenizeString;
|
|
48
|
+
private tokenizeTemplateLiteral;
|
|
49
|
+
private tokenizeTemplateLiteralContinuation;
|
|
50
|
+
private tokenizeJSXTag;
|
|
51
|
+
}
|
|
52
|
+
export declare function createJavaScriptTokenizer(): JavaScriptTokenizer;
|
|
53
|
+
export declare function createTypeScriptTokenizer(): JavaScriptTokenizer;
|
|
54
|
+
export declare function createJSXTokenizer(): JavaScriptTokenizer;
|
|
55
|
+
export declare function createTSXTokenizer(): JavaScriptTokenizer;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JavaScript/TypeScript tokenizer
|
|
3
|
+
*/
|
|
4
|
+
import { createToken } from '../base';
|
|
5
|
+
// JavaScript/TypeScript keywords
|
|
6
|
+
const keywords = new Set([
|
|
7
|
+
'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
|
|
8
|
+
'default', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'finally',
|
|
9
|
+
'for', 'function', 'if', 'import', 'in', 'instanceof', 'let', 'new', 'of',
|
|
10
|
+
'return', 'static', 'super', 'switch', 'this', 'throw', 'try', 'typeof',
|
|
11
|
+
'var', 'void', 'while', 'with', 'yield', 'async', 'from', 'as', 'get', 'set'
|
|
12
|
+
]);
|
|
13
|
+
// TypeScript-specific keywords
|
|
14
|
+
const tsKeywords = new Set([
|
|
15
|
+
'abstract', 'any', 'as', 'asserts', 'bigint', 'boolean', 'declare', 'infer',
|
|
16
|
+
'interface', 'is', 'keyof', 'module', 'namespace', 'never', 'null', 'number',
|
|
17
|
+
'object', 'override', 'private', 'protected', 'public', 'readonly', 'require',
|
|
18
|
+
'string', 'symbol', 'type', 'undefined', 'unique', 'unknown', 'void'
|
|
19
|
+
]);
|
|
20
|
+
// Control flow keywords
|
|
21
|
+
const controlKeywords = new Set([
|
|
22
|
+
'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'default', 'break',
|
|
23
|
+
'continue', 'return', 'throw', 'try', 'catch', 'finally', 'await', 'yield'
|
|
24
|
+
]);
|
|
25
|
+
// Built-in constants
|
|
26
|
+
const builtins = new Set(['true', 'false', 'null', 'undefined', 'NaN', 'Infinity']);
|
|
27
|
+
// Built-in type names
|
|
28
|
+
const builtinTypes = new Set([
|
|
29
|
+
'Array', 'Object', 'String', 'Number', 'Boolean', 'Function', 'Symbol',
|
|
30
|
+
'Promise', 'Map', 'Set', 'WeakMap', 'WeakSet', 'Date', 'RegExp', 'Error',
|
|
31
|
+
'TypeError', 'ReferenceError', 'SyntaxError', 'JSON', 'Math', 'console',
|
|
32
|
+
'window', 'document', 'globalThis', 'Buffer', 'process'
|
|
33
|
+
]);
|
|
34
|
+
/**
|
|
35
|
+
* Tokens after which a regex literal can appear (expression start context)
|
|
36
|
+
*/
|
|
37
|
+
const REGEX_VALID_AFTER = new Set([
|
|
38
|
+
// Keywords that expect expressions
|
|
39
|
+
'return', 'throw', 'case', 'in', 'of', 'typeof', 'instanceof', 'void',
|
|
40
|
+
'delete', 'new', 'await', 'yield', 'default', 'extends', 'else', 'do',
|
|
41
|
+
// Operators
|
|
42
|
+
'=', '==', '===', '!=', '!==', '<', '>', '<=', '>=',
|
|
43
|
+
'+', '-', '*', '/', '%', '**', '&', '|', '^', '~', '!',
|
|
44
|
+
'&&', '||', '??', '?', ':', ',', ';',
|
|
45
|
+
'+=', '-=', '*=', '/=', '%=', '**=', '&=', '|=', '^=',
|
|
46
|
+
'&&=', '||=', '??=', '<<', '>>', '>>>', '<<=', '>>=', '>>>=',
|
|
47
|
+
// Opening brackets
|
|
48
|
+
'(', '[', '{',
|
|
49
|
+
// Arrow
|
|
50
|
+
'=>'
|
|
51
|
+
]);
|
|
52
|
+
/**
|
|
53
|
+
* Tokens after which division operator appears (expression end context)
|
|
54
|
+
*/
|
|
55
|
+
const DIVISION_VALID_AFTER = new Set([
|
|
56
|
+
// These token types indicate an expression just ended
|
|
57
|
+
')', ']', '}', // Closing brackets
|
|
58
|
+
'++', '--', // Postfix operators
|
|
59
|
+
]);
|
|
60
|
+
export class JavaScriptTokenizer {
|
|
61
|
+
language;
|
|
62
|
+
isTypeScript;
|
|
63
|
+
isJSX;
|
|
64
|
+
constructor(options = {}) {
|
|
65
|
+
this.isTypeScript = options.typescript ?? false;
|
|
66
|
+
this.isJSX = options.jsx ?? false;
|
|
67
|
+
this.language = this.isTypeScript
|
|
68
|
+
? (this.isJSX ? 'tsx' : 'typescript')
|
|
69
|
+
: (this.isJSX ? 'jsx' : 'javascript');
|
|
70
|
+
}
|
|
71
|
+
getInitialState() {
|
|
72
|
+
return {
|
|
73
|
+
templateDepth: 0,
|
|
74
|
+
jsxDepth: 0
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
tokenizeLine(line, lineNumber, prevState) {
|
|
78
|
+
const tokens = [];
|
|
79
|
+
let pos = 0;
|
|
80
|
+
const state = {
|
|
81
|
+
...this.getInitialState(),
|
|
82
|
+
...prevState
|
|
83
|
+
};
|
|
84
|
+
// Reset context tracking at start of line if no continuation state
|
|
85
|
+
if (!state.inBlockComment && !state.inTemplateLiteral) {
|
|
86
|
+
// At line start, assume expression context (regex is valid)
|
|
87
|
+
state.lastToken = '';
|
|
88
|
+
}
|
|
89
|
+
// Handle block comment continuation
|
|
90
|
+
if (state.inBlockComment) {
|
|
91
|
+
const endIdx = line.indexOf('*/');
|
|
92
|
+
if (endIdx !== -1) {
|
|
93
|
+
tokens.push(createToken('comment.block', line.slice(0, endIdx + 2), 0));
|
|
94
|
+
pos = endIdx + 2;
|
|
95
|
+
state.inBlockComment = false;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
tokens.push(createToken('comment.block', line, 0));
|
|
99
|
+
return { lineNumber, tokens, text: line, state };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Handle template literal continuation
|
|
103
|
+
if (state.inTemplateLiteral && (state.templateDepth ?? 0) > 0) {
|
|
104
|
+
const result = this.tokenizeTemplateLiteralContinuation(line, pos, state);
|
|
105
|
+
tokens.push(...result.tokens);
|
|
106
|
+
pos = result.pos;
|
|
107
|
+
if (pos >= line.length) {
|
|
108
|
+
return { lineNumber, tokens, text: line, state };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
while (pos < line.length) {
|
|
112
|
+
const remaining = line.slice(pos);
|
|
113
|
+
const token = this.getNextToken(remaining, pos, state);
|
|
114
|
+
if (token) {
|
|
115
|
+
tokens.push(token);
|
|
116
|
+
this.updateLastToken(token, state);
|
|
117
|
+
pos = token.end;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// No match - shouldn't happen but handle gracefully
|
|
121
|
+
const fallbackToken = createToken('text', remaining[0], pos);
|
|
122
|
+
tokens.push(fallbackToken);
|
|
123
|
+
this.updateLastToken(fallbackToken, state);
|
|
124
|
+
pos += 1;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (tokens.length === 0) {
|
|
128
|
+
tokens.push(createToken('text', '', 0));
|
|
129
|
+
}
|
|
130
|
+
return { lineNumber, tokens, text: line, state };
|
|
131
|
+
}
|
|
132
|
+
getNextToken(text, pos, state) {
|
|
133
|
+
// Skip whitespace
|
|
134
|
+
const wsMatch = text.match(/^[ \t]+/);
|
|
135
|
+
if (wsMatch) {
|
|
136
|
+
return createToken('text', wsMatch[0], pos);
|
|
137
|
+
}
|
|
138
|
+
// Line comments
|
|
139
|
+
if (text.startsWith('//')) {
|
|
140
|
+
return createToken('comment.line', text, pos);
|
|
141
|
+
}
|
|
142
|
+
// Block comments
|
|
143
|
+
if (text.startsWith('/*')) {
|
|
144
|
+
const endIdx = text.indexOf('*/', 2);
|
|
145
|
+
if (endIdx !== -1) {
|
|
146
|
+
return createToken('comment.block', text.slice(0, endIdx + 2), pos);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
state.inBlockComment = true;
|
|
150
|
+
return createToken('comment.block', text, pos);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Template literals
|
|
154
|
+
if (text.startsWith('`')) {
|
|
155
|
+
return this.tokenizeTemplateLiteral(text, pos, state);
|
|
156
|
+
}
|
|
157
|
+
// Regular strings
|
|
158
|
+
if (text.startsWith('"') || text.startsWith("'")) {
|
|
159
|
+
return this.tokenizeString(text, pos, text[0]);
|
|
160
|
+
}
|
|
161
|
+
// Regular expressions vs division - use context to disambiguate
|
|
162
|
+
if (text.startsWith('/') && !text.startsWith('//') && !text.startsWith('/*')) {
|
|
163
|
+
// Check if we're in a context where regex is valid
|
|
164
|
+
const canBeRegex = this.canStartRegex(state);
|
|
165
|
+
if (canBeRegex) {
|
|
166
|
+
const regexLiteral = this.tryMatchRegex(text);
|
|
167
|
+
if (regexLiteral) {
|
|
168
|
+
return createToken('string.regex', regexLiteral, pos);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Otherwise, fall through to operator handling (will be tokenized as '/')
|
|
172
|
+
}
|
|
173
|
+
// Numbers
|
|
174
|
+
const numMatch = text.match(/^(?:0[xX][0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+|(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?n?)/);
|
|
175
|
+
if (numMatch) {
|
|
176
|
+
const numText = numMatch[0];
|
|
177
|
+
let type = 'number';
|
|
178
|
+
if (numText.startsWith('0x') || numText.startsWith('0X')) {
|
|
179
|
+
type = 'number.hex';
|
|
180
|
+
}
|
|
181
|
+
else if (numText.startsWith('0b') || numText.startsWith('0B')) {
|
|
182
|
+
type = 'number.binary';
|
|
183
|
+
}
|
|
184
|
+
else if (numText.includes('.') || numText.includes('e') || numText.includes('E')) {
|
|
185
|
+
type = 'number.float';
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
type = 'number.integer';
|
|
189
|
+
}
|
|
190
|
+
return createToken(type, numText, pos);
|
|
191
|
+
}
|
|
192
|
+
// Identifiers and keywords
|
|
193
|
+
const identMatch = text.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*/);
|
|
194
|
+
if (identMatch) {
|
|
195
|
+
const word = identMatch[0];
|
|
196
|
+
return createToken(this.classifyIdentifier(word, text, identMatch[0].length), word, pos);
|
|
197
|
+
}
|
|
198
|
+
// JSX tags (basic)
|
|
199
|
+
if (this.isJSX && text.startsWith('<')) {
|
|
200
|
+
return this.tokenizeJSXTag(text, pos, state);
|
|
201
|
+
}
|
|
202
|
+
// Operators
|
|
203
|
+
const opMatch = text.match(/^(?:>>>|===|!==|<<=|>>=|=>|&&|\|\||[+\-*/%&|^!<>=]=?|[?:.~]|\?\?|\?\.)/);
|
|
204
|
+
if (opMatch) {
|
|
205
|
+
return createToken('operator', opMatch[0], pos);
|
|
206
|
+
}
|
|
207
|
+
// Punctuation
|
|
208
|
+
const punctMatch = text.match(/^[{}[\](),;]/);
|
|
209
|
+
if (punctMatch) {
|
|
210
|
+
const char = punctMatch[0];
|
|
211
|
+
let type = 'punctuation';
|
|
212
|
+
if (char === '{' || char === '}')
|
|
213
|
+
type = 'punctuation.brace';
|
|
214
|
+
else if (char === '[' || char === ']')
|
|
215
|
+
type = 'punctuation.bracket';
|
|
216
|
+
else if (char === '(' || char === ')')
|
|
217
|
+
type = 'punctuation.paren';
|
|
218
|
+
else if (char === ',' || char === ';')
|
|
219
|
+
type = 'punctuation.separator';
|
|
220
|
+
return createToken(type, char, pos);
|
|
221
|
+
}
|
|
222
|
+
// Dot accessor
|
|
223
|
+
if (text.startsWith('.')) {
|
|
224
|
+
return createToken('punctuation.accessor', '.', pos);
|
|
225
|
+
}
|
|
226
|
+
// Default: single character
|
|
227
|
+
return createToken('text', text[0], pos);
|
|
228
|
+
}
|
|
229
|
+
classifyIdentifier(word, context, wordLength) {
|
|
230
|
+
// Check for built-in constants
|
|
231
|
+
if (builtins.has(word)) {
|
|
232
|
+
if (word === 'true' || word === 'false')
|
|
233
|
+
return 'constant.boolean';
|
|
234
|
+
if (word === 'null' || word === 'undefined')
|
|
235
|
+
return 'constant.null';
|
|
236
|
+
return 'constant.builtin';
|
|
237
|
+
}
|
|
238
|
+
// Check for keywords
|
|
239
|
+
if (keywords.has(word)) {
|
|
240
|
+
if (controlKeywords.has(word))
|
|
241
|
+
return 'keyword.control';
|
|
242
|
+
if (word === 'function' || word === 'class' || word === 'const' || word === 'let' || word === 'var') {
|
|
243
|
+
return 'keyword.definition';
|
|
244
|
+
}
|
|
245
|
+
if (word === 'import' || word === 'export' || word === 'from' || word === 'as') {
|
|
246
|
+
return 'keyword.module';
|
|
247
|
+
}
|
|
248
|
+
return 'keyword';
|
|
249
|
+
}
|
|
250
|
+
// TypeScript keywords
|
|
251
|
+
if (this.isTypeScript && tsKeywords.has(word)) {
|
|
252
|
+
if (word === 'interface' || word === 'type' || word === 'namespace' || word === 'module') {
|
|
253
|
+
return 'keyword.definition';
|
|
254
|
+
}
|
|
255
|
+
// Type keywords like 'string', 'number', 'boolean' etc
|
|
256
|
+
if (['string', 'number', 'boolean', 'any', 'unknown', 'never', 'void', 'null', 'undefined', 'object', 'symbol', 'bigint'].includes(word)) {
|
|
257
|
+
return 'type.builtin';
|
|
258
|
+
}
|
|
259
|
+
return 'keyword.storage';
|
|
260
|
+
}
|
|
261
|
+
// Check if followed by ( - likely a function call
|
|
262
|
+
const afterWord = context.slice(wordLength).trim();
|
|
263
|
+
if (afterWord.startsWith('(')) {
|
|
264
|
+
return 'function.call';
|
|
265
|
+
}
|
|
266
|
+
// Check if it's a type (PascalCase)
|
|
267
|
+
if (/^[A-Z][a-zA-Z0-9]*$/.test(word)) {
|
|
268
|
+
if (builtinTypes.has(word))
|
|
269
|
+
return 'type.builtin';
|
|
270
|
+
return 'type.class';
|
|
271
|
+
}
|
|
272
|
+
// Check context for function definition
|
|
273
|
+
const beforeContext = context.slice(0, 0); // We don't have before context here
|
|
274
|
+
// Would need more context to detect 'function foo' pattern
|
|
275
|
+
return 'variable';
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Check if a regex literal can start in the current context
|
|
279
|
+
* This disambiguates between `/` as division vs regex start.
|
|
280
|
+
* Reads the last significant token from the threaded per-line state.
|
|
281
|
+
*/
|
|
282
|
+
canStartRegex(state) {
|
|
283
|
+
const lastToken = state.lastToken;
|
|
284
|
+
// At start of expression or line, regex is valid
|
|
285
|
+
if (!lastToken)
|
|
286
|
+
return true;
|
|
287
|
+
// After certain operators and keywords, regex is valid
|
|
288
|
+
if (REGEX_VALID_AFTER.has(lastToken))
|
|
289
|
+
return true;
|
|
290
|
+
// After identifiers, numbers, or closing brackets, it's division
|
|
291
|
+
if (DIVISION_VALID_AFTER.has(lastToken))
|
|
292
|
+
return false;
|
|
293
|
+
// If last token was an identifier or number, it's likely division
|
|
294
|
+
// (e.g., `x / 2` or `5 / 2`)
|
|
295
|
+
if (/^[a-zA-Z_$]/.test(lastToken) || /^\d/.test(lastToken)) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
// Default to allowing regex in ambiguous cases
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Update the last token for context tracking.
|
|
303
|
+
* Stores onto the threaded per-line state so that a shared tokenizer
|
|
304
|
+
* instance does not leak context across documents.
|
|
305
|
+
*/
|
|
306
|
+
updateLastToken(token, state) {
|
|
307
|
+
// Track significant tokens for regex/division disambiguation
|
|
308
|
+
const text = token.text.trim();
|
|
309
|
+
if (text && token.type !== 'comment' && !token.type.startsWith('comment.')) {
|
|
310
|
+
state.lastToken = text;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Try to match a regex literal using iterative parsing (avoids regex DoS)
|
|
315
|
+
* Returns the matched regex string or null if not a valid regex
|
|
316
|
+
*/
|
|
317
|
+
tryMatchRegex(text) {
|
|
318
|
+
if (text.length < 2 || text[0] !== '/')
|
|
319
|
+
return null;
|
|
320
|
+
let i = 1;
|
|
321
|
+
let inCharClass = false;
|
|
322
|
+
// Match regex body
|
|
323
|
+
while (i < text.length) {
|
|
324
|
+
const char = text[i];
|
|
325
|
+
// End of line means not a valid regex literal
|
|
326
|
+
if (char === '\n')
|
|
327
|
+
return null;
|
|
328
|
+
// Handle escape sequences
|
|
329
|
+
if (char === '\\' && i + 1 < text.length) {
|
|
330
|
+
i += 2;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
// Track character class [...] to ignore / inside
|
|
334
|
+
if (char === '[' && !inCharClass) {
|
|
335
|
+
inCharClass = true;
|
|
336
|
+
i++;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (char === ']' && inCharClass) {
|
|
340
|
+
inCharClass = false;
|
|
341
|
+
i++;
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
// End of regex body (not inside character class)
|
|
345
|
+
if (char === '/' && !inCharClass) {
|
|
346
|
+
i++; // Include the closing /
|
|
347
|
+
// Match flags
|
|
348
|
+
while (i < text.length && /[gimsuvy]/.test(text[i])) {
|
|
349
|
+
i++;
|
|
350
|
+
}
|
|
351
|
+
return text.slice(0, i);
|
|
352
|
+
}
|
|
353
|
+
i++;
|
|
354
|
+
}
|
|
355
|
+
return null; // Unclosed regex
|
|
356
|
+
}
|
|
357
|
+
tokenizeString(text, pos, delimiter) {
|
|
358
|
+
let i = 1;
|
|
359
|
+
while (i < text.length) {
|
|
360
|
+
if (text[i] === '\\' && i + 1 < text.length) {
|
|
361
|
+
i += 2; // Skip escape sequence
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (text[i] === delimiter) {
|
|
365
|
+
return createToken('string', text.slice(0, i + 1), pos);
|
|
366
|
+
}
|
|
367
|
+
if (text[i] === '\n') {
|
|
368
|
+
// Unterminated string
|
|
369
|
+
return createToken('string', text.slice(0, i), pos);
|
|
370
|
+
}
|
|
371
|
+
i++;
|
|
372
|
+
}
|
|
373
|
+
// Unterminated string at end of line
|
|
374
|
+
return createToken('string', text, pos);
|
|
375
|
+
}
|
|
376
|
+
tokenizeTemplateLiteral(text, pos, state) {
|
|
377
|
+
let i = 1;
|
|
378
|
+
let result = '`';
|
|
379
|
+
while (i < text.length) {
|
|
380
|
+
if (text[i] === '\\' && i + 1 < text.length) {
|
|
381
|
+
result += text.slice(i, i + 2);
|
|
382
|
+
i += 2;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (text[i] === '`') {
|
|
386
|
+
result += '`';
|
|
387
|
+
return createToken('string.template', result, pos);
|
|
388
|
+
}
|
|
389
|
+
if (text[i] === '$' && text[i + 1] === '{') {
|
|
390
|
+
// Template expression - for simplicity, tokenize up to this point
|
|
391
|
+
if (result.length > 1) {
|
|
392
|
+
// Return string part first
|
|
393
|
+
state.inTemplateLiteral = true;
|
|
394
|
+
state.templateDepth = (state.templateDepth ?? 0) + 1;
|
|
395
|
+
return createToken('string.template', result, pos);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
result += text[i];
|
|
399
|
+
i++;
|
|
400
|
+
}
|
|
401
|
+
// Multi-line template literal
|
|
402
|
+
state.inTemplateLiteral = true;
|
|
403
|
+
return createToken('string.template', result, pos);
|
|
404
|
+
}
|
|
405
|
+
tokenizeTemplateLiteralContinuation(line, startPos, state) {
|
|
406
|
+
const tokens = [];
|
|
407
|
+
let pos = startPos;
|
|
408
|
+
let result = '';
|
|
409
|
+
while (pos < line.length) {
|
|
410
|
+
if (line[pos] === '\\' && pos + 1 < line.length) {
|
|
411
|
+
result += line.slice(pos, pos + 2);
|
|
412
|
+
pos += 2;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (line[pos] === '`') {
|
|
416
|
+
result += '`';
|
|
417
|
+
tokens.push(createToken('string.template', result, startPos));
|
|
418
|
+
state.inTemplateLiteral = false;
|
|
419
|
+
state.templateDepth = Math.max(0, (state.templateDepth ?? 1) - 1);
|
|
420
|
+
return { tokens, pos: pos + 1 };
|
|
421
|
+
}
|
|
422
|
+
result += line[pos];
|
|
423
|
+
pos++;
|
|
424
|
+
}
|
|
425
|
+
if (result) {
|
|
426
|
+
tokens.push(createToken('string.template', result, startPos));
|
|
427
|
+
}
|
|
428
|
+
return { tokens, pos };
|
|
429
|
+
}
|
|
430
|
+
tokenizeJSXTag(text, pos, state) {
|
|
431
|
+
// Simple JSX tag detection
|
|
432
|
+
const match = text.match(/^<\/?[a-zA-Z][a-zA-Z0-9.]*(?:\s+[^>]*)?\/?>/);
|
|
433
|
+
if (match) {
|
|
434
|
+
return createToken('tag', match[0], pos);
|
|
435
|
+
}
|
|
436
|
+
// Just the opening <
|
|
437
|
+
return createToken('tag.punctuation', '<', pos);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// Factory functions for convenience
|
|
441
|
+
export function createJavaScriptTokenizer() {
|
|
442
|
+
return new JavaScriptTokenizer();
|
|
443
|
+
}
|
|
444
|
+
export function createTypeScriptTokenizer() {
|
|
445
|
+
return new JavaScriptTokenizer({ typescript: true });
|
|
446
|
+
}
|
|
447
|
+
export function createJSXTokenizer() {
|
|
448
|
+
return new JavaScriptTokenizer({ jsx: true });
|
|
449
|
+
}
|
|
450
|
+
export function createTSXTokenizer() {
|
|
451
|
+
return new JavaScriptTokenizer({ typescript: true, jsx: true });
|
|
452
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON tokenizer
|
|
3
|
+
*/
|
|
4
|
+
import type { TokenizedLine, TokenizerState } from '../types';
|
|
5
|
+
export declare class JSONTokenizer {
|
|
6
|
+
language: string;
|
|
7
|
+
getInitialState(): TokenizerState;
|
|
8
|
+
tokenizeLine(line: string, lineNumber: number, prevState?: TokenizerState): TokenizedLine;
|
|
9
|
+
private getNextToken;
|
|
10
|
+
private findStringEnd;
|
|
11
|
+
}
|
|
12
|
+
export declare function createJSONTokenizer(): JSONTokenizer;
|