diff --git a/.gitignore b/.gitignore index 27a1f10..6ca8955 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *.vsix +out/ +node_modules/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 7bc18a4..c0fe156 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,11 @@ "runtimeExecutable": "${execPath}", "args": [ "--extensionDevelopmentPath=${workspaceFolder}" - ] + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "preLaunchTask": "npm: watch" } ] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ae0cae2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,119 @@ +{ + "name": "phoenix", + "version": "0.1.2", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "phoenix", + "version": "0.1.2", + "license": "MIT", + "devDependencies": { + "@types/vscode": "^1.95.0", + "path": "^0.12.7", + "typescript": "^5.6.3" + }, + "engines": { + "vscode": "^1.25.1" + } + }, + "node_modules/@types/vscode": { + "version": "1.95.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.95.0.tgz", + "integrity": "sha512-0LBD8TEiNbet3NvWsmn59zLzOFu/txSlGxnv5yAFHCrhG9WvAnR3IvfHzMOs2aeWqgvNjq9pO99IUw8d3n+unw==", + "dev": true + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "dev": true, + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "dependencies": { + "inherits": "2.0.3" + } + } + }, + "dependencies": { + "@types/vscode": { + "version": "1.95.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.95.0.tgz", + "integrity": "sha512-0LBD8TEiNbet3NvWsmn59zLzOFu/txSlGxnv5yAFHCrhG9WvAnR3IvfHzMOs2aeWqgvNjq9pO99IUw8d3n+unw==", + "dev": true + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "dev": true, + "requires": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true + }, + "typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + } + } +} diff --git a/package.json b/package.json index e991731..5a87f02 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Phoenix Framework", "homepage": "https://www.phoenixframework.org", "description": "Syntax highlighting support for HEEx", - "version": "0.1.2", + "version": "0.1.2-m1", "author": "Marlus Saraiva", "publisher": "phoenixframework", "license": "MIT", @@ -13,11 +13,15 @@ "url": "https://github.com/phoenixframework/vscode-phoenix.git" }, "engines": { - "vscode": "^1.25.1" + "vscode": "^1.95.0" }, "categories": [ "Programming Languages" ], + "main": "./out/commands/extension.js", + "activationEvents": [ + "onLanguage:elixir" + ], "contributes": { "languages": [ { @@ -48,6 +52,24 @@ "text.html.heex": "phoenix-heex" } } + ], + "commands": [ + { + "command": "phoenixFunctionNavigator.goToDefinition", + "title": "Go to Component Definition" + }, + { + "command": "phoenixFunctionNavigator.goToReference", + "title": "Go to Component References" + } ] + }, + "scripts": { + "watch": "tsc" + }, + "devDependencies": { + "@types/vscode": "^1.95.0", + "path": "^0.12.7", + "typescript": "^5.6.3" } } diff --git a/src/commands/extension.ts b/src/commands/extension.ts new file mode 100644 index 0000000..e24f31e --- /dev/null +++ b/src/commands/extension.ts @@ -0,0 +1,94 @@ +import * as vscode from 'vscode'; + + +export function activate(context: vscode.ExtensionContext) { + const languages = [{ scheme: 'file', language: 'elixir' }, { scheme: 'file', language: 'phoenix-heex' }]; + + // Register the definition provider for Elixir files + const definitionProvider = vscode.languages.registerDefinitionProvider( + languages, + new PhoenixComponentDefinitionProvider() + ); + + // Register the reference provider for Elixir files + const referenceProvider = vscode.languages.registerReferenceProvider( + languages, + new PhoenixComponentReferenceProvider() + ); + + context.subscriptions.push(definitionProvider, referenceProvider); +} + +class PhoenixComponentDefinitionProvider implements vscode.DefinitionProvider { + async provideDefinition( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken + ): Promise { + const range = document.getWordRangeAtPosition(position, /<\.\w+/); + if (!range) return; + + const tagText = document.getText(range); + const tagMatch = tagText.match(/<\.(\w+)/) + if(!tagMatch) return; + + const functionName = tagMatch[1]; + + const functionRegex = new RegExp(`defp?\\s+${functionName}\\s*`); + const privateMatch = functionRegex.exec(document.getText()) + if(privateMatch){ + const position = document.positionAt(privateMatch.index); + return new vscode.Location(document.uri, position); + } + + const files = await vscode.workspace.findFiles('lib/*_web/**/*.ex'); + console.log("Candidate .ex files", files) + for (const file of files) { + const fileContent = await vscode.workspace.openTextDocument(file); + + const match = functionRegex.exec(fileContent.getText()); + + if (match) { + const position = fileContent.positionAt(match.index); + return new vscode.Location(file, position); + } + } + return; + } +} + +class PhoenixComponentReferenceProvider implements vscode.ReferenceProvider { + async provideReferences( + document: vscode.TextDocument, + position: vscode.Position, + context: vscode.ReferenceContext, + token: vscode.CancellationToken + ): Promise { + const range = document.getWordRangeAtPosition(position, /<\.\w+\/>/); + if (!range) return []; + + const tagText = document.getText(range); + const match = tagText.match(/<\.(\w+)\s*\/>/); + if (!match) return []; + + const functionName = match[1]; + const referencePattern = new RegExp(`<\\.${functionName}\\s*\\/?>`, 'g'); + + const locations: vscode.Location[] = []; + const files = await vscode.workspace.findFiles('**/*.heex', '**/deps/**'); + + for (const file of files) { + const fileContent = await vscode.workspace.openTextDocument(file); + const text = fileContent.getText(); + let match; + + while ((match = referencePattern.exec(text)) !== null) { + const position = fileContent.positionAt(match.index); + locations.push(new vscode.Location(file, position)); + } + } + return locations; + } +} + +export function deactivate() {} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..cff2aa1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "commonjs", + "outDir": "out", + "rootDir": "src", + "strict": true, + "sourceMap": true + }, + "include": ["src"] +} \ No newline at end of file