diff --git a/vscode-extension/.gitignore b/vscode-extension/.gitignore new file mode 100644 index 0000000..d2703e4 --- /dev/null +++ b/vscode-extension/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +*.vsix diff --git a/vscode-extension/.vscode/launch.json b/vscode-extension/.vscode/launch.json new file mode 100644 index 0000000..de34de7 --- /dev/null +++ b/vscode-extension/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/dist/**/*.js" + ], + "preLaunchTask": "bun: compile" + } + ] +} diff --git a/vscode-extension/.vscode/tasks.json b/vscode-extension/.vscode/tasks.json new file mode 100644 index 0000000..cca534f --- /dev/null +++ b/vscode-extension/.vscode/tasks.json @@ -0,0 +1,30 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "label": "bun: compile", + "command": "bun", + "args": ["run", "compile"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": "$tsc", + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "type": "shell", + "label": "bun: watch", + "command": "bun", + "args": ["run", "watch"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": "$tsc-watch", + "isBackground": true + } + ] +} diff --git a/vscode-extension/.vscodeignore b/vscode-extension/.vscodeignore new file mode 100644 index 0000000..7fa6f56 --- /dev/null +++ b/vscode-extension/.vscodeignore @@ -0,0 +1,5 @@ +.vscode/** +src/** +tsconfig.json +node_modules/** +*.map diff --git a/vscode-extension/bun.lock b/vscode-extension/bun.lock new file mode 100644 index 0000000..2f9dd21 --- /dev/null +++ b/vscode-extension/bun.lock @@ -0,0 +1,22 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "shrimp", + "devDependencies": { + "@types/node": "22.x", + "@types/vscode": "^1.105.0", + "typescript": "^5.9.3", + }, + }, + }, + "packages": { + "@types/node": ["@types/node@22.19.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA=="], + + "@types/vscode": ["@types/vscode@1.105.0", "", {}, "sha512-Lotk3CTFlGZN8ray4VxJE7axIyLZZETQJVWi/lYoUVQuqfRxlQhVOfoejsD2V3dVXPSbS15ov5ZyowMAzgUqcw=="], + + "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=="], + } +} diff --git a/vscode-extension/icon.png b/vscode-extension/icon.png new file mode 100644 index 0000000..040c85b Binary files /dev/null and b/vscode-extension/icon.png differ diff --git a/vscode-extension/language-configuration.json b/vscode-extension/language-configuration.json new file mode 100644 index 0000000..271b346 --- /dev/null +++ b/vscode-extension/language-configuration.json @@ -0,0 +1,24 @@ +{ + "comments": { + "lineComment": { + "comment": "#" + } + }, + "brackets": [ + ["(", ")"], + ["[", "]"] + ], + "autoClosingPairs": [ + { "open": "(", "close": ")" }, + { "open": "[", "close": "]" }, + { "open": "'", "close": "'", "notIn": ["string"] }, + { "open": "\"", "close": "\"", "notIn": ["string"] } + ], + "surroundingPairs": [ + ["(", ")"], + ["[", "]"], + ["'", "'"], + ["\"", "\""] + ], + "wordPattern": "([a-z][a-z0-9-]*)|(-?\\d+\\.?\\d*)" +} diff --git a/vscode-extension/package.json b/vscode-extension/package.json new file mode 100644 index 0000000..ef82d3a --- /dev/null +++ b/vscode-extension/package.json @@ -0,0 +1,47 @@ +{ + "name": "shrimp", + "version": "0.0.1", + "main": "./dist/extension.js", + "devDependencies": { + "@types/vscode": "^1.105.0", + "@types/node": "22.x", + "typescript": "^5.9.3" + }, + "categories": [ + "Programming Languages" + ], + "contributes": { + "languages": [ + { + "id": "shrimp", + "aliases": [ + "Shrimp", + "shrimp" + ], + "extensions": [ + ".sh" + ], + "configuration": "./language-configuration.json" + } + ], + "configurationDefaults": { + "[shrimp]": { + "editor.semanticHighlighting.enabled": true + } + } + }, + "description": "Language support for Shrimp shell scripting language", + "displayName": "Shrimp", + "engines": { + "vscode": "^1.105.0" + }, + "icon": "icon.png", + "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", + "check-types": "tsc --noEmit" + } +} \ No newline at end of file diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts new file mode 100644 index 0000000..67dd16f --- /dev/null +++ b/vscode-extension/src/extension.ts @@ -0,0 +1,30 @@ +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 new file mode 100644 index 0000000..02a7536 --- /dev/null +++ b/vscode-extension/src/semanticTokens.ts @@ -0,0 +1,118 @@ +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 new file mode 100644 index 0000000..9794d47 --- /dev/null +++ b/vscode-extension/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "commonjs", + "moduleResolution": "node", + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}