diff --git a/.gitignore b/.gitignore index 0b60dfa..a61689a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ dist node_modules .vscode-test/ *.vsix +*.log \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 72c5b8e..ae0458f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,14 +9,12 @@ "version": "0.1.4", "license": "BSD-3-Clause", "devDependencies": { - "@types/mocha": "^10.0.10", - "@types/node": "22.x", + "@types/node": "^22.19.17", "@types/vscode": "^1.103.0", "@typescript-eslint/eslint-plugin": "^8.39.0", "@typescript-eslint/parser": "^8.39.0", "esbuild": "^0.25.8", "eslint": "^9.32.0", - "mocha": "^10.8.2", "npm-run-all": "^4.1.5", "typescript": "^5.9.2" }, @@ -770,17 +768,10 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mocha": { - "version": "10.0.10", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", - "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { - "version": "22.18.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.0.tgz", - "integrity": "sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==", + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1069,26 +1060,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1105,20 +1076,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1198,19 +1155,6 @@ "dev": true, "license": "MIT" }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -1234,13 +1178,6 @@ "node": ">=8" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true, - "license": "ISC" - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -1301,19 +1238,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1331,56 +1255,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1495,19 +1369,6 @@ } } }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1551,16 +1412,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/diff": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", - "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1576,13 +1427,6 @@ "node": ">= 0.4" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -1771,16 +1615,6 @@ "@esbuild/win32-x64": "0.25.9" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2113,16 +1947,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -2160,28 +1984,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2223,16 +2025,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2290,27 +2082,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2324,19 +2095,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -2488,16 +2246,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -2542,25 +2290,6 @@ "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -2637,19 +2366,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-boolean-object": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", @@ -2757,16 +2473,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-generator-function": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", @@ -2852,16 +2558,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -2961,19 +2657,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -3138,23 +2821,6 @@ "dev": true, "license": "MIT" }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3214,71 +2880,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", - "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3323,16 +2924,6 @@ "semver": "bin/semver" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/npm-run-all": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", @@ -3578,16 +3169,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3810,16 +3391,6 @@ ], "license": "MIT" }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -3835,19 +3406,6 @@ "node": ">=4" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -3892,16 +3450,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -3988,27 +3536,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -4057,16 +3584,6 @@ "node": ">=10" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -4278,21 +3795,6 @@ "node": ">= 0.4" } }, - "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, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/string.prototype.padend": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", @@ -4371,19 +3873,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -4726,93 +4215,6 @@ "node": ">=0.10.0" } }, - "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index fa56347..50452ee 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "test": "npm run compile-tests && node --test out/test/**/*.test.js" }, "devDependencies": { - "@types/node": "22.x", + "@types/node": "^22.19.17", "@types/vscode": "^1.103.0", "@typescript-eslint/eslint-plugin": "^8.39.0", "@typescript-eslint/parser": "^8.39.0", diff --git a/src/assembly/container.ts b/src/assembly/container.ts new file mode 100644 index 0000000..c8ec255 --- /dev/null +++ b/src/assembly/container.ts @@ -0,0 +1,52 @@ +import { extractLineInfo } from "../view/lineMaps"; +import { AssemblyBlock } from "./info"; + +export interface SourceRef { + srcFile: string; + srcLine: number; +} + +export class AssemblyContainer { + lineToSource: Map = new Map(); + sourceToLines: Map = new Map(); + /** Raw assembly blocks */ + blocks: Array = []; + /** constant info */ + constants: Array = []; + private _lineOffset = 0; + + constructor() {} + + addBlock(block: AssemblyBlock) { + if (block.header.startsWith("SWDARFCONST")) { + this.constants.push(block); + } + // Functions + else if (block.header.includes("STEXT")) { + this.blocks.push(block); + extractLineInfo(block.data, this.lineToSource, this.sourceToLines, this._lineOffset); + this._lineOffset += block.data.length; + } + } + + /** Rebuild line maps from an ordered block list (e.g. after prioritization). + * sourceFilter is called with the asm source file string; only matching lines + * are added to sourceToLines (used for cursor-sync from source → asm). */ + rebuildMaps(orderedBlocks: AssemblyBlock[], sourceFilter?: (file: string) => boolean) { + this.lineToSource.clear(); + this.sourceToLines.clear(); + let offset = 0; + for (const block of orderedBlocks) { + extractLineInfo(block.data, this.lineToSource, this.sourceToLines, offset, sourceFilter); + offset += block.data.length; + } + } + + clear() { + this.lineToSource.clear(); + this.sourceToLines.clear(); + this.blocks = []; + this.constants = []; + this._lineOffset = 0; + } +} diff --git a/src/assembly/content.ts b/src/assembly/content.ts index 0fa70bf..cf4972c 100644 --- a/src/assembly/content.ts +++ b/src/assembly/content.ts @@ -1,44 +1,36 @@ import { ProgressLocation, Uri, window, workspace } from "vscode"; -import { CommandOptions, executeCommand } from "../commands/commands"; -import { goFSPath, packageUri } from "../format"; +import { + CommandOptions, + executeCommand, + streamExecuteCommand, +} from "../commands/commands"; +import { goFSPath, packageUri, relativizeAsmLine } from "../format"; import { getFunctions } from "../go/dependencies"; -import { AssemblyBlock } from "./info"; import { logger } from "../logger/logger"; +import { AssemblyBlock } from "./info"; +import { prioritizeAssemblyBlocks } from "./order"; export async function getAsm( goUri: Uri, env?: NodeJS.ProcessEnv, - gcflags?: string + gcflags?: string, ): Promise { logger.info("getting assembly", { uri: goUri.toString() }); + const options = commandsOptions(goUri, env); + const cmmArgs = commandArgs(goUri, gcflags); - const options: CommandOptions = { env }; - const packageDir = goFSPath(packageUri(goUri)); - const ws = workspace.getWorkspaceFolder(goUri); - if (ws) { - options.cwd = ws.uri.fsPath; - } else if ( - workspace.workspaceFolders && - workspace.workspaceFolders?.length > 0 - ) { - options.cwd = workspace.workspaceFolders[0].uri.fsPath; - } - - // go build -gcflags="-S" ./package/subpackage const popts = { location: ProgressLocation.Window, cancellable: true, + title: "compiling go for assembly", }; const info = await window.withProgress(popts, async (progress, token) => { // Compiling package code progress.report({ message: "compiling..." }); - const result = await executeCommand( - "go", - ["build", gcflags ?? "-gcflags=-S", packageDir], - options, - token - ); + + // go build -gcflags="-S" ./package/subpackage + const result = await executeCommand("go", cmmArgs, options, token); if ((result.code ?? 0) > 0) { return [ @@ -53,28 +45,133 @@ export async function getAsm( progress.report({ message: "parsing..." }); let info = AssemblyBlock.parse(result.stderr); - // Filter only the functions from the file, recording their declaration order. + // Keep package assembly available so the view can reveal other-file code. progress.report({ message: "filtering..." }); const funcs = await getFunctions(goUri); - info = info.flatMap((block) => { - const idx = funcs.findIndex((f) => block.header.includes(f)); - if (idx === -1) { - return []; - } - block.sortIndex = idx; - return [block]; - }); - - info.sort((a, b) => (a.sortIndex ?? 0) - (b.sortIndex ?? 0)); + info = prioritizeAssemblyBlocks(info, funcs); // Done progress.report({ message: "done" }); return info; }); + const packageDir = goFSPath(packageUri(goUri)); + return info.map(printAssemblyInfo(packageDir, options.cwd)).join("\n"); } +export async function streamAsm( + goUri: Uri, + env: NodeJS.ProcessEnv | undefined, + gcflags: string | undefined, + asmCallback: (ab: AssemblyBlock) => void, +) { + logger.info("getting assembly", { uri: goUri.toString() }); + const options = commandsOptions(goUri, env); + const cmmArgs = commandArgs(goUri, gcflags); + const handler = new GoStreamHandler(asmCallback, options.cwd); + + const popts = { + location: ProgressLocation.Window, + cancellable: true, + + title: "compiling go for assembly", + }; + + return await window.withProgress(popts, async (progress, token) => { + // Compiling package code + progress.report({ message: "compiling and streaming result..." }); + + // go build -gcflags="-S" ./package/subpackage + const result = await streamExecuteCommand( + "go", + cmmArgs, + options, + token, + undefined, + handler.streamedStdErr.bind(handler), + ); + + if ((result.code ?? 0) > 0) { + asmCallback({ + header: "Whoops!", + data: ["Error compiling code", JSON.stringify(result, undefined, 2)], + }); + } + + handler.finish(); + }); +} + +// commandsOptions determines the options for executing the go command, such as the working directory and environment variables. +function commandsOptions(goUri: Uri, env?: NodeJS.ProcessEnv): CommandOptions { + const options: CommandOptions = { env }; + const ws = workspace.getWorkspaceFolder(goUri); + if (ws) { + options.cwd = ws.uri.fsPath; + } else if ( + workspace.workspaceFolders && + workspace.workspaceFolders?.length > 0 + ) { + options.cwd = workspace.workspaceFolders[0].uri.fsPath; + } + + return options; +} + +// commandArgs determines the arguments for the go command, such as the package directory and gcflags. +function commandArgs(goUri: Uri, gcflags?: string) { + const packageDir = goFSPath(packageUri(goUri)); + + return ["build", gcflags ?? "-gcflags=-S", packageDir]; +} + +class GoStreamHandler { + private _data: string; + + constructor( + public asmCallback: (ab: AssemblyBlock) => void, + private _workspaceDir?: string, + ) { + this._data = ""; + } + + streamedStdErr(data: Buffer) { + this._data += data.toString(); + + const offset = AssemblyBlock.isolate(this._data); + if (offset >= 0) { + const text = this._data.slice(0, offset); + this._data = this._data.slice(offset); + const blocks = AssemblyBlock.parse(text); + blocks.forEach((block) => { + if (this._workspaceDir) { + block.data = block.data.map((line) => + relativizeAsmLine(line, this._workspaceDir!), + ); + } + this.asmCallback(block); + }); + } + } + + finish() { + if (this._data.length === 0) { + return; + } + const blocks = AssemblyBlock.parse(this._data); + this._data = ""; + blocks.forEach((block) => { + if (this._workspaceDir) { + block.data = block.data.map((line) => + relativizeAsmLine(line, this._workspaceDir!), + ); + } + this.asmCallback(block); + }); + } +} + function printAssemblyInfo(packageDir: string, cwd: string | undefined) { packageDir = goFSPath(packageDir); if (!packageDir.endsWith("/")) { @@ -86,11 +183,10 @@ function printAssemblyInfo(packageDir: string, cwd: string | undefined) { return function (asm: AssemblyBlock): string { const header = asm.header.replaceAll(" ", "\n#\t"); - let lines = asm.data.map((e) => e.replaceAll(packageDir, "")); if (cwd) { - lines = lines.map((e) => e.replaceAll(cwd, ".")); + lines = lines.map((e) => relativizeAsmLine(e, cwd!)); } return `# ${header}\n${lines.join("\n")}\n`; diff --git a/src/assembly/index.ts b/src/assembly/index.ts index 95bdaa0..167875e 100644 --- a/src/assembly/index.ts +++ b/src/assembly/index.ts @@ -1,2 +1,2 @@ export * from './content'; -export * from './info'; \ No newline at end of file +export * from './info'; diff --git a/src/assembly/info.ts b/src/assembly/info.ts index b464c88..5b99aa7 100644 --- a/src/assembly/info.ts +++ b/src/assembly/info.ts @@ -2,6 +2,7 @@ export interface AssemblyBlock { header: string; data: string[]; sortIndex?: number; + } export namespace AssemblyBlock { @@ -32,4 +33,33 @@ export namespace AssemblyBlock { return result.filter((e) => e.header.length > 0 && e.data.length > 0); } + + // isolate grabs the last possible set of blocks, used for streaming, returns the offset of the isolated text, or -1 if no isolation was possible + export function isolate(text: string): number { + // Example: the last line is the /tcode of a block, + // the foo:bar is an example of a header, but we aren't sure yet that its finished + + // \tcode + // foo:bar + let foundHeader = false; + + // Walk backwards + for (let i = text.length - 1; i >= 0; i--) { + // + const c = text[i]; + if (c === "\n" || c === "\r") { + if (foundHeader) { + if (text[i + 1] === "\t") { + return i + 1; + } + } else { + if (text[i + 1] !== "\t") { + foundHeader = true; + } + } + } + } + + return -1; + } } diff --git a/src/assembly/order.ts b/src/assembly/order.ts new file mode 100644 index 0000000..0ec6371 --- /dev/null +++ b/src/assembly/order.ts @@ -0,0 +1,37 @@ +import { AssemblyBlock } from "./info"; + +export function prioritizeAssemblyBlocks( + blocks: AssemblyBlock[], + currentFileFunctions: string[], +): AssemblyBlock[] { + return blocks + .map((block, originalIndex) => ({ + block, + originalIndex, + currentFileIndex: currentFileFunctions.findIndex((name) => + block.header.includes(name), + ), + })) + .sort((left, right) => { + const leftIsCurrent = left.currentFileIndex !== -1; + const rightIsCurrent = right.currentFileIndex !== -1; + + if (leftIsCurrent !== rightIsCurrent) { + return leftIsCurrent ? -1 : 1; + } + + if ( + leftIsCurrent && + rightIsCurrent && + left.currentFileIndex !== right.currentFileIndex + ) { + return left.currentFileIndex - right.currentFileIndex; + } + + return left.originalIndex - right.originalIndex; + }) + .map(({ block, currentFileIndex }) => ({ + ...block, + sortIndex: currentFileIndex >= 0 ? currentFileIndex : undefined, + })); +} \ No newline at end of file diff --git a/src/cache/cache.ts b/src/cache/cache.ts new file mode 100644 index 0000000..65bb37b --- /dev/null +++ b/src/cache/cache.ts @@ -0,0 +1,19 @@ +interface CacheContainer { + __cache__: T | null | undefined; +} + +export namespace Cache { + // A simple utility to cache the result of a function that takes uses an object instance as the storage. + export function getOrCreate( + container: CacheContainer, + createFn: () => T, + ): T { + if (container.__cache__ !== undefined && container.__cache__ !== null) { + return container.__cache__; + } + + const value = createFn(); + container.__cache__ = value; + return value; + } +} diff --git a/src/commands/commands.ts b/src/commands/commands.ts index 886a47b..bd662a8 100644 --- a/src/commands/commands.ts +++ b/src/commands/commands.ts @@ -24,7 +24,7 @@ export async function executeCommand( command: "go" | "gopls", args: string[], options?: CommandOptions, - token?: CancellationToken + token?: CancellationToken, ): Promise { return new Promise((resolve, reject) => { const infoCommand = command + " " + args.join(" "); @@ -70,3 +70,54 @@ export async function executeCommand( } }); } + +// streaming version of executeCommand, allowing processing stdout/stderr in real time via callbacks +export async function streamExecuteCommand( + command: "go" | "gopls", + args: string[], + options?: CommandOptions, + token?: CancellationToken, + stdOutCallback?: (data: Buffer) => void, + stdErrCallback?: (data: Buffer) => void, +): Promise> { + return new Promise((resolve, reject) => { + const infoCommand = command + " " + args.join(" "); + logger.info("executing command: " + infoCommand, options); + + const cprocess = spawn(command, args, { + shell: false, + cwd: options?.cwd, + env: { ...process.env, ...options?.env }, + }); + + if (stdOutCallback) { + cprocess.stdout.on("data", (data: Buffer) => { + stdOutCallback(data); + }); + } + + if (stdErrCallback) { + cprocess.stderr.on("data", (data: Buffer) => { + stdErrCallback(data); + }); + } + + cprocess.on("error", (err) => { + logger.error("problem with command: " + infoCommand, options, err); + reject(err); + }); + + cprocess.on("close", (code: number | null) => { + logger.info("command done: " + infoCommand); + resolve({ code }); + }); + + // Handle cancellation + if (token) { + token.onCancellationRequested(() => { + cprocess.kill(); + reject(new Error("command cancelled")); + }); + } + }); +} diff --git a/src/documents/index.ts b/src/documents/index.ts index b6b6494..a13ac56 100644 --- a/src/documents/index.ts +++ b/src/documents/index.ts @@ -1,2 +1 @@ - -export * from './tracker'; +export * from './tracker'; \ No newline at end of file diff --git a/src/documents/tracker.ts b/src/documents/tracker.ts index a01bf62..0cf72aa 100644 --- a/src/documents/tracker.ts +++ b/src/documents/tracker.ts @@ -1,7 +1,7 @@ -import { Uri, Webview, Disposable } from "vscode"; -import { AssemblyView } from "../view/webview"; -import { logger } from "../logger/logger"; +import { Disposable, Uri } from "vscode"; import { GoEnvManager } from "../env"; +import { logger } from "../logger/logger"; +import { AssemblyView } from "../view/webview"; export class DocumentTracker { private _openFiles: Map; @@ -33,7 +33,7 @@ export class DocumentTracker { const result: [Uri, AssemblyView][] = []; for (const entry of this._openFiles.entries()) { - if (entry[0].fsPath.includes(packageUri.fsPath)) { + if (entry[0].path.startsWith(packageUri.path)) { result.push(entry); } } diff --git a/src/extension.ts b/src/extension.ts index 3f5db08..32c12fd 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -60,7 +60,7 @@ function init(context: vscode.ExtensionContext) { ), // Events vscode.workspace.onDidSaveTextDocument((e) => { - if (!e.uri.fsPath.endsWith(".go")) { + if (!e.uri.path.endsWith(".go")) { return; } logger.info("file saved, checking for assembly updates", { event: e }); diff --git a/src/format/uris.ts b/src/format/uris.ts index 29b63eb..b22e615 100644 --- a/src/format/uris.ts +++ b/src/format/uris.ts @@ -1,4 +1,3 @@ -import path from "path"; import { Uri } from "vscode"; export function goFSPath(uri: Uri | string): string { @@ -7,6 +6,27 @@ export function goFSPath(uri: Uri | string): string { return fs.replaceAll("\\\\", "//").replaceAll("\\", "/"); } +/** + * Replaces absolute Go source file paths embedded in an assembly line + * (e.g. `(f:/workspace/pkg/foo.go:9)`) with paths relative to the + * workspace directory. Falls back to the basename when the path does + * not share the workspace prefix. + */ +export function relativizeAsmLine(line: string, workspaceDir: string): string { + const normalizedWs = goFSPath(workspaceDir); + const prefix = normalizedWs.endsWith("/") ? normalizedWs : normalizedWs + "/"; + + return line.replace(/\(([^)]+\.go):(\d+)\)/g, (_match, filePath: string, lineNum: string) => { + const normalized = goFSPath(filePath); + if (normalized.toLowerCase().startsWith(prefix.toLowerCase())) { + return `(${normalized.slice(prefix.length)}:${lineNum})`; + } + // Fall back to just the filename when the workspace prefix doesn't match. + const basename = normalized.split("/").pop() ?? normalized; + return `(${basename}:${lineNum})`; + }); +} + /** * If given a file, returns the path to the package * @param uri @@ -16,5 +36,5 @@ export function packageUri(uri: Uri): Uri { } export function filename(uri: Uri): string { - return path.basename(uri.fsPath); + return uri.path.split("/").pop() ?? ""; } diff --git a/src/test/assembly-order.test.ts b/src/test/assembly-order.test.ts new file mode 100644 index 0000000..5968042 --- /dev/null +++ b/src/test/assembly-order.test.ts @@ -0,0 +1,43 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { prioritizeAssemblyBlocks } from "../assembly/order"; + +describe("prioritizeAssemblyBlocks", () => { + it("keeps current-file functions first without dropping other blocks", () => { + const blocks = [ + { header: "pkg.Helper STEXT", data: ["RET"] }, + { header: "pkg.CurrentOne STEXT", data: ["RET"] }, + { header: "pkg.Other STEXT", data: ["RET"] }, + { header: "pkg.CurrentTwo STEXT", data: ["RET"] }, + ]; + + const ordered = prioritizeAssemblyBlocks(blocks, [ + "CurrentOne", + "CurrentTwo", + ]); + + assert.deepStrictEqual( + ordered.map((block) => block.header), + [ + "pkg.CurrentOne STEXT", + "pkg.CurrentTwo STEXT", + "pkg.Helper STEXT", + "pkg.Other STEXT", + ], + ); + }); + + it("preserves original order when no current-file functions match", () => { + const blocks = [ + { header: "pkg.First STEXT", data: ["RET"] }, + { header: "pkg.Second STEXT", data: ["RET"] }, + ]; + + const ordered = prioritizeAssemblyBlocks(blocks, ["Missing"]); + + assert.deepStrictEqual( + ordered.map((block) => block.header), + ["pkg.First STEXT", "pkg.Second STEXT"], + ); + }); +}); \ No newline at end of file diff --git a/src/test/line-maps.test.ts b/src/test/line-maps.test.ts index 9910dcd..b297850 100644 --- a/src/test/line-maps.test.ts +++ b/src/test/line-maps.test.ts @@ -1,6 +1,6 @@ import { describe, it } from "node:test"; import assert from "node:assert/strict"; -import { buildLineMaps } from "../view/lineMaps"; +import { buildLineMaps, extractLineInfo } from "../view/lineMaps"; import { SourceFileMatchTarget } from "../view/sourceMatch"; describe("buildLineMaps", () => { @@ -11,20 +11,14 @@ describe("buildLineMaps", () => { '0x0002 00002 (./pkg/bar.go:20) RET', ].join("\n"); - const target: SourceFileMatchTarget = { - absolute: "users/me/work/mod/pkg/foo.go", - relative: "pkg/foo.go", - basename: "foo.go", - }; - - const { lineToSource, sourceToLines } = buildLineMaps(asm, target); + const { lineToSource, sourceToLines } = buildLineMaps(asm); assert.deepStrictEqual(lineToSource.get(0), { srcFile: "./pkg/foo.go", srcLine: 10 }); assert.deepStrictEqual(lineToSource.get(1), { srcFile: "./pkg/foo.go", srcLine: 10 }); assert.deepStrictEqual(lineToSource.get(2), { srcFile: "./pkg/bar.go", srcLine: 20 }); assert.deepStrictEqual(sourceToLines.get(10), [0, 1]); - assert.strictEqual(sourceToLines.has(20), false); + assert.deepStrictEqual(sourceToLines.get(20), [2]); }); it("ignores lines without source annotations", () => { @@ -33,16 +27,43 @@ describe("buildLineMaps", () => { '0x0001 00001 (./pkg/foo.go:10) RET', ].join("\n"); - const target: SourceFileMatchTarget = { - absolute: "users/me/work/mod/pkg/foo.go", - relative: "pkg/foo.go", - basename: "foo.go", - }; - - const { lineToSource, sourceToLines } = buildLineMaps(asm, target); + const { lineToSource, sourceToLines } = buildLineMaps(asm); assert.strictEqual(lineToSource.has(0), false); assert.deepStrictEqual(lineToSource.get(1), { srcFile: "./pkg/foo.go", srcLine: 10 }); assert.deepStrictEqual(sourceToLines.get(10), [1]); }); + + it("uses unique asm line keys when appending streamed blocks", () => { + const lineToSource = new Map(); + const sourceToLines = new Map(); + + extractLineInfo( + [ + '0x0000 00000 (./pkg/foo.go:10) MOVQ AX, BX', + '0x0001 00001 (./pkg/foo.go:11) RET', + ], + lineToSource, + sourceToLines, + 0, + ); + extractLineInfo( + [ + '0x0000 00000 (./pkg/foo.go:12) MOVQ AX, BX', + '0x0001 00001 (./pkg/foo.go:13) RET', + ], + lineToSource, + sourceToLines, + 2, + ); + + assert.deepStrictEqual(lineToSource.get(0), { srcFile: "./pkg/foo.go", srcLine: 10 }); + assert.deepStrictEqual(lineToSource.get(1), { srcFile: "./pkg/foo.go", srcLine: 11 }); + assert.deepStrictEqual(lineToSource.get(2), { srcFile: "./pkg/foo.go", srcLine: 12 }); + assert.deepStrictEqual(lineToSource.get(3), { srcFile: "./pkg/foo.go", srcLine: 13 }); + assert.deepStrictEqual(sourceToLines.get(10), [0]); + assert.deepStrictEqual(sourceToLines.get(11), [1]); + assert.deepStrictEqual(sourceToLines.get(12), [2]); + assert.deepStrictEqual(sourceToLines.get(13), [3]); + }); }); diff --git a/src/test/source-match.test.ts b/src/test/source-match.test.ts index 0a68b88..22e2d0e 100644 --- a/src/test/source-match.test.ts +++ b/src/test/source-match.test.ts @@ -51,4 +51,50 @@ describe("sourceMatch", () => { assert.strictEqual(matchesSourceFile("pkg/bar.go", target), false); }); + + // Windows-style paths from Go compiler output (e.g. C:/Users/...) + it("matches windows absolute path in asm output", () => { + const target: SourceFileMatchTarget = { + absolute: "c:/users/me/work/mod/pkg/foo.go", + relative: "pkg/foo.go", + basename: "foo.go", + }; + + assert.strictEqual(matchesSourceFile("C:\\Users\\me\\work\\mod\\pkg\\foo.go", target), true); + }); + + it("matches windows workspace-relative path in asm output", () => { + const target: SourceFileMatchTarget = { + absolute: "c:/users/me/work/mod/pkg/foo.go", + relative: "pkg/foo.go", + basename: "foo.go", + }; + + assert.strictEqual(matchesSourceFile(".\\pkg\\foo.go", target), true); + }); + + it("matches when asm has windows drive-letter path and target is unix-normalized", () => { + const target: SourceFileMatchTarget = { + absolute: "users/me/work/mod/pkg/foo.go", + relative: "pkg/foo.go", + basename: "foo.go", + }; + + // Go on Windows may emit absolute paths like C:/Users/... + assert.strictEqual(matchesSourceFile("C:/Users/me/work/mod/pkg/foo.go", target), true); + }); + + it("normalizes windows drive letter", () => { + assert.strictEqual( + normalizePathForCompare("C:\\Users\\Me\\Work\\pkg\\foo.go"), + "users/me/work/pkg/foo.go", + ); + }); + + it("normalizes unix absolute path", () => { + assert.strictEqual( + normalizePathForCompare("/home/me/work/pkg/foo.go"), + "home/me/work/pkg/foo.go", + ); + }); }); diff --git a/src/view/lineMaps.ts b/src/view/lineMaps.ts index 0ead27c..98a332d 100644 --- a/src/view/lineMaps.ts +++ b/src/view/lineMaps.ts @@ -1,5 +1,3 @@ -import { SourceFileMatchTarget, matchesSourceFile } from "./sourceMatch"; - export interface SourceRef { srcFile: string; srcLine: number; @@ -15,14 +13,23 @@ function addSourceLineMapping( sourceToLines.set(srcLine, existing); } -export function buildLineMaps( - asmText: string, - sourceMatchTarget: SourceFileMatchTarget, -) { +export function buildLineMaps(asmText: string) { const lines = asmText.split("\n"); const lineToSource = new Map(); const sourceToLines = new Map(); + extractLineInfo(lines, lineToSource, sourceToLines); + + return { lineToSource, sourceToLines }; +} + +export function extractLineInfo( + lines: string[], + lineToSource: Map, + sourceToLines: Map, + offset: number = 0, + sourceFilter?: (file: string) => boolean, +) { lines.forEach((line, idx) => { const match = line.match(/\(([^)]+\.go):(\d+)\)/); if (!match) { @@ -31,12 +38,11 @@ export function buildLineMaps( const file = match[1]; const srcLine = parseInt(match[2], 10); - lineToSource.set(idx, { srcFile: file, srcLine }); + const absIdx = idx + offset; + lineToSource.set(absIdx, { srcFile: file, srcLine }); - if (matchesSourceFile(file, sourceMatchTarget)) { - addSourceLineMapping(sourceToLines, srcLine, idx); + if (!sourceFilter || sourceFilter(file)) { + addSourceLineMapping(sourceToLines, srcLine, absIdx); } }); - - return { lineToSource, sourceToLines }; } diff --git a/src/view/sourceMatch.ts b/src/view/sourceMatch.ts index 8fbfbe7..73ec0df 100644 --- a/src/view/sourceMatch.ts +++ b/src/view/sourceMatch.ts @@ -1,5 +1,3 @@ -import path from "path"; - export interface SourceFileMatchTarget { absolute: string; relative?: string; @@ -9,6 +7,7 @@ export interface SourceFileMatchTarget { export function normalizePathForCompare(file: string): string { return file .replace(/\\/g, "/") + .replace(/^[a-z]:/i, "") .replace(/^\.\//, "") .replace(/^\/+/, "") .toLowerCase(); @@ -19,22 +18,28 @@ export function matchesSourceFile( target: SourceFileMatchTarget, ): boolean { const source = normalizePathForCompare(sourceFile); + const absolute = normalizePathForCompare(target.absolute); + const relative = target.relative + ? normalizePathForCompare(target.relative) + : undefined; + const basename = normalizePathForCompare(target.basename); + if (!source) { return false; } - if (source === target.absolute || target.absolute.endsWith("/" + source)) { + if (source === absolute || absolute.endsWith("/" + source)) { return true; } if ( - target.relative && - (source === target.relative || - source.endsWith("/" + target.relative) || - target.relative.endsWith("/" + source)) + relative && + (source === relative || + source.endsWith("/" + relative) || + relative.endsWith("/" + source)) ) { return true; } - return path.posix.basename(source) === target.basename; + return (source.split("/").pop() ?? "") === basename; } diff --git a/src/view/sourceMatchTarget.ts b/src/view/sourceMatchTarget.ts index 97f7090..08beacc 100644 --- a/src/view/sourceMatchTarget.ts +++ b/src/view/sourceMatchTarget.ts @@ -1,13 +1,14 @@ -import path from "path"; import { Uri, workspace } from "vscode"; import { normalizePathForCompare, SourceFileMatchTarget } from "./sourceMatch"; export function createSourceMatchTarget(fileUri: Uri): SourceFileMatchTarget { - const absolute = normalizePathForCompare(fileUri.fsPath); - const basename = normalizePathForCompare(path.basename(fileUri.fsPath)); + const absolute = normalizePathForCompare(fileUri.path); + const basename = normalizePathForCompare(fileUri.path.split("/").pop() ?? ""); const ws = workspace.getWorkspaceFolder(fileUri); const relative = ws - ? normalizePathForCompare(path.relative(ws.uri.fsPath, fileUri.fsPath)) + ? normalizePathForCompare( + fileUri.path.substring(ws.uri.path.length).replace(/^\//, ""), + ) : undefined; return { diff --git a/src/view/webview.ts b/src/view/webview.ts index c8ad439..691ded2 100644 --- a/src/view/webview.ts +++ b/src/view/webview.ts @@ -1,5 +1,6 @@ import { Disposable, + TextEditor, TextEditorDecorationType, TextEditorRevealType, ThemeColor, @@ -7,22 +8,29 @@ import { ViewColumn, WebviewPanel, window, - workspace, } from "vscode"; -import { getAsm } from "../assembly"; -import { filename } from "../format"; +import { AssemblyBlock, streamAsm } from "../assembly"; +import { AssemblyContainer } from "../assembly/container"; +import { prioritizeAssemblyBlocks } from "../assembly/order"; import { GoEnvManager } from "../env"; -import { buildLineMaps } from "./lineMaps"; -import { createSourceMatchTarget } from "./sourceMatchTarget"; +import { filename } from "../format"; +import { getFunctions } from "../go/dependencies"; import { matchesSourceFile, SourceFileMatchTarget } from "./sourceMatch"; -import { getHtml } from "./webviewHtml"; - +import { createSourceMatchTarget } from "./sourceMatchTarget"; +import { getHtml, getHtmlAssembly } from "./webviewHtml"; + +/** + * Finds the best matching source editor for the given source file. + * @param srcFile The source file path extracted from the assembly metadata, e.g. "src/foo/bar.go". + * @param sourceFileUri The URI of the source file for the currently displayed assembly, used for exact matching. This is needed because the srcFile can be a relative path and may not be unique across the workspace. The editor with an exact URI match will be prioritized over others that only match by filename or suffix. + * @returns The best matching TextEditor, or undefined if no match is found. + */ function findSourceEditor( srcFile: string, sourceFileUri: Uri, -) { +): TextEditor | undefined { const primaryEditor = window.visibleTextEditors.find( - (e) => e.document.uri.fsPath === sourceFileUri.fsPath, + (e) => e.document.uri.toString() === sourceFileUri.toString(), ); return window.visibleTextEditors.find( @@ -32,6 +40,11 @@ function findSourceEditor( ); } +/** + * A controller for a single assembly view panel. + * Responsible for rendering the assembly, handling interactions, and syncing highlights + * between the assembly and source code. + */ export class AssemblyView implements Disposable { readonly panel: WebviewPanel; readonly fileUri: Uri; @@ -40,8 +53,9 @@ export class AssemblyView implements Disposable { private readonly sourceMatchTarget: SourceFileMatchTarget; private _sourceHighlight: TextEditorDecorationType; - private _sourceToLines: Map = new Map(); private _disposables: Disposable[] = []; + private _asmContainer = new AssemblyContainer(); + private _updateGeneration = 0; constructor(uri: Uri, envManager: GoEnvManager) { this.fileUri = uri; @@ -60,11 +74,13 @@ export class AssemblyView implements Disposable { ViewColumn.Beside, { enableScripts: true }, ); - this.panel.webview.html = getHtml( - "loading...", - "", - new Map(), - this.sourceMatchTarget, + this.panel.webview.html = "loading..."; + getHtml("loading...", "", new Map(), this.sourceMatchTarget).then( + (html) => { + if (this.panel.webview.html === "loading...") { + this.panel.webview.html = html; + } + }, ); // Handle messages from the webview (ASM hover → source highlight) @@ -77,7 +93,7 @@ export class AssemblyView implements Disposable { // Handle cursor changes in source editor (source → ASM highlight) this._disposables.push( window.onDidChangeTextEditorSelection((e) => { - if (e.textEditor.document.uri.fsPath === this.fileUri.fsPath) { + if (e.textEditor.document.uri.toString() === this.fileUri.toString()) { this._syncFromSource(e.textEditor.selection.active.line + 1); } }), @@ -98,26 +114,68 @@ export class AssemblyView implements Disposable { return this.panel.onDidDispose; } + /** Triggers an update of the assembly view, re-fetching and re-rendering the assembly. */ async update() { + const generation = ++this._updateGeneration; + try { - const asm = await getAsm( + this._asmContainer.clear(); + + await streamAsm( this.fileUri, this.envManager.getEnvVars(), - this.envManager.getGcFlags() + this.envManager.getGcFlags(), + (b) => { + if (generation === this._updateGeneration) { + this._addBlock(b); + } + }, ); - const { lineToSource, sourceToLines } = buildLineMaps( - asm, + + if (generation !== this._updateGeneration) { + return; + } + + await this.updateView(); + } catch (err: any) { + if (generation !== this._updateGeneration) { + return; + } + this.panel.webview.html = await getHtml( + `got an error: ${JSON.stringify({ ...err }, undefined, 2)}`, + this.filename, + new Map(), this.sourceMatchTarget, ); - this._sourceToLines = sourceToLines; - this.panel.webview.html = getHtml( - asm, + } + } + + // Adds a new assembly block to the container. This is called for each block as they are streamed in, allowing for incremental rendering. + private _addBlock(b: AssemblyBlock) { + this._asmContainer.addBlock(b); + } + + /** Renders the current assembly blocks in the webview. + * Should be called after updating the assembly container with new blocks. */ + async updateView() { + try { + const funcs = await getFunctions(this.fileUri); + const prioritized = prioritizeAssemblyBlocks(this._asmContainer.blocks, funcs); + this._asmContainer.rebuildMaps(prioritized, (file) => matchesSourceFile(file, this.sourceMatchTarget)); + + let b = prioritized; + if (b.length > 1000) { + b = b.slice(0, 1000); + } + + this.panel.webview.html = await getHtmlAssembly( + b, this.filename, - lineToSource, + this._asmContainer.lineToSource, this.sourceMatchTarget, ); } catch (err: any) { - this.panel.webview.html = getHtml( + this.panel.webview.html = await getHtml( `got an error: ${JSON.stringify({ ...err }, undefined, 2)}`, this.filename, new Map(), @@ -139,32 +197,11 @@ export class AssemblyView implements Disposable { } } else if (m["type"] === "hoverEnd") { this._clearSourceHighlight(); - } else if (m["type"] === "export") { - const content = m["content"]; - if (typeof content === "string") { - this._exportAssembly(content); - } - } - } - - private async _exportAssembly(content: string) { - const defaultUri = Uri.file(this.filename.replace(/\.go$/, ".s")); - const uri = await window.showSaveDialog({ - defaultUri, - filters: { - "Assembly files": ["s"], - "Text files": ["txt"], - "All files": ["*"], - }, - saveLabel: "Export Assembly", - }); - if (!uri) { - return; } - await workspace.fs.writeFile(uri, new TextEncoder().encode(content)); - window.showInformationMessage(`Assembly exported to ${uri.fsPath}`); } + // Highlights the given source line in the editor. + // Called when hovering over an assembly line that maps to a source line. private _highlightSourceLine(srcFile: string, srcLine: number) { const editor = findSourceEditor(srcFile, this.fileUri); if (!editor) { @@ -181,14 +218,19 @@ export class AssemblyView implements Disposable { editor.revealRange(range, TextEditorRevealType.InCenterIfOutsideViewport); } + // Clears any source line highlights in all visible editors. + // Called when the hover ends in the assembly view. private _clearSourceHighlight() { window.visibleTextEditors.forEach((e) => { e.setDecorations(this._sourceHighlight, []); }); } + // Syncs the currently highlighted source line to the assembly view + // causing the corresponding assembly lines to be highlighted. private _syncFromSource(sourceLine: number) { - const asmLines = this._sourceToLines.get(sourceLine) ?? []; + const asmLines = this._asmContainer.sourceToLines.get(sourceLine) ?? []; + this.panel.webview.postMessage({ type: "highlightLines", lines: asmLines, diff --git a/src/view/webviewHtml.ts b/src/view/webviewHtml.ts index cd7684c..31a36b1 100644 --- a/src/view/webviewHtml.ts +++ b/src/view/webviewHtml.ts @@ -1,5 +1,8 @@ +import { AssemblyBlock } from "../assembly"; +import { logger } from "../logger/logger"; import { SourceRef } from "./lineMaps"; import { SourceFileMatchTarget, matchesSourceFile } from "./sourceMatch"; +import { ProgressLocation, window } from "vscode"; interface RenderBlock { header: string; @@ -16,11 +19,12 @@ function escapeHtml(text: string): string { function syntaxHighlight(escaped: string): string { return escaped - .replace(/(0x[0-9a-f]+)/g, '$1') + .replace(/(0x[0-9a-fA-F]+)/g, '$1') .replace( - /\b(AX|AL|BX|CX|DX|SI|DI|R[0-9]+|SP|SB|BP)\b/g, + /\b(AX|AL|BX|BL|CX|CL|DX|DL|SI|DI|R[0-9]+|SP|SB|BP|FP|PC|TLS)\b/g, '$1', ) + .replace(/(\$-?\d+)/g, '$1') .replace(/\b([A-Z]{3,})\b/g, '$1') .replace(/\(([^)]+\.go:\d+)\)/g, '($1)') .replace(/(#.*$|\/\/.*$)/g, '$1'); @@ -72,6 +76,29 @@ function parseRenderBlocks( return blocks.filter((b) => b.lines.length > 0); } +function convertToRenderBlocks( + rawBlocks: AssemblyBlock[], + lineToSource: Map, + sourceMatchTarget: SourceFileMatchTarget, +): RenderBlock[] { + let offset = 0; + return rawBlocks.map((block) => { + const blockOffset = offset; + offset += block.data.length; + return { + header: block.header, + lines: block.data.map((line, idx) => + createAssemblyLine( + line, + idx + blockOffset, + lineToSource, + sourceMatchTarget, + ), + ), + }; + }); +} + function renderBodyContent(content: RenderBlock[] | string[]): string { const isBlocks = content.length > 0 && @@ -94,7 +121,10 @@ function renderBodyContent(content: RenderBlock[] | string[]): string { .join("\n"); } -function renderHtml(filename: string, content: RenderBlock[] | string[]): string { +function renderHtml( + filename: string, + content: RenderBlock[] | string[], +): string { const bodyContent = renderBodyContent(content); return ` @@ -102,10 +132,11 @@ function renderHtml(filename: string, content: RenderBlock[] | string[]): string