diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index efcff49..082abab 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -280,13 +280,27 @@ export class Compiler { const opValue = input.slice(operator.from, operator.to) switch (opValue) { - case '+=': instructions.push(['ADD']); break - case '-=': instructions.push(['SUB']); break - case '*=': instructions.push(['MUL']); break - case '/=': instructions.push(['DIV']); break - case '%=': instructions.push(['MOD']); break + case '+=': + instructions.push(['ADD']) + break + case '-=': + instructions.push(['SUB']) + break + case '*=': + instructions.push(['MUL']) + break + case '/=': + instructions.push(['DIV']) + break + case '%=': + instructions.push(['MOD']) + break default: - throw new CompilerError(`Unknown compound operator: ${opValue}`, operator.from, operator.to) + throw new CompilerError( + `Unknown compound operator: ${opValue}`, + operator.from, + operator.to + ) } // DUP and store (same as regular assignment) @@ -304,10 +318,8 @@ export class Compiler { } case terms.FunctionDef: { - const { paramNames, bodyNodes, catchVariable, catchBody, finallyBody } = getFunctionDefParts( - node, - input - ) + const { paramNames, bodyNodes, catchVariable, catchBody, finallyBody } = + getFunctionDefParts(node, input) const instructions: ProgramItem[] = [] const functionLabel: Label = `.func_${this.fnLabelCount++}` const afterLabel: Label = `.after_${functionLabel}` @@ -330,7 +342,13 @@ export class Compiler { if (catchVariable || finallyBody) { // If function has catch or finally, wrap body in try/catch/finally instructions.push( - ...this.#compileTryCatchFinally(compileFunctionBody, catchVariable, catchBody, finallyBody, input) + ...this.#compileTryCatchFinally( + compileFunctionBody, + catchVariable, + catchBody, + finallyBody, + input + ) ) } else { instructions.push(...compileFunctionBody()) diff --git a/src/compiler/tests/compiler.test.ts b/src/compiler/tests/compiler.test.ts index 9418f0b..74cf248 100644 --- a/src/compiler/tests/compiler.test.ts +++ b/src/compiler/tests/compiler.test.ts @@ -304,9 +304,12 @@ describe('default params', () => { }) test.skip('dict default', () => { - expect('make-person = do person=[name=Bob age=60]: person end; make-person') - .toEvaluateTo({ name: 'Bob', age: 60 }) - expect('make-person = do person=[name=Bob age=60]: person end; make-person [name=Jon age=21]') - .toEvaluateTo({ name: 'Jon', age: 21 }) + expect('make-person = do person=[name=Bob age=60]: person end; make-person').toEvaluateTo({ + name: 'Bob', + age: 60, + }) + expect( + 'make-person = do person=[name=Bob age=60]: person end; make-person [name=Jon age=21]' + ).toEvaluateTo({ name: 'Jon', age: 21 }) }) -}) \ No newline at end of file +}) diff --git a/vscode-extension/.gitignore b/vscode-extension/.gitignore index d2703e4..3a53163 100644 --- a/vscode-extension/.gitignore +++ b/vscode-extension/.gitignore @@ -1,3 +1,4 @@ node_modules -dist +client/dist +server/dist *.vsix diff --git a/vscode-extension/.vscode/launch.json b/vscode-extension/.vscode/launch.json index de34de7..92a7d04 100644 --- a/vscode-extension/.vscode/launch.json +++ b/vscode-extension/.vscode/launch.json @@ -9,7 +9,8 @@ "--extensionDevelopmentPath=${workspaceFolder}" ], "outFiles": [ - "${workspaceFolder}/dist/**/*.js" + "${workspaceFolder}/client/dist/**/*.js", + "${workspaceFolder}/server/dist/**/*.js" ], "preLaunchTask": "bun: compile" } diff --git a/vscode-extension/bun.lock b/vscode-extension/bun.lock index 2f9dd21..72072f7 100644 --- a/vscode-extension/bun.lock +++ b/vscode-extension/bun.lock @@ -3,6 +3,11 @@ "workspaces": { "": { "name": "shrimp", + "dependencies": { + "vscode-languageclient": "^9.0.1", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-textdocument": "^1.0.12", + }, "devDependencies": { "@types/node": "22.x", "@types/vscode": "^1.105.0", @@ -15,8 +20,28 @@ "@types/vscode": ["@types/vscode@1.105.0", "", {}, "sha512-Lotk3CTFlGZN8ray4VxJE7axIyLZZETQJVWi/lYoUVQuqfRxlQhVOfoejsD2V3dVXPSbS15ov5ZyowMAzgUqcw=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="], + + "vscode-languageclient": ["vscode-languageclient@9.0.1", "", { "dependencies": { "minimatch": "^5.1.0", "semver": "^7.3.7", "vscode-languageserver-protocol": "3.17.5" } }, "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA=="], + + "vscode-languageserver": ["vscode-languageserver@9.0.1", "", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="], + + "vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.17.5", "", { "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg=="], + + "vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.12", "", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="], + + "vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], } } diff --git a/vscode-extension/client/src/extension.ts b/vscode-extension/client/src/extension.ts new file mode 100644 index 0000000..7b32915 --- /dev/null +++ b/vscode-extension/client/src/extension.ts @@ -0,0 +1,74 @@ +import { + LanguageClient, + LanguageClientOptions, + ServerOptions, + TransportKind, +} from 'vscode-languageclient/node' +import * as vscode from 'vscode' + +export function activate(context: vscode.ExtensionContext) { + const serverModule = context.asAbsolutePath('server/dist/server.js') + + const serverOptions: ServerOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { module: serverModule, transport: TransportKind.ipc }, + } + + const clientOptions: LanguageClientOptions = { + documentSelector: [{ scheme: 'file', language: 'shrimp' }], + } + + const client = new LanguageClient( + 'shrimpLanguageServer', + 'Shrimp Language Server', + serverOptions, + clientOptions + ) + + client.start() + context.subscriptions.push(client) + + // Command: Show Parse Tree + context.subscriptions.push( + vscode.commands.registerCommand('shrimp.showParseTree', async () => { + const editor = vscode.window.activeTextEditor + if (!editor || editor.document.languageId !== 'shrimp') { + vscode.window.showErrorMessage('No active Shrimp file') + return + } + + const result = await client.sendRequest('shrimp/parseTree', { + uri: editor.document.uri.toString(), + }) + + const doc = await vscode.workspace.openTextDocument({ + content: result, + language: 'text', + }) + await vscode.window.showTextDocument(doc, { preview: false }) + }) + ) + + // Command: Show Bytecode + context.subscriptions.push( + vscode.commands.registerCommand('shrimp.showBytecode', async () => { + const editor = vscode.window.activeTextEditor + if (!editor || editor.document.languageId !== 'shrimp') { + vscode.window.showErrorMessage('No active Shrimp file') + return + } + + const result = await client.sendRequest('shrimp/bytecode', { + uri: editor.document.uri.toString(), + }) + + const doc = await vscode.workspace.openTextDocument({ + content: result, + language: 'text', + }) + await vscode.window.showTextDocument(doc, { preview: false }) + }) + ) +} + +export function deactivate() {} diff --git a/vscode-extension/package.json b/vscode-extension/package.json index ef82d3a..0dfdac0 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -1,7 +1,7 @@ { "name": "shrimp", "version": "0.0.1", - "main": "./dist/extension.js", + "main": "./client/dist/extension.js", "devDependencies": { "@types/vscode": "^1.105.0", "@types/node": "22.x", @@ -28,7 +28,29 @@ "[shrimp]": { "editor.semanticHighlighting.enabled": true } - } + }, + "commands": [ + { + "command": "shrimp.showParseTree", + "title": "Shrimp: Show Parse Tree" + }, + { + "command": "shrimp.showBytecode", + "title": "Shrimp: Show Bytecode" + } + ], + "keybindings": [ + { + "command": "shrimp.showParseTree", + "key": "alt+k alt+i", + "when": "editorLangId == shrimp" + }, + { + "command": "shrimp.showBytecode", + "key": "alt+k alt+,", + "when": "editorLangId == shrimp" + } + ] }, "description": "Language support for Shrimp shell scripting language", "displayName": "Shrimp", @@ -39,9 +61,16 @@ "publisher": "shrimp-lang", "scripts": { "vscode:prepublish": "bun run package", - "compile": "bun build src/extension.ts --outdir dist --target node --format cjs --external vscode", - "watch": "bun build src/extension.ts --outdir dist --target node --format cjs --external vscode --watch", - "package": "bun build src/extension.ts --outdir dist --target node --format cjs --external vscode --minify", + "compile": "bun run compile:client && bun run compile:server", + "compile:client": "bun build client/src/extension.ts --outdir client/dist --target node --format cjs --external vscode", + "compile:server": "bun build server/src/server.ts --outdir server/dist --target node --format cjs --external vscode-languageserver --external vscode-languageserver-textdocument", + "watch": "bun run compile:client --watch", + "package": "bun run compile:client --minify && bun run compile:server --minify", "check-types": "tsc --noEmit" + }, + "dependencies": { + "vscode-languageclient": "^9.0.1", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-textdocument": "^1.0.12" } } \ No newline at end of file diff --git a/vscode-extension/server/src/diagnostics.ts b/vscode-extension/server/src/diagnostics.ts new file mode 100644 index 0000000..2f1a449 --- /dev/null +++ b/vscode-extension/server/src/diagnostics.ts @@ -0,0 +1,93 @@ +import { TextDocument, Position } from 'vscode-languageserver-textdocument' +import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver/node' +import { parser } from '../../../src/parser/shrimp' +import { Compiler } from '../../../src/compiler/compiler' +import { CompilerError } from '../../../src/compiler/compilerError' + +export const buildDiagnostics = (textDocument: TextDocument): Diagnostic[] => { + const text = textDocument.getText() + const diagnostics = getParseErrors(textDocument) + + if (diagnostics.length > 0) { + return diagnostics + } + const diagnostic = getCompilerError(text) + if (diagnostic) return [diagnostic] + + return [] +} + +const getCompilerError = (text: string): Diagnostic | undefined => { + try { + new Compiler(text) + } catch (e) { + if (!(e instanceof CompilerError)) { + return unknownDiagnostic(getErrorMessage(e)) + } + + const lineInfo = e.lineAtPosition(text)! + const cause = e.cause ? ` Cause: ${e.cause}` : '' + const message = e.message + + if (!lineInfo) { + return unknownDiagnostic(message + cause) + } + + const diagnostic: Diagnostic = { + severity: DiagnosticSeverity.Error, + range: { + start: { line: lineInfo.lineNumber, character: lineInfo.columnStart }, + end: { line: lineInfo.lineNumber, character: lineInfo.columnEnd }, + }, + message: `Compiler error: ${message}${cause}`, + source: 'shrimp', + } + return diagnostic + } +} + +const unknownDiagnostic = (message: string): Diagnostic => { + const diagnostic: Diagnostic = { + severity: DiagnosticSeverity.Error, + range: { + start: { line: 0, character: 0 }, + end: { line: -1, character: -1 }, + }, + message, + source: 'shrimp', + } + return diagnostic +} + +const getParseErrors = (textDocument: TextDocument): Diagnostic[] => { + const tree = parser.parse(textDocument.getText()) + + const ranges: { start: Position; end: Position }[] = [] + tree.iterate({ + enter(n) { + if (n.type.isError) { + ranges.push({ + start: textDocument.positionAt(n.from), + end: textDocument.positionAt(n.to), + }) + return false + } + }, + }) + + return ranges.map((range) => { + return { + range, + severity: DiagnosticSeverity.Error, + message: 'Parse error: Invalid syntax', + source: 'shrimp', + } + }) +} + +const getErrorMessage = (error: unknown): string => { + if (error instanceof Error) { + return error.message + } + return String(error) +} diff --git a/vscode-extension/server/src/semanticTokens.ts b/vscode-extension/server/src/semanticTokens.ts new file mode 100644 index 0000000..cd2ac28 --- /dev/null +++ b/vscode-extension/server/src/semanticTokens.ts @@ -0,0 +1,101 @@ +import { parser } from '../../../src/parser/shrimp' +import * as Terms from '../../../src/parser/shrimp.terms' +import { SyntaxNode } from '@lezer/common' +import { TextDocument } from 'vscode-languageserver-textdocument' +import { SemanticTokensBuilder, SemanticTokenTypes } from 'vscode-languageserver/node' + +export const TOKEN_TYPES = [ + SemanticTokenTypes.function, + SemanticTokenTypes.variable, + SemanticTokenTypes.string, + SemanticTokenTypes.number, + SemanticTokenTypes.operator, + SemanticTokenTypes.keyword, + SemanticTokenTypes.parameter, + SemanticTokenTypes.property, + SemanticTokenTypes.regexp, +] + +export const TOKEN_MODIFIERS: string[] = [] + +export function buildSemanticTokens(document: TextDocument): number[] { + const text = document.getText() + const tree = parser.parse(text) + const builder = new SemanticTokensBuilder() + + walkTree(tree.topNode, document, builder) + + return builder.build().data +} + +// Walk the tree and collect tokens +function walkTree(node: SyntaxNode, document: TextDocument, builder: SemanticTokensBuilder) { + const tokenType = getTokenType(node.type.id) + + if (tokenType !== undefined) { + const start = document.positionAt(node.from) + const length = node.to - node.from + builder.push(start.line, start.character, length, tokenType, 0) + } + + let child = node.firstChild + while (child) { + walkTree(child, document, builder) + child = child.nextSibling + } +} + +// Map Lezer node IDs to semantic token type indices +function getTokenType(nodeTypeId: number): number | undefined { + switch (nodeTypeId) { + case Terms.FunctionCall: + case Terms.FunctionDef: + return TOKEN_TYPES.indexOf(SemanticTokenTypes.function) + + case Terms.Identifier: + case Terms.AssignableIdentifier: + case Terms.FunctionCallOrIdentifier: + return TOKEN_TYPES.indexOf(SemanticTokenTypes.variable) + + case Terms.String: + case Terms.StringFragment: + case Terms.Word: + return TOKEN_TYPES.indexOf(SemanticTokenTypes.string) + + case Terms.Number: + return TOKEN_TYPES.indexOf(SemanticTokenTypes.number) + + case Terms.Plus: + case Terms.Minus: + case Terms.Star: + case Terms.Slash: + case Terms.Eq: + case Terms.EqEq: + case Terms.Neq: + case Terms.Lt: + case Terms.Lte: + case Terms.Gt: + case Terms.Gte: + case Terms.Modulo: + case Terms.And: + case Terms.Or: + return TOKEN_TYPES.indexOf(SemanticTokenTypes.operator) + + case Terms.keyword: + case Terms.Do: + return TOKEN_TYPES.indexOf(SemanticTokenTypes.keyword) + + case Terms.Params: + case Terms.NamedParam: + return TOKEN_TYPES.indexOf(SemanticTokenTypes.parameter) + + case Terms.DotGet: + return TOKEN_TYPES.indexOf(SemanticTokenTypes.property) + + case Terms.Regex: + return TOKEN_TYPES.indexOf(SemanticTokenTypes.regexp) + + default: + return undefined + } +} diff --git a/vscode-extension/server/src/server.ts b/vscode-extension/server/src/server.ts new file mode 100644 index 0000000..40ce3fc --- /dev/null +++ b/vscode-extension/server/src/server.ts @@ -0,0 +1,105 @@ +import { TextDocuments } from 'vscode-languageserver/node' +import { TextDocument } from 'vscode-languageserver-textdocument' +import { createConnection, ProposedFeatures } from 'vscode-languageserver/node' +import { buildDiagnostics } from './diagnostics' +import { buildSemanticTokens, TOKEN_MODIFIERS, TOKEN_TYPES } from './semanticTokens' +import { parser } from '../../../src/parser/shrimp' +import { Compiler } from '../../../src/compiler/compiler' + +const connection = createConnection(ProposedFeatures.all) +const documents = new TextDocuments(TextDocument) +documents.listen(connection) + +connection.onInitialize(() => { + connection.console.log('🦐 Server initialized with capabilities') + return { + capabilities: { + textDocumentSync: 1, + semanticTokensProvider: { + legend: { + tokenTypes: TOKEN_TYPES, + tokenModifiers: TOKEN_MODIFIERS, + }, + full: true, + }, + }, + } +}) + +connection.languages.semanticTokens.on((params) => { + const document = documents.get(params.textDocument.uri) + if (!document) { + return { data: [] } + } + + const data = buildSemanticTokens(document) + return { data } +}) + +documents.onDidChangeContent((change) => { + const textDocument = change.document + + const diagnostics = buildDiagnostics(textDocument) + connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }) +}) + +connection.onRequest('shrimp/parseTree', (params: { uri: string }) => { + connection.console.log(`🦐 Parse tree requested for: ${params.uri}`) + const document = documents.get(params.uri) + if (!document) return 'Document not found' + + const text = document.getText() + const tree = parser.parse(text) + const treeString = tree.toString() + + // Format with indentation, without parentheses + let formatted = '' + let indent = 0 + for (let i = 0; i < treeString.length; i++) { + const char = treeString[i] + if (char === '(') { + formatted += '\n' + indent++ + formatted += ' '.repeat(indent) + } else if (char === ')') { + indent-- + } else if (char === ',') { + formatted += '\n' + formatted += ' '.repeat(indent) + } else { + formatted += char + } + } + + return formatted +}) + +connection.onRequest('shrimp/bytecode', (params: { uri: string }) => { + connection.console.log(`🦐 Bytecode requested for: ${params.uri}`) + const document = documents.get(params.uri) + if (!document) return 'Document not found' + + try { + const text = document.getText() + const compiler = new Compiler(text) + + // Format bytecode as readable string + let output = 'Bytecode:\n\n' + const bytecode = compiler.bytecode + + output += bytecode.instructions + .map((op, i) => `${i.toString().padStart(4)}: ${JSON.stringify(op)}`) + .join('\n') + + // Strip ANSI color codes + output = output.replace(/\x1b\[[0-9;]*m/g, '') + + return output + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error) + // Strip ANSI color codes from error message too + return `Compilation failed: ${errorMsg.replace(/\x1b\[[0-9;]*m/g, '')}` + } +}) + +connection.listen() diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts deleted file mode 100644 index 67dd16f..0000000 --- a/vscode-extension/src/extension.ts +++ /dev/null @@ -1,30 +0,0 @@ -import * as vscode from 'vscode' -import { ShrimpSemanticTokensProvider, legend } from './semanticTokens' -import { parser } from '../../src/parser/shrimp' - -// This method is called when your extension is activated -export function activate(context: vscode.ExtensionContext) { - console.log('Shrimp extension is now active!') - console.log('Parser loaded:', typeof parser, parser) - - // Test the parser - try { - const testTree = parser.parse('x = 42') - console.log('Parser test successful:', testTree.topNode.toString()) - } catch (error) { - console.error('Parser test failed:', error) - } - - // Register semantic tokens provider for Shrimp language - const provider = new ShrimpSemanticTokensProvider() - const selector: vscode.DocumentSelector = { language: 'shrimp', scheme: 'file' } - - const disposable = vscode.languages.registerDocumentSemanticTokensProvider(selector, provider, legend) - console.log('Registered semantic tokens provider:', disposable) - context.subscriptions.push(disposable) - - console.log('Legend token types:', legend.tokenTypes) -} - -// This method is called when your extension is deactivated -export function deactivate() {} diff --git a/vscode-extension/src/semanticTokens.ts b/vscode-extension/src/semanticTokens.ts deleted file mode 100644 index 02a7536..0000000 --- a/vscode-extension/src/semanticTokens.ts +++ /dev/null @@ -1,118 +0,0 @@ -import * as vscode from 'vscode' -import { parser } from '../../src/parser/shrimp' -import { Tree, SyntaxNode } from '@lezer/common' - -// Define the token types we'll use -const tokenTypes = [ - 'function', - 'variable', - 'string', - 'number', - 'operator', - 'keyword', - 'parameter', - 'property', - 'regexp', -] - -const tokenModifiers: string[] = [] - -export const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers) - -export class ShrimpSemanticTokensProvider implements vscode.DocumentSemanticTokensProvider { - async provideDocumentSemanticTokens( - document: vscode.TextDocument, - _token: vscode.CancellationToken - ): Promise { - try { - console.log('provideDocumentSemanticTokens called for:', document.fileName) - const tokensBuilder = new vscode.SemanticTokensBuilder(legend) - const text = document.getText() - console.log('Document text:', text) - console.log('About to parse with parser:', typeof parser) - const tree: Tree = parser.parse(text) - console.log('Parsed tree:', tree.topNode.toString()) - this.walkTree(tree.topNode, document, tokensBuilder) - - const result = tokensBuilder.build() - console.log('Built tokens, data length:', result.data.length) - return result - } catch (error) { - console.error('Error in provideDocumentSemanticTokens:', error) - throw error - } - } - - // Map Lezer node types to semantic token types - walkTree(node: SyntaxNode, document: vscode.TextDocument, builder: vscode.SemanticTokensBuilder) { - const tokenType = this.getTokenType(node.type.name) - - if (tokenType !== undefined) { - const start = document.positionAt(node.from) - const length = node.to - node.from - builder.push(start.line, start.character, length, tokenType, 0) - } - - // Recursively walk children - let child = node.firstChild - while (child) { - this.walkTree(child, document, builder) - child = child.nextSibling - } - } - - getTokenType(nodeTypeName: string): number | undefined { - // Map Lezer node names to VSCode semantic token types - switch (nodeTypeName) { - case 'FunctionCall': - case 'FunctionDef': - return tokenTypes.indexOf('function') - - case 'Identifier': - case 'AssignableIdentifier': - case 'FunctionCallOrIdentifier': - return tokenTypes.indexOf('variable') - - case 'String': - case 'StringFragment': - case 'Word': - return tokenTypes.indexOf('string') - - case 'Number': - return tokenTypes.indexOf('number') - - case 'Plus': - case 'Minus': - case 'Star': - case 'Slash': - case 'Eq': - case 'EqEq': - case 'Neq': - case 'Lt': - case 'Lte': - case 'Gt': - case 'Gte': - case 'Modulo': - case 'And': - case 'Or': - return tokenTypes.indexOf('operator') - - case 'keyword': - case 'Do': - return tokenTypes.indexOf('keyword') - - case 'Params': - case 'NamedParam': - return tokenTypes.indexOf('parameter') - - case 'DotGet': - return tokenTypes.indexOf('property') - - case 'Regex': - return tokenTypes.indexOf('regexp') - - default: - return undefined - } - } -} diff --git a/vscode-extension/tsconfig.json b/vscode-extension/tsconfig.json index 9794d47..a915a81 100644 --- a/vscode-extension/tsconfig.json +++ b/vscode-extension/tsconfig.json @@ -3,7 +3,7 @@ "target": "ES2022", "lib": ["ES2022"], "module": "commonjs", - "moduleResolution": "node", + "moduleResolution": "bundler", "outDir": "./dist", "strict": true, "esModuleInterop": true, @@ -11,6 +11,6 @@ "forceConsistentCasingInFileNames": true, "resolveJsonModule": true }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] + "include": ["client/src/**/*", "server/src/**/*", "../src/**/*"], + "exclude": ["node_modules", "client/dist", "server/dist"] }