mirror of
https://github.com/godotengine/godot-vscode-plugin.git
synced 2025-12-31 13:48:24 +03:00
DebugAdapter variables overhaul (#793)
- Redesigned the representation of godot objects to match internal structure of godot server - Lazy evaluation for the godot objects - Stack frames now can be switched with variables updated
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4.2.0
|
||||
with:
|
||||
node-version: 16.x
|
||||
node-version: 22.x
|
||||
|
||||
- name: Install Godot (Ubuntu)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
|
||||
@@ -2,7 +2,7 @@ const { defineConfig } = require('@vscode/test-cli');
|
||||
|
||||
module.exports = defineConfig(
|
||||
{
|
||||
// version: '1.84.0',
|
||||
// version: '1.96.4',
|
||||
label: 'unitTests',
|
||||
files: 'out/**/*.test.js',
|
||||
launchArgs: ['--disable-extensions'],
|
||||
|
||||
23
.vscode/launch.json
vendored
23
.vscode/launch.json
vendored
@@ -5,7 +5,6 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "Run Extension",
|
||||
"type": "extensionHost",
|
||||
@@ -48,5 +47,27 @@
|
||||
"VSCODE_DEBUG_MODE": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Run Extension with local workspace file",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--profile=temp",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"${workspaceFolder}/workspace.code-workspace"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"skipFiles": [
|
||||
"**/extensionHostProcess.js",
|
||||
"<node_internals>/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "npm: watch",
|
||||
"env": {
|
||||
"VSCODE_DEBUG_MODE": "true"
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
489
package-lock.json
generated
489
package-lock.json
generated
@@ -25,24 +25,27 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.11",
|
||||
"@types/chai-as-promised": "^8.0.1",
|
||||
"@types/chai-subset": "^1.3.5",
|
||||
"@types/marked": "^4.0.8",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@types/node": "^18.15.0",
|
||||
"@types/node": "^18.19.75",
|
||||
"@types/prismjs": "^1.16.8",
|
||||
"@types/vscode": "^1.96.0",
|
||||
"@types/ws": "^8.5.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||
"@typescript-eslint/eslint-plugin-tslint": "^5.57.1",
|
||||
"@typescript-eslint/parser": "^5.57.1",
|
||||
"@vscode/test-cli": "^0.0.4",
|
||||
"@vscode/test-cli": "^0.0.10",
|
||||
"@vscode/test-electron": "^2.3.8",
|
||||
"@vscode/vsce": "^2.29.0",
|
||||
"chai": "^4.3.10",
|
||||
"chai": "^4.5.0",
|
||||
"chai-as-promised": "^8.0.1",
|
||||
"chai-subset": "^1.6.0",
|
||||
"esbuild": "^0.17.15",
|
||||
"eslint": "^8.37.0",
|
||||
"mocha": "^10.2.0",
|
||||
"mocha": "^10.8.2",
|
||||
"sinon": "^19.0.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tslint": "^5.20.1",
|
||||
@@ -381,6 +384,12 @@
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bcoe/v8-coverage": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
|
||||
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
@@ -960,6 +969,15 @@
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@istanbuljs/schema": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
|
||||
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||
@@ -1030,6 +1048,50 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinonjs/commons": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
|
||||
"integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"type-detect": "4.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinonjs/commons/node_modules/type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinonjs/fake-timers": {
|
||||
"version": "13.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz",
|
||||
"integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sinonjs/commons": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinonjs/samsam": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz",
|
||||
"integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sinonjs/commons": "^3.0.1",
|
||||
"lodash.get": "^4.4.2",
|
||||
"type-detect": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinonjs/text-encoding": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz",
|
||||
"integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@tootallnate/once": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
||||
@@ -1069,6 +1131,15 @@
|
||||
"integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/chai-as-promised": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-8.0.1.tgz",
|
||||
"integrity": "sha512-dAlDhLjJlABwAVYObo9TPWYTRg9NaQM5CXeaeJYcYAkvzUf0JRLIiog88ao2Wqy/20WUnhbbUZcgvngEbJ3YXQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/chai": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/chai-subset": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.5.tgz",
|
||||
@@ -1078,6 +1149,12 @@
|
||||
"@types/chai": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
|
||||
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz",
|
||||
@@ -1097,10 +1174,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.18.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.3.tgz",
|
||||
"integrity": "sha512-0OVfGupTl3NBFr8+iXpfZ8NR7jfFO+P1Q+IO/q0wbo02wYkP5gy36phojeYWpLQ6WAMjl+VfmqUk2YbUfp0irA==",
|
||||
"dev": true
|
||||
"version": "18.19.75",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.75.tgz",
|
||||
"integrity": "sha512-UIksWtThob6ZVSyxcOqCLOUNg/dyO1Qvx4McgeuhrEtHTLFTf7BBhEazaE4K806FGTPtzd/2sE90qn4fVr7cyw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prismjs": {
|
||||
"version": "1.16.8",
|
||||
@@ -1441,13 +1521,15 @@
|
||||
"integrity": "sha512-2J27dysaXmvnfuhFGhfeuxfHRXunqNPxtBoR3koiTOA9rdxWNDTa1zIFLCFMSHJ9MPTPKFcBeblsyaCJCIlQxg=="
|
||||
},
|
||||
"node_modules/@vscode/test-cli": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.4.tgz",
|
||||
"integrity": "sha512-Tx0tfbxeSb2Xlo+jpd+GJrNLgKQHobhRHrYvOipZRZQYWZ82sKiK02VY09UjU1Czc/YnZnqyAnjUfaVGl3h09w==",
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.10.tgz",
|
||||
"integrity": "sha512-B0mMH4ia+MOOtwNiLi79XhA+MLmUItIC8FckEuKrVAVriIuSWjt7vv4+bF8qVFiNFe4QRfzPaIZk39FZGWEwHA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/mocha": "^10.0.2",
|
||||
"c8": "^9.1.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"enhanced-resolve": "^5.15.0",
|
||||
"glob": "^10.3.10",
|
||||
"minimatch": "^9.0.3",
|
||||
"mocha": "^10.2.0",
|
||||
@@ -1456,6 +1538,9 @@
|
||||
},
|
||||
"bin": {
|
||||
"vscode-test": "out/bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@vscode/test-cli/node_modules/ansi-regex": {
|
||||
@@ -2162,6 +2247,116 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/c8": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz",
|
||||
"integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@bcoe/v8-coverage": "^0.2.3",
|
||||
"@istanbuljs/schema": "^0.1.3",
|
||||
"find-up": "^5.0.0",
|
||||
"foreground-child": "^3.1.1",
|
||||
"istanbul-lib-coverage": "^3.2.0",
|
||||
"istanbul-lib-report": "^3.0.1",
|
||||
"istanbul-reports": "^3.1.6",
|
||||
"test-exclude": "^6.0.0",
|
||||
"v8-to-istanbul": "^9.0.0",
|
||||
"yargs": "^17.7.2",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"c8": "bin/c8.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/c8/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/c8/node_modules/cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/c8/node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/c8/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/c8/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/c8/node_modules/yargs": {
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cliui": "^8.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.3",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/c8/node_modules/yargs-parser": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
@@ -2220,6 +2415,27 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/chai-as-promised": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-8.0.1.tgz",
|
||||
"integrity": "sha512-OIEJtOL8xxJSH8JJWbIoRjybbzR52iFuDHuF8eb+nTPD6tgXLjRqsgnUGqQfFODxYvq5QdirT0pN9dZ0+Gz6rA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"check-error": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"chai": ">= 2.1.2 < 6"
|
||||
}
|
||||
},
|
||||
"node_modules/chai-as-promised/node_modules/check-error": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
|
||||
"integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
}
|
||||
},
|
||||
"node_modules/chai-subset": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chai-subset/-/chai-subset-1.6.0.tgz",
|
||||
@@ -2463,6 +2679,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
@@ -2771,6 +2993,19 @@
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
|
||||
@@ -3569,6 +3804,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/graphemer": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
|
||||
@@ -3660,6 +3901,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
|
||||
@@ -3914,6 +4161,63 @@
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/istanbul-lib-coverage": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
|
||||
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-lib-report": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
|
||||
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"istanbul-lib-coverage": "^3.0.0",
|
||||
"make-dir": "^4.0.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-lib-report/node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-lib-report/node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-reports": {
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
|
||||
"integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"html-escaper": "^2.0.0",
|
||||
"istanbul-lib-report": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
|
||||
@@ -4054,6 +4358,12 @@
|
||||
"setimmediate": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/just-extend": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz",
|
||||
"integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
|
||||
@@ -4151,6 +4461,13 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.get": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
|
||||
"deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
@@ -4305,6 +4622,33 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
|
||||
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"semver": "^7.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir/node_modules/semver": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
@@ -4672,6 +5016,19 @@
|
||||
"resolved": "https://registry.npmjs.org/net/-/net-1.0.2.tgz",
|
||||
"integrity": "sha1-0XV+yaf7I3HYPPR1XOPifhCCk4g="
|
||||
},
|
||||
"node_modules/nise": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz",
|
||||
"integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sinonjs/commons": "^3.0.1",
|
||||
"@sinonjs/fake-timers": "^13.0.1",
|
||||
"@sinonjs/text-encoding": "^0.7.3",
|
||||
"just-extend": "^6.2.0",
|
||||
"path-to-regexp": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "2.30.1",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz",
|
||||
@@ -4928,6 +5285,15 @@
|
||||
"node": "14 || >=16.14"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
|
||||
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
@@ -5381,6 +5747,54 @@
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sinon": {
|
||||
"version": "19.0.2",
|
||||
"resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz",
|
||||
"integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sinonjs/commons": "^3.0.1",
|
||||
"@sinonjs/fake-timers": "^13.0.2",
|
||||
"@sinonjs/samsam": "^8.0.1",
|
||||
"diff": "^7.0.0",
|
||||
"nise": "^6.1.1",
|
||||
"supports-color": "^7.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/sinon"
|
||||
}
|
||||
},
|
||||
"node_modules/sinon/node_modules/diff": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
|
||||
"integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/sinon/node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/sinon/node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
@@ -5560,6 +5974,15 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
||||
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
||||
@@ -5616,6 +6039,20 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
|
||||
"integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@istanbuljs/schema": "^0.1.2",
|
||||
"glob": "^7.1.4",
|
||||
"minimatch": "^3.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/text-table": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
@@ -5847,6 +6284,12 @@
|
||||
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
@@ -5883,6 +6326,30 @@
|
||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/v8-to-istanbul": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
|
||||
"integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.12",
|
||||
"@types/istanbul-lib-coverage": "^2.0.1",
|
||||
"convert-source-map": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-jsonrpc": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz",
|
||||
|
||||
11
package.json
11
package.json
@@ -871,24 +871,27 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.11",
|
||||
"@types/chai-as-promised": "^8.0.1",
|
||||
"@types/chai-subset": "^1.3.5",
|
||||
"@types/marked": "^4.0.8",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@types/node": "^18.15.0",
|
||||
"@types/node": "^18.19.75",
|
||||
"@types/prismjs": "^1.16.8",
|
||||
"@types/vscode": "^1.96.0",
|
||||
"@types/ws": "^8.5.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||
"@typescript-eslint/eslint-plugin-tslint": "^5.57.1",
|
||||
"@typescript-eslint/parser": "^5.57.1",
|
||||
"@vscode/test-cli": "^0.0.4",
|
||||
"@vscode/test-cli": "^0.0.10",
|
||||
"@vscode/test-electron": "^2.3.8",
|
||||
"@vscode/vsce": "^2.29.0",
|
||||
"chai": "^4.3.10",
|
||||
"chai": "^4.5.0",
|
||||
"chai-as-promised": "^8.0.1",
|
||||
"chai-subset": "^1.6.0",
|
||||
"esbuild": "^0.17.15",
|
||||
"eslint": "^8.37.0",
|
||||
"mocha": "^10.2.0",
|
||||
"mocha": "^10.8.2",
|
||||
"sinon": "^19.0.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tslint": "^5.20.1",
|
||||
|
||||
@@ -46,7 +46,7 @@ export interface GodotVariable {
|
||||
scope_path?: string;
|
||||
sub_values?: GodotVariable[];
|
||||
value: any;
|
||||
type?: bigint;
|
||||
type?: number;
|
||||
id?: bigint;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ import { GodotDebugSession as Godot4DebugSession } from "./godot4/debug_session"
|
||||
import { register_command, set_context, createLogger, get_project_version } from "../utils";
|
||||
import { SceneTreeProvider, SceneNode } from "./scene_tree_provider";
|
||||
import { InspectorProvider, RemoteProperty } from "./inspector_provider";
|
||||
import { GodotVariable, RawObject } from "./debug_runtime";
|
||||
import { GodotObject, GodotObjectPromise } from "./godot4/variables/godot_object_promise";
|
||||
import { InvalidatedEvent } from "@vscode/debugadapter";
|
||||
|
||||
const log = createLogger("debugger", { output: "Godot Debugger" });
|
||||
|
||||
@@ -256,52 +259,58 @@ export class GodotDebugger implements DebugAdapterDescriptorFactory, DebugConfig
|
||||
}
|
||||
}
|
||||
|
||||
public inspect_node(element: SceneNode | RemoteProperty) {
|
||||
this.session?.controller.request_inspect_object(BigInt(element.object_id));
|
||||
public async inspect_node(element: SceneNode | RemoteProperty) {
|
||||
await this.fill_provider_tree(element.label, BigInt(element.object_id));
|
||||
}
|
||||
|
||||
private create_godot_variable(godot_object: GodotObject): GodotVariable {
|
||||
return {
|
||||
value: {
|
||||
type_name: function() { return godot_object.type; },
|
||||
stringify_value: function() { return `<${godot_object.godot_id}>`; },
|
||||
sub_values: function() {return godot_object.sub_values; },
|
||||
},
|
||||
} as GodotVariable;
|
||||
}
|
||||
|
||||
private async fill_provider_tree(label: string, godot_id: bigint, force_refresh = false) {
|
||||
if (this.session instanceof Godot4DebugSession) {
|
||||
const godot_object = await this.session.variables_manager.get_godot_object(BigInt(godot_id), force_refresh);
|
||||
const va = this.create_godot_variable(godot_object);
|
||||
this.inspectorProvider.fill_tree(label, godot_object.type, Number(godot_object.godot_id), va);
|
||||
} else {
|
||||
this.session?.controller.request_inspect_object(BigInt(godot_id));
|
||||
this.session?.inspect_callbacks.set(
|
||||
BigInt(element.object_id),
|
||||
BigInt(godot_id),
|
||||
(class_name, variable) => {
|
||||
this.inspectorProvider.fill_tree(
|
||||
element.label,
|
||||
label,
|
||||
class_name,
|
||||
element.object_id,
|
||||
Number(godot_id),
|
||||
variable
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public refresh_scene_tree() {
|
||||
this.session?.controller.request_scene_tree();
|
||||
}
|
||||
|
||||
public refresh_inspector() {
|
||||
public async refresh_inspector() {
|
||||
if (this.inspectorProvider.has_tree()) {
|
||||
const name = this.inspectorProvider.get_top_name();
|
||||
const label = this.inspectorProvider.get_top_name();
|
||||
const id = this.inspectorProvider.get_top_id();
|
||||
|
||||
this.session?.controller.request_inspect_object(BigInt(id));
|
||||
this.session?.inspect_callbacks.set(
|
||||
BigInt(id),
|
||||
(class_name, variable) => {
|
||||
this.inspectorProvider.fill_tree(
|
||||
name,
|
||||
class_name,
|
||||
id,
|
||||
variable
|
||||
);
|
||||
},
|
||||
);
|
||||
await this.fill_provider_tree(label, BigInt(id), /*force_refresh*/ true);
|
||||
}
|
||||
}
|
||||
|
||||
public edit_value(property: RemoteProperty) {
|
||||
public async edit_value(property: RemoteProperty) {
|
||||
const previous_value = property.value;
|
||||
const type = typeof previous_value;
|
||||
const is_float = type === "number" && !Number.isInteger(previous_value);
|
||||
window
|
||||
.showInputBox({ value: `${property.description}` })
|
||||
.then((value) => {
|
||||
const value = await window.showInputBox({ value: `${property.description}` });
|
||||
let new_parsed_value: any;
|
||||
switch (type) {
|
||||
case "string":
|
||||
@@ -356,21 +365,12 @@ export class GodotDebugger implements DebugAdapterDescriptorFactory, DebugConfig
|
||||
);
|
||||
}
|
||||
|
||||
const name = this.inspectorProvider.get_top_name();
|
||||
const id = this.inspectorProvider.get_top_id();
|
||||
const label = this.inspectorProvider.get_top_name();
|
||||
const godot_id = BigInt(this.inspectorProvider.get_top_id());
|
||||
|
||||
this.session?.controller.request_inspect_object(BigInt(id));
|
||||
this.session?.inspect_callbacks.set(
|
||||
BigInt(id),
|
||||
(class_name, variable) => {
|
||||
this.inspectorProvider.fill_tree(
|
||||
name,
|
||||
class_name,
|
||||
id,
|
||||
variable
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
await this.fill_provider_tree(label, godot_id, /*force_refresh*/ true);
|
||||
// const res = await debug.activeDebugSession?.customRequest("refreshVariables"); // refresh vscode.debug variables
|
||||
this.session.sendEvent(new InvalidatedEvent(["variables"]));
|
||||
console.log("foo");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,36 +9,25 @@ import {
|
||||
import { DebugProtocol } from "@vscode/debugprotocol";
|
||||
import { Subject } from "await-notify";
|
||||
import * as fs from "node:fs";
|
||||
import { debug } from "vscode";
|
||||
|
||||
import { createLogger } from "../../utils";
|
||||
import { GodotDebugData, GodotStackVars, GodotVariable } from "../debug_runtime";
|
||||
import { GodotDebugData } from "../debug_runtime";
|
||||
import { AttachRequestArguments, LaunchRequestArguments } from "../debugger";
|
||||
import { SceneTreeProvider } from "../scene_tree_provider";
|
||||
import { is_variable_built_in_type, parse_variable } from "./helpers";
|
||||
import { ServerController } from "./server_controller";
|
||||
import { ObjectId, RawObject } from "./variables/variants";
|
||||
import { VariablesManager } from "./variables/variables_manager";
|
||||
|
||||
const log = createLogger("debugger.session", { output: "Godot Debugger" });
|
||||
|
||||
interface Variable {
|
||||
variable: GodotVariable;
|
||||
index: number;
|
||||
object_id: number;
|
||||
}
|
||||
|
||||
export class GodotDebugSession extends LoggingDebugSession {
|
||||
private all_scopes: GodotVariable[];
|
||||
public controller = new ServerController(this);
|
||||
public debug_data = new GodotDebugData(this);
|
||||
public sceneTree: SceneTreeProvider;
|
||||
private exception = false;
|
||||
private got_scope: Subject = new Subject();
|
||||
private ongoing_inspections: bigint[] = [];
|
||||
private previous_inspections: bigint[] = [];
|
||||
private configuration_done: Subject = new Subject();
|
||||
private mode: "launch" | "attach" | "" = "";
|
||||
public inspect_callbacks: Map<bigint, (class_name: string, variable: GodotVariable) => void> = new Map();
|
||||
|
||||
public variables_manager: VariablesManager;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
@@ -126,34 +115,6 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
}
|
||||
}
|
||||
|
||||
protected async evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments) {
|
||||
log.info("evaluateRequest", args);
|
||||
await debug.activeDebugSession.customRequest("scopes", { frameId: 0 });
|
||||
|
||||
if (this.all_scopes) {
|
||||
try {
|
||||
const variable = this.get_variable(args.expression, null, null, null);
|
||||
const parsed_variable = parse_variable(variable.variable);
|
||||
response.body = {
|
||||
result: parsed_variable.value,
|
||||
variablesReference: !is_variable_built_in_type(variable.variable) ? variable.index : 0,
|
||||
};
|
||||
} catch (error) {
|
||||
response.success = false;
|
||||
response.message = error.toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.body) {
|
||||
response.body = {
|
||||
result: "null",
|
||||
variablesReference: 0,
|
||||
};
|
||||
}
|
||||
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments) {
|
||||
log.info("nextRequest", args);
|
||||
if (!this.exception) {
|
||||
@@ -170,21 +131,6 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
}
|
||||
}
|
||||
|
||||
protected async scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments) {
|
||||
log.info("scopesRequest", args);
|
||||
this.controller.request_stack_frame_vars(args.frameId);
|
||||
await this.got_scope.wait(2000);
|
||||
|
||||
response.body = {
|
||||
scopes: [
|
||||
{ name: "Locals", variablesReference: 1, expensive: false },
|
||||
{ name: "Members", variablesReference: 2, expensive: false },
|
||||
{ name: "Globals", variablesReference: 3, expensive: false },
|
||||
],
|
||||
};
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected setBreakPointsRequest(
|
||||
response: DebugProtocol.SetBreakpointsResponse,
|
||||
args: DebugProtocol.SetBreakpointsArguments,
|
||||
@@ -225,25 +171,6 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
}
|
||||
}
|
||||
|
||||
protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments) {
|
||||
log.info("stackTraceRequest", args);
|
||||
if (this.debug_data.last_frame) {
|
||||
response.body = {
|
||||
totalFrames: this.debug_data.last_frames.length,
|
||||
stackFrames: this.debug_data.last_frames.map((sf) => {
|
||||
return {
|
||||
id: sf.id,
|
||||
name: sf.function,
|
||||
line: sf.line,
|
||||
column: 1,
|
||||
source: new Source(sf.file, `${this.debug_data.projectPath}/${sf.file.replace("res://", "")}`),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments) {
|
||||
log.info("stepInRequest", args);
|
||||
if (!this.exception) {
|
||||
@@ -272,299 +199,97 @@ export class GodotDebugSession extends LoggingDebugSession {
|
||||
protected threadsRequest(response: DebugProtocol.ThreadsResponse) {
|
||||
log.info("threadsRequest");
|
||||
response.body = { threads: [new Thread(0, "thread_1")] };
|
||||
log.info("threadsRequest response", response);
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected async variablesRequest(
|
||||
response: DebugProtocol.VariablesResponse,
|
||||
args: DebugProtocol.VariablesArguments,
|
||||
) {
|
||||
log.info("variablesRequest", args);
|
||||
if (!this.all_scopes) {
|
||||
protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments) {
|
||||
log.info("stackTraceRequest", args);
|
||||
if (this.debug_data.last_frame) {
|
||||
response.body = {
|
||||
variables: [],
|
||||
totalFrames: this.debug_data.last_frames.length,
|
||||
stackFrames: this.debug_data.last_frames.map((sf) => {
|
||||
return {
|
||||
id: sf.id,
|
||||
name: sf.function,
|
||||
line: sf.line,
|
||||
column: 1,
|
||||
source: new Source(sf.file, `${this.debug_data.projectPath}/${sf.file.replace("res://", "")}`),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
log.info("stackTraceRequest response", response);
|
||||
this.sendResponse(response);
|
||||
return;
|
||||
}
|
||||
|
||||
const reference = this.all_scopes[args.variablesReference];
|
||||
let variables: DebugProtocol.Variable[];
|
||||
protected async scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments) {
|
||||
log.info("scopesRequest", args);
|
||||
// this.variables_manager.variablesFrameId = args.frameId;
|
||||
|
||||
if (!reference.sub_values) {
|
||||
variables = [];
|
||||
} else {
|
||||
variables = reference.sub_values.map((va) => {
|
||||
const sva = this.all_scopes.find(
|
||||
(sva) => sva && sva.scope_path === va.scope_path && sva.name === va.name,
|
||||
);
|
||||
if (sva) {
|
||||
return parse_variable(
|
||||
sva,
|
||||
this.all_scopes.findIndex(
|
||||
(va_idx) =>
|
||||
va_idx &&
|
||||
va_idx.scope_path === `${reference.scope_path}.${reference.name}` &&
|
||||
va_idx.name === va.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
// TODO: create scopes dynamically for a given frame
|
||||
const vscode_scope_ids = this.variables_manager.get_or_create_frame_scopes(args.frameId);
|
||||
const scopes_with_references = [
|
||||
{name: "Locals", variablesReference: vscode_scope_ids.Locals, expensive: false},
|
||||
{name: "Members", variablesReference: vscode_scope_ids.Members, expensive: false},
|
||||
{name: "Globals", variablesReference: vscode_scope_ids.Globals, expensive: false},
|
||||
];
|
||||
|
||||
response.body = {
|
||||
scopes: scopes_with_references
|
||||
// scopes: [
|
||||
// { name: "Locals", variablesReference: 1, expensive: false },
|
||||
// { name: "Members", variablesReference: 2, expensive: false },
|
||||
// { name: "Globals", variablesReference: 3, expensive: false },
|
||||
// ],
|
||||
};
|
||||
|
||||
log.info("scopesRequest response", response);
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments) {
|
||||
log.info("variablesRequest", args);
|
||||
try {
|
||||
const variables = await this.variables_manager.get_vscode_object(args.variablesReference);
|
||||
|
||||
response.body = {
|
||||
variables: variables,
|
||||
};
|
||||
} catch (error) {
|
||||
log.error("variablesRequest", error);
|
||||
response.success = false;
|
||||
response.message = error.toString();
|
||||
}
|
||||
|
||||
log.info("variablesRequest response", response);
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected async evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments) {
|
||||
log.info("evaluateRequest", args);
|
||||
|
||||
try {
|
||||
const parsed_variable = await this.variables_manager.get_vscode_variable_by_name(args.expression, args.frameId);
|
||||
response.body = {
|
||||
result: parsed_variable.value,
|
||||
variablesReference: parsed_variable.variablesReference
|
||||
};
|
||||
} catch (error) {
|
||||
response.success = false;
|
||||
response.message = error.toString();
|
||||
response.body = {
|
||||
result: "null",
|
||||
variablesReference: 0,
|
||||
};
|
||||
}
|
||||
|
||||
log.info("evaluateRequest response", response);
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
public set_exception(exception: boolean) {
|
||||
this.exception = true;
|
||||
}
|
||||
|
||||
public set_scopes(stackVars: GodotStackVars) {
|
||||
this.all_scopes = [
|
||||
undefined,
|
||||
{
|
||||
name: "local",
|
||||
value: undefined,
|
||||
sub_values: stackVars.locals,
|
||||
scope_path: "@",
|
||||
},
|
||||
{
|
||||
name: "member",
|
||||
value: undefined,
|
||||
sub_values: stackVars.members,
|
||||
scope_path: "@",
|
||||
},
|
||||
{
|
||||
name: "global",
|
||||
value: undefined,
|
||||
sub_values: stackVars.globals,
|
||||
scope_path: "@",
|
||||
},
|
||||
];
|
||||
|
||||
for (const va of stackVars.locals) {
|
||||
va.scope_path = "@.local";
|
||||
this.append_variable(va);
|
||||
}
|
||||
|
||||
for (const va of stackVars.members) {
|
||||
va.scope_path = "@.member";
|
||||
this.append_variable(va);
|
||||
}
|
||||
|
||||
for (const va of stackVars.globals) {
|
||||
va.scope_path = "@.global";
|
||||
this.append_variable(va);
|
||||
}
|
||||
|
||||
this.add_to_inspections();
|
||||
|
||||
if (this.ongoing_inspections.length === 0 && stackVars.remaining == 0) {
|
||||
// in case if stackVars are empty, the this.ongoing_inspections will be empty also
|
||||
// godot 4.3 generates empty stackVars with remaining > 0 on a breakpoint stop
|
||||
// godot will continue sending `stack_frame_vars` until all `stackVars.remaining` are sent
|
||||
// at this moment `stack_frame_vars` will call `set_scopes` again with cumulated stackVars
|
||||
// TODO: godot won't send the recursive variable, see related https://github.com/godotengine/godot/issues/76019
|
||||
// in that case the vscode extension fails to call this.got_scope.notify();
|
||||
// hence the extension needs to be refactored to handle missing `stack_frame_vars` messages
|
||||
this.previous_inspections = [];
|
||||
this.got_scope.notify();
|
||||
}
|
||||
}
|
||||
|
||||
public set_inspection(id: bigint, rawObject: RawObject, sub_values: GodotVariable[]) {
|
||||
const variables = this.all_scopes.filter((va) => va && va.value instanceof ObjectId && va.value.id === id);
|
||||
|
||||
for (const va of variables) {
|
||||
const index = this.all_scopes.findIndex((va_id) => va_id === va);
|
||||
if (index < 0) {
|
||||
continue;
|
||||
}
|
||||
const old = this.all_scopes.splice(index, 1);
|
||||
// GodotVariable instance will be different for different variables, even if the referenced object id is the same:
|
||||
const replacement = {value: rawObject, sub_values: sub_values } as GodotVariable;
|
||||
replacement.name = old[0].name;
|
||||
replacement.scope_path = old[0].scope_path;
|
||||
this.append_variable(replacement, index);
|
||||
}
|
||||
|
||||
const ongoing_inspections_index = this.ongoing_inspections.findIndex((va_id) => va_id === id);
|
||||
if (ongoing_inspections_index >= 0) {
|
||||
this.ongoing_inspections.splice(ongoing_inspections_index, 1);
|
||||
}
|
||||
|
||||
|
||||
this.previous_inspections.push(id);
|
||||
|
||||
// this.add_to_inspections();
|
||||
|
||||
if (this.ongoing_inspections.length === 0) {
|
||||
// the `ongoing_inspections` is not empty, until all scopes are fully resolved
|
||||
// once last inspection is resolved: Notify that we got full scope
|
||||
this.previous_inspections = [];
|
||||
this.got_scope.notify();
|
||||
}
|
||||
}
|
||||
|
||||
private add_to_inspections() {
|
||||
const scopes_to_check = this.all_scopes.filter((va) => va && va.value instanceof ObjectId);
|
||||
for (const va of scopes_to_check) {
|
||||
if (
|
||||
!this.ongoing_inspections.includes(va.value.id) &&
|
||||
!this.previous_inspections.includes(va.value.id)
|
||||
) {
|
||||
this.controller.request_inspect_object(va.value.id);
|
||||
this.ongoing_inspections.push(va.value.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected get_variable(
|
||||
expression: string,
|
||||
root: GodotVariable = null,
|
||||
index = 0,
|
||||
object_id: number = null,
|
||||
): Variable {
|
||||
let result: Variable = {
|
||||
variable: null,
|
||||
index: null,
|
||||
object_id: null,
|
||||
};
|
||||
|
||||
if (!root) {
|
||||
if (!expression.includes("self")) {
|
||||
expression = "self." + expression;
|
||||
}
|
||||
|
||||
root = this.all_scopes.find((x) => x && x.name === "self");
|
||||
object_id = this.all_scopes.find((x) => x && x.name === "id" && x.scope_path === "@.member.self").value;
|
||||
}
|
||||
|
||||
const items = expression.split(".");
|
||||
let propertyName = items[index + 1];
|
||||
let path = items
|
||||
.slice(0, index + 1)
|
||||
.join(".")
|
||||
.split("self.")
|
||||
.join("")
|
||||
.split("self")
|
||||
.join("")
|
||||
.split("[")
|
||||
.join(".")
|
||||
.split("]")
|
||||
.join("");
|
||||
|
||||
if (items.length === 1 && items[0] === "self") {
|
||||
propertyName = "self";
|
||||
}
|
||||
|
||||
// Detect index/key
|
||||
let key = (propertyName.match(/(?<=\[).*(?=\])/) || [null])[0];
|
||||
if (key) {
|
||||
key = key.replace(/['"]+/g, "");
|
||||
propertyName = propertyName
|
||||
.split(/(?<=\[).*(?=\])/)
|
||||
.join("")
|
||||
.split("[]")
|
||||
.join("");
|
||||
if (path) path += ".";
|
||||
path += propertyName;
|
||||
propertyName = key;
|
||||
}
|
||||
|
||||
function sanitizeName(name: string) {
|
||||
return name.split("Members/").join("").split("Locals/").join("");
|
||||
}
|
||||
|
||||
function sanitizeScopePath(scope_path: string) {
|
||||
return scope_path
|
||||
.split("@.member.self.")
|
||||
.join("")
|
||||
.split("@.member.self")
|
||||
.join("")
|
||||
.split("@.member.")
|
||||
.join("")
|
||||
.split("@.member")
|
||||
.join("")
|
||||
.split("@.local.")
|
||||
.join("")
|
||||
.split("@.local")
|
||||
.join("")
|
||||
.split("Locals/")
|
||||
.join("")
|
||||
.split("Members/")
|
||||
.join("")
|
||||
.split("@")
|
||||
.join("");
|
||||
}
|
||||
|
||||
const sanitized_all_scopes = this.all_scopes
|
||||
.filter((x) => x)
|
||||
.map((x) => ({
|
||||
sanitized: {
|
||||
name: sanitizeName(x.name),
|
||||
scope_path: sanitizeScopePath(x.scope_path),
|
||||
},
|
||||
real: x,
|
||||
}));
|
||||
|
||||
result.variable = sanitized_all_scopes.find(
|
||||
(x) => x.sanitized.name === propertyName && x.sanitized.scope_path === path,
|
||||
)?.real;
|
||||
if (!result.variable) {
|
||||
throw new Error(`Could not find: ${propertyName}`);
|
||||
}
|
||||
|
||||
if (root.value.entries) {
|
||||
if (result.variable.name === "self") {
|
||||
result.object_id = this.all_scopes.find(
|
||||
(x) => x && x.name === "id" && x.scope_path === "@.member.self",
|
||||
).value;
|
||||
} else if (key) {
|
||||
const collection = path.split(".")[path.split(".").length - 1];
|
||||
const collection_items = Array.from(root.value.entries()).find(
|
||||
(x) => x && x[0].split("Members/").join("").split("Locals/").join("") === collection,
|
||||
)[1];
|
||||
result.object_id = collection_items.get ? collection_items.get(key)?.id : collection_items[key]?.id;
|
||||
} else {
|
||||
const item = Array.from(root.value.entries()).find(
|
||||
(x) => x && x[0].split("Members/").join("").split("Locals/").join("") === propertyName,
|
||||
);
|
||||
result.object_id = item?.[1].id;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.object_id) {
|
||||
result.object_id = object_id;
|
||||
}
|
||||
|
||||
result.index = this.all_scopes.findIndex(
|
||||
(x) => x && x.name === result.variable.name && x.scope_path === result.variable.scope_path,
|
||||
);
|
||||
|
||||
if (items.length > 2 && index < items.length - 2) {
|
||||
result = this.get_variable(items.join("."), result.variable, index + 1, result.object_id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private append_variable(variable: GodotVariable, index?: number) {
|
||||
if (index) {
|
||||
this.all_scopes.splice(index, 0, variable);
|
||||
} else {
|
||||
this.all_scopes.push(variable);
|
||||
}
|
||||
const base_path = `${variable.scope_path}.${variable.name}`;
|
||||
if (variable.sub_values) {
|
||||
variable.sub_values.forEach((va, i) => {
|
||||
va.scope_path = base_path;
|
||||
this.append_variable(va, index ? index + i + 1 : undefined);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GodotVariable, } from "../debug_runtime";
|
||||
import { SceneNode } from "../scene_tree_provider";
|
||||
import { ObjectId } from "./variables/variants";
|
||||
|
||||
export function parse_next_scene_node(params: any[], ofs: { offset: number } = { offset: 0 }): SceneNode {
|
||||
const childCount: number = params[ofs.offset++];
|
||||
@@ -31,12 +32,7 @@ export function split_buffers(buffer: Buffer) {
|
||||
return buffers;
|
||||
}
|
||||
|
||||
export function is_variable_built_in_type(va: GodotVariable) {
|
||||
var type = typeof va.value;
|
||||
return ["number", "bigint", "boolean", "string"].some(x => x == type);
|
||||
}
|
||||
|
||||
export function get_sub_values(value: any) {
|
||||
export function get_sub_values(value: any): GodotVariable[] {
|
||||
let subValues: GodotVariable[] = undefined;
|
||||
|
||||
if (value) {
|
||||
@@ -45,19 +41,12 @@ export function get_sub_values(value: any) {
|
||||
return { name: `${i}`, value: va } as GodotVariable;
|
||||
});
|
||||
} else if (value instanceof Map) {
|
||||
subValues = Array.from(value.keys()).map((va) => {
|
||||
if (typeof va["stringify_value"] === "function") {
|
||||
return {
|
||||
name: `${va.type_name()}${va.stringify_value()}`,
|
||||
value: value.get(va),
|
||||
} as GodotVariable;
|
||||
} else {
|
||||
return {
|
||||
name: `${va}`,
|
||||
value: value.get(va),
|
||||
} as GodotVariable;
|
||||
subValues = [];
|
||||
for (const [key, val] of value.entries()) {
|
||||
const name = typeof key["stringify_value"] === "function" ? `${key.type_name()}${key.stringify_value()}` : `${key}`;
|
||||
const godot_id = val instanceof ObjectId ? val.id : undefined;
|
||||
subValues.push({id: godot_id, name, value: val } as GodotVariable);
|
||||
}
|
||||
});
|
||||
} else if (typeof value["sub_values"] === "function") {
|
||||
subValues = value.sub_values()?.map((sva) => {
|
||||
return { name: sva.name, value: sva.value } as GodotVariable;
|
||||
@@ -71,54 +60,3 @@ export function get_sub_values(value: any) {
|
||||
|
||||
return subValues;
|
||||
}
|
||||
|
||||
export function parse_variable(va: GodotVariable, i?: number) {
|
||||
const value = va.value;
|
||||
let rendered_value = "";
|
||||
let reference = 0;
|
||||
let array_size = 0;
|
||||
let array_type = undefined;
|
||||
|
||||
if (typeof value === "number") {
|
||||
if (Number.isInteger(value)) {
|
||||
rendered_value = `${value}`;
|
||||
} else {
|
||||
rendered_value = `${parseFloat(value.toFixed(5))}`;
|
||||
}
|
||||
} else if (
|
||||
typeof value === "bigint" ||
|
||||
typeof value === "boolean" ||
|
||||
typeof value === "string"
|
||||
) {
|
||||
rendered_value = `${value}`;
|
||||
} else if (typeof value === "undefined") {
|
||||
rendered_value = "null";
|
||||
} else {
|
||||
if (Array.isArray(value)) {
|
||||
rendered_value = `Array[${value.length}]`;
|
||||
array_size = value.length;
|
||||
array_type = "indexed";
|
||||
reference = i ? i : 0;
|
||||
} else if (value instanceof Map) {
|
||||
rendered_value = value["class_name"] ?? `Dictionary[${value.size}]`;
|
||||
array_size = value.size;
|
||||
array_type = "named";
|
||||
reference = i ? i : 0;
|
||||
} else {
|
||||
try {
|
||||
rendered_value = `${value.type_name()}${value.stringify_value()}`;
|
||||
} catch (e) {
|
||||
rendered_value = `${value}`;
|
||||
}
|
||||
reference = i ? i : 0;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: va.name,
|
||||
value: rendered_value,
|
||||
variablesReference: reference,
|
||||
array_size: array_size > 0 ? array_size : undefined,
|
||||
filter: array_type,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,13 +16,14 @@ import {
|
||||
} from "../../utils";
|
||||
import { prompt_for_godot_executable } from "../../utils/prompts";
|
||||
import { killSubProcesses, subProcess } from "../../utils/subspawn";
|
||||
import { GodotStackFrame, GodotStackVars, GodotVariable } from "../debug_runtime";
|
||||
import { GodotStackFrame, GodotVariable } from "../debug_runtime";
|
||||
import { AttachRequestArguments, LaunchRequestArguments, pinnedScene } from "../debugger";
|
||||
import { GodotDebugSession } from "./debug_session";
|
||||
import { get_sub_values, parse_next_scene_node, split_buffers } from "./helpers";
|
||||
import { VariantDecoder } from "./variables/variant_decoder";
|
||||
import { VariantEncoder } from "./variables/variant_encoder";
|
||||
import { RawObject } from "./variables/variants";
|
||||
import { VariablesManager } from "./variables/variables_manager";
|
||||
|
||||
const log = createLogger("debugger.controller", { output: "Godot Debugger" });
|
||||
const socketLog = createLogger("debugger.socket");
|
||||
@@ -35,6 +36,33 @@ class Command {
|
||||
public threadId: number = 0;
|
||||
}
|
||||
|
||||
class GodotPartialStackVars {
|
||||
Locals: GodotVariable[] = [];
|
||||
Members: GodotVariable[] = [];
|
||||
Globals: GodotVariable [] = [];
|
||||
public remaining: number;
|
||||
public stack_frame_id: number;
|
||||
constructor(stack_frame_id: number) {
|
||||
this.stack_frame_id = stack_frame_id;
|
||||
}
|
||||
|
||||
public reset(remaining: number) {
|
||||
this.remaining = remaining;
|
||||
this.Locals = [];
|
||||
this.Members = [];
|
||||
this.Globals = [];
|
||||
}
|
||||
|
||||
public append(name: string, godotScopeIndex: 0|1|2, type: number, value: any, sub_values?: GodotVariable[]) {
|
||||
const scopeName = ["Locals", "Members", "Globals"][godotScopeIndex];
|
||||
const scope = this[scopeName];
|
||||
// const objectId = value instanceof ObjectId ? value : undefined; // won't work, unless the value is re-created through new ObjectId(godot_id)
|
||||
const godot_id = type === 24 ? value.id : undefined;
|
||||
scope.push({ id: godot_id, name, value, type, sub_values } as GodotVariable);
|
||||
this.remaining--;
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerController {
|
||||
private commandBuffer: Buffer[] = [];
|
||||
private encoder = new VariantEncoder();
|
||||
@@ -46,7 +74,7 @@ export class ServerController {
|
||||
private socket?: net.Socket;
|
||||
private steppingOut = false;
|
||||
private didFirstOutput = false;
|
||||
private partialStackVars = new GodotStackVars();
|
||||
private partialStackVars: GodotPartialStackVars;
|
||||
private connectedVersion = "";
|
||||
|
||||
public constructor(public session: GodotDebugSession) {}
|
||||
@@ -93,8 +121,16 @@ export class ServerController {
|
||||
this.send_command("get_stack_dump");
|
||||
}
|
||||
|
||||
public request_stack_frame_vars(frame_id: number) {
|
||||
this.send_command("get_stack_frame_vars", [frame_id]);
|
||||
public request_stack_frame_vars(stack_frame_id: number) {
|
||||
if (this.partialStackVars !== undefined) {
|
||||
log.warn("Partial stack frames have been requested, while existing request hasn't been completed yet." +
|
||||
`Remaining stack_frames: ${this.partialStackVars.remaining}` +
|
||||
`Current stack_frame_id: ${this.partialStackVars.stack_frame_id}` +
|
||||
`Requested stack_frame_id: ${stack_frame_id}`
|
||||
);
|
||||
}
|
||||
this.partialStackVars = new GodotPartialStackVars(stack_frame_id);
|
||||
this.send_command("get_stack_frame_vars", [stack_frame_id]);
|
||||
}
|
||||
|
||||
public set_object_property(objectId: bigint, label: string, newParsedValue) {
|
||||
@@ -259,7 +295,7 @@ export class ServerController {
|
||||
return;
|
||||
}
|
||||
|
||||
socketLog.debug("rx:", data[0]);
|
||||
socketLog.debug("rx:", data[0], data[0][2]);
|
||||
const command = this.parse_message(data[0]);
|
||||
this.handle_command(command);
|
||||
}
|
||||
@@ -362,9 +398,11 @@ export class ServerController {
|
||||
this.set_exception("");
|
||||
}
|
||||
this.request_stack_dump();
|
||||
this.session.variables_manager = new VariablesManager(this);
|
||||
break;
|
||||
}
|
||||
case "debug_exit":
|
||||
this.session.variables_manager = undefined;
|
||||
break;
|
||||
case "message:click_ctrl":
|
||||
// TODO: what is this?
|
||||
@@ -381,14 +419,14 @@ export class ServerController {
|
||||
break;
|
||||
}
|
||||
case "scene:inspect_object": {
|
||||
let id = BigInt(command.parameters[0]);
|
||||
let godot_id = BigInt(command.parameters[0]);
|
||||
const className: string = command.parameters[1];
|
||||
const properties: string[] = command.parameters[2];
|
||||
|
||||
// message:inspect_object returns the id as an unsigned 64 bit integer, but it is decoded as a signed 64 bit integer,
|
||||
// thus we need to convert it to its equivalent unsigned value here.
|
||||
if (id < 0) {
|
||||
id = id + BigInt(2) ** BigInt(64);
|
||||
if (godot_id < 0) {
|
||||
godot_id = godot_id + BigInt(2) ** BigInt(64);
|
||||
}
|
||||
|
||||
const rawObject = new RawObject(className);
|
||||
@@ -397,13 +435,18 @@ export class ServerController {
|
||||
}
|
||||
const sub_values = get_sub_values(rawObject);
|
||||
|
||||
const inspect_callback = this.session.inspect_callbacks.get(BigInt(id));
|
||||
if (inspect_callback !== undefined) {
|
||||
const inspectedVariable = { name: "", value: rawObject, sub_values: sub_values } as GodotVariable;
|
||||
inspect_callback(inspectedVariable.name, inspectedVariable);
|
||||
this.session.inspect_callbacks.delete(BigInt(id));
|
||||
// race condition here:
|
||||
// 0. DebuggerStop1 happens
|
||||
// 1. the DA may have sent the "inspect_object" message
|
||||
// 2. the vscode hit "continue"
|
||||
// 3. new breakpoint hit, DebuggerStop2 happens
|
||||
// 4. the godot server will return response for `1.` with "scene:inspect_object"
|
||||
// at this moment there is no way to tell if "scene:inspect_object" is for DebuggerStop1 or DebuggerStop2
|
||||
try {
|
||||
this.session.variables_manager?.resolve_variable(godot_id, className, sub_values);
|
||||
} catch (error) {
|
||||
log.error("Race condition error error in scene:inspect_object", error);
|
||||
}
|
||||
this.session.set_inspection(id, rawObject, sub_values);
|
||||
break;
|
||||
}
|
||||
case "stack_dump": {
|
||||
@@ -423,17 +466,53 @@ export class ServerController {
|
||||
break;
|
||||
}
|
||||
case "stack_frame_vars": {
|
||||
this.partialStackVars.reset(command.parameters[0]);
|
||||
this.session.set_scopes(this.partialStackVars);
|
||||
/** first response to {@link request_stack_frame_vars} */
|
||||
if (this.partialStackVars !== undefined) {
|
||||
log.warn("'stack_frame_vars' received again from godot engine before all partial 'stack_frame_var' are received");
|
||||
}
|
||||
const remaining = command.parameters[0];
|
||||
// init this.partialStackVars, which will be filled with "stack_frame_var" responses data
|
||||
this.partialStackVars.reset(remaining);
|
||||
break;
|
||||
}
|
||||
case "stack_frame_var": {
|
||||
this.do_stack_frame_var(
|
||||
command.parameters[0],
|
||||
command.parameters[1],
|
||||
command.parameters[2],
|
||||
command.parameters[3],
|
||||
);
|
||||
if (this.partialStackVars === undefined) {
|
||||
log.error("Unexpected 'stack_frame_var' received. Should have received 'stack_frame_vars' first.");
|
||||
return;
|
||||
}
|
||||
if (typeof command.parameters[0] !== "string") {
|
||||
log.error("Unexpected parameter type for 'stack_frame_var'. Expected string for name, got " + typeof command.parameters[0]);
|
||||
return;
|
||||
}
|
||||
if (typeof command.parameters[1] !== "number" || command.parameters[1] !== 0 && command.parameters[1] !== 1 && command.parameters[1] !== 2) {
|
||||
log.error("Unexpected parameter type for 'stack_frame_var'. Expected number for scope, got " + typeof command.parameters[1]);
|
||||
return;
|
||||
}
|
||||
if (typeof command.parameters[2] !== "number") {
|
||||
log.error("Unexpected parameter type for 'stack_frame_var'. Expected number for type, got " + typeof command.parameters[2]);
|
||||
return;
|
||||
}
|
||||
var name: string = command.parameters[0];
|
||||
var scope: 0 | 1 | 2 = command.parameters[1]; // 0 = locals, 1 = members, 2 = globals
|
||||
var type: number = command.parameters[2];
|
||||
var value: any = command.parameters[3];
|
||||
var subValues: GodotVariable[] = get_sub_values(value);
|
||||
this.partialStackVars.append(name, scope, type, value, subValues);
|
||||
|
||||
if (this.partialStackVars.remaining === 0) {
|
||||
const stackVars = this.partialStackVars;
|
||||
this.partialStackVars = undefined;
|
||||
log.info("All partial 'stack_frame_var' are received.");
|
||||
// godot server doesn't send the frame_id for the stack_vars, assume the remembered stack_frame_id:
|
||||
const frame_id = BigInt(stackVars.stack_frame_id);
|
||||
const local_scopes_godot_id = -frame_id*3n-1n;
|
||||
const member_scopes_godot_id = -frame_id*3n-2n;
|
||||
const global_scopes_godot_id = -frame_id*3n-3n;
|
||||
|
||||
this.session.variables_manager.resolve_variable(local_scopes_godot_id, "Locals", stackVars.Locals);
|
||||
this.session.variables_manager.resolve_variable(member_scopes_godot_id, "Members", stackVars.Members);
|
||||
this.session.variables_manager.resolve_variable(global_scopes_godot_id, "Globals", stackVars.Globals);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "output": {
|
||||
@@ -616,7 +695,7 @@ export class ServerController {
|
||||
commandArray.push(this.threadId);
|
||||
}
|
||||
commandArray.push(parameters ?? []);
|
||||
socketLog.debug("tx:", commandArray);
|
||||
socketLog.debug("tx:", commandArray, commandArray[2]);
|
||||
const buffer = this.encoder.encode_variant(commandArray);
|
||||
this.commandBuffer.push(buffer);
|
||||
this.send_buffer();
|
||||
@@ -632,26 +711,4 @@ export class ServerController {
|
||||
this.draining = !this.socket.write(command);
|
||||
}
|
||||
}
|
||||
|
||||
private do_stack_frame_var(
|
||||
name: string,
|
||||
scope: 0 | 1 | 2, // 0 = locals, 1 = members, 2 = globals
|
||||
type: bigint,
|
||||
value: any,
|
||||
) {
|
||||
if (this.partialStackVars.remaining === 0) {
|
||||
throw new Error("More stack frame variables were sent than expected.");
|
||||
}
|
||||
|
||||
const sub_values = get_sub_values(value);
|
||||
const variable = { name, value, type, sub_values } as GodotVariable;
|
||||
|
||||
const scopeName = ["locals", "members", "globals"][scope];
|
||||
this.partialStackVars[scopeName].push(variable);
|
||||
this.partialStackVars.remaining--;
|
||||
|
||||
if (this.partialStackVars.remaining === 0) {
|
||||
this.session.set_scopes(this.partialStackVars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,12 @@ import * as vscode from "vscode";
|
||||
import { DebugProtocol } from "@vscode/debugprotocol";
|
||||
import chai from "chai";
|
||||
import chaiSubset from "chai-subset";
|
||||
var chaiAsPromised = import("chai-as-promised");
|
||||
// const chaiAsPromised = await import("chai-as-promised"); // TODO: use after migration to ECMAScript modules
|
||||
|
||||
chaiAsPromised.then((module) => {
|
||||
chai.use(module.default);
|
||||
});
|
||||
|
||||
import { promisify } from "util";
|
||||
import { execFile } from "child_process";
|
||||
@@ -83,19 +89,31 @@ async function waitForBreakpoint(breakpoint: vscode.SourceBreakpoint, timeoutMs:
|
||||
}
|
||||
|
||||
enum VariableScope {
|
||||
Locals = 1,
|
||||
Members = 2,
|
||||
Globals = 3
|
||||
Locals,
|
||||
Members,
|
||||
Globals
|
||||
}
|
||||
|
||||
async function getVariablesForScope(scope: VariableScope): Promise<DebugProtocol.Variable[]> {
|
||||
async function getVariablesForVSCodeID(vscode_id: number): Promise<DebugProtocol.Variable[]> {
|
||||
// corresponds to file://./debug_session.ts protected async variablesRequest
|
||||
const variablesResponse = await vscode.debug.activeDebugSession?.customRequest("variables", {
|
||||
variablesReference: scope
|
||||
variablesReference: vscode_id
|
||||
});
|
||||
return variablesResponse?.variables || [];
|
||||
}
|
||||
|
||||
async function getVariablesForScope(scope: VariableScope, stack_frame_id: number = 0): Promise<DebugProtocol.Variable[]> {
|
||||
const res_scopes = await vscode.debug.activeDebugSession.customRequest("scopes", {frameId: stack_frame_id});
|
||||
const scope_name = VariableScope[scope];
|
||||
const scope_res = res_scopes.scopes.find(s => s.name == scope_name);
|
||||
if (scope_res === undefined) {
|
||||
throw new Error(`No ${scope_name} scope found in responce from "scopes" request`);
|
||||
}
|
||||
const vscode_id = scope_res.variablesReference;
|
||||
const variables = await getVariablesForVSCodeID(vscode_id);
|
||||
return variables;
|
||||
}
|
||||
|
||||
async function evaluateRequest(scope: VariableScope, expression: string, context = "watch", frameId = 0): Promise<any> {
|
||||
// corresponds to file://./debug_session.ts protected async evaluateRequest
|
||||
const evaluateResponse: DebugProtocol.EvaluateResponse = await vscode.debug.activeDebugSession?.customRequest("evaluate", {
|
||||
@@ -118,6 +136,31 @@ function formatMessage(this: Mocha.Context, msg: string): string {
|
||||
|
||||
var fmt: (msg: string) => string; // formatMessage bound to Mocha.Context
|
||||
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Chai {
|
||||
interface Assertion {
|
||||
unique: Assertion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chai.Assertion.addProperty("unique", function() {
|
||||
const actual = this._obj; // The object being tested
|
||||
if (!Array.isArray(actual)) {
|
||||
throw new chai.AssertionError("Expected value to be an array");
|
||||
}
|
||||
const uniqueArray = [...new Set(actual)];
|
||||
this.assert(
|
||||
actual.length === uniqueArray.length,
|
||||
"expected #{this} to contain only unique elements",
|
||||
"expected #{this} to not contain only unique elements",
|
||||
uniqueArray,
|
||||
actual
|
||||
);
|
||||
});
|
||||
|
||||
async function startDebugging(scene: "ScopeVars.tscn" | "ExtensiveVars.tscn" | "BuiltInTypes.tscn" = "ScopeVars.tscn"): Promise<void> {
|
||||
const t0 = performance.now();
|
||||
const debugConfig: vscode.DebugConfiguration = {
|
||||
@@ -138,7 +181,10 @@ async function startDebugging(scene: "ScopeVars.tscn" | "ExtensiveVars.tscn" | "
|
||||
|
||||
suite("DAP Integration Tests - Variable Scopes", () => {
|
||||
// workspaceFolder should match `.vscode-test.js`::workspaceFolder
|
||||
const workspaceFolder = path.resolve(__dirname, "../../../test_projects/test-dap-project-godot4");
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0].uri.fsPath;
|
||||
if (!workspaceFolder || !workspaceFolder.endsWith("test-dap-project-godot4")) {
|
||||
throw new Error(`workspaceFolder should contain 'test-dap-project-godot4' project, got: ${workspaceFolder}`);
|
||||
}
|
||||
|
||||
suiteSetup(async function() {
|
||||
this.timeout(20000); // enough time to do `godot --import`
|
||||
@@ -149,11 +195,11 @@ suite("DAP Integration Tests - Variable Scopes", () => {
|
||||
|
||||
// init the godot project by importing it in godot engine:
|
||||
const config = vscode.workspace.getConfiguration("godotTools");
|
||||
// config.update("editorPath.godot4", "godot4", vscode.ConfigurationTarget.Workspace);
|
||||
var godot4_path = config.get<string>("editorPath.godot4");
|
||||
// get the path for currently opened project in vscode test instance:
|
||||
var project_path = vscode.workspace.workspaceFolders?.[0].uri.fsPath;
|
||||
console.log("Executing", [godot4_path, "--headless", "--import", project_path]);
|
||||
const exec_res = await execFileAsync(godot4_path, ["--headless", "--import", project_path], {shell: true, cwd: project_path});
|
||||
console.log("Executing", [godot4_path, "--headless", "--import", workspaceFolder]);
|
||||
const exec_res = await execFileAsync(godot4_path, ["--headless", "--import", workspaceFolder], {shell: true, cwd: workspaceFolder});
|
||||
if (exec_res.stderr !== "") {
|
||||
throw new Error(exec_res.stderr);
|
||||
}
|
||||
@@ -172,24 +218,25 @@ suite("DAP Integration Tests - Variable Scopes", () => {
|
||||
|
||||
|
||||
teardown(async function() {
|
||||
this.timeout(3000);
|
||||
await sleep(1000);
|
||||
if (vscode.debug.activeDebugSession !== undefined) {
|
||||
console.log("Closing debug session");
|
||||
await vscode.debug.stopDebugging();
|
||||
await sleep(1000);
|
||||
}
|
||||
console.log(`⬛ Test '${this.currentTest.title}' result: ${this.currentTest.state}, duration: ${performance.now() - this.testStart}ms`);
|
||||
});
|
||||
|
||||
|
||||
test("sample test", async function() {
|
||||
// await sleep(1000);
|
||||
await startDebugging("ScopeVars.tscn");
|
||||
});
|
||||
|
||||
// test("sample test", async function() {
|
||||
// expect(true).to.equal(true);
|
||||
// expect([1,2,3]).to.be.unique;
|
||||
// expect([1,1]).not.to.be.unique;
|
||||
// });
|
||||
|
||||
test("should return correct scopes", async function() {
|
||||
const breakpointLocations = await getBreakpointLocations(path.join(workspaceFolder, "ScopeVars.gd"));
|
||||
const breakpoint = new vscode.SourceBreakpoint(breakpointLocations["breakpoint::ScopeVars::_ready"]);
|
||||
const breakpoint = new vscode.SourceBreakpoint(breakpointLocations["breakpoint::ScopeVars::ClassFoo::test_function"]);
|
||||
vscode.debug.addBreakpoints([breakpoint]);
|
||||
|
||||
await startDebugging("ScopeVars.tscn");
|
||||
@@ -200,23 +247,40 @@ suite("DAP Integration Tests - Variable Scopes", () => {
|
||||
await sleep(2000);
|
||||
|
||||
// corresponds to file://./debug_session.ts async scopesRequest
|
||||
const res_scopes = await vscode.debug.activeDebugSession.customRequest("scopes", {frameId: 1});
|
||||
|
||||
const stack_scopes_map: Map<number, {
|
||||
"Locals": number;
|
||||
"Members": number;
|
||||
"Globals": number;
|
||||
}> = new Map();
|
||||
for (var stack_frame_id = 0; stack_frame_id < 3; stack_frame_id++) {
|
||||
const res_scopes = await vscode.debug.activeDebugSession.customRequest("scopes", {frameId: stack_frame_id});
|
||||
expect(res_scopes).to.exist;
|
||||
expect(res_scopes.scopes).to.exist;
|
||||
expect(res_scopes.scopes.length).to.equal(3, "Expected 3 scopes");
|
||||
expect(res_scopes.scopes[0].name).to.equal(VariableScope[VariableScope.Locals], "Expected Locals scope");
|
||||
expect(res_scopes.scopes[1].name).to.equal(VariableScope[VariableScope.Members], "Expected Members scope");
|
||||
expect(res_scopes.scopes[2].name).to.equal(VariableScope[VariableScope.Globals], "Expected Globals scope");
|
||||
const vscode_ids = res_scopes.scopes.map(s => s.variablesReference);
|
||||
expect(vscode_ids, "VSCode IDs should be unique for each scope").to.be.unique;
|
||||
stack_scopes_map[stack_frame_id] = {
|
||||
"Locals": vscode_ids[0],
|
||||
"Members": vscode_ids[1],
|
||||
"Globals": vscode_ids[2]
|
||||
};
|
||||
}
|
||||
|
||||
const scopes = res_scopes.scopes;
|
||||
expect(scopes.length).to.equal(3, "Expected 3 scopes");
|
||||
expect(scopes[0].name).to.equal(VariableScope[VariableScope.Locals], "Expected Locals scope");
|
||||
expect(scopes[0].variablesReference).to.equal(VariableScope.Locals, "Expected Locals variablesReference");
|
||||
expect(scopes[1].name).to.equal(VariableScope[VariableScope.Members], "Expected Members scope");
|
||||
expect(scopes[1].variablesReference).to.equal(VariableScope.Members, "Expected Members variablesReference");
|
||||
expect(scopes[2].name).to.equal(VariableScope[VariableScope.Globals], "Expected Globals scope");
|
||||
expect(scopes[2].variablesReference).to.equal(VariableScope.Globals, "Expected Globals variablesReference");
|
||||
const all_scopes_vscode_ids = Array.from(stack_scopes_map.values()).flatMap(s => Object.values(s));
|
||||
expect(all_scopes_vscode_ids, "All scopes should be unique").to.be.unique;
|
||||
|
||||
await sleep(1000);
|
||||
await vscode.debug.stopDebugging();
|
||||
})?.timeout(5000);
|
||||
const vars_frame0_locals = await getVariablesForVSCodeID(stack_scopes_map[0].Locals);
|
||||
expect(vars_frame0_locals).to.containSubset([{name: "str_var", value: "ScopeVars::ClassFoo::test_function::local::str_var"}]);
|
||||
|
||||
const vars_frame1_locals = await getVariablesForVSCodeID(stack_scopes_map[1].Locals);
|
||||
expect(vars_frame1_locals).to.containSubset([{name: "str_var", value: "ScopeVars::test::local::str_var"}]);
|
||||
|
||||
const vars_frame2_locals = await getVariablesForVSCodeID(stack_scopes_map[2].Locals);
|
||||
expect(vars_frame2_locals).to.containSubset([{name: "str_var", value: "ScopeVars::_ready::local::str_var"}]);
|
||||
})?.timeout(10000);
|
||||
|
||||
test("should return global variables", async function() {
|
||||
const breakpointLocations = await getBreakpointLocations(path.join(workspaceFolder, "ScopeVars.gd"));
|
||||
@@ -232,12 +296,10 @@ suite("DAP Integration Tests - Variable Scopes", () => {
|
||||
|
||||
const variables = await getVariablesForScope(VariableScope.Globals);
|
||||
expect(variables).to.containSubset([{name: "GlobalScript"}]);
|
||||
})?.timeout(10000);
|
||||
|
||||
await sleep(1000);
|
||||
await vscode.debug.stopDebugging();
|
||||
})?.timeout(7000);
|
||||
|
||||
test("should return local variables", async function() {
|
||||
test("should return all local variables", async function() {
|
||||
/** {@link file://./../../../../test_projects/test-dap-project-godot4/ScopeVars.gd"} */
|
||||
const breakpointLocations = await getBreakpointLocations(path.join(workspaceFolder, "ScopeVars.gd"));
|
||||
const breakpoint = new vscode.SourceBreakpoint(breakpointLocations["breakpoint::ScopeVars::_ready"]);
|
||||
vscode.debug.addBreakpoints([breakpoint]);
|
||||
@@ -251,14 +313,12 @@ suite("DAP Integration Tests - Variable Scopes", () => {
|
||||
|
||||
const variables = await getVariablesForScope(VariableScope.Locals);
|
||||
expect(variables.length).to.equal(2);
|
||||
expect(variables).to.containSubset([{name: "local1"}]);
|
||||
expect(variables).to.containSubset([{name: "local2"}]);
|
||||
expect(variables).to.containSubset([{name: "str_var"}]);
|
||||
expect(variables).to.containSubset([{name: "self_var"}]);
|
||||
})?.timeout(10000);
|
||||
|
||||
await sleep(1000);
|
||||
await vscode.debug.stopDebugging();
|
||||
})?.timeout(5000);
|
||||
|
||||
test("should return member variables", async function() {
|
||||
test("should return all member variables", async function() {
|
||||
/** {@link file://./../../../../test_projects/test-dap-project-godot4/ScopeVars.gd"} */
|
||||
const breakpointLocations = await getBreakpointLocations(path.join(workspaceFolder, "ScopeVars.gd"));
|
||||
const breakpoint = new vscode.SourceBreakpoint(breakpointLocations["breakpoint::ScopeVars::_ready"]);
|
||||
vscode.debug.addBreakpoints([breakpoint]);
|
||||
@@ -271,13 +331,12 @@ suite("DAP Integration Tests - Variable Scopes", () => {
|
||||
await sleep(2000);
|
||||
|
||||
const variables = await getVariablesForScope(VariableScope.Members);
|
||||
expect(variables.length).to.equal(2);
|
||||
expect(variables.length).to.equal(4);
|
||||
expect(variables).to.containSubset([{name: "self"}]);
|
||||
expect(variables).to.containSubset([{name: "member1"}]);
|
||||
|
||||
await sleep(1000);
|
||||
await vscode.debug.stopDebugging();
|
||||
})?.timeout(5000);
|
||||
expect(variables).to.containSubset([{name: "str_var", value: "ScopeVars::member::str_var"}]);
|
||||
expect(variables).to.containSubset([{name: "str_var_member_only", value: "ScopeVars::member::str_var_member_only"}]);
|
||||
})?.timeout(10000);
|
||||
|
||||
test("should retrieve all built-in types correctly", async function() {
|
||||
const breakpointLocations = await getBreakpointLocations(path.join(workspaceFolder, "BuiltInTypes.gd"));
|
||||
@@ -302,15 +361,12 @@ suite("DAP Integration Tests - Variable Scopes", () => {
|
||||
expect(variables).to.containSubset([{ name: "vector3", value: "Vector3(1, 2, 3)" }]);
|
||||
expect(variables).to.containSubset([{ name: "rect2", value: "Rect2((0, 0) - (100, 50))" }]);
|
||||
expect(variables).to.containSubset([{ name: "quaternion", value: "Quat(0, 0, 0, 1)" }]);
|
||||
// expect(variables).to.containSubset([{ name: "simple_array", value: "[1, 2, 3]" }]);
|
||||
expect(variables).to.containSubset([{ name: "simple_array", value: "Array[3]" }]);
|
||||
expect(variables).to.containSubset([{ name: "simple_array", value: "(3) [1, 2, 3]" }]);
|
||||
// expect(variables).to.containSubset([{ name: "nested_dict.nested_key", value: `"Nested Value"` }]);
|
||||
// expect(variables).to.containSubset([{ name: "nested_dict.sub_dict.sub_key", value: "99" }]);
|
||||
expect(variables).to.containSubset([{ name: "nested_dict", value: "Dictionary[2]" }]);
|
||||
// expect(variables).to.containSubset([{ name: "byte_array", value: "[0, 1, 2, 255]" }]);
|
||||
expect(variables).to.containSubset([{ name: "byte_array", value: "Array[4]" }]);
|
||||
// expect(variables).to.containSubset([{ name: "int32_array", value: "[100, 200, 300]" }]);
|
||||
expect(variables).to.containSubset([{ name: "int32_array", value: "Array[3]" }]);
|
||||
expect(variables).to.containSubset([{ name: "nested_dict", value: "Dictionary(2)" }]);
|
||||
expect(variables).to.containSubset([{ name: "byte_array", value: "(4) [0, 1, 2, 255]" }]);
|
||||
expect(variables).to.containSubset([{ name: "int32_array", value: "(3) [100, 200, 300]" }]);
|
||||
expect(variables).to.containSubset([{ name: "color_var", value: "Color(1, 0, 0, 1)" }]);
|
||||
expect(variables).to.containSubset([{ name: "aabb_var", value: "AABB((0, 0, 0), (1, 1, 1))" }]);
|
||||
expect(variables).to.containSubset([{ name: "plane_var", value: "Plane(0, 1, 0, -5)" }]);
|
||||
@@ -318,10 +374,7 @@ suite("DAP Integration Tests - Variable Scopes", () => {
|
||||
expect(variables).to.containSubset([{ name: "signal_var" }]);
|
||||
const signal_var = variables.find(v => v.name === "signal_var");
|
||||
expect(signal_var.value).to.match(/Signal\(member_signal\, <\d+>\)/, "Should be in format of 'Signal(member_signal, <28236055815>)'");
|
||||
|
||||
await sleep(1000);
|
||||
await vscode.debug.stopDebugging();
|
||||
})?.timeout(5000);
|
||||
})?.timeout(10000);
|
||||
|
||||
test("should retrieve all complex variables correctly", async function() {
|
||||
const breakpointLocations = await getBreakpointLocations(path.join(workspaceFolder, "ExtensiveVars.gd"));
|
||||
@@ -337,23 +390,28 @@ suite("DAP Integration Tests - Variable Scopes", () => {
|
||||
|
||||
const memberVariables = await getVariablesForScope(VariableScope.Members);
|
||||
|
||||
expect(memberVariables.length).to.equal(3);
|
||||
expect(memberVariables.length).to.equal(3, "Incorrect member variables count");
|
||||
expect(memberVariables).to.containSubset([{name: "self"}]);
|
||||
expect(memberVariables).to.containSubset([{name: "self_var"}]);
|
||||
expect(memberVariables).to.containSubset([{name: "label"}]);
|
||||
const self = memberVariables.find(v => v.name === "self");
|
||||
const self_var = memberVariables.find(v => v.name === "self_var");
|
||||
expect(self.value).to.deep.equal(self_var.value);
|
||||
|
||||
const localVariables = await getVariablesForScope(VariableScope.Locals);
|
||||
expect(localVariables.length).to.equal(4);
|
||||
expect(localVariables).to.containSubset([
|
||||
{ name: "local_label", value: "Label" },
|
||||
{ name: "local_self_var_through_label", value: "Node2D" },
|
||||
{ name: "local_classA", value: "RefCounted" },
|
||||
{ name: "local_classB", value: "RefCounted" }
|
||||
]);
|
||||
|
||||
await sleep(1000);
|
||||
await vscode.debug.stopDebugging();
|
||||
const expectedLocalVariables = [
|
||||
{ name: "local_label", value: /Label<\d+>/ },
|
||||
{ name: "local_self_var_through_label", value: /Node2D<\d+>/ },
|
||||
{ name: "local_classA", value: /RefCounted<\d+>/ },
|
||||
{ name: "local_classB", value: /RefCounted<\d+>/ },
|
||||
{ name: "str_var", value: /^ExtensiveVars::_ready::local::str_var$/ },
|
||||
];
|
||||
expect(localVariables.length).to.equal(expectedLocalVariables.length, "Incorrect local variables count");
|
||||
expect(localVariables).to.containSubset(expectedLocalVariables.map(v => ({ name: v.name })));
|
||||
for (const expectedLocalVariable of expectedLocalVariables) {
|
||||
const localVariable = localVariables.find(v => v.name === expectedLocalVariable.name);
|
||||
expect(localVariable).to.exist;
|
||||
expect(localVariable.value).to.match(expectedLocalVariable.value, `Variable '${expectedLocalVariable.name}' has incorrect value'`);
|
||||
}
|
||||
})?.timeout(15000);
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { expect } from "chai";
|
||||
import { GodotIdWithPath, GodotIdToVscodeIdMapper } from "./godot_id_to_vscode_id_mapper";
|
||||
|
||||
suite("GodotIdToVscodeIdMapper", () => {
|
||||
test("create_vscode_id assigns unique ID", () => {
|
||||
const mapper = new GodotIdToVscodeIdMapper();
|
||||
const godotId = new GodotIdWithPath(BigInt(1), ["path1"]);
|
||||
const vscodeId = mapper.create_vscode_id(godotId);
|
||||
expect(vscodeId).to.equal(1);
|
||||
});
|
||||
|
||||
test("create_vscode_id throws error on duplicate", () => {
|
||||
const mapper = new GodotIdToVscodeIdMapper();
|
||||
const godotId = new GodotIdWithPath(BigInt(1), ["path1"]);
|
||||
mapper.create_vscode_id(godotId);
|
||||
expect(() => mapper.create_vscode_id(godotId)).to.throw("Duplicate godot_id: 1:path1");
|
||||
});
|
||||
|
||||
test("get_godot_id_with_path returns correct object", () => {
|
||||
const mapper = new GodotIdToVscodeIdMapper();
|
||||
const godotId = new GodotIdWithPath(BigInt(2), ["path2"]);
|
||||
const vscodeId = mapper.create_vscode_id(godotId);
|
||||
expect(mapper.get_godot_id_with_path(vscodeId)).to.deep.equal(godotId);
|
||||
});
|
||||
|
||||
test("get_godot_id_with_path throws error if not found", () => {
|
||||
const mapper = new GodotIdToVscodeIdMapper();
|
||||
expect(() => mapper.get_godot_id_with_path(999)).to.throw("Unknown vscode_id: 999");
|
||||
});
|
||||
|
||||
test("get_vscode_id retrieves correct ID", () => {
|
||||
const mapper = new GodotIdToVscodeIdMapper();
|
||||
const godotId = new GodotIdWithPath(BigInt(3), ["path3"]);
|
||||
const vscodeId = mapper.create_vscode_id(godotId);
|
||||
expect(mapper.get_vscode_id(godotId)).to.equal(vscodeId);
|
||||
});
|
||||
|
||||
test("get_vscode_id throws error if not found", () => {
|
||||
const mapper = new GodotIdToVscodeIdMapper();
|
||||
const godotId = new GodotIdWithPath(BigInt(4), ["path4"]);
|
||||
expect(() => mapper.get_vscode_id(godotId)).to.throw("Unknown godot_id_with_path: 4:path4");
|
||||
});
|
||||
|
||||
test("get_or_create_vscode_id creates new ID if not found", () => {
|
||||
const mapper = new GodotIdToVscodeIdMapper();
|
||||
const godotId = new GodotIdWithPath(BigInt(5), ["path5"]);
|
||||
const vscodeId = mapper.get_or_create_vscode_id(godotId);
|
||||
expect(vscodeId).to.equal(1);
|
||||
});
|
||||
|
||||
test("get_or_create_vscode_id retrieves existing ID if already created", () => {
|
||||
const mapper = new GodotIdToVscodeIdMapper();
|
||||
const godotId = new GodotIdWithPath(BigInt(6), ["path6"]);
|
||||
const vscodeId1 = mapper.get_or_create_vscode_id(godotId);
|
||||
const vscodeId2 = mapper.get_or_create_vscode_id(godotId);
|
||||
expect(vscodeId1).to.equal(vscodeId2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
export class GodotIdWithPath {
|
||||
constructor(public godot_id: bigint, public path: string[] = []) {
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `${this.godot_id.toString()}:${this.path.join("/")}`;
|
||||
}
|
||||
}
|
||||
|
||||
type GodotIdWithPathString = string;
|
||||
|
||||
export class GodotIdToVscodeIdMapper {
|
||||
// Maps `godot_id` to `vscode_id` and back.
|
||||
// Each `vscode_id` corresponds to expandable variable in vscode UI.
|
||||
// Each `godot_id` corresponds to object in godot server.
|
||||
// `vscode_id` maps 1:1 with [`godot_id`, path_to_variable_inside_godot_object].
|
||||
// For example, if godot_object with id 12345 looks like: { SomeDict: { SomeField: [1,2,3] } },
|
||||
// then `vscode_id` for the 'SomeField' will map to [12345, ["SomeDict", "SomeField"]] in order to allow expansion of SomeField in the vscode UI.
|
||||
// Note: `vscode_id` is a number and `godot_id` is a bigint.
|
||||
|
||||
private godot_to_vscode: Map<GodotIdWithPathString, number>; // use GodotIdWithPathString, since JS Map treats GodotIdWithPath only by reference
|
||||
private vscode_to_godot: Map<number, GodotIdWithPath>;
|
||||
private next_vscode_id: number;
|
||||
|
||||
constructor() {
|
||||
this.godot_to_vscode = new Map<GodotIdWithPathString, number>();
|
||||
this.vscode_to_godot = new Map<number, GodotIdWithPath>();
|
||||
this.next_vscode_id = 1;
|
||||
}
|
||||
|
||||
// Creates `vscode_id` for a given `godot_id` and path
|
||||
create_vscode_id(godot_id_with_path: GodotIdWithPath): number {
|
||||
const godot_id_with_path_str = godot_id_with_path.toString();
|
||||
if (this.godot_to_vscode.has(godot_id_with_path_str)) {
|
||||
throw new Error(`Duplicate godot_id: ${godot_id_with_path_str}`);
|
||||
}
|
||||
|
||||
const vscode_id = this.next_vscode_id++;
|
||||
this.godot_to_vscode.set(godot_id_with_path_str, vscode_id);
|
||||
this.vscode_to_godot.set(vscode_id, godot_id_with_path);
|
||||
return vscode_id;
|
||||
}
|
||||
|
||||
get_godot_id_with_path(vscode_id: number): GodotIdWithPath {
|
||||
const godot_id_with_path = this.vscode_to_godot.get(vscode_id);
|
||||
if (godot_id_with_path === undefined) {
|
||||
throw new Error(`Unknown vscode_id: ${vscode_id}`);
|
||||
}
|
||||
return godot_id_with_path;
|
||||
}
|
||||
|
||||
get_vscode_id(godot_id_with_path: GodotIdWithPath, fail_if_not_found = true): number | undefined {
|
||||
const vscode_id = this.godot_to_vscode.get(godot_id_with_path.toString());
|
||||
if (fail_if_not_found && vscode_id === undefined) {
|
||||
throw new Error(`Unknown godot_id_with_path: ${godot_id_with_path}`);
|
||||
}
|
||||
return vscode_id;
|
||||
}
|
||||
|
||||
get_or_create_vscode_id(godot_id_with_path: GodotIdWithPath): number {
|
||||
let vscode_id = this.get_vscode_id(godot_id_with_path, false);
|
||||
if (vscode_id === undefined) {
|
||||
vscode_id = this.create_vscode_id(godot_id_with_path);
|
||||
}
|
||||
return vscode_id;
|
||||
}
|
||||
}
|
||||
79
src/debugger/godot4/variables/godot_object_promise.test.ts
Normal file
79
src/debugger/godot4/variables/godot_object_promise.test.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import sinon from "sinon";
|
||||
import chai from "chai";
|
||||
import { GodotObject, GodotObjectPromise } from "./godot_object_promise";
|
||||
// import chaiAsPromised from "chai-as-promised";
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
var chaiAsPromised = import("chai-as-promised");
|
||||
// const chaiAsPromised = await import("chai-as-promised"); // TODO: use after migration to ECMAScript modules
|
||||
|
||||
chaiAsPromised.then((module) => {
|
||||
chai.use(module.default);
|
||||
});
|
||||
const { expect } = chai;
|
||||
|
||||
|
||||
suite("GodotObjectPromise", () => {
|
||||
let clock;
|
||||
|
||||
setup(() => {
|
||||
clock = sinon.useFakeTimers(); // Use Sinon to control time
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
clock.restore(); // Restore the real timers after each test
|
||||
});
|
||||
|
||||
test("resolves successfully with a valid GodotObject", async () => {
|
||||
const godotObject: GodotObject = {
|
||||
godot_id: BigInt(1),
|
||||
type: "TestType",
|
||||
sub_values: []
|
||||
};
|
||||
|
||||
const promise = new GodotObjectPromise();
|
||||
setTimeout(() => promise.resolve(godotObject), 10);
|
||||
clock.tick(10); // Fast-forward time
|
||||
await expect(promise.promise).to.eventually.equal(godotObject);
|
||||
});
|
||||
|
||||
test("rejects with an error when explicitly called", async () => {
|
||||
const promise = new GodotObjectPromise();
|
||||
const error = new Error("Test rejection");
|
||||
setTimeout(() => promise.reject(error), 10);
|
||||
clock.tick(10); // Fast-forward time
|
||||
await expect(promise.promise).to.be.rejectedWith("Test rejection");
|
||||
});
|
||||
|
||||
test("rejects due to timeout", async () => {
|
||||
const promise = new GodotObjectPromise(50);
|
||||
clock.tick(50); // Fast-forward time
|
||||
await expect(promise.promise).to.be.rejectedWith("GodotObjectPromise timed out");
|
||||
});
|
||||
|
||||
test("does not reject if resolved before timeout", async () => {
|
||||
const godotObject: GodotObject = {
|
||||
godot_id: BigInt(2),
|
||||
type: "AnotherTestType",
|
||||
sub_values: []
|
||||
};
|
||||
|
||||
const promise = new GodotObjectPromise(100);
|
||||
setTimeout(() => promise.resolve(godotObject), 10);
|
||||
clock.tick(10); // Fast-forward time
|
||||
await expect(promise.promise).to.eventually.equal(godotObject);
|
||||
});
|
||||
|
||||
test("clears timeout when resolved", async () => {
|
||||
const promise = new GodotObjectPromise(1000);
|
||||
promise.resolve({ godot_id: BigInt(3), type: "ResolvedType", sub_values: [] });
|
||||
clock.tick(1000); // Fast-forward time
|
||||
await expect(promise.promise).to.eventually.be.fulfilled;
|
||||
});
|
||||
|
||||
test("clears timeout when rejected", async () => {
|
||||
const promise = new GodotObjectPromise(1000);
|
||||
promise.reject(new Error("Rejected"));
|
||||
clock.tick(1000); // Fast-forward time
|
||||
await expect(promise.promise).to.be.rejectedWith("Rejected");
|
||||
});
|
||||
});
|
||||
52
src/debugger/godot4/variables/godot_object_promise.ts
Normal file
52
src/debugger/godot4/variables/godot_object_promise.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { GodotVariable } from "../../debug_runtime";
|
||||
|
||||
export interface GodotObject {
|
||||
godot_id: bigint;
|
||||
type: string;
|
||||
sub_values: GodotVariable[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A promise that resolves to a {@link GodotObject}.
|
||||
*
|
||||
* This promise is used to handle the asynchronous nature of requesting a Godot object.
|
||||
* It is used as a placeholder until the actual object is received.
|
||||
*
|
||||
* When the object is received from the server, the promise is resolved with the object.
|
||||
* If the object is not received within a certain time, the promise is rejected with an error.
|
||||
*/
|
||||
export class GodotObjectPromise {
|
||||
private _resolve!: (value: GodotObject | PromiseLike<GodotObject>) => void;
|
||||
private _reject!: (reason?: any) => void;
|
||||
public promise: Promise<GodotObject>;
|
||||
private timeoutId?: NodeJS.Timeout;
|
||||
|
||||
constructor(timeoutMs?: number) {
|
||||
this.promise = new Promise<GodotObject>((resolve_arg, reject_arg) => {
|
||||
this._resolve = resolve_arg;
|
||||
this._reject = reject_arg;
|
||||
|
||||
if (timeoutMs !== undefined) {
|
||||
this.timeoutId = setTimeout(() => {
|
||||
reject_arg(new Error("GodotObjectPromise timed out"));
|
||||
}, timeoutMs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async resolve(value: GodotObject) {
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId);
|
||||
this.timeoutId = undefined;
|
||||
}
|
||||
await this._resolve(value);
|
||||
}
|
||||
|
||||
async reject(reason: Error) {
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId);
|
||||
this.timeoutId = undefined;
|
||||
}
|
||||
await this._reject(reason);
|
||||
}
|
||||
}
|
||||
240
src/debugger/godot4/variables/variables_manager.ts
Normal file
240
src/debugger/godot4/variables/variables_manager.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import { DebugProtocol } from "@vscode/debugprotocol";
|
||||
import { ServerController } from "../server_controller";
|
||||
import { GodotObject, GodotObjectPromise } from "./godot_object_promise";
|
||||
import { GodotVariable } from "../../debug_runtime";
|
||||
import { ObjectId } from "./variants";
|
||||
import { GodotIdToVscodeIdMapper, GodotIdWithPath } from "./godot_id_to_vscode_id_mapper";
|
||||
|
||||
export interface VsCodeScopeIDs {
|
||||
Locals: number;
|
||||
Members: number;
|
||||
Globals: number;
|
||||
}
|
||||
|
||||
export class VariablesManager {
|
||||
constructor(public controller: ServerController) {
|
||||
}
|
||||
|
||||
public godot_object_promises: Map<bigint, GodotObjectPromise>= new Map();
|
||||
public godot_id_to_vscode_id_mapper = new GodotIdToVscodeIdMapper();
|
||||
|
||||
// variablesFrameId: number;
|
||||
|
||||
private frame_id_to_scopes_map: Map<number, VsCodeScopeIDs> = new Map();
|
||||
|
||||
/**
|
||||
* Returns Locals, Members, and Globals vscode_ids
|
||||
* @param stack_frame_id the id of the stack frame
|
||||
* @returns an object with Locals, Members, and Globals vscode_ids
|
||||
*/
|
||||
public get_or_create_frame_scopes(stack_frame_id: number): VsCodeScopeIDs {
|
||||
var scopes = this.frame_id_to_scopes_map.get(stack_frame_id);
|
||||
if (scopes === undefined) {
|
||||
const frame_id = BigInt(stack_frame_id);
|
||||
scopes = {} as VsCodeScopeIDs;
|
||||
scopes.Locals = this.godot_id_to_vscode_id_mapper.get_or_create_vscode_id(new GodotIdWithPath(-frame_id*3n-1n, []));
|
||||
scopes.Members = this.godot_id_to_vscode_id_mapper.get_or_create_vscode_id(new GodotIdWithPath(-frame_id*3n-2n, []));
|
||||
scopes.Globals = this.godot_id_to_vscode_id_mapper.get_or_create_vscode_id(new GodotIdWithPath(-frame_id*3n-3n, []));
|
||||
this.frame_id_to_scopes_map.set(stack_frame_id, scopes);
|
||||
}
|
||||
|
||||
return scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a Godot object from the cache or godot debug server
|
||||
* @param godot_id the id of the object
|
||||
* @returns a promise that resolves to the requested object
|
||||
*/
|
||||
public async get_godot_object(godot_id: bigint, force_refresh = false) {
|
||||
if (force_refresh) {
|
||||
// delete the object
|
||||
this.godot_object_promises.delete(godot_id);
|
||||
|
||||
// check if member scopes also need to be refreshed:
|
||||
for (const [stack_frame_id, scopes] of this.frame_id_to_scopes_map) {
|
||||
const members_godot_id = this.godot_id_to_vscode_id_mapper.get_godot_id_with_path(scopes.Members);
|
||||
const scopes_object = await this.get_godot_object(members_godot_id.godot_id);
|
||||
const self = scopes_object.sub_values.find((sv) => sv.name === "self");
|
||||
if (self !== undefined && self.value instanceof ObjectId) {
|
||||
if (self.value.id === godot_id) {
|
||||
this.godot_object_promises.delete(members_godot_id.godot_id); // force refresh the member scope
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var variable_promise = this.godot_object_promises.get(godot_id);
|
||||
if (variable_promise === undefined) {
|
||||
// variable not found, request one
|
||||
if (godot_id < 0) {
|
||||
// special case for scopes, which have godot_id below 0. see @this.get_or_create_frame_scopes
|
||||
// all 3 scopes for current stackFrameId are retrieved at the same time, aka [-1,-2-,3], [-4,-5,-6], etc..
|
||||
// init corresponding promises
|
||||
const requested_stack_frame_id = (-godot_id-1n)/3n;
|
||||
// this.variablesFrameId will be undefined when the debugger just stopped at breakpoint:
|
||||
// evaluateRequest is called before scopesRequest
|
||||
const local_scopes_godot_id = -requested_stack_frame_id*3n-1n;
|
||||
const member_scopes_godot_id = -requested_stack_frame_id*3n-2n;
|
||||
const global_scopes_godot_id = -requested_stack_frame_id*3n-3n;
|
||||
this.godot_object_promises.set(local_scopes_godot_id, new GodotObjectPromise());
|
||||
this.godot_object_promises.set(member_scopes_godot_id, new GodotObjectPromise());
|
||||
this.godot_object_promises.set(global_scopes_godot_id, new GodotObjectPromise());
|
||||
variable_promise = this.godot_object_promises.get(godot_id);
|
||||
// request stack vars from godot server, which will resolve variable promises 1,2 & 3
|
||||
// see file://../server_controller.ts 'case "stack_frame_vars":'
|
||||
this.controller.request_stack_frame_vars(Number(requested_stack_frame_id));
|
||||
} else {
|
||||
this.godot_id_to_vscode_id_mapper.get_or_create_vscode_id(new GodotIdWithPath(godot_id, []));
|
||||
variable_promise = new GodotObjectPromise();
|
||||
this.godot_object_promises.set(godot_id, variable_promise);
|
||||
// request the object from godot server. Once godot server responds, the controller will resolve the variable_promise
|
||||
this.controller.request_inspect_object(godot_id);
|
||||
}
|
||||
}
|
||||
const godot_object = await variable_promise.promise;
|
||||
|
||||
return godot_object;
|
||||
}
|
||||
|
||||
public async get_vscode_object(vscode_id: number): Promise<DebugProtocol.Variable[]> {
|
||||
const godot_id_with_path = this.godot_id_to_vscode_id_mapper.get_godot_id_with_path(vscode_id);
|
||||
if (godot_id_with_path === undefined) {
|
||||
throw new Error(`Unknown variablesReference ${vscode_id}`);
|
||||
}
|
||||
const godot_object = await this.get_godot_object(godot_id_with_path.godot_id);
|
||||
if (godot_object === undefined) {
|
||||
throw new Error(`Cannot retrieve path '${godot_id_with_path.toString()}'. Godot object with id ${godot_id_with_path.godot_id} not found.`);
|
||||
}
|
||||
|
||||
let sub_values: GodotVariable[] = godot_object.sub_values;
|
||||
|
||||
// if the path is specified, walk the godot_object using it to access the requested variable:
|
||||
for (const [idx, path] of godot_id_with_path.path.entries()) {
|
||||
const sub_val = sub_values.find((sv) => sv.name === path);
|
||||
if (sub_val === undefined) {
|
||||
throw new Error(`Cannot retrieve path '${godot_id_with_path.toString()}'. Following subpath not found: '${godot_id_with_path.path.slice(0, idx+1).join("/")}'.`);
|
||||
}
|
||||
sub_values = sub_val.sub_values;
|
||||
}
|
||||
|
||||
const variables: DebugProtocol.Variable[] = [];
|
||||
for (const va of sub_values) {
|
||||
const godot_id_with_path_sub = va.id !== undefined ? new GodotIdWithPath(va.id, []) : undefined;
|
||||
const vscode_id = godot_id_with_path_sub !== undefined ? this.godot_id_to_vscode_id_mapper.get_or_create_vscode_id(godot_id_with_path_sub) : 0;
|
||||
const variable: DebugProtocol.Variable = await this.parse_variable(va, vscode_id, godot_id_with_path.godot_id, godot_id_with_path.path, this.godot_id_to_vscode_id_mapper);
|
||||
variables.push(variable);
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
public async get_vscode_variable_by_name(variable_name: string, stack_frame_id: number): Promise<DebugProtocol.Variable> {
|
||||
let variable: GodotVariable;
|
||||
|
||||
const variable_names = variable_name.split(".");
|
||||
|
||||
for (var i = 0; i < variable_names.length; i++) {
|
||||
if (i === 0) {
|
||||
// find the first part of variable_name in scopes. Locals first, then Members, then Globals
|
||||
const vscode_scope_ids = this.get_or_create_frame_scopes(stack_frame_id);
|
||||
const vscode_ids = [vscode_scope_ids.Locals, vscode_scope_ids.Members, vscode_scope_ids.Globals];
|
||||
const godot_ids = vscode_ids.map(vscode_id => this.godot_id_to_vscode_id_mapper.get_godot_id_with_path(vscode_id))
|
||||
.map(godot_id_with_path => godot_id_with_path.godot_id);
|
||||
for (var godot_id of godot_ids) {
|
||||
// check each scope for requested variable
|
||||
const scope = await this.get_godot_object(godot_id);
|
||||
variable = scope.sub_values.find((sv) => sv.name === variable_names[0]);
|
||||
if (variable !== undefined) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// just look up the subpath using the current variable
|
||||
if (variable.value instanceof ObjectId) {
|
||||
const godot_object = await this.get_godot_object(variable.value.id);
|
||||
variable = godot_object.sub_values.find((sv) => sv.name === variable_names[i]);
|
||||
} else {
|
||||
variable = variable.sub_values.find((sv) => sv.name === variable_names[i]);
|
||||
}
|
||||
}
|
||||
if (variable === undefined) {
|
||||
throw new Error(`Cannot retrieve path '${variable_name}'. Following subpath not found: '${variable_names.slice(0, i+1).join(".")}'`);
|
||||
}
|
||||
}
|
||||
|
||||
const parsed_variable = await this.parse_variable(variable, undefined, godot_id, [], this.godot_id_to_vscode_id_mapper);
|
||||
if (parsed_variable.variablesReference === undefined) {
|
||||
const objectId = variable.value instanceof ObjectId ? variable.value : undefined;
|
||||
const vscode_id = objectId !== undefined ? this.godot_id_to_vscode_id_mapper.get_or_create_vscode_id(new GodotIdWithPath(objectId.id, [])) : 0;
|
||||
parsed_variable.variablesReference = vscode_id;
|
||||
}
|
||||
|
||||
return parsed_variable;
|
||||
}
|
||||
|
||||
private async parse_variable(va: GodotVariable, vscode_id?: number, parent_godot_id?: bigint, relative_path?: string[], mapper?: GodotIdToVscodeIdMapper): Promise<DebugProtocol.Variable> {
|
||||
const value = va.value;
|
||||
let rendered_value = "";
|
||||
let reference = 0;
|
||||
|
||||
if (typeof value === "number") {
|
||||
if (Number.isInteger(value)) {
|
||||
rendered_value = `${value}`;
|
||||
} else {
|
||||
rendered_value = `${parseFloat(value.toFixed(5))}`;
|
||||
}
|
||||
} else if (
|
||||
typeof value === "bigint" ||
|
||||
typeof value === "boolean" ||
|
||||
typeof value === "string"
|
||||
) {
|
||||
rendered_value = `${value}`;
|
||||
} else if (typeof value === "undefined") {
|
||||
rendered_value = "null";
|
||||
} else {
|
||||
if (Array.isArray(value)) {
|
||||
rendered_value = `(${value.length}) [${value.slice(0, 10).join(", ")}]`;
|
||||
reference = mapper.get_or_create_vscode_id(new GodotIdWithPath(parent_godot_id, [...relative_path, va.name]));
|
||||
} else if (value instanceof Map) {
|
||||
rendered_value = value["class_name"] ?? `Dictionary(${value.size})`;
|
||||
reference = mapper.get_or_create_vscode_id(new GodotIdWithPath(parent_godot_id, [...relative_path, va.name]));
|
||||
} else if (value instanceof ObjectId) {
|
||||
if (value.id === undefined) {
|
||||
throw new Error("Invalid godot object: instanceof ObjectId but id is undefined");
|
||||
}
|
||||
// Godot returns only ID for the object.
|
||||
// In order to retrieve the class name, we need to request the object
|
||||
const godot_object = await this.get_godot_object(value.id);
|
||||
rendered_value = `${godot_object.type}${value.stringify_value()}`;
|
||||
// rendered_value = `${value.type_name()}${value.stringify_value()}`;
|
||||
reference = vscode_id;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
rendered_value = `${value.type_name()}${value.stringify_value()}`;
|
||||
} catch (e) {
|
||||
rendered_value = `${value}`;
|
||||
}
|
||||
reference = mapper.get_or_create_vscode_id(new GodotIdWithPath(parent_godot_id, [...relative_path, va.name]));
|
||||
// reference = vsode_id ? vsode_id : 0;
|
||||
}
|
||||
}
|
||||
|
||||
const variable: DebugProtocol.Variable = {
|
||||
name: va.name,
|
||||
value: rendered_value,
|
||||
variablesReference: reference
|
||||
};
|
||||
|
||||
return variable;
|
||||
}
|
||||
|
||||
public resolve_variable(godot_id: bigint, className: string, sub_values: GodotVariable[]) {
|
||||
const variable_promise = this.godot_object_promises.get(godot_id);
|
||||
if (variable_promise === undefined) {
|
||||
throw new Error(`Received 'inspect_object' for godot_id ${godot_id} but no variable promise to resolve found`);
|
||||
}
|
||||
|
||||
variable_promise.resolve({godot_id: godot_id, type: className, sub_values: sub_values} as GodotObject);
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,7 @@ export class ObjectId implements GDObject {
|
||||
}
|
||||
|
||||
public type_name(): string {
|
||||
return "Object";
|
||||
return "ObjectId";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,15 +24,15 @@ export class InspectorProvider implements TreeDataProvider<RemoteProperty> {
|
||||
this._on_did_change_tree_data.fire(undefined);
|
||||
}
|
||||
|
||||
public getChildren(element?: RemoteProperty): ProviderResult<RemoteProperty[]> {
|
||||
public getChildren(element?: RemoteProperty): RemoteProperty[] {
|
||||
if (!this.tree) {
|
||||
return Promise.resolve([]);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!element) {
|
||||
return Promise.resolve([this.tree]);
|
||||
return [this.tree];
|
||||
} else {
|
||||
return Promise.resolve(element.properties);
|
||||
return element.properties;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,15 +28,15 @@ export class SceneTreeProvider implements TreeDataProvider<SceneNode> {
|
||||
this._on_did_change_tree_data.fire(undefined);
|
||||
}
|
||||
|
||||
public getChildren(element?: SceneNode): ProviderResult<SceneNode[]> {
|
||||
public getChildren(element?: SceneNode): SceneNode[] {
|
||||
if (!this.tree) {
|
||||
return Promise.resolve([]);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!element) {
|
||||
return Promise.resolve([this.tree]);
|
||||
return [this.tree];
|
||||
} else {
|
||||
return Promise.resolve(element.children);
|
||||
return element.children;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,10 @@ func _ready() -> void:
|
||||
var simple_array = [1, 2, 3]
|
||||
var nested_dict = {
|
||||
"nested_key": "Nested Value",
|
||||
"sub_dict": {"sub_key": 99}
|
||||
"sub_dict": {"sub_key": 99},
|
||||
}
|
||||
var mixed_dict = {
|
||||
"nested_array": [1,2, {"nested_dict": [3,4,5]}]
|
||||
}
|
||||
var byte_array = PackedByteArray([0, 1, 2, 255])
|
||||
var int32_array = PackedInt32Array([100, 200, 300])
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://bl7k8rh4vgbma
|
||||
@@ -1,11 +1,22 @@
|
||||
extends Node2D
|
||||
|
||||
class_name ExtensiveVars
|
||||
|
||||
var self_var := self
|
||||
@onready var label: ExtensiveVars_Label = $Label
|
||||
|
||||
# var editor_description := "ExtensiveVars::member::text overrides"
|
||||
# var rotation = 2
|
||||
|
||||
class ClassA:
|
||||
var member_classB
|
||||
var member_self := self
|
||||
var str_var := "ExtensiveVars::ClassA::member::str_var"
|
||||
func test_function(delta: float) -> void:
|
||||
var str_var := "ExtensiveVars::ClassA::test_function::local::str_var"
|
||||
var local_self := self.member_self;
|
||||
print("breakpoint::ExtensiveVars::ClassA::test_function")
|
||||
|
||||
|
||||
class ClassB:
|
||||
var member_classA
|
||||
@@ -19,6 +30,8 @@ func _ready() -> void:
|
||||
local_classA.member_classB = local_classB
|
||||
local_classB.member_classA = local_classA
|
||||
|
||||
var str_var := "ExtensiveVars::_ready::local::str_var"
|
||||
|
||||
# Circular reference.
|
||||
# Note: that causes the godot engine to omit this variable, since stack_frame_var cannot be completed and sent
|
||||
# https://github.com/godotengine/godot/issues/76019
|
||||
@@ -28,11 +41,21 @@ func _ready() -> void:
|
||||
print("breakpoint::ExtensiveVars::_ready")
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
var str_var := "ExtensiveVars::_process::local::str_var"
|
||||
test(delta)
|
||||
|
||||
func test(delta: float):
|
||||
var str_var := "ExtensiveVars::test::local::str_var"
|
||||
var local_label := label
|
||||
var local_self_var_through_label := label.parent_var
|
||||
|
||||
var local_classA = ClassA.new()
|
||||
var local_classB = ClassB.new()
|
||||
local_classA.member_classB = local_classB
|
||||
local_classB.member_classA = local_classA
|
||||
var large_dict = {}
|
||||
for i in range(1000):
|
||||
large_dict["variable" + str(i)] = "Some very long value, which will be in the dictionary"
|
||||
|
||||
var local_classA2 = ClassA.new()
|
||||
var local_classB2 = ClassB.new()
|
||||
local_classA2.member_classB = local_classB2
|
||||
local_classB2.member_classA = local_classA2
|
||||
local_classA2.test_function(delta);
|
||||
pass
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://jj6y8lb0lkij
|
||||
@@ -0,0 +1 @@
|
||||
uid://ca1f5tmqgm6hu
|
||||
@@ -0,0 +1 @@
|
||||
uid://c4ypojhmiyhhf
|
||||
1
test_projects/test-dap-project-godot4/Node1.gd.uid
Normal file
1
test_projects/test-dap-project-godot4/Node1.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bxlldk7s267hd
|
||||
@@ -5,5 +5,6 @@ extends Node2D
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
var local_node_1 = node_1;
|
||||
print("breakpoint::NodeVars::_ready")
|
||||
pass
|
||||
|
||||
1
test_projects/test-dap-project-godot4/NodeVars.gd.uid
Normal file
1
test_projects/test-dap-project-godot4/NodeVars.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ciokiqoyaox13
|
||||
@@ -2,7 +2,25 @@ extends Node
|
||||
|
||||
var member1 := TestClassA.new()
|
||||
|
||||
var str_var := "ScopeVars::member::str_var"
|
||||
var str_var_member_only := "ScopeVars::member::str_var_member_only"
|
||||
|
||||
class ClassFoo:
|
||||
var member_ClassFoo
|
||||
var str_var := "ScopeVars::ClassFoo::member::str_var"
|
||||
var str_var_member_only := "ScopeVars::ClassFoo::member::str_var_member_only"
|
||||
func test_function(delta: float) -> void:
|
||||
var str_var := "ScopeVars::ClassFoo::test_function::local::str_var"
|
||||
print("breakpoint::ScopeVars::ClassFoo::test_function")
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var local1 := TestClassA.new()
|
||||
var local2 = GlobalScript.globalMember
|
||||
var str_var := "ScopeVars::_ready::local::str_var"
|
||||
var self_var := self
|
||||
print("breakpoint::ScopeVars::_ready")
|
||||
test(0.123);
|
||||
|
||||
func test(val: float):
|
||||
var str_var := "ScopeVars::test::local::str_var"
|
||||
var foo := ClassFoo.new()
|
||||
foo.test_function(val)
|
||||
1
test_projects/test-dap-project-godot4/ScopeVars.gd.uid
Normal file
1
test_projects/test-dap-project-godot4/ScopeVars.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cbgugy44s0uia
|
||||
1
test_projects/test-dap-project-godot4/TestClassA.gd.uid
Normal file
1
test_projects/test-dap-project-godot4/TestClassA.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ct5jeingo4ge
|
||||
Reference in New Issue
Block a user