Compare commits
424 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2912d5cd89 | |||
| c699577183 | |||
| 707fe9e0a3 | |||
| 71dd603c3d | |||
| 7aa5f1e4ec | |||
| 7c4e92a030 | |||
| 76125cc83e | |||
| 3fc0fd0bc0 | |||
| 5b0d8f4418 | |||
| 5f76fa8d7d | |||
| f6358e319b | |||
| 033de587f2 | |||
| bd0db41f67 | |||
| a5ac79c6a4 | |||
| 67a253164e | |||
| 3c93e0a414 | |||
| 1d9e0faaa3 | |||
| b440c8732f | |||
| 2aae01f3b6 | |||
| c1ffca04a7 | |||
| de703e82cb | |||
| a2a9cdba60 | |||
| bde51106a1 | |||
| 3ac9dcf0c0 | |||
| 7e8af9c2ef | |||
| a94ce5d474 | |||
| 7decc8e146 | |||
| 7c0ba71314 | |||
| 878a439acd | |||
| f047ef9c55 | |||
| db6ebeb9f2 | |||
| 7631b41b27 | |||
| b2d8ca3023 | |||
| f11ff3475b | |||
| e74cc15d71 | |||
| 0294cb3b98 | |||
| 88dc32a43e | |||
| 2dcd95cf2a | |||
| 499abdf26c | |||
| 52e92ac3f9 | |||
| 885ebc8528 | |||
| 4f4ef694ff | |||
| 8cc68989dd | |||
| 57795b3210 | |||
| 68be4dcf57 | |||
| eb4d2a6c7b | |||
| 36277eaed8 | |||
| ba4cac3fb6 | |||
| 3ce2911e3d | |||
| 89dc3c8805 | |||
| 1bbd6f59fe | |||
| bf6cb764ec | |||
| a2a265423f | |||
| 5f39e314b6 | |||
| b7e87b4af8 | |||
| b34b2f3d7b | |||
| 5b6bf42576 | |||
| a72ca7868e | |||
| a0f43c90c4 | |||
| 41b85052f0 | |||
| 4120031e0d | |||
| a499fa19fb | |||
| 6867dabf09 | |||
| 93875818f5 | |||
| d09223c362 | |||
| fb5ae96a44 | |||
| dbe62e82e1 | |||
| e5c72ac13e | |||
| dc143e7cb5 | |||
| 80443892f3 | |||
| 70e89c8f20 | |||
| 57e8ef5463 | |||
| d5581b1bb4 | |||
| 1482ebba64 | |||
| 6d4253dff9 | |||
| cc4c863c82 | |||
| 4618e81d20 | |||
| 7fa971cbcd | |||
| 2267757c41 | |||
| e07727393b | |||
| 4e56321eb9 | |||
| 9e177c73ca | |||
| ccbe1911d2 | |||
| a6d5a5f325 | |||
| e566db8396 | |||
| b52cf80bd5 | |||
| f1a31246b2 | |||
| e33b3ceae1 | |||
| c4e78cd3ba | |||
| fa1e74acf2 | |||
| 112a770f2c | |||
| b1bcac5a6d | |||
| ae79229c00 | |||
| 28ac708904 | |||
| 161b446bb2 | |||
| 072484dd5d | |||
| a86d3dc82b | |||
| b141b2edfe | |||
| cf9482bd76 | |||
| 4be2c3b6fd | |||
| 69a75f5c47 | |||
| 0ab4f8513b | |||
| 93f8597dd4 | |||
| 0f0e56a263 | |||
| 91a23bb2d3 | |||
| 19503d92b4 | |||
| e2648bba3e | |||
| 090eaba3aa | |||
| 1f98e75a4f | |||
| a38b4c8ee0 | |||
| 1def205b28 | |||
| 32bee63b50 | |||
| 4798bbdc86 | |||
| a854a341d3 | |||
| 1a4623589c | |||
| 51b19a6831 | |||
| b58ad5f876 | |||
| 4951116a6c | |||
| 46ca49e02b | |||
| 9133d6ecf5 | |||
| ad736edd9e | |||
| 1c7494861d | |||
| 31163e8516 | |||
| 853a26ae8e | |||
| 16c7aaf599 | |||
| d6df2514e1 | |||
| 2b33294a85 | |||
| 3ecdae6ab6 | |||
| fd1c840618 | |||
| 71f8315e06 | |||
| 633f7ffee5 | |||
| 35f53e66dd | |||
| 82851d519d | |||
| cfc18827bb | |||
| 04e8a204a3 | |||
| 6fd514230c | |||
| f75b4f34d8 | |||
| 6ecf7be826 | |||
| 47998e4ee5 | |||
| 995f39b61f | |||
| ec2ae0e190 | |||
| 2bef719754 | |||
| c6376eb1db | |||
| db2c60db82 | |||
| a6f1b1e0ad | |||
| 9edca589a0 | |||
| da6710a079 | |||
| f60d5cad2f | |||
| 75f1a2d79b | |||
| 22dd42ba70 | |||
| 1675879fa4 | |||
| d6a7921526 | |||
| 00734ce327 | |||
| 3e584d01a3 | |||
| 847b57c406 | |||
| d4e9dbb82a | |||
| f3451d5077 | |||
| 159d52a716 | |||
| 29f8fa6e1d | |||
| 9dc7632d9b | |||
| e1d16b3de4 | |||
| 4301eb2bb1 | |||
| 4ad82cff07 | |||
| 22a16974f9 | |||
| cf9eca0564 | |||
| 2eafda9132 | |||
| c889acadc0 | |||
| b3522ad73c | |||
| 96867326ae | |||
| 4946d3c2a0 | |||
| 6a6cb56d87 | |||
| eed71d9582 | |||
| 90a282a1f7 | |||
| 8511681710 | |||
| 4dff3e216a | |||
| e894be803b | |||
| 07e995fc2f | |||
| cffe053226 | |||
| dafc509fa1 | |||
| 794c4593e6 | |||
| 5d825ad970 | |||
| cc4115c8c4 | |||
| b86356d2af | |||
| 8dab3ac854 | |||
| 1326f19a5e | |||
| 69697542ba | |||
| a7728a6030 | |||
| 9ee14e6a71 | |||
| 2ae098ee96 | |||
| 0dae638be7 | |||
| dd321121e3 | |||
| 932b4974e9 | |||
| 20d89cd07c | |||
| f75dee39b3 | |||
| acd711f93b | |||
| 744d387fe1 | |||
| ec505b653b | |||
| ad471a3015 | |||
| 86fddaa0e0 | |||
| b9405a76cd | |||
| 730758cbec | |||
| b26b62998d | |||
| 6856e23e2a | |||
| b305fc5034 | |||
| 9ab3ec3c11 | |||
| 5b3471a7d7 | |||
| 6bb0966eea | |||
| 5d0a4c8157 | |||
| a1c7b254d3 | |||
| 4cf79f7a3b | |||
| b1e50798d8 | |||
| a320323aa1 | |||
| ad2e5f2616 | |||
| f64f6b54b4 | |||
| 6d8f843b12 | |||
| a96e2f9a08 | |||
| e08e728fc8 | |||
| 18d2d9f5ee | |||
| f2caa9876c | |||
| 4a06d79170 | |||
| 9a8814cc50 | |||
| d6327f2b03 | |||
| 4528270937 | |||
| 8ef693b218 | |||
| 46db2c6e95 | |||
| 6b897a6f71 | |||
| 7aecbe25cd | |||
| f79d438084 | |||
| 35eb93d829 | |||
| 9350c0dcdf | |||
| 39e989d3cc | |||
| a3cfd0d023 | |||
| 7b3a82f1d2 | |||
| 17ac9c081c | |||
| 97b44a94e4 | |||
| 4a9af17736 | |||
| a33160f33e | |||
| 4bd6f4f8ac | |||
| 71dc1ab93e | |||
| 0ec9e4a54e | |||
| 4284461b06 | |||
| 3f7f2fb40c | |||
| 47e93d1cf3 | |||
| a99d1c99a5 | |||
| 7b76df235a | |||
| c6d246f783 | |||
| ec8eb8bded | |||
| 39fb3bc304 | |||
| aca5deb814 | |||
| 824de11f21 | |||
| a4d93ac95a | |||
| c1e83e458b | |||
| 7ea5870a11 | |||
| cf6cef8e0e | |||
| bf2843b1bd | |||
| 8a6b75b6bb | |||
| e7e5e5ebf8 | |||
| 00e57dd1bf | |||
| 744841b092 | |||
| 136840cca4 | |||
| dbdd40bd3d | |||
| 8f3a752562 | |||
| fa43a9f937 | |||
| c222a801a8 | |||
| def78affb4 | |||
| edefe841d5 | |||
| 27b782ff32 | |||
| 654b577ce6 | |||
| 8dcc3bf187 | |||
| cd512402d9 | |||
| bc6f68e105 | |||
| b7f764799c | |||
| 6e39b225b2 | |||
| 744f0ed852 | |||
| 1b6d81f4e0 | |||
| bb370b390c | |||
| 378ef493b5 | |||
| a70f606362 | |||
| f66fbcd911 | |||
| 1c2e8c0c59 | |||
| 09ba9387bc | |||
| b97c5089ce | |||
| 7563f595f3 | |||
| 79ef6b5773 | |||
| 11d376325e | |||
| 1ef8011bb0 | |||
| fa1f407b76 | |||
| 094e05070a | |||
| 6d5d4c34d7 | |||
| 967f7dc85e | |||
| d541d8fbb6 | |||
| 89ac7738e4 | |||
| 7aca03de5c | |||
| 4e9dc5ce4a | |||
| d6d5f77183 | |||
| 62b3a7d7be | |||
| 437244540a | |||
| 05d102f354 | |||
| e4b0061f66 | |||
| e70e5d4208 | |||
| ccd0db635b | |||
| ab81479340 | |||
| 65be47d970 | |||
| 217a33cc2e | |||
| 6a55abcd81 | |||
| 330092d023 | |||
| 7b665d05cf | |||
| 352496fc3a | |||
| eda11f29d9 | |||
| 2300559081 | |||
| 4a9c0c864f | |||
| 4212e50ac5 | |||
| 009dfbef37 | |||
| 75ef9d5528 | |||
| ec4c52aff9 | |||
| bccc6a64ef | |||
| 93c0bb1d04 | |||
| 4061e6382a | |||
| b5dbd42eed | |||
| c42a782bde | |||
| 6be0b74047 | |||
| 0fb9222f3a | |||
| db7ec0272a | |||
| 7558d4dc1c | |||
| f89b036d17 | |||
| 2b7df8c604 | |||
| ac2fb9e36d | |||
| 5ad5fe7014 | |||
| 5eb2bcfef6 | |||
| cd532435c2 | |||
| 6e2a803628 | |||
| 1e5e3d7f01 | |||
| ae15fd1f38 | |||
| bc1a9d692e | |||
| dce3f392ce | |||
| 6d3de34818 | |||
| f292fa8df6 | |||
| 82ffd4e8f4 | |||
| 6b5792aaf7 | |||
| e1a1ca6275 | |||
| d2e096957e | |||
| 3bb9d5b3a5 | |||
| 1be8d17899 | |||
| 46e8fb1ea5 | |||
| 02d8527244 | |||
| aeb3f4f97c | |||
| 46a8e147d0 | |||
| a5b68448b5 | |||
| c0ea6cc4d8 | |||
| 1086b6272a | |||
| cc6a231b3c | |||
| ead1d9229f | |||
| 1b77fc7091 | |||
| ee6d5e47ec | |||
| a6c9b5f089 | |||
| c9984ce011 | |||
| 4eaffd90d1 | |||
| 027c0ee6bd | |||
| 4ba7352374 | |||
| 866547df79 | |||
| 7f278aafcb | |||
| 5c5a44e4ac | |||
| 83f9ff54ab | |||
| 1259e71589 | |||
| c9cc6cc6a1 | |||
| dc65016ecb | |||
| 9aae56682e | |||
| 176d302258 | |||
| e23fee5191 | |||
| 4f34af61f3 | |||
| 7311f3eaf7 | |||
| 1257e54ebe | |||
| fcbad101a4 | |||
| d2b5531a33 | |||
| 993c171e06 | |||
| b6ea05948f | |||
| 495dc45c10 | |||
| e4cf30ef07 | |||
| 5655280d45 | |||
| c793db8aeb | |||
| f711c68798 | |||
| dbc14a0713 | |||
| 417259123b | |||
| b82d668437 | |||
| 93d40be1ac | |||
| cfc39b83bb | |||
| 9c76a35837 | |||
| 4a00bd3e00 | |||
| 6d6e2fa974 | |||
| 971ce88df2 | |||
| aa835d468b | |||
| 661eba3ae9 | |||
| de17b3add1 | |||
| 6fa3901ab0 | |||
| d14a2f1fff | |||
| c1f3b92c51 | |||
| 0de63a088d | |||
| 0de1c20551 | |||
| a769eb4db0 | |||
| 3e7e54b88f | |||
| 1b43799371 | |||
| 38370c751e | |||
| 764b1a2ccf | |||
| 1dffbfb061 | |||
| df6de2d7fc | |||
| 4ebfa91a83 | |||
| 08581776ae | |||
| af6f9fc570 | |||
| ecdef730d5 | |||
| 0f4120c1a0 | |||
| c8db4529b2 | |||
| c3c760c03b | |||
| 12c686c102 | |||
| 7bceceef85 | |||
| ba15b31661 | |||
| 3b09174587 | |||
| 7482e2aa82 | |||
| e3c826b253 | |||
| db69464ddc | |||
| 014c87bc3e | |||
| ba606d229a | |||
| fbb4faacbc | |||
| 9b7fd09278 | |||
| b90973b195 |
@@ -1,5 +1,5 @@
|
||||
disturl="https://electronjs.org/headers"
|
||||
target="34.2.0"
|
||||
target="34.3.2"
|
||||
ms_build_id="11044223"
|
||||
runtime="electron"
|
||||
build_from_source="true"
|
||||
|
||||
+4
-3
@@ -12,7 +12,7 @@ There are a few ways to contribute:
|
||||
|
||||
### Codebase Guide
|
||||
|
||||
We highly recommend reading [this](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) article on VSCode's sourcecode organization.
|
||||
We [highly recommend reading this](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) article on VSCode's sourcecode organization too. Void's codebase is pretty simple when you know what a service is and what `browser/` and `common/` mean, and the article covers all the jargon.
|
||||
|
||||
<!-- ADD BLOG HERE
|
||||
We wrote a [guide to working in VSCode].
|
||||
@@ -61,13 +61,14 @@ To build Void, open `void/` inside VSCode. Then open your terminal and run:
|
||||
3. Build Void.
|
||||
- Press <kbd>Cmd+Shift+B</kbd> (Mac).
|
||||
- Press <kbd>Ctrl+Shift+B</kbd> (Windows/Linux).
|
||||
- This step can take ~5 min. The build is done when you see two check marks.
|
||||
- This step can take ~5 min. The build is done when you see two check marks (one of the items will continue spinning indefinitely - it compiles our React code).
|
||||
4. Run Void.
|
||||
- Run `./scripts/code.sh` (Mac/Linux).
|
||||
- Run `./scripts/code.bat` (Windows).
|
||||
6. Nice-to-knows.
|
||||
- You can always press <kbd>Ctrl+R</kbd> (<kbd>Cmd+R</kbd>) inside the new window to reload and see your new changes. It's faster than <kbd>Ctrl+Shift+P</kbd> and `Reload Window`.
|
||||
- You might want to add the flags `--user-data-dir ./.tmp/user-data --extensions-dir ./.tmp/extensions` to the above run command, which lets you delete the `.tmp` folder to reset any IDE changes you made when testing.
|
||||
- You can kill any of the build scripts by pressing `Ctrl+D` in VSCode terminal. If you press `Ctrl+C` the script will close but will keep running in the background (to open all background scripts, just re-build).
|
||||
|
||||
#### Building Void from Terminal
|
||||
|
||||
@@ -85,7 +86,7 @@ Alternatively, if you want to build Void from the terminal, instead of pressing
|
||||
#### Common Fixes
|
||||
|
||||
- Make sure you followed the prerequisite steps.
|
||||
- Make sure you have Node version `20.16.0` (the version in `.nvmrc`)!
|
||||
- Make sure you have Node version `20.18.2` (the version in `.nvmrc`)!
|
||||
- If you get `"TypeError: Failed to fetch dynamically imported module"`, make sure all imports end with `.js`.
|
||||
- If you see missing styles, wait a few seconds and then reload.
|
||||
- If you have any questions, feel free to [submit an issue](https://github.com/voideditor/void/issues/new). You can also refer to VSCode's complete [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) page.
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
The Void team put together this list of links to get up and running with VSCode's sourcecode. We hope it's helpful!
|
||||
|
||||
For a complete guide on building Void, see [Contributing](https://github.com/voideditor/void/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
- [How VSCode's sourcecode is organized](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) - this explains where the entry point files are, what `browser/` and `common/` mean, etc. This is the most important read on this whole list! We recommend reading the whole thing.
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
c0a187acca68906c4a6387e8fabd052cb031ace6132d60a71001d9a0e891958e *chromedriver-v34.2.0-darwin-arm64.zip
|
||||
fa5a46d752267d8497d375e19079e8b6a8df70c234a79b2d6b48f5862e1a0abc *chromedriver-v34.2.0-darwin-x64.zip
|
||||
61e03d4fa570976d80f740637f56192b6448a05a73d1fba9717900b29f2b1b4d *chromedriver-v34.2.0-linux-arm64.zip
|
||||
ec774d9b1a1b828a0db1502a1017fcab1dfed99b1b6b2fd2308dd600a1efa98a *chromedriver-v34.2.0-linux-armv7l.zip
|
||||
cc15a6e6206485a2d96649ceb60509b9da04fa2811c4824b2e0eb43d1f4b1417 *chromedriver-v34.2.0-linux-x64.zip
|
||||
9777122f6684180ef375b9b21dcabbc731d8a8befa300d1d47ad954a5b64c1c8 *chromedriver-v34.2.0-mas-arm64.zip
|
||||
69451fa148b105fec9644646b22ca758a206499574c5816591354835c8056679 *chromedriver-v34.2.0-mas-x64.zip
|
||||
eb7adc7e720f5e0f1d2c12ecbe886bdc01f2c9aaa3954bd6ebd313750bb18819 *chromedriver-v34.2.0-win32-arm64.zip
|
||||
cb1973b0c2f5565974d5c2cb51816692f064b6cdc7897fa341d528ba7f9b14bf *chromedriver-v34.2.0-win32-ia32.zip
|
||||
af5575b4727c3dbe7272016cbbaa8872043f843168a47d86748a50397efb4f77 *chromedriver-v34.2.0-win32-x64.zip
|
||||
fcc718af2a28fb953290dc971e945818b4dbb293f297e6e25acb669d450cc0dd *electron-api.json
|
||||
fa47e752e559a6472b87d8907f63296ed8cd53ecf862a4ae113018474d40aa8e *electron-v34.2.0-darwin-arm64-dsym-snapshot.zip
|
||||
336c3374e721e2379901141be6345459f78d243b037c65b55bac4ae8cb14bfbe *electron-v34.2.0-darwin-arm64-dsym.zip
|
||||
ac3b9d712d9f036f066d8eba42797117a513e2d250fcc117f0354300b7d5c1e5 *electron-v34.2.0-darwin-arm64-symbols.zip
|
||||
ee447c17b2ac545e48083113d7e39a916821c1316f60f42cbcbee4fffe7c022a *electron-v34.2.0-darwin-arm64.zip
|
||||
d7510bc038d06b26690ac9a78fbb256626502303ff7f5b1c2242f64a02832b1b *electron-v34.2.0-darwin-x64-dsym-snapshot.zip
|
||||
0e95c2bbda00afe78e6229b824e3ffe0cc8612956dc11a5a30380224acdbecae *electron-v34.2.0-darwin-x64-dsym.zip
|
||||
6d734ef8e8fd007071aae9a13534894dd801f11900f3e73e49cf5352b426e575 *electron-v34.2.0-darwin-x64-symbols.zip
|
||||
8ef741819c8a5370dabc3b9df5e6ac217366477c5d5c656ed23c800bc984d887 *electron-v34.2.0-darwin-x64.zip
|
||||
c2f448882a0392ebfd9810058a6a9580b087002c74fca3dcd3cf4ba5c3b27a7c *electron-v34.2.0-linux-arm64-debug.zip
|
||||
51e887c382593021127593ceba89ad662d3a6de0f748b94fe3a0ce78a7393923 *electron-v34.2.0-linux-arm64-symbols.zip
|
||||
818c91309da8ff948c43df58a996c05c0d27daa690e1d659355d9db01e351919 *electron-v34.2.0-linux-arm64.zip
|
||||
c2f448882a0392ebfd9810058a6a9580b087002c74fca3dcd3cf4ba5c3b27a7c *electron-v34.2.0-linux-armv7l-debug.zip
|
||||
71ec2b7473db766194bcf04648229c4affedce06e150e692745cc72dbc3749c5 *electron-v34.2.0-linux-armv7l-symbols.zip
|
||||
0c75996c6bf2d37d0441d3e1021386a9348f8d21783d75571cdb10ede606424f *electron-v34.2.0-linux-armv7l.zip
|
||||
d1f17be8df8ec6dc1a23afd8a7752f0b949c755a493b8a2c1e83f76c629258ca *electron-v34.2.0-linux-x64-debug.zip
|
||||
d8fa79154b0b663dbd0054d69f53aec326997449ef21943d27225a2d6899660f *electron-v34.2.0-linux-x64-symbols.zip
|
||||
f12a02d86cc657500978d263ec6d1841b6e2085cd3bd4d30a97cfe14685396f3 *electron-v34.2.0-linux-x64.zip
|
||||
71ef8bfebb8513a405fd2beb44ad18f802bbac2248f81a5328ddaaa12906d70c *electron-v34.2.0-mas-arm64-dsym-snapshot.zip
|
||||
437aca6cad3158f15fd852e5913462637052940f812b20ccc10fccc133d5a0d6 *electron-v34.2.0-mas-arm64-dsym.zip
|
||||
7f253375a7b43d34b770c03153e47185be7a64bc0c10a783993a93144eb35492 *electron-v34.2.0-mas-arm64-symbols.zip
|
||||
1f601c20430b036b485c7dc02a39a297f0d08905462a4568306c12f299375e2f *electron-v34.2.0-mas-arm64.zip
|
||||
d32664181804a17f825bf1fbfd8f0cbe01e0b41b284937359e6d9754b3481ed8 *electron-v34.2.0-mas-x64-dsym-snapshot.zip
|
||||
f6b394b89fb77dfeefdf525739fe8cd9695f8c2ac2251ed7c571a376cde059e9 *electron-v34.2.0-mas-x64-dsym.zip
|
||||
539328d93e9bc122e6e34faeab6a42f4845c523f796c7a11bbdcfe5e856f6b98 *electron-v34.2.0-mas-x64-symbols.zip
|
||||
749b6ced7a9d35a719ad98d4c3bf1a08c315c19ce97d2497235d4ac302cda665 *electron-v34.2.0-mas-x64.zip
|
||||
e843ea4cb7a93686728d056c957c124760886167932ff619b518e45917edf38a *electron-v34.2.0-win32-arm64-pdb.zip
|
||||
4908423be5f8ad1b5dd737edfd69ee0870460e16bb639bb963b0981bda2628e4 *electron-v34.2.0-win32-arm64-symbols.zip
|
||||
9d13b2bd61416eec28f43faa399cc5c0dc9e36dec226314bbf397828f55d74de *electron-v34.2.0-win32-arm64-toolchain-profile.zip
|
||||
1629cec7b5620e6ca3b5305c393ae147d1a3871a8f164d686b7bee3810fc1109 *electron-v34.2.0-win32-arm64.zip
|
||||
e7182f1ef5ed13187fe12f35133cefb50c59e1d52ada758cbb72813dda575205 *electron-v34.2.0-win32-ia32-pdb.zip
|
||||
a10f778f62bf060a7e38f5ea75ea472679b9ef4f12767ee0703e6d77effae78a *electron-v34.2.0-win32-ia32-symbols.zip
|
||||
9d13b2bd61416eec28f43faa399cc5c0dc9e36dec226314bbf397828f55d74de *electron-v34.2.0-win32-ia32-toolchain-profile.zip
|
||||
96396712a0240f04471f71246b3885a513b08e3f2d40154272d34e59419db7e6 *electron-v34.2.0-win32-ia32.zip
|
||||
0c55ef5b1a6ea4604e3f0e9f8b5e946944abd1cfc3b974432d40e924a9996812 *electron-v34.2.0-win32-x64-pdb.zip
|
||||
d6b67cf12edabcc62ae21e7c90ac6b1161dbefe4e6b765c69fc7040096b7d026 *electron-v34.2.0-win32-x64-symbols.zip
|
||||
9d13b2bd61416eec28f43faa399cc5c0dc9e36dec226314bbf397828f55d74de *electron-v34.2.0-win32-x64-toolchain-profile.zip
|
||||
a4fdf617dca787b7f1c6f8d0d1cb69c8adb37ee23f9553fe69803f9cad713360 *electron-v34.2.0-win32-x64.zip
|
||||
5ca44a4ee9cc74a56c9556ce3f3774986dbb8b4f4953088c7f4577bfba4356a8 *electron.d.ts
|
||||
86247a6815cc98f321374edcbfc0ab7ee79bcb13a5c25213f42151e4059ec690 *ffmpeg-v34.2.0-darwin-arm64.zip
|
||||
e94f4707a91194f97d0451f9d5a27982c873a5c13d83d20926ce6fdb613f536c *ffmpeg-v34.2.0-darwin-x64.zip
|
||||
947d7b7cb035eab94cc15b602dfa8a925cf238c1d9225c01732fe0c41f59c571 *ffmpeg-v34.2.0-linux-arm64.zip
|
||||
64d74b6b94ac4fd755b97e0ec8d50af9cd73810fdb52f6c081b13a3337ace0ea *ffmpeg-v34.2.0-linux-armv7l.zip
|
||||
a62cb20c5e5f2ba6f1df6f1bc406cc30f7ed44fe6380b506afa333d1b4ad7a87 *ffmpeg-v34.2.0-linux-x64.zip
|
||||
715bbb3d193b1ca57d4b329edd05cd6b125dc188cdb73d1c66f220f62c8562e3 *ffmpeg-v34.2.0-mas-arm64.zip
|
||||
866bf47106e8c3e50f046783eb8f5c756069c6de962a46f4edc7a5ee7e4593ee *ffmpeg-v34.2.0-mas-x64.zip
|
||||
71a2a6c4cca15ccbcbf8912f5d73f855b0ca79804f941f68527ae808f8163957 *ffmpeg-v34.2.0-win32-arm64.zip
|
||||
399f841cca166781078ca42c8ea060e3d5850ec6cb79716186d0806f3ce20842 *ffmpeg-v34.2.0-win32-ia32.zip
|
||||
afad2a44f20a0c0c01fb1fea637f3f821842399593c9961c74d650ca12d32cbb *ffmpeg-v34.2.0-win32-x64.zip
|
||||
c95fdc9dba05aa68aeccb69d4c34f0cb1fb98d7f5291d974d0b638488693655f *hunspell_dictionaries.zip
|
||||
0abe74138afdb6e45a085d77407659f13c75ab96f694313d4e98bd662f9c6df2 *libcxx-objects-v34.2.0-linux-arm64.zip
|
||||
3d0cbf6fb150b006428eab78235856d2204d5e93ca85f162e429b4c8bd9b0d3b *libcxx-objects-v34.2.0-linux-armv7l.zip
|
||||
3a5491e32cec825499919be1b8bf0550d28fe5a31ff00a95572d49a58bb4820a *libcxx-objects-v34.2.0-linux-x64.zip
|
||||
ff8753d52f759041b8e5462125ee2b96798fe8b5047f8fb8ae60cd07af2fb13d *libcxx_headers.zip
|
||||
c98cce0091681bc367a48f66c5f4602961aa9cb6dd1a995d8969d6b39ce732f3 *libcxxabi_headers.zip
|
||||
78a9606190fb227460ddcd276ad540873595d6a82fd1007f42900f53347e3eb1 *mksnapshot-v34.2.0-darwin-arm64.zip
|
||||
6d4587a36f509356a908b6752de527cfe8a294a2d435b82ea3a98288c3a3a178 *mksnapshot-v34.2.0-darwin-x64.zip
|
||||
17737bd34f7feefc5143b745f2c4f0e5a41678780e144eb43de41f8d4b9d7852 *mksnapshot-v34.2.0-linux-arm64-x64.zip
|
||||
565aa9a84d913b7b5eb8d3b868cff151cb8a6c16548233ffd92b2f9bf3789227 *mksnapshot-v34.2.0-linux-armv7l-x64.zip
|
||||
7332d7864ab4e5ee7fa8d00580d9357f30d0e69d3d1434d67c61f79f666314a4 *mksnapshot-v34.2.0-linux-x64.zip
|
||||
cc81bfc9894378a9fc8a777429e04cc80860663b3dff8eba1f8cc72559a4f23d *mksnapshot-v34.2.0-mas-arm64.zip
|
||||
c98230088698638159f6e7e0c5ddde3cb4dba9fa81d76e3c58fc86f96b63ef81 *mksnapshot-v34.2.0-mas-x64.zip
|
||||
cc7b42943d998e1083ad8119dc2201dd27d709c15aa2a9b78f409485d2927592 *mksnapshot-v34.2.0-win32-arm64-x64.zip
|
||||
ade9e3126f113318e226f9bdeba068b4383b30a98517a361386eff448459effa *mksnapshot-v34.2.0-win32-ia32.zip
|
||||
344292ea318dc0e21f37bc7c82d57a57449f0fb278d9868f1b1b597bdcb1f36f *mksnapshot-v34.2.0-win32-x64.zip
|
||||
c9b82c9f381742e839fea00aeb14f24519bcaf38a0f4eed25532191701f9535b *chromedriver-v34.3.2-darwin-arm64.zip
|
||||
d556c1e2b06f1bf131e83c2fb981de755c28e1083a884d257eb964815be16b0c *chromedriver-v34.3.2-darwin-x64.zip
|
||||
1cabad4f3303ac2ff172a9f22185f64944dbaa6fc68271609077158eaefdee35 *chromedriver-v34.3.2-linux-arm64.zip
|
||||
4213ce52c72ef414179b5c5c22ae8423847ff030d438296bd6c2aac763930a7b *chromedriver-v34.3.2-linux-armv7l.zip
|
||||
3c64c08221fdfc0f4be60ea8b1b126f2ecca45f60001b63778522f711022c6ea *chromedriver-v34.3.2-linux-x64.zip
|
||||
e8388734d88e011cb6cd79795431de9206820749219d80565ee49d90501d2bf3 *chromedriver-v34.3.2-mas-arm64.zip
|
||||
3ad1dd37bd6e0bb37e8503898db7aedd56bd5213e6d6760b05c3d11f4625062b *chromedriver-v34.3.2-mas-x64.zip
|
||||
d567b481a0f5d88e84bba7718f89fb08f56363bfc4cb5914e1c2086358a5c252 *chromedriver-v34.3.2-win32-arm64.zip
|
||||
df6732e9dc61cb20a3c0b2a2de453aac7e2bd54e7cbff43512afa614852c15fa *chromedriver-v34.3.2-win32-ia32.zip
|
||||
dda0765c8d064924632e18cd152014ecd767f3808fc51c8249a053bfb7ca70a2 *chromedriver-v34.3.2-win32-x64.zip
|
||||
1945f15caff98f2e0f1ee539c483d352fb8d4d0c13f342caa7abe247676d828c *electron-api.json
|
||||
c078bbf727b3c3026f60e07a0f4643b85c06c581b54be017d0a6c284ba6772d3 *electron-v34.3.2-darwin-arm64-dsym-snapshot.zip
|
||||
35f587754d6a3272606258386bf73688d63dd53c7e572d3a7cbaae6f3f60bdae *electron-v34.3.2-darwin-arm64-dsym.zip
|
||||
08b14ee02c98353de3c738120dfd017322666e82b914a7f6de9b9888dcc5c0f0 *electron-v34.3.2-darwin-arm64-symbols.zip
|
||||
2a4aa7e8fa30f229e465ebd18d3e4722e2b41529dc51a68a954d333a7e556ffe *electron-v34.3.2-darwin-arm64.zip
|
||||
1509ccdeb80024f5e3edd5ecf804b4cef4e47ea2bd74e33ef0b39044b0ccf892 *electron-v34.3.2-darwin-x64-dsym-snapshot.zip
|
||||
3bbe5d587c3f582ed8c126b0fb635cc02ad9a14d077b04892fe6f862092445b0 *electron-v34.3.2-darwin-x64-dsym.zip
|
||||
fa7ece82e6ecaf1c94ed341e8ebff98e64687c68fe113f52cd9a21400302e22f *electron-v34.3.2-darwin-x64-symbols.zip
|
||||
23938c62257a65a863ed7aa7c7966ba5f257a7d3dc16c78293e826962cc39c5c *electron-v34.3.2-darwin-x64.zip
|
||||
0547eecf8ab538d74fa854f591ce8b888a3dbb339256d2db3038e7bb2c6dd929 *electron-v34.3.2-linux-arm64-debug.zip
|
||||
676d0dc2b1c1c85c8b2abbb8cd5376ee22ecdb910493b910d9ae5a998532136a *electron-v34.3.2-linux-arm64-symbols.zip
|
||||
774e4ccb39d553e5487994a9f8c60774a90f08cdb049ff65f3963fc27c969ff2 *electron-v34.3.2-linux-arm64.zip
|
||||
0547eecf8ab538d74fa854f591ce8b888a3dbb339256d2db3038e7bb2c6dd929 *electron-v34.3.2-linux-armv7l-debug.zip
|
||||
ba33bf53fcb35dea568a2795f5b23ecf46c218abe8258946611c72a1f42f716c *electron-v34.3.2-linux-armv7l-symbols.zip
|
||||
73ae92c8fffb351d3a455569cf57ce9a3f676f42bf61939c613c607fe0fc3bfb *electron-v34.3.2-linux-armv7l.zip
|
||||
e61a9a69dd7ea6f2687708a8e83516670cdea53c728226e598e2f6f1fad5b77b *electron-v34.3.2-linux-x64-debug.zip
|
||||
f1a04df7fe67dd1cd29e7b87871525458d2eb24c0cf3b5835a1c56974707562a *electron-v34.3.2-linux-x64-symbols.zip
|
||||
7b74c0c4fae82e27c7e9cbca13e9763e046113dba8737d3e27de9a0b300ac87e *electron-v34.3.2-linux-x64.zip
|
||||
8571a6aa83e00925ceb39fdc5a45a9f6b9aa3d92fd84951c6f252ed723aea4ae *electron-v34.3.2-mas-arm64-dsym-snapshot.zip
|
||||
477410c6f9a6c5eeaedf376058a02c2996fc0a334aa40eeec7d3734c09347f4d *electron-v34.3.2-mas-arm64-dsym.zip
|
||||
c2e62dcd6630cb51b2d8e2869e74e47d29bda785521cea6e82e546d0fc58aabb *electron-v34.3.2-mas-arm64-symbols.zip
|
||||
a1698e8546a062fd59b7f8e5507a7f3220fb00b347f2377de83fc9a07f7f3507 *electron-v34.3.2-mas-arm64.zip
|
||||
741a24ac230a3651dca81d211f9f00b835c428a5ed0c5f67d370d4e88b62f8d6 *electron-v34.3.2-mas-x64-dsym-snapshot.zip
|
||||
aeff97ec9e5c9e173ac89e38acd94476025c5640d5f27be1e8c2abd54398bab3 *electron-v34.3.2-mas-x64-dsym.zip
|
||||
9f14b66b1d612ac66697288e8763171c388f7f200854871a5f0ab464a6a921c2 *electron-v34.3.2-mas-x64-symbols.zip
|
||||
c979d7e7175f1e8e03ca187997d4c156b878189fc3611b347fadebcb16f3e027 *electron-v34.3.2-mas-x64.zip
|
||||
f43c700641e8220205dd356952e32718d113cf530520c4ed7209b59851eac266 *electron-v34.3.2-win32-arm64-pdb.zip
|
||||
3ba6e01c99bffac6b5dd3fd6f122ecdb571cf6f675dc5498c65050bd7a382ef8 *electron-v34.3.2-win32-arm64-symbols.zip
|
||||
c23f84aabb09c24cd2ae759a547fdba4206af19a3bb0f4554a91cd9528648ad0 *electron-v34.3.2-win32-arm64-toolchain-profile.zip
|
||||
9b9cb65d75a16782088b492f9ef3bb4d27525012b819c12bf29bd27e159d749b *electron-v34.3.2-win32-arm64.zip
|
||||
1006e7af4c149114b5ebc3497617aaa6cd1bb0b131e0a225fd73709ff308f9c5 *electron-v34.3.2-win32-ia32-pdb.zip
|
||||
1ecb6430cd04454f08f557c9579163f3552144bfcc0b67b768dad8868b5b891d *electron-v34.3.2-win32-ia32-symbols.zip
|
||||
c23f84aabb09c24cd2ae759a547fdba4206af19a3bb0f4554a91cd9528648ad0 *electron-v34.3.2-win32-ia32-toolchain-profile.zip
|
||||
d004fd5f853754001fafaec33e383d1950b30c935ee71b297ec1c9e084355e9b *electron-v34.3.2-win32-ia32.zip
|
||||
4e0721552fd2f09e9466e88089af8b965f1bfbc4ae00a59aaf6245b1d1efabfd *electron-v34.3.2-win32-x64-pdb.zip
|
||||
9dea812a7e7cd0fb18e5fed9a99db5531959a068c24d3c0ecedceb644cd3ffa0 *electron-v34.3.2-win32-x64-symbols.zip
|
||||
c23f84aabb09c24cd2ae759a547fdba4206af19a3bb0f4554a91cd9528648ad0 *electron-v34.3.2-win32-x64-toolchain-profile.zip
|
||||
1785e161420fb90d2331c26e50bba3413cae9625b7db3c8524ea02ade631efba *electron-v34.3.2-win32-x64.zip
|
||||
722b304c31ddac58b0083d94a241c5276464f04bd8ea4fcbfd33051d197be103 *electron.d.ts
|
||||
31ce159b2e47d1de5bc907d8e1c89726b0f2ba530ec2e9d7a8e5c723b1ccf6e0 *ffmpeg-v34.3.2-darwin-arm64.zip
|
||||
565539bac64a6ee9cf6f188070f520210a1507341718f5dc388ac7c454b1e1d5 *ffmpeg-v34.3.2-darwin-x64.zip
|
||||
6006ea0f46ab229feb2685be086b0fafd65981e2939dd2218a078459c75ab527 *ffmpeg-v34.3.2-linux-arm64.zip
|
||||
9404ce2e85df7c40f809f2cf62c7af607de299839fe6b7ae978c3015500abcc8 *ffmpeg-v34.3.2-linux-armv7l.zip
|
||||
79aec96898b7e2462826780ee0b52b9ab299dc662af333e128a34fd5ddae87f1 *ffmpeg-v34.3.2-linux-x64.zip
|
||||
9190743c78210574faf5d5ecb82a00f8fa15e5f2253378cb925a99ca9d39961b *ffmpeg-v34.3.2-mas-arm64.zip
|
||||
48915adcb1a6342efeda896035101300f0432c0304cfb38f2378e98c6309ebae *ffmpeg-v34.3.2-mas-x64.zip
|
||||
745d5ef786de6d4a720475079836e2fda7b501cfcd255819485a47de5b24b74e *ffmpeg-v34.3.2-win32-arm64.zip
|
||||
d0d86d60978439dc8ae4a723d4e4c1f853891d596bfd84033440a232fa762e2f *ffmpeg-v34.3.2-win32-ia32.zip
|
||||
4441539fd8c9cbe79880ff1bade9bdc0c3744c33d7409130af6404e57ee401ff *ffmpeg-v34.3.2-win32-x64.zip
|
||||
39edd1eeefe881aa75af0e438204e0b1c6e6724e34fa5819109276331c0c2c9a *hunspell_dictionaries.zip
|
||||
20dd417536e5f4ebc01f480221284c0673729c27b082bc04e2922f16cd571719 *libcxx-objects-v34.3.2-linux-arm64.zip
|
||||
7e53c5779c04f895f8282c0450ec4a63074d15a0e910e41006cfea133d0288af *libcxx-objects-v34.3.2-linux-armv7l.zip
|
||||
92e2283c924ab523ffec3ea22513beaab6417f7fc3d570f57d51a1e1ceb7f510 *libcxx-objects-v34.3.2-linux-x64.zip
|
||||
9bf3c6e8ad68f523fe086fada4148dd04e5bb0b9290d104873e66f2584a5cf50 *libcxx_headers.zip
|
||||
34e4b44f9c5e08b557a2caed55456ce7690abab910196a783a2a47b58d2b9ac9 *libcxxabi_headers.zip
|
||||
11f67635e6659f9188198e4086c51b89890b61a22f6c17c99eff35595ee8f51d *mksnapshot-v34.3.2-darwin-arm64.zip
|
||||
c0add9ef4ac27c73fa043d04b4c9635fd3fd9f5c16d7a03e961864ba05252813 *mksnapshot-v34.3.2-darwin-x64.zip
|
||||
6262adf86a340d8d28059937b01ef1117b93212e945fddbceea5c18d7e7c64f0 *mksnapshot-v34.3.2-linux-arm64-x64.zip
|
||||
f7db8ebe91a1cc8d24ef6aad12949a18d8e4975ac296e3e5e9ecd88c9bccb143 *mksnapshot-v34.3.2-linux-armv7l-x64.zip
|
||||
6642038e86bda362980ff1c8973a404e2b02efdd87de9e35b650fc1e743833da *mksnapshot-v34.3.2-linux-x64.zip
|
||||
15883bf8e8cd737c3682d1e719d7cbac92f50b525681aac324dca876861dfc7d *mksnapshot-v34.3.2-mas-arm64.zip
|
||||
4da23a950bfcc377ef21c37d496017ab4c36da03f3b41049ac114042c42608ce *mksnapshot-v34.3.2-mas-x64.zip
|
||||
fab59573d3c2f9bdf31146a1896d24ac0c51f736aad86d2f3c7ecef13c05a7fd *mksnapshot-v34.3.2-win32-arm64-x64.zip
|
||||
66f25e07c6f8d5d2009577a129440255a3baf63c929a5b60b2e77cd52e46105b *mksnapshot-v34.3.2-win32-ia32.zip
|
||||
8168bfbf61882cfac80aed1e71e364e1c7f2fccd11eac298e6abade8b46894ea *mksnapshot-v34.3.2-win32-x64.zip
|
||||
@@ -54,9 +54,11 @@ async function main(buildDir) {
|
||||
...defaultOpts,
|
||||
// TODO(deepak1556): Incorrectly declared type in electron-osx-sign
|
||||
ignore: (filePath) => {
|
||||
const ext = path_1.default.extname(filePath);
|
||||
return filePath.includes(gpuHelperAppName) ||
|
||||
filePath.includes(rendererHelperAppName) ||
|
||||
filePath.includes(pluginHelperAppName);
|
||||
filePath.includes(pluginHelperAppName) ||
|
||||
ext === '.asar' || ext === '.dat' || ext === '.gif' || ext === '.icns' || ext === '.ico' || ext === '.json' || ext === '.mp3' || ext === '.nib' || ext === '.pak' || ext === '.png' || ext === '.scpt' || ext === '.ttf' || ext === '.wasm' || ext === '.woff' || ext === '.woff2';
|
||||
}
|
||||
};
|
||||
const gpuHelperOpts = {
|
||||
|
||||
@@ -58,9 +58,11 @@ async function main(buildDir?: string): Promise<void> {
|
||||
...defaultOpts,
|
||||
// TODO(deepak1556): Incorrectly declared type in electron-osx-sign
|
||||
ignore: (filePath: string) => {
|
||||
const ext = path.extname(filePath);
|
||||
return filePath.includes(gpuHelperAppName) ||
|
||||
filePath.includes(rendererHelperAppName) ||
|
||||
filePath.includes(pluginHelperAppName);
|
||||
filePath.includes(pluginHelperAppName) ||
|
||||
ext === '.asar' || ext === '.dat' || ext === '.gif' || ext === '.icns' || ext === '.ico' || ext === '.json' || ext === '.mp3' || ext === '.nib' || ext === '.pak' || ext === '.png' || ext === '.scpt' || ext === '.ttf' || ext === '.wasm' || ext === '.woff' || ext === '.woff2';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+16
-4
@@ -154,9 +154,20 @@ function getNodeChecksum(expectedName) {
|
||||
}
|
||||
|
||||
function extractAlpinefromDocker(nodeVersion, platform, arch) {
|
||||
const imageName = arch === 'arm64' ? 'arm64v8/node' : 'node';
|
||||
let imageName = 'node';
|
||||
let dockerPlatform = '';
|
||||
|
||||
if (arch === 'arm64') {
|
||||
imageName = 'arm64v8/node';
|
||||
|
||||
const architecture = cp.execSync(`docker info --format '{{json .Architecture}}'`, { encoding: 'utf8' }).trim();
|
||||
if (architecture !== '"aarch64"') {
|
||||
dockerPlatform = '--platform=linux/arm64';
|
||||
}
|
||||
}
|
||||
|
||||
log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from docker image ${imageName}`);
|
||||
const contents = cp.execSync(`docker run --rm ${imageName}:${nodeVersion}-alpine /bin/sh -c 'cat \`which node\`'`, { maxBuffer: 100 * 1024 * 1024, encoding: 'buffer' });
|
||||
const contents = cp.execSync(`docker run --rm ${dockerPlatform} ${imageName}:${nodeVersion}-alpine /bin/sh -c 'cat \`which node\`'`, { maxBuffer: 100 * 1024 * 1024, encoding: 'buffer' });
|
||||
return es.readArray([new File({ path: 'node', contents, stat: { mode: parseInt('755', 8) } })]);
|
||||
}
|
||||
|
||||
@@ -308,10 +319,11 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa
|
||||
}
|
||||
|
||||
const name = product.nameShort;
|
||||
const release = packageJson.release;
|
||||
|
||||
let packageJsonContents;
|
||||
const packageJsonStream = gulp.src(['remote/package.json'], { base: 'remote' })
|
||||
.pipe(json({ name, version, dependencies: undefined, optionalDependencies: undefined, type: 'module' }))
|
||||
.pipe(json({ name, version, release, dependencies: undefined, optionalDependencies: undefined, type: 'module' }))
|
||||
.pipe(es.through(function (file) {
|
||||
packageJsonContents = file.contents.toString();
|
||||
this.emit('data', file);
|
||||
@@ -319,7 +331,7 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa
|
||||
|
||||
let productJsonContents;
|
||||
const productJsonStream = gulp.src(['product.json'], { base: '.' })
|
||||
.pipe(json({ commit, date: readISODate('out-build'), version }))
|
||||
.pipe(json({ commit, date: readISODate('out-build'), version, release }))
|
||||
.pipe(es.through(function (file) {
|
||||
productJsonContents = file.contents.toString();
|
||||
this.emit('data', file);
|
||||
|
||||
@@ -267,7 +267,8 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
|
||||
}
|
||||
|
||||
const name = product.nameShort;
|
||||
const packageJsonUpdates = { name, version };
|
||||
const release = packageJson.release;
|
||||
const packageJsonUpdates = { name, version, release };
|
||||
|
||||
if (platform === 'linux') {
|
||||
packageJsonUpdates.desktopName = `${product.applicationName}.desktop`;
|
||||
@@ -284,7 +285,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
|
||||
// Void - this is important, creates the product.json in .app
|
||||
let productJsonContents;
|
||||
const productJsonStream = gulp.src(['product.json'], { base: '.' })
|
||||
.pipe(json({ commit, date: readISODate('out-build'), checksums, version }))
|
||||
.pipe(json({ commit, date: readISODate('out-build'), checksums, version, release }))
|
||||
.pipe(es.through(function (file) {
|
||||
productJsonContents = file.contents.toString();
|
||||
this.emit('data', file);
|
||||
@@ -372,7 +373,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
|
||||
} else if (platform === 'darwin') {
|
||||
const shortcut = gulp.src('resources/darwin/bin/code.sh')
|
||||
.pipe(replace('@@APPNAME@@', product.applicationName))
|
||||
.pipe(rename('bin/code'));
|
||||
.pipe(rename('bin/' + product.applicationName));
|
||||
|
||||
all = es.merge(all, shortcut);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ const exec = util.promisify(cp.exec);
|
||||
const root = path.dirname(__dirname);
|
||||
const commit = getVersion(root);
|
||||
|
||||
const linuxPackageRevision = Math.floor(new Date().getTime() / 1000);
|
||||
|
||||
/**
|
||||
* @param {string} arch
|
||||
@@ -87,7 +86,7 @@ function prepareDebPackage(arch) {
|
||||
const dependencies = await dependenciesGenerator.getDependencies('deb', binaryDir, product.applicationName, debArch);
|
||||
gulp.src('resources/linux/debian/control.template', { base: '.' })
|
||||
.pipe(replace('@@NAME@@', product.applicationName))
|
||||
.pipe(replace('@@VERSION@@', packageJson.version + '-' + linuxPackageRevision))
|
||||
.pipe(replace('@@VERSION@@', `${packageJson.version}.${packageJson.release}`))
|
||||
.pipe(replace('@@ARCHITECTURE@@', debArch))
|
||||
.pipe(replace('@@DEPENDS@@', dependencies.join(', ')))
|
||||
.pipe(replace('@@RECOMMENDS@@', debianRecommendedDependencies.join(', ')))
|
||||
@@ -202,8 +201,7 @@ function prepareRpmPackage(arch) {
|
||||
.pipe(replace('@@NAME@@', product.applicationName))
|
||||
.pipe(replace('@@NAME_LONG@@', product.nameLong))
|
||||
.pipe(replace('@@ICON@@', product.linuxIconName))
|
||||
.pipe(replace('@@VERSION@@', packageJson.version))
|
||||
.pipe(replace('@@RELEASE@@', linuxPackageRevision))
|
||||
.pipe(replace('@@VERSION@@', `${packageJson.version}.${packageJson.release}`))
|
||||
.pipe(replace('@@ARCHITECTURE@@', rpmArch))
|
||||
.pipe(replace('@@LICENSE@@', product.licenseName))
|
||||
.pipe(replace('@@QUALITY@@', product.quality || '@@QUALITY@@'))
|
||||
@@ -279,7 +277,7 @@ function prepareSnapPackage(arch) {
|
||||
|
||||
const snapcraft = gulp.src('resources/linux/snap/snapcraft.yaml', { base: '.' })
|
||||
.pipe(replace('@@NAME@@', product.applicationName))
|
||||
.pipe(replace('@@VERSION@@', commit.substr(0, 8)))
|
||||
.pipe(replace('@@VERSION@@', `${packageJson.version}.${packageJson.release}`))
|
||||
// Possible run-on values https://snapcraft.io/docs/architectures
|
||||
.pipe(replace('@@ARCHITECTURE@@', arch === 'x64' ? 'amd64' : arch))
|
||||
.pipe(rename('snap/snapcraft.yaml'));
|
||||
|
||||
@@ -87,8 +87,8 @@ function buildWin32Setup(arch, target) {
|
||||
NameLong: product.nameLong,
|
||||
NameShort: product.nameShort,
|
||||
DirName: product.win32DirName,
|
||||
Version: pkg.version,
|
||||
RawVersion: pkg.version.replace(/-\w+$/, ''),
|
||||
Version: `${pkg.version}.${pkg.release}`,
|
||||
RawVersion: `${pkg.version.replace(/-\w+$/, '')}.${pkg.release}`,
|
||||
NameVersion: product.win32NameVersion + (target === 'user' ? ' (User)' : ''),
|
||||
ExeBasename: product.nameShort,
|
||||
RegValueName: product.win32RegValueName,
|
||||
|
||||
@@ -85,10 +85,7 @@ function getExtensionDownloadStream(extension) {
|
||||
if (extension.vsix) {
|
||||
input = ext.fromVsix(path_1.default.join(root, extension.vsix), extension);
|
||||
}
|
||||
else if (productjson.extensionsGallery?.serviceUrl) {
|
||||
input = ext.fromMarketplace(productjson.extensionsGallery.serviceUrl, extension);
|
||||
}
|
||||
else {
|
||||
else { // Void - ext-from-gh.patch
|
||||
input = ext.fromGithub(extension);
|
||||
}
|
||||
return input.pipe((0, gulp_rename_1.default)(p => p.dirname = `${extension.name}/${p.dirname}`));
|
||||
|
||||
@@ -73,9 +73,7 @@ function getExtensionDownloadStream(extension: IExtensionDefinition) {
|
||||
|
||||
if (extension.vsix) {
|
||||
input = ext.fromVsix(path.join(root, extension.vsix), extension);
|
||||
} else if (productjson.extensionsGallery?.serviceUrl) {
|
||||
input = ext.fromMarketplace(productjson.extensionsGallery.serviceUrl, extension);
|
||||
} else {
|
||||
} else { // Void - ext-from-gh.patch
|
||||
input = ext.fromGithub(extension);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ function getMangledFileContents(projectPath) {
|
||||
* @type {webpack.LoaderDefinitionFunction}
|
||||
*/
|
||||
module.exports = async function (source, sourceMap, meta) {
|
||||
if (this.mode !== 'production') {
|
||||
if (true) { // Void - extensions-disable-mangler
|
||||
// Only enable mangling in production builds
|
||||
return source;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"type": "string",
|
||||
"description": "The URL from where the vscode server will be downloaded. You can use the following variables and they will be replaced dynamically:\n- ${quality}: vscode server quality, e.g. stable or insiders\n- ${version}: vscode server version, e.g. 1.69.0\n- ${commit}: vscode server release commit\n- ${arch}: vscode server arch, e.g. x64, armhf, arm64\n- ${release}: release number",
|
||||
"scope": "application",
|
||||
"default": "https://github.com/voideditor/${NAME_OF_REPO}/releases/download/${version}.${release}/void-server-${os}-${arch}-${version}.${release}.tar.gz"
|
||||
"default": "https://github.com/voideditor/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz"
|
||||
},
|
||||
"remote.SSH.remotePlatform": {
|
||||
"type": "object",
|
||||
|
||||
@@ -32,12 +32,14 @@ export async function getVSCodeServerConfig(): Promise<IServerConfig> {
|
||||
const customServerBinaryName = vscode.workspace.getConfiguration('remote.SSH.experimental').get<string>('serverBinaryName', '');
|
||||
|
||||
return {
|
||||
version: vscode.version.replace('-insider', ''),
|
||||
// version: vscode.version.replace('-insider', ''),
|
||||
commit: productJson.commit,
|
||||
quality: productJson.quality,
|
||||
release: productJson.release,
|
||||
serverApplicationName: customServerBinaryName || productJson.serverApplicationName,
|
||||
serverDataFolderName: productJson.serverDataFolderName,
|
||||
serverDownloadUrlTemplate: productJson.serverDownloadUrlTemplate
|
||||
serverDownloadUrlTemplate: productJson.serverDownloadUrlTemplate,
|
||||
// Void changed this
|
||||
version: productJson.voidVersion
|
||||
};
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export class ServerInstallError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_DOWNLOAD_URL_TEMPLATE = 'https://github.com/voideditor/void-updates-server/releases/download/test/void-server-${os}-${arch}.tar.gz';
|
||||
const DEFAULT_DOWNLOAD_URL_TEMPLATE = 'https://github.com/voideditor/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz';
|
||||
|
||||
export async function installCodeServer(conn: SSHConnection, serverDownloadUrlTemplate: string | undefined, extensionIds: string[], envVariables: string[], platform: string | undefined, useSocketPath: boolean, logger: Log): Promise<ServerInstallResult> {
|
||||
let shell = 'powershell';
|
||||
|
||||
Generated
+326
-31
@@ -75,6 +75,7 @@
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.50.0",
|
||||
"@stylistic/eslint-plugin-ts": "^2.8.0",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/cookie": "^0.3.3",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/diff": "^7.0.1",
|
||||
@@ -116,7 +117,7 @@
|
||||
"cssnano": "^6.0.3",
|
||||
"debounce": "^1.0.0",
|
||||
"deemon": "^1.8.0",
|
||||
"electron": "34.2.0",
|
||||
"electron": "34.3.2",
|
||||
"eslint": "^9.11.1",
|
||||
"eslint-formatter-compact": "^8.40.0",
|
||||
"eslint-plugin-header": "3.1.1",
|
||||
@@ -168,13 +169,13 @@
|
||||
"pump": "^1.0.1",
|
||||
"rcedit": "^1.1.0",
|
||||
"rimraf": "^2.7.1",
|
||||
"scope-tailwind": "^1.0.6",
|
||||
"scope-tailwind": "^1.0.9",
|
||||
"sinon": "^12.0.1",
|
||||
"sinon-test": "^3.1.3",
|
||||
"source-map": "0.6.1",
|
||||
"source-map-support": "^0.3.2",
|
||||
"style-loader": "^3.3.2",
|
||||
"tailwindcss": "^4.0.9",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsec": "0.2.7",
|
||||
@@ -193,6 +194,19 @@
|
||||
"windows-foreground-love": "0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
|
||||
@@ -3573,6 +3587,13 @@
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/helpers/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@szmarczak/http-timer": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
|
||||
@@ -3585,6 +3606,36 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz",
|
||||
"integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash.castarray": "^4.4.0",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"postcss-selector-parser": "6.0.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
|
||||
"version": "6.0.10",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@thisismanta/pessimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@thisismanta/pessimist/-/pessimist-1.2.0.tgz",
|
||||
@@ -6289,6 +6340,16 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase-css": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-api": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
|
||||
@@ -6370,16 +6431,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
@@ -6392,6 +6448,9 @@
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
@@ -7721,6 +7780,13 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
|
||||
@@ -7742,6 +7808,13 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dlv": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/doctrine": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
|
||||
@@ -7935,9 +8008,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "34.2.0",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-34.2.0.tgz",
|
||||
"integrity": "sha512-SYwBJNeXBTm1q/ErybQMUBZAYqEreBUqBwTrNkw1rV4YatDZk5Aittpcus3PPeC4UoI/tqmJ946uG8AKHTd6CA==",
|
||||
"version": "34.3.2",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-34.3.2.tgz",
|
||||
"integrity": "sha512-n9tzmFexVLxipZXwMTY30H10f0X9k2OP0SkpSwL5VvnDZi0l/Hc+8CEArKkQPbbSf/IS7nxgc96gtTaR+XoSBg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -13303,6 +13376,16 @@
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "1.21.7",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/joycon": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
|
||||
@@ -13891,9 +13974,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
|
||||
"integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -13994,6 +14077,13 @@
|
||||
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY= sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.castarray": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
|
||||
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.clone": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz",
|
||||
@@ -14012,6 +14102,13 @@
|
||||
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
@@ -15707,6 +15804,16 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-hash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
@@ -16894,6 +17001,44 @@
|
||||
"postcss": "^8.4.31"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-import": {
|
||||
"version": "15.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
||||
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.0.0",
|
||||
"read-cache": "^1.0.0",
|
||||
"resolve": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-js": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
|
||||
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"camelcase-css": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12 || ^14 || >= 16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.4.21"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-load-config": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
|
||||
@@ -17854,6 +17999,26 @@
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pify": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache/node_modules/pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-pkg": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
|
||||
@@ -18252,18 +18417,22 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.9.0",
|
||||
"is-core-module": "^2.16.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
@@ -18703,9 +18872,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/scope-tailwind": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/scope-tailwind/-/scope-tailwind-1.0.6.tgz",
|
||||
"integrity": "sha512-tkISLsaesYKKXL9YrLsRWFOD/FhrRVGKeinjgTuFtEidryLzwlBB3G17ArmHWHYcfdMp00XwnRMcGFkF8wwG6w==",
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/scope-tailwind/-/scope-tailwind-1.0.9.tgz",
|
||||
"integrity": "sha512-sxtAKxJq143lYK/RCE36YGq13ficBZ9/9Z0TZa78k0AEiKNT5nH4kfhD8YAfEXR/qPR+G7tl9KL4UoHh+Cs93g==",
|
||||
"dev": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
@@ -20449,12 +20618,125 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.0.9",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.9.tgz",
|
||||
"integrity": "sha512-12laZu+fv1ONDRoNR9ipTOpUD7RN9essRVkX36sjxuRUInpN7hIiHN4lBd/SIFjbISvnXzp8h/hXzmU8SQQYhw==",
|
||||
"version": "3.4.17",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"arg": "^5.0.2",
|
||||
"chokidar": "^3.6.0",
|
||||
"didyoumean": "^1.2.2",
|
||||
"dlv": "^1.1.3",
|
||||
"fast-glob": "^3.3.2",
|
||||
"glob-parent": "^6.0.2",
|
||||
"is-glob": "^4.0.3",
|
||||
"jiti": "^1.21.6",
|
||||
"lilconfig": "^3.1.3",
|
||||
"micromatch": "^4.0.8",
|
||||
"normalize-path": "^3.0.0",
|
||||
"object-hash": "^3.0.0",
|
||||
"picocolors": "^1.1.1",
|
||||
"postcss": "^8.4.47",
|
||||
"postcss-import": "^15.1.0",
|
||||
"postcss-js": "^4.0.1",
|
||||
"postcss-load-config": "^4.0.2",
|
||||
"postcss-nested": "^6.2.0",
|
||||
"postcss-selector-parser": "^6.1.2",
|
||||
"resolve": "^1.22.8",
|
||||
"sucrase": "^3.35.0"
|
||||
},
|
||||
"bin": {
|
||||
"tailwind": "lib/cli.js",
|
||||
"tailwindcss": "lib/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss/node_modules/arg": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwindcss/node_modules/glob-parent": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss/node_modules/postcss-load-config": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
|
||||
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lilconfig": "^3.0.0",
|
||||
"yaml": "^2.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": ">=8.0.9",
|
||||
"ts-node": ">=9.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"postcss": {
|
||||
"optional": true
|
||||
},
|
||||
"ts-node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss/node_modules/postcss-nested": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
||||
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "^6.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.2.14"
|
||||
}
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz",
|
||||
@@ -21168,9 +21450,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
|
||||
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
@@ -22730,6 +23012,19 @@
|
||||
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
|
||||
"integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
|
||||
+4
-3
@@ -136,6 +136,7 @@
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.50.0",
|
||||
"@stylistic/eslint-plugin-ts": "^2.8.0",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/cookie": "^0.3.3",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/diff": "^7.0.1",
|
||||
@@ -177,7 +178,7 @@
|
||||
"cssnano": "^6.0.3",
|
||||
"debounce": "^1.0.0",
|
||||
"deemon": "^1.8.0",
|
||||
"electron": "34.2.0",
|
||||
"electron": "34.3.2",
|
||||
"eslint": "^9.11.1",
|
||||
"eslint-formatter-compact": "^8.40.0",
|
||||
"eslint-plugin-header": "3.1.1",
|
||||
@@ -229,13 +230,13 @@
|
||||
"pump": "^1.0.1",
|
||||
"rcedit": "^1.1.0",
|
||||
"rimraf": "^2.7.1",
|
||||
"scope-tailwind": "^1.0.6",
|
||||
"scope-tailwind": "^1.0.9",
|
||||
"sinon": "^12.0.1",
|
||||
"sinon-test": "^3.1.3",
|
||||
"source-map": "0.6.1",
|
||||
"source-map-support": "^0.3.2",
|
||||
"style-loader": "^3.3.2",
|
||||
"tailwindcss": "^4.0.9",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsec": "0.2.7",
|
||||
|
||||
+8
-3
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"nameShort": "Void",
|
||||
"nameLong": "Void",
|
||||
"voidVersion": "1.0.3",
|
||||
"applicationName": "void",
|
||||
"dataFolderName": ".void-editor",
|
||||
"win32MutexName": "voideditor",
|
||||
@@ -31,8 +32,12 @@
|
||||
"nodejsRepository": "https://nodejs.org",
|
||||
"urlProtocol": "void",
|
||||
"extensionsGallery": {
|
||||
"serviceUrl": "https://open-vsx.org/vscode/gallery",
|
||||
"itemUrl": "https://open-vsx.org/vscode/item"
|
||||
"serviceUrl": "https://marketplace.visualstudio.com/_apis/public/gallery",
|
||||
"itemUrl": "https://marketplace.visualstudio.com/items"
|
||||
},
|
||||
"builtInExtensions": []
|
||||
"builtInExtensions": [],
|
||||
"linkProtectionTrustedDomains": [
|
||||
"https://voideditor.com",
|
||||
"https://voideditor.dev"
|
||||
]
|
||||
}
|
||||
|
||||
+1231
-1039
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 200 KiB |
Executable
+168
@@ -0,0 +1,168 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Exit on error
|
||||
set -e
|
||||
|
||||
# Check platform
|
||||
platform=$(uname)
|
||||
|
||||
if [[ "$platform" == "Darwin" ]]; then
|
||||
echo "Running on macOS. Note that the AppImage created will only work on Linux systems."
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo "Docker Desktop for Mac is not installed. Please install it from https://www.docker.com/products/docker-desktop"
|
||||
exit 1
|
||||
fi
|
||||
elif [[ "$platform" == "Linux" ]]; then
|
||||
echo "Running on Linux. Proceeding with AppImage creation..."
|
||||
else
|
||||
echo "This script is intended to run on macOS or Linux. Current platform: $platform"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Enable BuildKit
|
||||
export DOCKER_BUILDKIT=1
|
||||
|
||||
BUILD_IMAGE_NAME="void-appimage-builder"
|
||||
|
||||
# Check if Docker is running
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
echo "Docker is not running. Please start Docker first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check and install Buildx if needed
|
||||
if ! docker buildx version >/dev/null 2>&1; then
|
||||
echo "Installing Docker Buildx..."
|
||||
mkdir -p ~/.docker/cli-plugins/
|
||||
curl -SL https://github.com/docker/buildx/releases/download/v0.13.1/buildx-v0.13.1.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
|
||||
chmod +x ~/.docker/cli-plugins/docker-buildx
|
||||
fi
|
||||
|
||||
# Download appimagetool if not present
|
||||
if [ ! -f "appimagetool" ]; then
|
||||
echo "Downloading appimagetool..."
|
||||
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod +x appimagetool
|
||||
fi
|
||||
|
||||
# Delete any existing AppImage to avoid bloating the build
|
||||
rm -f Void-x86_64.AppImage
|
||||
|
||||
# Create build Dockerfile
|
||||
echo "Creating build Dockerfile..."
|
||||
cat > Dockerfile.build << 'EOF'
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM ubuntu:20.04
|
||||
|
||||
# Install required dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libfuse2 \
|
||||
libglib2.0-0 \
|
||||
libgtk-3-0 \
|
||||
libx11-xcb1 \
|
||||
libxss1 \
|
||||
libxtst6 \
|
||||
libnss3 \
|
||||
libasound2 \
|
||||
libdrm2 \
|
||||
libgbm1 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
EOF
|
||||
|
||||
# Create .dockerignore file
|
||||
echo "Creating .dockerignore file..."
|
||||
cat > .dockerignore << EOF
|
||||
Dockerfile.build
|
||||
.dockerignore
|
||||
.git
|
||||
.gitignore
|
||||
.DS_Store
|
||||
*~
|
||||
*.swp
|
||||
*.swo
|
||||
*.tmp
|
||||
*.bak
|
||||
*.log
|
||||
*.err
|
||||
node_modules/
|
||||
venv/
|
||||
*.egg-info/
|
||||
*.tox/
|
||||
dist/
|
||||
EOF
|
||||
|
||||
# Build Docker image without cache
|
||||
echo "Building Docker image (no cache)..."
|
||||
docker build --no-cache -t "$BUILD_IMAGE_NAME" -f Dockerfile.build .
|
||||
|
||||
# Create AppImage using local appimagetool
|
||||
echo "Creating AppImage..."
|
||||
docker run --rm --privileged -v "$(pwd):/app" "$BUILD_IMAGE_NAME" bash -c '
|
||||
cd /app && \
|
||||
rm -rf VoidApp.AppDir && \
|
||||
mkdir -p VoidApp.AppDir/usr/bin VoidApp.AppDir/usr/lib VoidApp.AppDir/usr/share/applications && \
|
||||
find . -maxdepth 1 ! -name VoidApp.AppDir ! -name "." ! -name ".." -exec cp -r {} VoidApp.AppDir/usr/bin/ \; && \
|
||||
cp void.png VoidApp.AppDir/ && \
|
||||
echo "[Desktop Entry]" > VoidApp.AppDir/void.desktop && \
|
||||
echo "Name=Void" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Comment=Open source AI code editor." >> VoidApp.AppDir/void.desktop && \
|
||||
echo "GenericName=Text Editor" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Exec=void %F" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Icon=void" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Type=Application" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "StartupNotify=false" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "StartupWMClass=Void" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Categories=TextEditor;Development;IDE;" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "MimeType=application/x-void-workspace;" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Keywords=void;" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Actions=new-empty-window;" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "[Desktop Action new-empty-window]" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Name=New Empty Window" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Name[de]=Neues leeres Fenster" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Name[es]=Nueva ventana vacía" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Name[fr]=Nouvelle fenêtre vide" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Name[it]=Nuova finestra vuota" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Name[ja]=新しい空のウィンドウ" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Name[ko]=새 빈 창" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Name[ru]=Новое пустое окно" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Name[zh_CN]=新建空窗口" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Name[zh_TW]=開新空視窗" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Exec=void --new-window %F" >> VoidApp.AppDir/void.desktop && \
|
||||
echo "Icon=void" >> VoidApp.AppDir/void.desktop && \
|
||||
chmod +x VoidApp.AppDir/void.desktop && \
|
||||
cp VoidApp.AppDir/void.desktop VoidApp.AppDir/usr/share/applications/ && \
|
||||
echo "[Desktop Entry]" > VoidApp.AppDir/void-url-handler.desktop && \
|
||||
echo "Name=Void - URL Handler" > VoidApp.AppDir/void-url-handler.desktop && \
|
||||
echo "Comment=Open source AI code editor." > VoidApp.AppDir/void-url-handler.desktop && \
|
||||
echo "GenericName=Text Editor" > VoidApp.AppDir/void-url-handler.desktop && \
|
||||
echo "Exec=void --open-url %U" > VoidApp.AppDir/void-url-handler.desktop && \
|
||||
echo "Icon=void" > VoidApp.AppDir/void-url-handler.desktop && \
|
||||
echo "Type=Application" > VoidApp.AppDir/void-url-handler.desktop && \
|
||||
echo "NoDisplay=true" > VoidApp.AppDir/void-url-handler.desktop && \
|
||||
echo "StartupNotify=true" > VoidApp.AppDir/void-url-handler.desktop && \
|
||||
echo "Categories=Utility;TextEditor;Development;IDE;" > VoidApp.AppDir/void-url-handler.desktop && \
|
||||
echo "MimeType=x-scheme-handler/void;" > VoidApp.AppDir/void-url-handler.desktop && \
|
||||
echo "Keywords=void;" > VoidApp.AppDir/void-url-handler.desktop && \
|
||||
chmod +x VoidApp.AppDir/void-url-handler.desktop && \
|
||||
cp VoidApp.AppDir/void-url-handler.desktop VoidApp.AppDir/usr/share/applications/ && \
|
||||
echo "#!/bin/bash" > VoidApp.AppDir/AppRun && \
|
||||
echo "HERE=\$(dirname \"\$(readlink -f \"\${0}\")\")" >> VoidApp.AppDir/AppRun && \
|
||||
echo "export PATH=\${HERE}/usr/bin:\${PATH}" >> VoidApp.AppDir/AppRun && \
|
||||
echo "export LD_LIBRARY_PATH=\${HERE}/usr/lib:\${LD_LIBRARY_PATH}" >> VoidApp.AppDir/AppRun && \
|
||||
echo "exec \${HERE}/usr/bin/void --no-sandbox \"\$@\"" >> VoidApp.AppDir/AppRun && \
|
||||
chmod +x VoidApp.AppDir/AppRun && \
|
||||
chmod -R 755 VoidApp.AppDir && \
|
||||
|
||||
# Strip unneeded symbols from the binary to reduce size
|
||||
strip --strip-unneeded VoidApp.AppDir/usr/bin/void
|
||||
|
||||
ls -la VoidApp.AppDir/ && \
|
||||
ARCH=x86_64 ./appimagetool -n VoidApp.AppDir Void-x86_64.AppImage
|
||||
'
|
||||
|
||||
# Clean up
|
||||
rm -rf VoidApp.AppDir .dockerignore appimagetool
|
||||
|
||||
echo "AppImage creation complete! Your AppImage is: Void-x86_64.AppImage"
|
||||
@@ -0,0 +1,114 @@
|
||||
# Void AppImage Creation Script
|
||||
|
||||
This script automates the process of creating an AppImage for the Void Editor using Docker. It works on macOS and Linux platforms.
|
||||
## Requirements
|
||||
|
||||
* **Docker:** The script relies on Docker to build the AppImage inside a container.
|
||||
* **macOS or Linux:** The script is designed for these platforms. On macOS, it generates a Linux-compatible AppImage.
|
||||
* **Internet Connection:** Required for downloading necessary tools (like `docker-buildx` and `appimagetool` inside the Docker container).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Install Docker:**
|
||||
|
||||
* **macOS:** Download and install Docker Desktop from [docker.com](docker.com).
|
||||
* **Ubuntu:**
|
||||
```bash
|
||||
sudo apt install docker.io
|
||||
```
|
||||
* **Arch Linux:**
|
||||
```bash
|
||||
sudo pacman -S docker
|
||||
```
|
||||
* **Fedora:**
|
||||
```bash
|
||||
sudo dnf install docker
|
||||
```
|
||||
|
||||
2. **Set Docker User Group:**
|
||||
|
||||
Docker requires users to be part of the `docker` group to run Docker commands without `sudo`.
|
||||
|
||||
```bash
|
||||
sudo usermod -aG docker $USER
|
||||
```
|
||||
|
||||
After running this command, log out and log back in for the group changes to take effect.
|
||||
|
||||
3. **Enable and Start Docker:**
|
||||
|
||||
```bash
|
||||
sudo systemctl enable docker
|
||||
sudo systemctl start docker
|
||||
```
|
||||
|
||||
## Ubuntu Dependencies (Installed via Docker)
|
||||
|
||||
These dependencies are installed within the Docker container (Ubuntu 20.04 base). You generally don't need to install them manually:
|
||||
|
||||
* `libfuse2`
|
||||
* `libglib2.0-0`
|
||||
* `libgtk-3-0`
|
||||
* `libx11-xcb1`
|
||||
* `libxss1`
|
||||
* `libxtst6`
|
||||
* `libnss3`
|
||||
* `libasound2`
|
||||
* `libdrm2`
|
||||
* `libgbm1`
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
1. **Clone or Download the Script:**
|
||||
|
||||
Save the script to your system as `create_appimage.sh`.
|
||||
|
||||
2. **Make the Script Executable:**
|
||||
|
||||
```bash
|
||||
chmod +x create_appimage.sh
|
||||
```
|
||||
|
||||
3. **Copy Required Files:**
|
||||
|
||||
Copy the following files to the directory where the app binary is being bundled (created during the build process):
|
||||
|
||||
* `create_appimage.sh`
|
||||
* `void.desktop`
|
||||
* `void.png`
|
||||
|
||||
4. **Run the Script:**
|
||||
|
||||
```bash
|
||||
./create_appimage.sh
|
||||
```
|
||||
|
||||
5. **Result:**
|
||||
|
||||
After the script completes, it will generate an AppImage named `Void-x86_64.AppImage` (or similar, depending on your architecture) in the current directory.
|
||||
|
||||
## Script Overview
|
||||
|
||||
* **Platform Check:** Checks for macOS or Linux. Exits if unsupported.
|
||||
* **Docker Checks:** Ensures Docker is installed and running.
|
||||
* **Buildx Installation:** Installs `docker buildx` if missing.
|
||||
* **`appimagetool` Download:** Downloads `appimagetool` inside the Docker container.
|
||||
* **Dockerfile Creation:** Creates a temporary `Dockerfile.build` for the Ubuntu-based environment.
|
||||
* **Docker Image Build:** Builds a Docker image and runs the build process.
|
||||
* **AppImage Creation:**
|
||||
* Creates the `VoidApp.AppDir` structure.
|
||||
* Copies binaries, resources, and the `.desktop` entry.
|
||||
* Copies `void.desktop` and `void.png`.
|
||||
* Strips unnecessary symbols from the binary.
|
||||
* Runs `appimagetool` to generate the AppImage.
|
||||
* **Cleanup:** Removes the temporary `Dockerfile.build`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* **Docker Not Running:** Ensure Docker is installed and running.
|
||||
* **Permission Issues:** Try running the script with `sudo` or check Docker permissions.
|
||||
* **Outdated Dependencies:** Ensure you have the minimum required versions.
|
||||
|
||||
## License
|
||||
|
||||
This script is provided "as is". It is free to use, modify, and distribute, but comes with no warranty.
|
||||
@@ -0,0 +1,12 @@
|
||||
[Desktop Entry]
|
||||
Name=Void - URL Handler
|
||||
Comment=Open source AI code editor.
|
||||
GenericName=Text Editor
|
||||
Exec=void --open-url %U
|
||||
Icon=void
|
||||
Type=Application
|
||||
NoDisplay=true
|
||||
StartupNotify=true
|
||||
Categories=Utility;TextEditor;Development;IDE;
|
||||
MimeType=x-scheme-handler/void;
|
||||
Keywords=void;
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
[Desktop Entry]
|
||||
Name=Void
|
||||
Comment=Open source AI code editor.
|
||||
GenericName=Text Editor
|
||||
Exec=void %F
|
||||
Icon=void
|
||||
Type=Application
|
||||
StartupNotify=false
|
||||
StartupWMClass=Void
|
||||
Categories=TextEditor;Development;IDE;
|
||||
MimeType=application/x-void-workspace;
|
||||
Keywords=void;
|
||||
Actions=new-empty-window;
|
||||
|
||||
[Desktop Action new-empty-window]
|
||||
Name=New Empty Window
|
||||
Name[de]=Neues leeres Fenster
|
||||
Name[es]=Nueva ventana vacía
|
||||
Name[fr]=Nouvelle fenêtre vide
|
||||
Name[it]=Nuova finestra vuota
|
||||
Name[ja]=新しい空のウィンドウ
|
||||
Name[ko]=새 빈 창
|
||||
Name[ru]=Новое пустое окно
|
||||
Name[zh_CN]=新建空窗口
|
||||
Name[zh_TW]=開新空視窗
|
||||
Exec=void --new-window %F
|
||||
Icon=void
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 795 KiB |
+14
@@ -6,6 +6,7 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'original-fs';
|
||||
import * as os from 'os';
|
||||
import { createRequire } from 'node:module';
|
||||
import { performance } from 'perf_hooks';
|
||||
import { configurePortable } from './bootstrap-node.js';
|
||||
import { bootstrapESM } from './bootstrap-esm.js';
|
||||
@@ -22,6 +23,7 @@ import { INLSConfiguration } from './vs/nls.js';
|
||||
import { NativeParsedArgs } from './vs/platform/environment/common/argv.js';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
perf.mark('code/didStartMain');
|
||||
|
||||
@@ -110,6 +112,17 @@ protocol.registerSchemesAsPrivileged([
|
||||
// Global app listeners
|
||||
registerListeners();
|
||||
|
||||
function resolveUserProduct() {
|
||||
const userProductPath = path.join(userDataPath, 'product.json');
|
||||
try {
|
||||
// Assign the product configuration to the global scope
|
||||
const productJson = require(userProductPath);
|
||||
// @ts-expect-error
|
||||
globalThis._VSCODE_USER_PRODUCT_JSON = productJson;
|
||||
} catch (ex) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We can resolve the NLS configuration early if it is defined
|
||||
* in argv.json before `app.ready` event. Otherwise we can only
|
||||
@@ -206,6 +219,7 @@ async function onReady() {
|
||||
async function startup(codeCachePath: string | undefined, nlsConfig: INLSConfiguration): Promise<void> {
|
||||
process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig);
|
||||
process.env['VSCODE_CODE_CACHE_PATH'] = codeCachePath || '';
|
||||
resolveUserProduct();
|
||||
|
||||
// Bootstrap ESM
|
||||
await bootstrapESM();
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
}
|
||||
|
||||
/* custom speed & easing for loading icon */
|
||||
.codicon-loading,
|
||||
.codicon-loading:not(.codicon-no-default-spin), /* Void changed this as it is literally broken to the !important */
|
||||
.codicon-tree-item-loading::before {
|
||||
animation-duration: 1s !important;
|
||||
animation-timing-function: cubic-bezier(0.53, 0.21, 0.29, 0.67) !important;
|
||||
|
||||
@@ -56,6 +56,8 @@ export type ExtensionVirtualWorkspaceSupport = {
|
||||
|
||||
export interface IProductConfiguration {
|
||||
readonly version: string;
|
||||
readonly voidVersion: string; // Void added this
|
||||
readonly release: string; // VSCodium added this
|
||||
readonly date?: string;
|
||||
readonly quality?: string;
|
||||
readonly commit?: string;
|
||||
|
||||
@@ -125,7 +125,7 @@ import { IMetricsService } from '../../workbench/contrib/void/common/metricsServ
|
||||
import { IVoidUpdateService } from '../../workbench/contrib/void/common/voidUpdateService.js';
|
||||
import { MetricsMainService } from '../../workbench/contrib/void/electron-main/metricsMainService.js';
|
||||
import { VoidMainUpdateService } from '../../workbench/contrib/void/electron-main/voidUpdateMainService.js';
|
||||
import { LLMMessageChannel } from '../../workbench/contrib/void/electron-main/llmMessageChannel.js';
|
||||
import { LLMMessageChannel } from '../../workbench/contrib/void/electron-main/sendLLMMessageChannel.js';
|
||||
|
||||
/**
|
||||
* The main VS Code application. There will only ever be one instance,
|
||||
@@ -1256,8 +1256,8 @@ export class CodeApplication extends Disposable {
|
||||
const voidUpdatesChannel = ProxyChannel.fromService(accessor.get(IVoidUpdateService), disposables);
|
||||
mainProcessElectronServer.registerChannel('void-channel-update', voidUpdatesChannel);
|
||||
|
||||
const llmMessageChannel = new LLMMessageChannel(accessor.get(IMetricsService));
|
||||
mainProcessElectronServer.registerChannel('void-channel-llmMessageService', llmMessageChannel);
|
||||
const sendLLMMessageChannel = new LLMMessageChannel(accessor.get(IMetricsService));
|
||||
mainProcessElectronServer.registerChannel('void-channel-llmMessage', sendLLMMessageChannel);
|
||||
|
||||
// Extension Host Debug Broadcasting
|
||||
const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService));
|
||||
|
||||
@@ -3570,7 +3570,7 @@ class EditorQuickSuggestions extends BaseEditorOption<EditorOption.quickSuggesti
|
||||
const defaults: InternalQuickSuggestionsOptions = {
|
||||
other: 'on',
|
||||
comments: 'off',
|
||||
strings: 'off'
|
||||
strings: 'on' // Void changed this setting
|
||||
};
|
||||
const types: IJSONSchema[] = [
|
||||
{ type: 'boolean' },
|
||||
|
||||
@@ -230,7 +230,7 @@ export class DiagnosticsService implements IDiagnosticsService {
|
||||
|
||||
private formatEnvironment(info: IMainProcessDiagnostics): string {
|
||||
const output: string[] = [];
|
||||
output.push(`Version: ${this.productService.nameShort} ${this.productService.version} (${this.productService.commit || 'Commit unknown'}, ${this.productService.date || 'Date unknown'})`);
|
||||
output.push(`Version: ${this.productService.nameShort} ${this.productService.version} ${this.productService.release || 'Release unknown'} (${this.productService.commit || 'Commit unknown'}, ${this.productService.date || 'Date unknown'})`);
|
||||
output.push(`OS Version: ${osLib.type()} ${osLib.arch()} ${osLib.release()}`);
|
||||
const cpus = osLib.cpus();
|
||||
if (cpus && cpus.length > 0) {
|
||||
|
||||
@@ -52,7 +52,6 @@ import { IProductService } from '../../product/common/productService.js';
|
||||
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
|
||||
import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
|
||||
import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
|
||||
import { IConfigurationService } from '../../configuration/common/configuration.js';
|
||||
import { isLinux } from '../../../base/common/platform.js';
|
||||
|
||||
export const INativeServerExtensionManagementService = refineServiceDecorator<IExtensionManagementService, INativeServerExtensionManagementService>(IExtensionManagementService);
|
||||
@@ -85,7 +84,6 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
|
||||
@IDownloadService private downloadService: IDownloadService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IProductService productService: IProductService,
|
||||
@IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,
|
||||
@IUriIdentityService uriIdentityService: IUriIdentityService,
|
||||
@@ -324,8 +322,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
|
||||
|
||||
private async downloadExtension(extension: IGalleryExtension, operation: InstallOperation, verifySignature: boolean, clientTargetPlatform?: TargetPlatform): Promise<{ readonly location: URI; readonly verificationStatus: ExtensionSignatureVerificationCode | undefined }> {
|
||||
if (verifySignature) {
|
||||
const value = this.configurationService.getValue('extensions.verifySignature');
|
||||
verifySignature = isBoolean(value) ? value : true;
|
||||
verifySignature = false; // Void disable-signature-verification-patch
|
||||
}
|
||||
const { location, verificationStatus } = await this.extensionsDownloader.download(extension, operation, verifySignature, clientTargetPlatform);
|
||||
|
||||
|
||||
@@ -404,7 +404,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
||||
}
|
||||
|
||||
private async getShellCommandLink(): Promise<{ readonly source: string; readonly target: string }> {
|
||||
const target = resolve(this.environmentMainService.appRoot, 'bin', 'code');
|
||||
const target = resolve(this.environmentMainService.appRoot, 'bin', this.productService.applicationName);
|
||||
const source = `/usr/local/bin/${this.productService.applicationName}`;
|
||||
|
||||
// Ensure source exists
|
||||
@@ -639,7 +639,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
||||
|
||||
// macOS
|
||||
if (this.environmentMainService.isBuilt) {
|
||||
return join(this.environmentMainService.appRoot, 'bin', 'code');
|
||||
return join(this.environmentMainService.appRoot, 'bin', `${this.productService.applicationName}`);
|
||||
}
|
||||
|
||||
return join(this.environmentMainService.appRoot, 'scripts', 'code-cli.sh');
|
||||
|
||||
@@ -43,10 +43,11 @@ else if (globalThis._VSCODE_PRODUCT_JSON && globalThis._VSCODE_PACKAGE_JSON) {
|
||||
// want to have it running out of sources so we
|
||||
// read it from package.json only when we need it.
|
||||
if (!product.version) {
|
||||
const pkg = globalThis._VSCODE_PACKAGE_JSON as { version: string };
|
||||
const pkg = globalThis._VSCODE_PACKAGE_JSON as { version: string, release: string };
|
||||
|
||||
Object.assign(product, {
|
||||
version: pkg.version
|
||||
version: pkg.version,
|
||||
release: pkg.release
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,8 @@ export const enum StateType {
|
||||
export const enum UpdateType {
|
||||
Setup,
|
||||
Archive,
|
||||
Snap
|
||||
Snap,
|
||||
WindowsInstaller
|
||||
}
|
||||
|
||||
export const enum DisablementReason {
|
||||
@@ -108,3 +109,41 @@ export interface IUpdateService {
|
||||
isLatestVersion(): Promise<boolean | undefined>;
|
||||
_applySpecificUpdate(packagePath: string): Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
export type Architecture =
|
||||
| 'arm'
|
||||
| 'arm64'
|
||||
| 'ia32'
|
||||
| 'loong64'
|
||||
| 'mips'
|
||||
| 'mipsel'
|
||||
| 'ppc'
|
||||
| 'ppc64'
|
||||
| 'riscv64'
|
||||
| 's390'
|
||||
| 's390x'
|
||||
| 'x64';
|
||||
|
||||
export type Platform =
|
||||
| 'aix'
|
||||
| 'android'
|
||||
| 'darwin'
|
||||
| 'freebsd'
|
||||
| 'haiku'
|
||||
| 'linux'
|
||||
| 'openbsd'
|
||||
| 'sunos'
|
||||
| 'win32'
|
||||
| 'cygwin'
|
||||
| 'netbsd';
|
||||
|
||||
export type Quality =
|
||||
| 'insider'
|
||||
| 'stable';
|
||||
|
||||
export type Target =
|
||||
| 'archive'
|
||||
| 'msi'
|
||||
| 'system'
|
||||
| 'user';
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// import { timeout } from '../../../base/common/async.js';
|
||||
import { timeout } from '../../../base/common/async.js';
|
||||
import { CancellationToken } from '../../../base/common/cancellation.js';
|
||||
import { Emitter, Event } from '../../../base/common/event.js';
|
||||
import { IConfigurationService } from '../../configuration/common/configuration.js';
|
||||
@@ -13,13 +14,16 @@ import { ILifecycleMainService, LifecycleMainPhase } from '../../lifecycle/elect
|
||||
import { ILogService } from '../../log/common/log.js';
|
||||
import { IProductService } from '../../product/common/productService.js';
|
||||
import { IRequestService } from '../../request/common/request.js';
|
||||
import { AvailableForDownload, DisablementReason, IUpdateService, State, StateType, UpdateType } from '../common/update.js';
|
||||
import { Architecture, AvailableForDownload, DisablementReason, IUpdateService, Platform, State, StateType, Target, UpdateType } from '../common/update.js';
|
||||
|
||||
export function createUpdateURL(platform: string, quality: string, productService: IProductService): string {
|
||||
// return `https://voideditor.dev/api/update/${platform}/stable`;
|
||||
// return `${productService.updateUrl}/api/update/${platform}/${quality}/${productService.commit}`;
|
||||
// https://github.com/VSCodium/update-api
|
||||
return `https://updates.voideditor.dev/api/update/${platform}/${quality}/${productService.commit}`;
|
||||
// Void - VSCodium's version-1-update.patch
|
||||
export function createUpdateURL(productService: IProductService, quality: string, platform: Platform, architecture: Architecture, target?: Target): string { // return `https://voideditor.dev/api/update/${platform}/stable`;
|
||||
if (target) {
|
||||
return `${productService.updateUrl}/${quality}/${platform}/${architecture}/${target}/latest.json`;
|
||||
} else { // we shouldn't usually have a target:
|
||||
// https://raw.githubusercontent.com/voideditor/versions/refs/heads/main/stable/darwin/arm64/latest.json
|
||||
return `${productService.updateUrl}/${quality}/${platform}/${architecture}/latest.json`;
|
||||
}
|
||||
}
|
||||
|
||||
export type UpdateErrorClassification = {
|
||||
@@ -81,22 +85,20 @@ export abstract class AbstractUpdateService implements IUpdateService {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(State.Disabled(DisablementReason.ManuallyDisabled));
|
||||
// Void - re-enabled auto updates
|
||||
// this.setState(State.Disabled(DisablementReason.ManuallyDisabled));
|
||||
|
||||
|
||||
// Void - temporarily disabled while we figure out how to do this the right way
|
||||
|
||||
// this.setState(State.Idle(this.getUpdateType()));
|
||||
this.setState(State.Idle(this.getUpdateType()));
|
||||
|
||||
// start checking for updates after 10 seconds
|
||||
// this.scheduleCheckForUpdates(10 * 1000).then(undefined, err => this.logService.error(err));
|
||||
this.scheduleCheckForUpdates(10 * 1000).then(undefined, err => this.logService.error(err));
|
||||
}
|
||||
|
||||
// private async scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise<void> {
|
||||
// await timeout(delay);
|
||||
// await this.checkForUpdates(false);
|
||||
// return await this.scheduleCheckForUpdates(60 * 60 * 1000);
|
||||
// }
|
||||
private async scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise<void> {
|
||||
await timeout(delay);
|
||||
await this.checkForUpdates(false);
|
||||
return await this.scheduleCheckForUpdates(60 * 60 * 1000);
|
||||
}
|
||||
|
||||
async checkForUpdates(explicit: boolean): Promise<void> {
|
||||
this.logService.trace('update#checkForUpdates, state = ', this.state.type);
|
||||
|
||||
@@ -13,11 +13,12 @@ import { IEnvironmentMainService } from '../../environment/electron-main/environ
|
||||
import { ILifecycleMainService, IRelaunchHandler, IRelaunchOptions } from '../../lifecycle/electron-main/lifecycleMainService.js';
|
||||
import { ILogService } from '../../log/common/log.js';
|
||||
import { IProductService } from '../../product/common/productService.js';
|
||||
import { IRequestService } from '../../request/common/request.js';
|
||||
import { IRequestService, asJson } from '../../request/common/request.js';
|
||||
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
|
||||
import { IUpdate, State, StateType, UpdateType } from '../common/update.js';
|
||||
import { AbstractUpdateService, createUpdateURL, UpdateErrorClassification } from './abstractUpdateService.js';
|
||||
|
||||
import { CancellationToken } from '../../../base/common/cancellation.js';
|
||||
import * as semver from 'semver';
|
||||
export class DarwinUpdateService extends AbstractUpdateService implements IRelaunchHandler {
|
||||
|
||||
private readonly disposables = new DisposableStore();
|
||||
@@ -75,13 +76,7 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau
|
||||
|
||||
// Void: buildUpdateFeedUrl -> doBuildUpdateFeedUrl
|
||||
protected doBuildUpdateFeedUrl(quality: string): string | undefined {
|
||||
let assetID: string;
|
||||
if (!this.productService.darwinUniversalAssetId) {
|
||||
assetID = process.arch === 'x64' ? 'darwin' : 'darwin-arm64';
|
||||
} else {
|
||||
assetID = this.productService.darwinUniversalAssetId;
|
||||
}
|
||||
const url = createUpdateURL(assetID, quality, this.productService);
|
||||
const url = createUpdateURL(this.productService, quality, process.platform, process.arch);
|
||||
try {
|
||||
electron.autoUpdater.setFeedURL({ url });
|
||||
} catch (e) {
|
||||
@@ -93,8 +88,36 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau
|
||||
}
|
||||
|
||||
protected doCheckForUpdates(context: any): void {
|
||||
if (!this.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(State.CheckingForUpdates(context));
|
||||
electron.autoUpdater.checkForUpdates();
|
||||
// electron.autoUpdater.checkForUpdates();
|
||||
|
||||
this.requestService.request({ url: this.url }, CancellationToken.None)
|
||||
.then<IUpdate | null>(asJson)
|
||||
.then(update => {
|
||||
if (!update || !update.url || !update.version || !update.productVersion) {
|
||||
this.setState(State.Idle(UpdateType.Setup));
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const fetchedVersion = update.productVersion.replace(/(\d+\.\d+\.\d+)(?:\.(\d+))(\-\w+)?/, '$1$3+$2');
|
||||
const currentVersion = `${this.productService.voidVersion}+${this.productService.release}`;
|
||||
// Void compares voidVersion, not VSCode version
|
||||
// const currentVersion = `${this.productService.version}+${this.productService.release}`;
|
||||
|
||||
if (semver.compareBuild(currentVersion, fetchedVersion) >= 0) {
|
||||
this.setState(State.Idle(UpdateType.Setup));
|
||||
}
|
||||
else {
|
||||
electron.autoUpdater.checkForUpdates();
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
}
|
||||
|
||||
private onUpdateAvailable(): void {
|
||||
|
||||
@@ -30,7 +30,7 @@ export class LinuxUpdateService extends AbstractUpdateService {
|
||||
|
||||
// Void: buildUpdateFeedUrl -> doBuildUpdateFeedUrl
|
||||
protected doBuildUpdateFeedUrl(quality: string): string {
|
||||
return createUpdateURL(`linux-${process.arch}`, quality, this.productService);
|
||||
return createUpdateURL(this.productService, quality, process.platform, process.arch);
|
||||
}
|
||||
|
||||
protected doCheckForUpdates(context: any): void {
|
||||
|
||||
@@ -9,7 +9,6 @@ import { tmpdir } from 'os';
|
||||
import { timeout } from '../../../base/common/async.js';
|
||||
import { CancellationToken } from '../../../base/common/cancellation.js';
|
||||
import { memoize } from '../../../base/common/decorators.js';
|
||||
import { hash } from '../../../base/common/hash.js';
|
||||
import * as path from '../../../base/common/path.js';
|
||||
import { URI } from '../../../base/common/uri.js';
|
||||
import { checksum } from '../../../base/node/crypto.js';
|
||||
@@ -23,8 +22,9 @@ import { INativeHostMainService } from '../../native/electron-main/nativeHostMai
|
||||
import { IProductService } from '../../product/common/productService.js';
|
||||
import { asJson, IRequestService } from '../../request/common/request.js';
|
||||
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
|
||||
import { AvailableForDownload, DisablementReason, IUpdate, State, StateType, UpdateType } from '../common/update.js';
|
||||
import { AbstractUpdateService, createUpdateURL, UpdateErrorClassification } from './abstractUpdateService.js';
|
||||
import { AvailableForDownload, DisablementReason, IUpdate, State, StateType, Target, UpdateType } from '../common/update.js';
|
||||
import { AbstractUpdateService, createUpdateURL } from './abstractUpdateService.js';
|
||||
import * as semver from 'semver';
|
||||
|
||||
async function pollUntil(fn: () => boolean, millis = 1000): Promise<void> {
|
||||
while (!fn()) {
|
||||
@@ -40,9 +40,13 @@ interface IAvailableUpdate {
|
||||
let _updateType: UpdateType | undefined = undefined;
|
||||
function getUpdateType(): UpdateType {
|
||||
if (typeof _updateType === 'undefined') {
|
||||
_updateType = fs.existsSync(path.join(path.dirname(process.execPath), 'unins000.exe'))
|
||||
? UpdateType.Setup
|
||||
: UpdateType.Archive;
|
||||
if (fs.existsSync(path.join(path.dirname(process.execPath), 'unins000.exe'))) {
|
||||
_updateType = UpdateType.Setup;
|
||||
} else if (path.basename(path.normalize(path.join(process.execPath, '..', '..'))) === 'Program Files') {
|
||||
_updateType = UpdateType.WindowsInstaller;
|
||||
} else {
|
||||
_updateType = UpdateType.Archive;
|
||||
}
|
||||
}
|
||||
|
||||
return _updateType;
|
||||
@@ -61,6 +65,7 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
|
||||
constructor(
|
||||
@ILifecycleMainService lifecycleMainService: ILifecycleMainService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
// @ts-expect-error
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IEnvironmentMainService environmentMainService: IEnvironmentMainService,
|
||||
@IRequestService requestService: IRequestService,
|
||||
@@ -101,15 +106,24 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
|
||||
|
||||
// Void: buildUpdateFeedUrl -> doBuildUpdateFeedUrl
|
||||
protected doBuildUpdateFeedUrl(quality: string): string | undefined {
|
||||
let platform = `win32-${process.arch}`;
|
||||
|
||||
if (getUpdateType() === UpdateType.Archive) {
|
||||
platform += '-archive';
|
||||
} else if (this.productService.target === 'user') {
|
||||
platform += '-user';
|
||||
let target: Target;
|
||||
switch (getUpdateType()) {
|
||||
case UpdateType.Archive:
|
||||
target = 'archive'
|
||||
break;
|
||||
case UpdateType.WindowsInstaller:
|
||||
target = 'msi'
|
||||
break;
|
||||
default:
|
||||
if (this.productService.target === 'user') {
|
||||
target = 'user'
|
||||
}
|
||||
else {
|
||||
target = 'system'
|
||||
}
|
||||
}
|
||||
|
||||
return createUpdateURL(platform, quality, this.productService);
|
||||
return createUpdateURL(this.productService, quality, process.platform, process.arch, target);
|
||||
}
|
||||
|
||||
protected doCheckForUpdates(context: any): void {
|
||||
@@ -129,6 +143,16 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const fetchedVersion = update.productVersion.replace(/(\d+\.\d+\.\d+)(?:\.(\d+))(\-\w+)?/, '$1$3+$2');
|
||||
const currentVersion = `${this.productService.voidVersion}+${this.productService.release}`;
|
||||
// Void compares voidVersion, not VSCode version
|
||||
// const currentVersion = `${this.productService.version}+${this.productService.release}`;
|
||||
|
||||
if (semver.compareBuild(currentVersion, fetchedVersion) >= 0) {
|
||||
this.setState(State.Idle(updateType));
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
if (updateType === UpdateType.Archive) {
|
||||
this.setState(State.AvailableForDownload(update));
|
||||
return Promise.resolve(null);
|
||||
@@ -155,7 +179,7 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
|
||||
this.availableUpdate = { packagePath };
|
||||
this.setState(State.Downloaded(update));
|
||||
|
||||
const fastUpdatesEnabled = this.configurationService.getValue('update.enableWindowsBackgroundUpdates');
|
||||
const fastUpdatesEnabled = getUpdateType() === UpdateType.Setup && this.configurationService.getValue('update.enableWindowsBackgroundUpdates');
|
||||
if (fastUpdatesEnabled) {
|
||||
if (this.productService.target === 'user') {
|
||||
this.doApplyUpdate();
|
||||
@@ -167,7 +191,6 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
|
||||
});
|
||||
})
|
||||
.then(undefined, err => {
|
||||
this.telemetryService.publicLog2<{ messageHash: string }, UpdateErrorClassification>('update:error', { messageHash: String(hash(String(err))) });
|
||||
this.logService.error(err);
|
||||
|
||||
// only show message when explicitly checking for updates
|
||||
@@ -251,10 +274,18 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
|
||||
if (this.availableUpdate.updateFilePath) {
|
||||
fs.unlinkSync(this.availableUpdate.updateFilePath);
|
||||
} else {
|
||||
spawn(this.availableUpdate.packagePath, ['/silent', '/log', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'ignore', 'ignore']
|
||||
});
|
||||
const type = getUpdateType();
|
||||
if (type === UpdateType.WindowsInstaller) {
|
||||
spawn('msiexec.exe', ['/i', this.availableUpdate.packagePath], {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'ignore', 'ignore']
|
||||
});
|
||||
} else {
|
||||
spawn(this.availableUpdate.packagePath, ['/silent', '/log', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'ignore', 'ignore']
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import { MarkdownRenderer, openLinkFromMarkdown } from '../../../../editor/brows
|
||||
import { defaultButtonStyles, defaultCheckboxStyles, defaultDialogStyles, defaultInputBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js';
|
||||
import { ResultKind } from '../../../../platform/keybinding/common/keybindingResolver.js';
|
||||
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
|
||||
import { getReleaseString } from '../../../../workbench/common/release.js';
|
||||
|
||||
export class BrowserDialogHandler extends AbstractDialogHandler {
|
||||
|
||||
@@ -79,13 +80,14 @@ export class BrowserDialogHandler extends AbstractDialogHandler {
|
||||
|
||||
async about(): Promise<void> {
|
||||
const detailString = (useAgo: boolean): string => {
|
||||
const releaseString = getReleaseString();
|
||||
return localize('aboutDetail',
|
||||
"Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}",
|
||||
this.productService.version || 'Unknown',
|
||||
this.productService.commit || 'Unknown',
|
||||
this.productService.date ? `${this.productService.date}${useAgo ? ' (' + fromNow(new Date(this.productService.date), true) + ')' : ''}` : 'Unknown',
|
||||
navigator.userAgent
|
||||
);
|
||||
).replace('\n', `\n${releaseString} ${this.productService.release || 'Unknown'}\n`);
|
||||
};
|
||||
|
||||
const detail = detailString(true);
|
||||
|
||||
@@ -250,7 +250,9 @@ export class EditorGroupWatermark extends Disposable {
|
||||
linkSpan.appendChild(dirSpan);
|
||||
|
||||
return linkSpan
|
||||
}).filter(v => !!v)
|
||||
})
|
||||
.filter(v => !!v)
|
||||
.slice(0, 5) // take 5 most recent
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// added by VSCodium
|
||||
import { language } from '../../../vs/base/common/platform.js';
|
||||
|
||||
const DEFAULT_LABEL = 'Release:';
|
||||
const LABELS: { [key: string]: string } = {
|
||||
'en': DEFAULT_LABEL,
|
||||
'fr': 'Révision :',
|
||||
'ru': 'Релиз:',
|
||||
'zh-hans': '发布版本:',
|
||||
'zh-hant': '發布版本:',
|
||||
};
|
||||
|
||||
export function getReleaseString(): string {
|
||||
return LABELS[language] ?? DEFAULT_LABEL;
|
||||
}
|
||||
@@ -4,23 +4,20 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { coalesce, isNonEmptyArray } from '../../../../base/common/arrays.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { toErrorMessage } from '../../../../base/common/errorMessage.js';
|
||||
import { Event } from '../../../../base/common/event.js';
|
||||
import { MarkdownString } from '../../../../base/common/htmlContent.js';
|
||||
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
|
||||
import { Disposable, DisposableMap, DisposableStore } from '../../../../base/common/lifecycle.js';
|
||||
import * as strings from '../../../../base/common/strings.js';
|
||||
import { localize, localize2 } from '../../../../nls.js';
|
||||
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { localize } from '../../../../nls.js';
|
||||
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { ExtensionIdentifier, IExtensionManifest } from '../../../../platform/extensions/common/extensions.js';
|
||||
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
|
||||
import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
import { IProductService } from '../../../../platform/product/common/productService.js';
|
||||
import { Registry } from '../../../../platform/registry/common/platform.js';
|
||||
import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js';
|
||||
import { IWorkbenchContribution } from '../../../common/contributions.js';
|
||||
import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from '../../../common/views.js';
|
||||
import { IViewsRegistry, Extensions as ViewExtensions } from '../../../common/views.js';
|
||||
import { IExtensionFeatureTableRenderer, IRenderedData, ITableData, IRowData, IExtensionFeaturesRegistry, Extensions } from '../../../services/extensionManagement/common/extensionFeatures.js';
|
||||
import { isProposedApiEnabled } from '../../../services/extensions/common/extensions.js';
|
||||
import * as extensionsRegistry from '../../../services/extensions/common/extensionsRegistry.js';
|
||||
@@ -30,90 +27,91 @@ import { ChatAgentLocation, IChatAgentData, IChatAgentService } from '../common/
|
||||
import { ChatContextKeys } from '../common/chatContextKeys.js';
|
||||
import { IRawChatParticipantContribution } from '../common/chatParticipantContribTypes.js';
|
||||
import { ChatViewId } from './chat.js';
|
||||
import { CHAT_EDITING_SIDEBAR_PANEL_ID, CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from './chatViewPane.js';
|
||||
|
||||
// --- Chat Container & View Registration
|
||||
|
||||
const chatViewContainer: ViewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
id: CHAT_SIDEBAR_PANEL_ID,
|
||||
title: localize2('chat.viewContainer.label', "Chat"),
|
||||
icon: Codicon.commentDiscussion,
|
||||
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [CHAT_SIDEBAR_PANEL_ID, { mergeViewWithContainerWhenSingleView: true }]),
|
||||
storageId: CHAT_SIDEBAR_PANEL_ID,
|
||||
hideIfEmpty: true,
|
||||
order: 100,
|
||||
}, ViewContainerLocation.AuxiliaryBar, { isDefault: true, doNotRegisterOpenCommand: true });
|
||||
// Void commented this out
|
||||
// const chatViewContainer: ViewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
// id: CHAT_SIDEBAR_PANEL_ID,
|
||||
// title: localize2('chat.viewContainer.label', "Chat"),
|
||||
// icon: Codicon.commentDiscussion,
|
||||
// ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [CHAT_SIDEBAR_PANEL_ID, { mergeViewWithContainerWhenSingleView: true }]),
|
||||
// storageId: CHAT_SIDEBAR_PANEL_ID,
|
||||
// hideIfEmpty: true,
|
||||
// order: 100,
|
||||
// }, ViewContainerLocation.AuxiliaryBar, { isDefault: true, doNotRegisterOpenCommand: true });
|
||||
|
||||
const chatViewDescriptor: IViewDescriptor[] = [{
|
||||
id: ChatViewId,
|
||||
containerIcon: chatViewContainer.icon,
|
||||
containerTitle: chatViewContainer.title.value,
|
||||
singleViewPaneContainerTitle: chatViewContainer.title.value,
|
||||
name: localize2('chat.viewContainer.label', "Chat"),
|
||||
canToggleVisibility: false,
|
||||
canMoveView: true,
|
||||
openCommandActionDescriptor: {
|
||||
id: CHAT_SIDEBAR_PANEL_ID,
|
||||
title: chatViewContainer.title,
|
||||
mnemonicTitle: localize({ key: 'miToggleChat', comment: ['&& denotes a mnemonic'] }, "&&Chat"),
|
||||
keybindings: {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyI,
|
||||
mac: {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KeyI
|
||||
}
|
||||
},
|
||||
order: 1
|
||||
},
|
||||
ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.Panel }]),
|
||||
when: ContextKeyExpr.or(
|
||||
ChatContextKeys.Setup.hidden.negate(),
|
||||
ChatContextKeys.Setup.installed,
|
||||
ChatContextKeys.panelParticipantRegistered,
|
||||
ChatContextKeys.extensionInvalid
|
||||
)
|
||||
}];
|
||||
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews(chatViewDescriptor, chatViewContainer);
|
||||
// const chatViewDescriptor: IViewDescriptor[] = [{
|
||||
// id: ChatViewId,
|
||||
// containerIcon: chatViewContainer.icon,
|
||||
// containerTitle: chatViewContainer.title.value,
|
||||
// singleViewPaneContainerTitle: chatViewContainer.title.value,
|
||||
// name: localize2('chat.viewContainer.label', "Chat"),
|
||||
// canToggleVisibility: false,
|
||||
// canMoveView: true,
|
||||
// openCommandActionDescriptor: {
|
||||
// id: CHAT_SIDEBAR_PANEL_ID,
|
||||
// title: chatViewContainer.title,
|
||||
// mnemonicTitle: localize({ key: 'miToggleChat', comment: ['&& denotes a mnemonic'] }, "&&Chat"),
|
||||
// keybindings: {
|
||||
// primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyI,
|
||||
// mac: {
|
||||
// primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KeyI
|
||||
// }
|
||||
// },
|
||||
// order: 1
|
||||
// },
|
||||
// ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.Panel }]),
|
||||
// when: ContextKeyExpr.or(
|
||||
// ChatContextKeys.Setup.hidden.negate(),
|
||||
// ChatContextKeys.Setup.installed,
|
||||
// ChatContextKeys.panelParticipantRegistered,
|
||||
// ChatContextKeys.extensionInvalid
|
||||
// )
|
||||
// }];
|
||||
// Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews(chatViewDescriptor, chatViewContainer);
|
||||
|
||||
// --- Edits Container & View Registration
|
||||
|
||||
const editsViewContainer: ViewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
id: CHAT_EDITING_SIDEBAR_PANEL_ID,
|
||||
title: localize2('chatEditing.viewContainer.label', "Copilot Edits"),
|
||||
icon: Codicon.editSession,
|
||||
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [CHAT_EDITING_SIDEBAR_PANEL_ID, { mergeViewWithContainerWhenSingleView: true }]),
|
||||
storageId: CHAT_EDITING_SIDEBAR_PANEL_ID,
|
||||
hideIfEmpty: true,
|
||||
order: 101,
|
||||
}, ViewContainerLocation.AuxiliaryBar, { doNotRegisterOpenCommand: true });
|
||||
// Void commented this out
|
||||
// const editsViewContainer: ViewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
// id: CHAT_EDITING_SIDEBAR_PANEL_ID,
|
||||
// title: localize2('chatEditing.viewContainer.label', "Copilot Edits"),
|
||||
// icon: Codicon.editSession,
|
||||
// ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [CHAT_EDITING_SIDEBAR_PANEL_ID, { mergeViewWithContainerWhenSingleView: true }]),
|
||||
// storageId: CHAT_EDITING_SIDEBAR_PANEL_ID,
|
||||
// hideIfEmpty: true,
|
||||
// order: 101,
|
||||
// }, ViewContainerLocation.AuxiliaryBar, { doNotRegisterOpenCommand: true });
|
||||
|
||||
const editsViewDescriptor: IViewDescriptor[] = [{
|
||||
id: 'workbench.panel.chat.view.edits',
|
||||
containerIcon: editsViewContainer.icon,
|
||||
containerTitle: editsViewContainer.title.value,
|
||||
singleViewPaneContainerTitle: editsViewContainer.title.value,
|
||||
name: editsViewContainer.title,
|
||||
canToggleVisibility: false,
|
||||
canMoveView: true,
|
||||
openCommandActionDescriptor: {
|
||||
id: CHAT_EDITING_SIDEBAR_PANEL_ID,
|
||||
title: editsViewContainer.title,
|
||||
mnemonicTitle: localize({ key: 'miToggleEdits', comment: ['&& denotes a mnemonic'] }, "Copilot Ed&&its"),
|
||||
keybindings: {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI,
|
||||
linux: {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyI
|
||||
}
|
||||
},
|
||||
order: 2
|
||||
},
|
||||
ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.EditingSession }]),
|
||||
when: ContextKeyExpr.or(
|
||||
ChatContextKeys.Setup.hidden.negate(),
|
||||
ChatContextKeys.Setup.installed,
|
||||
ChatContextKeys.editingParticipantRegistered
|
||||
)
|
||||
}];
|
||||
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews(editsViewDescriptor, editsViewContainer);
|
||||
// const editsViewDescriptor: IViewDescriptor[] = [{
|
||||
// id: 'workbench.panel.chat.view.edits',
|
||||
// containerIcon: editsViewContainer.icon,
|
||||
// containerTitle: editsViewContainer.title.value,
|
||||
// singleViewPaneContainerTitle: editsViewContainer.title.value,
|
||||
// name: editsViewContainer.title,
|
||||
// canToggleVisibility: false,
|
||||
// canMoveView: true,
|
||||
// openCommandActionDescriptor: {
|
||||
// id: CHAT_EDITING_SIDEBAR_PANEL_ID,
|
||||
// title: editsViewContainer.title,
|
||||
// mnemonicTitle: localize({ key: 'miToggleEdits', comment: ['&& denotes a mnemonic'] }, "Copilot Ed&&its"),
|
||||
// keybindings: {
|
||||
// primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI,
|
||||
// linux: {
|
||||
// primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyI
|
||||
// }
|
||||
// },
|
||||
// order: 2
|
||||
// },
|
||||
// ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.EditingSession }]),
|
||||
// when: ContextKeyExpr.or(
|
||||
// ChatContextKeys.Setup.hidden.negate(),
|
||||
// ChatContextKeys.Setup.installed,
|
||||
// ChatContextKeys.editingParticipantRegistered
|
||||
// )
|
||||
// }];
|
||||
// Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews(editsViewDescriptor, editsViewContainer);
|
||||
|
||||
const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawChatParticipantContribution[]>({
|
||||
extensionPoint: 'chatParticipants',
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
|
||||
import { localize } from '../../../../nls.js';
|
||||
import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js';
|
||||
import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';
|
||||
import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';
|
||||
import { IFileService } from '../../../../platform/files/common/files.js';
|
||||
@@ -15,7 +15,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo
|
||||
import { createSyncHeaders, IAuthenticationProvider, IResourceRefHandle } from '../../../../platform/userDataSync/common/userDataSync.js';
|
||||
import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from '../../../services/authentication/common/authentication.js';
|
||||
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
|
||||
import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_CATEGORY, IEditSessionsStorageService, EDIT_SESSIONS_SIGNED_IN_KEY, IEditSessionsLogService, SyncResource, EDIT_SESSIONS_PENDING_KEY } from '../common/editSessions.js';
|
||||
import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_CATEGORY, IEditSessionsStorageService, EDIT_SESSIONS_SIGNED_IN_KEY, IEditSessionsLogService, SyncResource } from '../common/editSessions.js';
|
||||
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
|
||||
import { generateUuid } from '../../../../base/common/uuid.js';
|
||||
import { getCurrentAuthenticationSessionInfo } from '../../../services/authentication/browser/authenticationService.js';
|
||||
@@ -91,7 +91,6 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
|
||||
// If another window changes the preferred session storage, reset our cached auth state in memory
|
||||
this._register(this.storageService.onDidChangeValue(StorageScope.APPLICATION, EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY, this._store)(() => this.onDidChangeStorage()));
|
||||
|
||||
this.registerSignInAction();
|
||||
this.registerResetAuthenticationAction();
|
||||
|
||||
this.signedInContext = EDIT_SESSIONS_SIGNED_IN.bindTo(this.contextKeyService);
|
||||
@@ -454,46 +453,6 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
|
||||
}
|
||||
}
|
||||
|
||||
private registerSignInAction() {
|
||||
if (!this.serverConfiguration?.url) {
|
||||
return;
|
||||
}
|
||||
const that = this;
|
||||
const id = 'workbench.editSessions.actions.signIn';
|
||||
const when = ContextKeyExpr.and(ContextKeyExpr.equals(EDIT_SESSIONS_PENDING_KEY, false), ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, false));
|
||||
this._register(registerAction2(class ResetEditSessionAuthenticationAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id,
|
||||
title: localize('sign in', 'Turn on Cloud Changes...'),
|
||||
category: EDIT_SESSION_SYNC_CATEGORY,
|
||||
precondition: when,
|
||||
menu: [{
|
||||
id: MenuId.CommandPalette,
|
||||
},
|
||||
{
|
||||
id: MenuId.AccountsContext,
|
||||
group: '2_editSessions',
|
||||
when,
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
async run() {
|
||||
return await that.initialize('write', false);
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
|
||||
group: '2_editSessions',
|
||||
command: {
|
||||
id,
|
||||
title: localize('sign in badge', 'Turn on Cloud Changes... (1)'),
|
||||
},
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals(EDIT_SESSIONS_PENDING_KEY, true), ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, false))
|
||||
}));
|
||||
}
|
||||
|
||||
private registerResetAuthenticationAction() {
|
||||
const that = this;
|
||||
this._register(registerAction2(class ResetEditSessionAuthenticationAction extends Action2 {
|
||||
|
||||
@@ -27,6 +27,7 @@ import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
import { IExplorerService } from './files.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { Categories } from '../../../../platform/action/common/actionCommonCategories.js';
|
||||
import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../void/browser/voidSettingsPane.js';
|
||||
|
||||
// Contribute Global Actions
|
||||
|
||||
@@ -675,6 +676,18 @@ for (const menuId of [MenuId.EmptyEditorGroupContext, MenuId.EditorTabsBarContex
|
||||
|
||||
// File menu
|
||||
|
||||
|
||||
// Void added this:
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
|
||||
group: '0_void',
|
||||
command: {
|
||||
id: VOID_OPEN_SETTINGS_ACTION_ID,
|
||||
title: nls.localize({ key: 'openVoid', comment: ['&& denotes a mnemonic'] }, "&&Open Void Settings"),
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
|
||||
group: '1_new',
|
||||
command: {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
|
||||
import { localize2 } from '../../../../nls.js';
|
||||
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
|
||||
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
|
||||
|
||||
|
||||
export interface IDummyService {
|
||||
readonly _serviceBrand: undefined;
|
||||
}
|
||||
|
||||
export const IDummyService = createDecorator<IDummyService>('DummyService');
|
||||
|
||||
|
||||
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
f1: true,
|
||||
id: 'void.dummy',
|
||||
title: localize2('dummy', 'dummy: Init'),
|
||||
keybinding: {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Digit0,
|
||||
weight: KeybindingWeight.VoidExtension,
|
||||
}
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
console.log('hi')
|
||||
const n = accessor.get(IDummyService)
|
||||
console.log('Hi', n._serviceBrand)
|
||||
}
|
||||
})
|
||||
|
||||
// on mount
|
||||
class DummyService extends Disposable implements IWorkbenchContribution, IDummyService {
|
||||
static readonly ID = 'workbench.contrib.void.dummy'
|
||||
_serviceBrand: undefined;
|
||||
|
||||
constructor(
|
||||
) {
|
||||
super()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IDummyService, DummyService, InstantiationType.Eager);
|
||||
|
||||
registerWorkbenchContribution2(DummyService.ID, DummyService, WorkbenchPhase.BlockRestore);
|
||||
+3
-3
@@ -39,7 +39,7 @@ class MarkerCheckService extends Disposable implements IMarkerCheckService {
|
||||
|
||||
console.log(`----------------------------------------------`);
|
||||
|
||||
console.log(`${error.resource.toString()}: ${error.startLineNumber} ${error.message} ${error.severity}`); // ! all errors in the file
|
||||
console.log(`${error.resource.fsPath}: ${error.startLineNumber} ${error.message} ${error.severity}`); // ! all errors in the file
|
||||
|
||||
try {
|
||||
// Get the text model for the file
|
||||
@@ -122,11 +122,11 @@ class MarkerCheckService extends Disposable implements IMarkerCheckService {
|
||||
// const markers = this._markerService.read({ resource });
|
||||
|
||||
// if (markers.length === 0) {
|
||||
// console.log(`${resource.toString()}: No diagnostics`);
|
||||
// console.log(`${resource.fsPath}: No diagnostics`);
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// console.log(`Diagnostics for ${resource.toString()}:`);
|
||||
// console.log(`Diagnostics for ${resource.fsPath}:`);
|
||||
// markers.forEach(marker => this._logMarker(marker));
|
||||
// }
|
||||
// };
|
||||
@@ -3,185 +3,106 @@
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
// import { URI } from '../../../../base/common/uri.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
// import { IToolService, ToolService } from '../common/toolsService.js';
|
||||
// 1. search(ai)
|
||||
// - tool use to find all possible changes
|
||||
// - if search only: is this file related to the search?
|
||||
// - if search + replace: should I modify this file?
|
||||
// 2. replace(ai)
|
||||
// - what changes to make?
|
||||
// 3. postprocess errors
|
||||
// -fastapply changes simultaneously
|
||||
// -iterate on syntax errors (all files can be changed from a syntax error, not just the one with the error)
|
||||
|
||||
|
||||
// private async _searchUsingAI({ searchClause }: { searchClause: string }) {
|
||||
|
||||
export type ChatMessageLocation = {
|
||||
threadId: string;
|
||||
messageIdx: number;
|
||||
}
|
||||
// // const relevantURIs: URI[] = []
|
||||
// // const gatherPrompt = `\
|
||||
// // asdasdas
|
||||
// // `
|
||||
// // const filterPrompt = `\
|
||||
// // Is this file relevant?
|
||||
// // `
|
||||
|
||||
|
||||
export type SearchAndReplaceBlock = {
|
||||
search: string;
|
||||
replace: string;
|
||||
}
|
||||
// // // optimizations (DO THESE LATER!!!!!!)
|
||||
// // // if tool includes a uri in uriSet, skip it obviously
|
||||
// // let uriSet = new Set<URI>()
|
||||
// // // gather
|
||||
// // let messages = []
|
||||
// // while (true) {
|
||||
// // const result = await new Promise((res, rej) => {
|
||||
// // sendLLMMessage({
|
||||
// // messages,
|
||||
// // tools: ['search_files'],
|
||||
// // onFinalMessage: ({ result: r, }) => {
|
||||
// // res(r)
|
||||
// // },
|
||||
// // onError: (error) => {
|
||||
// // rej(error)
|
||||
// // }
|
||||
// // })
|
||||
// // })
|
||||
|
||||
// service that manages state
|
||||
export type ApplyState = {
|
||||
[applyBoxId: string]: {
|
||||
searchAndReplaceBlocks: SearchAndReplaceBlock;
|
||||
}
|
||||
}
|
||||
// // messages.push({ role: 'tool', content: turnToString(result) })
|
||||
|
||||
// the purpose of this service is to generate search and replace blocks for a given codeblock `codeblockId` and on a file `fileName` and version `fileVersion`
|
||||
// // sendLLMMessage({
|
||||
// // messages: { 'Output ': result },
|
||||
// // onFinalMessage: (r) => {
|
||||
// // // output is file1\nfile2\nfile3\n...
|
||||
// // }
|
||||
// // })
|
||||
|
||||
export interface IFastApplyService {
|
||||
readonly _serviceBrand: undefined;
|
||||
// // uriSet.add(...)
|
||||
// // }
|
||||
|
||||
// readonly state: ApplyState; // readonly to the user
|
||||
// setState(newState: Partial<ApplyState>): void;
|
||||
// onDidChangeState: Event<void>;
|
||||
}
|
||||
// // // writes
|
||||
// // if (!replaceClause) return
|
||||
|
||||
export const IVoidFastApplyService = createDecorator<IFastApplyService>('voidFastApplyService');
|
||||
class VoidFastApplyService extends Disposable implements IFastApplyService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
// static readonly ID = 'voidFastApplyService';
|
||||
|
||||
private readonly _onDidChangeState = new Emitter<void>();
|
||||
readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
|
||||
|
||||
|
||||
// state
|
||||
// state: ApplyState
|
||||
|
||||
constructor(
|
||||
// @IToolService private readonly toolService: ToolService
|
||||
) {
|
||||
super()
|
||||
|
||||
// initial state
|
||||
// this.state = { currentUri: undefined }
|
||||
}
|
||||
|
||||
setState(newState: Partial<ApplyState>) {
|
||||
|
||||
// this.state = { ...this.state, ...newState }
|
||||
this._onDidChangeState.fire()
|
||||
}
|
||||
|
||||
aiSearch(searchStr: string) {
|
||||
|
||||
}
|
||||
|
||||
aiReplace(searchStr: string, replaceStr: string) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 1. search(ai)
|
||||
// - tool use to find all possible changes
|
||||
// - if search only: is this file related to the search?
|
||||
// - if search + replace: should I modify this file?
|
||||
// 2. replace(ai)
|
||||
// - what changes to make?
|
||||
// 3. postprocess errors
|
||||
// -fastapply changes simultaneously
|
||||
// -iterate on syntax errors (all files can be changed from a syntax error, not just the one with the error)
|
||||
|
||||
|
||||
// private async _searchUsingAI({ searchClause }: { searchClause: string }) {
|
||||
|
||||
// // const relevantURIs: URI[] = []
|
||||
// // const gatherPrompt = `\
|
||||
// // asdasdas
|
||||
// // `
|
||||
// // const filterPrompt = `\
|
||||
// // Is this file relevant?
|
||||
// // `
|
||||
|
||||
|
||||
// // // optimizations (DO THESE LATER!!!!!!)
|
||||
// // // if tool includes a uri in uriSet, skip it obviously
|
||||
// // let uriSet = new Set<URI>()
|
||||
// // // gather
|
||||
// // let messages = []
|
||||
// // while (true) {
|
||||
// // const result = await new Promise((res, rej) => {
|
||||
// // sendLLMMessage({
|
||||
// // messages,
|
||||
// // tools: ['search'],
|
||||
// // onFinalMessage: ({ result: r, }) => {
|
||||
// // res(r)
|
||||
// // },
|
||||
// // onError: (error) => {
|
||||
// // rej(error)
|
||||
// // }
|
||||
// // })
|
||||
// // })
|
||||
|
||||
// // messages.push({ role: 'tool', content: turnToString(result) })
|
||||
|
||||
// // sendLLMMessage({
|
||||
// // messages: { 'Output ': result },
|
||||
// // onFinalMessage: (r) => {
|
||||
// // // output is file1\nfile2\nfile3\n...
|
||||
// // }
|
||||
// // })
|
||||
|
||||
// // uriSet.add(...)
|
||||
// // }
|
||||
|
||||
// // // writes
|
||||
// // if (!replaceClause) return
|
||||
|
||||
// // for (const uri of uriSet) {
|
||||
// // // in future, batch these
|
||||
// // applyWorkflow({ uri, applyStr: replaceClause })
|
||||
// // }
|
||||
// // for (const uri of uriSet) {
|
||||
// // // in future, batch these
|
||||
// // applyWorkflow({ uri, applyStr: replaceClause })
|
||||
// // }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// // while (true) {
|
||||
// // const result = new Promise((res, rej) => {
|
||||
// // sendLLMMessage({
|
||||
// // messages,
|
||||
// // tools: ['search'],
|
||||
// // onResult: (r) => {
|
||||
// // res(r)
|
||||
// // }
|
||||
// // })
|
||||
// // })
|
||||
// // while (true) {
|
||||
// // const result = new Promise((res, rej) => {
|
||||
// // sendLLMMessage({
|
||||
// // messages,
|
||||
// // tools: ['search_files'],
|
||||
// // onResult: (r) => {
|
||||
// // res(r)
|
||||
// // }
|
||||
// // })
|
||||
// // })
|
||||
|
||||
// // messages.push(result)
|
||||
// // messages.push(result)
|
||||
|
||||
// // }
|
||||
// // }
|
||||
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// private async _replaceUsingAI({ searchClause, replaceClause, relevantURIs }: { searchClause: string, replaceClause: string, relevantURIs: URI[] }) {
|
||||
// private async _replaceUsingAI({ searchClause, replaceClause, relevantURIs }: { searchClause: string, replaceClause: string, relevantURIs: URI[] }) {
|
||||
|
||||
// for (const uri of relevantURIs) {
|
||||
// for (const uri of relevantURIs) {
|
||||
|
||||
// uri
|
||||
// uri
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// // should I change this file?
|
||||
// // if so what changes to make?
|
||||
// // should I change this file?
|
||||
// // if so what changes to make?
|
||||
|
||||
|
||||
|
||||
// // fast apply the changes
|
||||
// }
|
||||
// // fast apply the changes
|
||||
// }
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IVoidFastApplyService, VoidFastApplyService, InstantiationType.Eager);
|
||||
|
||||
@@ -15,12 +15,19 @@ import { IEditorService } from '../../../services/editor/common/editorService.js
|
||||
import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js';
|
||||
import { EditorResourceAccessor } from '../../../common/editor.js';
|
||||
import { IModelService } from '../../../../editor/common/services/model.js';
|
||||
import { extractCodeFromRegular } from './helpers/extractCodeFromResult.js';
|
||||
import { extractCodeFromRegular } from '../common/helpers/extractCodeFromResult.js';
|
||||
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
|
||||
import { ILLMMessageService } from '../common/llmMessageService.js';
|
||||
import { _ln, allLinebreakSymbols } from '../common/voidFileService.js';
|
||||
import { ILLMMessageService } from '../common/sendLLMMessageService.js';
|
||||
import { isWindows } from '../../../../base/common/platform.js';
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||
import { FeatureName } from '../common/voidSettingsTypes.js';
|
||||
// import { IContextGatheringService } from './contextGatheringService.js';
|
||||
|
||||
|
||||
|
||||
const allLinebreakSymbols = ['\r\n', '\n']
|
||||
const _ln = isWindows ? allLinebreakSymbols[0] : allLinebreakSymbols[1]
|
||||
|
||||
// The extension this was called from is here - https://github.com/voideditor/void/blob/autocomplete/extensions/void/src/extension/extension.ts
|
||||
|
||||
|
||||
@@ -630,9 +637,12 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
||||
token: CancellationToken,
|
||||
): Promise<InlineCompletion[]> {
|
||||
|
||||
const isEnabled = this._settingsService.state.globalSettings.enableAutocomplete
|
||||
if (!isEnabled) return []
|
||||
|
||||
const testMode = false
|
||||
|
||||
const docUriStr = model.uri.toString();
|
||||
const docUriStr = model.uri.fsPath;
|
||||
|
||||
const prefixAndSuffix = getPrefixAndSuffixInfo(model, position)
|
||||
const { prefix, suffix } = prefixAndSuffix
|
||||
@@ -762,8 +772,6 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
||||
|
||||
|
||||
|
||||
// console.log('B')
|
||||
|
||||
// create a new autocompletion and add it to cache
|
||||
const newAutocompletion: Autocompletion = {
|
||||
id: this._autocompletionId++,
|
||||
@@ -783,6 +791,11 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
||||
|
||||
console.log('starting autocomplete...', predictionType)
|
||||
|
||||
const featureName: FeatureName = 'Autocomplete'
|
||||
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
||||
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
||||
|
||||
|
||||
// set parameters of `newAutocompletion` appropriately
|
||||
newAutocompletion.llmPromise = new Promise((resolve, reject) => {
|
||||
|
||||
@@ -793,7 +806,8 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
||||
suffix: llmSuffix,
|
||||
stopTokens: stopTokens,
|
||||
},
|
||||
useProviderFor: 'Autocomplete',
|
||||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
logging: { loggingName: 'Autocomplete' },
|
||||
onText: () => { }, // unused in FIMMessage
|
||||
// onText: async ({ fullText, newText }) => {
|
||||
@@ -838,6 +852,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
||||
newAutocompletion.status = 'error'
|
||||
reject(message)
|
||||
},
|
||||
onAbort: () => { },
|
||||
})
|
||||
newAutocompletion.requestId = requestId
|
||||
|
||||
@@ -877,6 +892,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
||||
@ILLMMessageService private readonly _llmMessageService: ILLMMessageService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IModelService private readonly _modelService: IModelService,
|
||||
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
|
||||
// @IContextGatheringService private readonly _contextGatheringService: IContextGatheringService,
|
||||
) {
|
||||
super()
|
||||
@@ -900,7 +916,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
||||
if (!resource) return;
|
||||
const model = this._modelService.getModel(resource)
|
||||
if (!model) return;
|
||||
const docUriStr = resource.toString();
|
||||
const docUriStr = resource.fsPath;
|
||||
if (!this._autocompletionsOfDocument[docUriStr]) return;
|
||||
|
||||
const { prefix, } = getPrefixAndSuffixInfo(model, position)
|
||||
@@ -929,4 +945,3 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
||||
registerWorkbenchContribution2(AutocompleteService.ID, AutocompleteService, WorkbenchPhase.BlockRestore);
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,317 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IFileService } from '../../../../platform/files/common/files.js';
|
||||
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
|
||||
import { ShallowDirectoryItem, ToolCallParams, ToolResultType } from '../common/toolsServiceTypes.js';
|
||||
import { MAX_CHILDREN_URIs_PAGE } from './toolsService.js';
|
||||
import { IExplorerService } from '../../files/browser/files.js';
|
||||
import { SortOrder } from '../../files/common/files.js';
|
||||
import { ExplorerItem } from '../../files/common/explorerModel.js';
|
||||
import { VoidDirectoryItem } from '../common/directoryStrTypes.js';
|
||||
|
||||
|
||||
const MAX_CHARS_TOTAL_BEGINNING = 20_000
|
||||
const MAX_CHARS_TOTAL_TOOL = 20_000
|
||||
// const MAX_FILES_TOTAL = 200
|
||||
|
||||
|
||||
export interface IDirectoryStrService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
getDirectoryStrTool(uri: URI): Promise<{ wasCutOff: boolean, str: string }>
|
||||
getAllDirectoriesStr(): Promise<{ wasCutOff: boolean, str: string }>
|
||||
|
||||
}
|
||||
export const IDirectoryStrService = createDecorator<IDirectoryStrService>('voidDirectoryStrService');
|
||||
|
||||
|
||||
|
||||
|
||||
// Check if it's a known filtered type like .git
|
||||
const shouldExcludeDirectory = (item: ExplorerItem) => {
|
||||
if (item.name === '.git' ||
|
||||
item.name === 'node_modules' ||
|
||||
item.name.startsWith('.') ||
|
||||
item.name === 'dist' ||
|
||||
item.name === 'build' ||
|
||||
item.name === 'out' ||
|
||||
item.name === 'bin' ||
|
||||
item.name === 'coverage' ||
|
||||
item.name === '__pycache__' ||
|
||||
item.name === 'env' ||
|
||||
item.name === 'venv' ||
|
||||
item.name === 'tmp' ||
|
||||
item.name === 'temp' ||
|
||||
item.name === 'artifacts' ||
|
||||
item.name === 'target' ||
|
||||
item.name === 'obj' ||
|
||||
item.name === 'vendor' ||
|
||||
item.name === 'logs' ||
|
||||
item.name === 'cache'
|
||||
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---------- ONE LAYER DEEP ----------
|
||||
|
||||
export const computeDirectoryTree1Deep = async (
|
||||
fileService: IFileService,
|
||||
rootURI: URI,
|
||||
pageNumber: number = 1,
|
||||
): Promise<ToolResultType['ls_dir']> => {
|
||||
const stat = await fileService.resolve(rootURI, { resolveMetadata: false });
|
||||
if (!stat.isDirectory) {
|
||||
return { children: null, hasNextPage: false, hasPrevPage: false, itemsRemaining: 0 };
|
||||
}
|
||||
|
||||
const nChildren = stat.children?.length ?? 0;
|
||||
|
||||
const fromChildIdx = MAX_CHILDREN_URIs_PAGE * (pageNumber - 1);
|
||||
const toChildIdx = MAX_CHILDREN_URIs_PAGE * pageNumber - 1; // INCLUSIVE
|
||||
const listChildren = stat.children?.slice(fromChildIdx, toChildIdx + 1);
|
||||
|
||||
const children: ShallowDirectoryItem[] = listChildren?.map(child => ({
|
||||
name: child.name,
|
||||
uri: child.resource,
|
||||
isDirectory: child.isDirectory,
|
||||
isSymbolicLink: child.isSymbolicLink
|
||||
})) ?? [];
|
||||
|
||||
const hasNextPage = (nChildren - 1) > toChildIdx;
|
||||
const hasPrevPage = pageNumber > 1;
|
||||
const itemsRemaining = Math.max(0, nChildren - (toChildIdx + 1));
|
||||
|
||||
return {
|
||||
children,
|
||||
hasNextPage,
|
||||
hasPrevPage,
|
||||
itemsRemaining
|
||||
};
|
||||
};
|
||||
|
||||
export const stringifyDirectoryTree1Deep = (params: ToolCallParams['ls_dir'], result: ToolResultType['ls_dir']): string => {
|
||||
if (!result.children) {
|
||||
return `Error: ${params.rootURI} is not a directory`;
|
||||
}
|
||||
|
||||
let output = '';
|
||||
const entries = result.children;
|
||||
|
||||
if (!result.hasPrevPage) { // is first page
|
||||
output += `${params.rootURI.fsPath}\n`;
|
||||
}
|
||||
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const entry = entries[i];
|
||||
const isLast = i === entries.length - 1 && !result.hasNextPage;
|
||||
const prefix = isLast ? '└── ' : '├── ';
|
||||
|
||||
output += `${prefix}${entry.name}${entry.isDirectory ? '/' : ''}${entry.isSymbolicLink ? ' (symbolic link)' : ''}\n`;
|
||||
}
|
||||
|
||||
if (result.hasNextPage) {
|
||||
output += `└── (${result.itemsRemaining} results remaining...)\n`;
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
// ---------- IN GENERAL ----------
|
||||
|
||||
|
||||
// if the filter exists use it to filter out files and folders when creating the tree
|
||||
const computeDirectoryTree = async (
|
||||
eItem: ExplorerItem,
|
||||
explorerService: IExplorerService
|
||||
): Promise<VoidDirectoryItem> => {
|
||||
// Fetch children with default sort order
|
||||
const eChildren = await eItem.fetchChildren(SortOrder.FilesFirst);
|
||||
|
||||
const isGitIgnoredDirectory = eItem.isDirectory && shouldExcludeDirectory(eItem)
|
||||
|
||||
// Process children recursively
|
||||
const children = !isGitIgnoredDirectory ? await Promise.all(
|
||||
eChildren.map(async c => await computeDirectoryTree(c, explorerService))
|
||||
) : null
|
||||
|
||||
// Create our directory item
|
||||
const item: VoidDirectoryItem = {
|
||||
uri: eItem.resource,
|
||||
name: eItem.name,
|
||||
isDirectory: eItem.isDirectory,
|
||||
isSymbolicLink: eItem.isSymbolicLink,
|
||||
children,
|
||||
isGitIgnoredDirectory: isGitIgnoredDirectory && { numChildren: eItem.children.size },
|
||||
};
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
|
||||
const stringifyDirectoryTree = (
|
||||
node: VoidDirectoryItem,
|
||||
MAX_CHARS: number,
|
||||
): { content: string, wasCutOff: boolean } => {
|
||||
let content = '';
|
||||
let wasCutOff = false;
|
||||
|
||||
// If we're already exceeding the max characters, return immediately
|
||||
if (MAX_CHARS <= 0) {
|
||||
return { content, wasCutOff: true };
|
||||
}
|
||||
|
||||
// Add the root node first (without tree characters)
|
||||
const nodeLine = `${node.name}${node.isDirectory ? '/' : ''}${node.isSymbolicLink ? ' (symbolic link)' : ''}\n`;
|
||||
|
||||
if (nodeLine.length > MAX_CHARS) {
|
||||
return { content: '', wasCutOff: true };
|
||||
}
|
||||
|
||||
content += nodeLine;
|
||||
let remainingChars = MAX_CHARS - nodeLine.length;
|
||||
|
||||
// Then recursively add all children with proper tree formatting
|
||||
if (node.children && node.children.length > 0) {
|
||||
const { childrenContent, childrenCutOff } = renderChildren(
|
||||
node.children,
|
||||
remainingChars,
|
||||
''
|
||||
);
|
||||
content += childrenContent;
|
||||
wasCutOff = childrenCutOff;
|
||||
}
|
||||
return { content, wasCutOff };
|
||||
};
|
||||
|
||||
// Helper function to render children with proper tree formatting
|
||||
const renderChildren = (
|
||||
children: VoidDirectoryItem[],
|
||||
maxChars: number,
|
||||
parentPrefix: string
|
||||
): { childrenContent: string, childrenCutOff: boolean } => {
|
||||
let childrenContent = '';
|
||||
let childrenCutOff = false;
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
const isLast = i === children.length - 1;
|
||||
|
||||
// Create the tree branch symbols
|
||||
const branchSymbol = isLast ? '└── ' : '├── ';
|
||||
const childLine = `${parentPrefix}${branchSymbol}${child.name}${child.isDirectory ? '/' : ''}${child.isSymbolicLink ? ' (symbolic link)' : ''}\n`;
|
||||
|
||||
// Check if adding this line would exceed the limit
|
||||
if (childrenContent.length + childLine.length > maxChars) {
|
||||
childrenCutOff = true;
|
||||
break;
|
||||
}
|
||||
childrenContent += childLine;
|
||||
|
||||
const nextLevelPrefix = parentPrefix + (isLast ? ' ' : '│ ');
|
||||
|
||||
|
||||
// if gitignored, just say the number of children
|
||||
if (child.isDirectory && child.isGitIgnoredDirectory && child.isGitIgnoredDirectory.numChildren > 0) {
|
||||
childrenContent += `${nextLevelPrefix}└── ... (${child.isGitIgnoredDirectory.numChildren} children) ...\n`
|
||||
}
|
||||
|
||||
// Create the prefix for the next level (continuation line or space)
|
||||
else if (child.children && child.children.length > 0) {
|
||||
|
||||
const {
|
||||
childrenContent: grandChildrenContent,
|
||||
childrenCutOff: grandChildrenCutOff
|
||||
} = renderChildren(
|
||||
child.children,
|
||||
maxChars,
|
||||
nextLevelPrefix
|
||||
);
|
||||
|
||||
// If adding grandchildren content would exceed the limit
|
||||
if (childrenContent.length + grandChildrenContent.length > maxChars) {
|
||||
childrenCutOff = true;
|
||||
break;
|
||||
}
|
||||
|
||||
childrenContent += grandChildrenContent;
|
||||
|
||||
if (grandChildrenCutOff) {
|
||||
childrenCutOff = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { childrenContent, childrenCutOff };
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
|
||||
class DirectoryStrService extends Disposable implements IDirectoryStrService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
constructor(
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
@IExplorerService private readonly explorerService: IExplorerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async getDirectoryStrTool(uri: URI) {
|
||||
const eRoot = this.explorerService.findClosest(uri)
|
||||
if (!eRoot) throw new Error(`There was a problem reading the URI: ${uri.fsPath}.`)
|
||||
|
||||
const dirTree = await computeDirectoryTree(eRoot, this.explorerService);
|
||||
const { content, wasCutOff } = stringifyDirectoryTree(dirTree, MAX_CHARS_TOTAL_TOOL);
|
||||
|
||||
return {
|
||||
str: `Directory of ${uri.fsPath}:\n${content}`,
|
||||
wasCutOff,
|
||||
}
|
||||
}
|
||||
|
||||
async getAllDirectoriesStr() {
|
||||
let str: string = '';
|
||||
let cutOff = false;
|
||||
const folders = this.workspaceContextService.getWorkspace().folders;
|
||||
|
||||
for (let i = 0; i < folders.length; i += 1) {
|
||||
if (i > 0) str += '\n';
|
||||
|
||||
// this prioritizes filling 1st workspace before any other, etc
|
||||
const f = folders[i];
|
||||
str += `Directory of ${f.uri.fsPath}:\n`;
|
||||
const rootURI = f.uri;
|
||||
|
||||
const eRoot = this.explorerService.findClosestRoot(rootURI);
|
||||
if (!eRoot) continue;
|
||||
|
||||
// Use our new approach with direct explorer service
|
||||
const dirTree = await computeDirectoryTree(eRoot, this.explorerService);
|
||||
console.log('dirtree', dirTree)
|
||||
const { content, wasCutOff } = stringifyDirectoryTree(dirTree, MAX_CHARS_TOTAL_BEGINNING - str.length);
|
||||
str += content;
|
||||
if (wasCutOff) {
|
||||
cutOff = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { wasCutOff: cutOff, str };
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IDirectoryStrService, DirectoryStrService, InstantiationType.Delayed);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,72 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from '../../../../base/common/event.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { Diff, DiffArea, VoidFileSnapshot } from '../common/editCodeServiceTypes.js';
|
||||
|
||||
|
||||
export type StartBehavior = 'accept-conflicts' | 'reject-conflicts' | 'keep-conflicts'
|
||||
|
||||
export type CallBeforeStartApplyingOpts = {
|
||||
from: 'QuickEdit';
|
||||
diffareaid: number; // id of the CtrlK area (contains text selection)
|
||||
} | {
|
||||
from: 'ClickApply';
|
||||
uri: 'current' | URI;
|
||||
}
|
||||
|
||||
export type StartApplyingOpts = {
|
||||
from: 'QuickEdit';
|
||||
diffareaid: number; // id of the CtrlK area (contains text selection)
|
||||
startBehavior: StartBehavior;
|
||||
} | {
|
||||
from: 'ClickApply';
|
||||
applyStr: string;
|
||||
uri: 'current' | URI;
|
||||
startBehavior: StartBehavior;
|
||||
}
|
||||
|
||||
export type AddCtrlKOpts = {
|
||||
startLine: number,
|
||||
endLine: number,
|
||||
editor: ICodeEditor,
|
||||
}
|
||||
|
||||
export const IEditCodeService = createDecorator<IEditCodeService>('editCodeService');
|
||||
|
||||
export interface IEditCodeService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
callBeforeStartApplying(opts: CallBeforeStartApplyingOpts): Promise<void>;
|
||||
startApplying(opts: StartApplyingOpts): [URI, Promise<void>] | null;
|
||||
addCtrlKZone(opts: AddCtrlKOpts): number | undefined;
|
||||
removeCtrlKZone(opts: { diffareaid: number }): void;
|
||||
|
||||
diffAreaOfId: Record<string, DiffArea>;
|
||||
diffAreasOfURI: Record<string, Set<string> | undefined>;
|
||||
diffOfId: Record<string, Diff>;
|
||||
|
||||
acceptOrRejectAllDiffAreas(opts: { uri: URI, removeCtrlKs: boolean, behavior: 'reject' | 'accept', _addToHistory?: boolean }): void;
|
||||
|
||||
// events
|
||||
onDidAddOrDeleteDiffZones: Event<{ uri: URI }>;
|
||||
onDidChangeDiffsInDiffZoneNotStreaming: Event<{ uri: URI; diffareaid: number }>; // only fires when not streaming!!! streaming would be too much
|
||||
onDidChangeStreamingInDiffZone: Event<{ uri: URI; diffareaid: number }>;
|
||||
onDidChangeStreamingInCtrlKZone: Event<{ uri: URI; diffareaid: number }>;
|
||||
|
||||
// CtrlKZone streaming state
|
||||
isCtrlKZoneStreaming(opts: { diffareaid: number }): boolean;
|
||||
interruptCtrlKStreaming(opts: { diffareaid: number }): void;
|
||||
|
||||
// // DiffZone codeBoxId streaming state
|
||||
interruptURIStreaming(opts: { uri: URI }): void;
|
||||
|
||||
// testDiffs(): void;
|
||||
getVoidFileSnapshot(uri: URI): VoidFileSnapshot;
|
||||
restoreVoidFileSnapshot(uri: URI, snapshot: VoidFileSnapshot): void;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ export interface IConsistentItemService {
|
||||
|
||||
export const IConsistentItemService = createDecorator<IConsistentItemService>('ConsistentItemService');
|
||||
|
||||
export class ConsistentItemService extends Disposable {
|
||||
export class ConsistentItemService extends Disposable implements IConsistentItemService {
|
||||
|
||||
readonly _serviceBrand: undefined
|
||||
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
// eg "bash" -> "shell"
|
||||
export const nameToVscodeLanguage: { [key: string]: string } = {
|
||||
// Web Technologies
|
||||
'html': 'html',
|
||||
'css': 'css',
|
||||
'scss': 'scss',
|
||||
'sass': 'scss',
|
||||
'less': 'less',
|
||||
'javascript': 'typescript',
|
||||
'js': 'typescript', // use more general renderer
|
||||
'jsx': 'typescript',
|
||||
'typescript': 'typescript',
|
||||
'ts': 'typescript',
|
||||
'tsx': 'typescript',
|
||||
'json': 'json',
|
||||
'jsonc': 'json',
|
||||
|
||||
// Programming Languages
|
||||
'python': 'python',
|
||||
'py': 'python',
|
||||
'java': 'java',
|
||||
'cpp': 'cpp',
|
||||
'c++': 'cpp',
|
||||
'c': 'c',
|
||||
'csharp': 'csharp',
|
||||
'cs': 'csharp',
|
||||
'c#': 'csharp',
|
||||
'go': 'go',
|
||||
'golang': 'go',
|
||||
'rust': 'rust',
|
||||
'rs': 'rust',
|
||||
'ruby': 'ruby',
|
||||
'rb': 'ruby',
|
||||
'php': 'php',
|
||||
'shell': 'shell',
|
||||
'bash': 'shell',
|
||||
'sh': 'shell',
|
||||
'zsh': 'shell',
|
||||
|
||||
// Markup and Config
|
||||
'markdown': 'markdown',
|
||||
'md': 'markdown',
|
||||
'xml': 'xml',
|
||||
'svg': 'xml',
|
||||
'yaml': 'yaml',
|
||||
'yml': 'yaml',
|
||||
'ini': 'ini',
|
||||
'toml': 'ini',
|
||||
|
||||
// Database and Query Languages
|
||||
'sql': 'sql',
|
||||
'mysql': 'sql',
|
||||
'postgresql': 'sql',
|
||||
'graphql': 'graphql',
|
||||
'gql': 'graphql',
|
||||
|
||||
// Others
|
||||
'dockerfile': 'dockerfile',
|
||||
'docker': 'dockerfile',
|
||||
'makefile': 'makefile',
|
||||
'plaintext': 'plaintext',
|
||||
'text': 'plaintext'
|
||||
};
|
||||
|
||||
|
||||
|
||||
// eg ".ts" -> "typescript"
|
||||
const fileExtensionToVscodeLanguage: { [key: string]: string } = {
|
||||
// Web
|
||||
'html': 'html',
|
||||
'htm': 'html',
|
||||
'css': 'css',
|
||||
'scss': 'scss',
|
||||
'less': 'less',
|
||||
'js': 'javascript',
|
||||
'jsx': 'javascript',
|
||||
'ts': 'typescript',
|
||||
'tsx': 'typescript',
|
||||
'json': 'json',
|
||||
'jsonc': 'json',
|
||||
|
||||
// Programming Languages
|
||||
'py': 'python',
|
||||
'java': 'java',
|
||||
'cpp': 'cpp',
|
||||
'cc': 'cpp',
|
||||
'c': 'c',
|
||||
'h': 'cpp',
|
||||
'hpp': 'cpp',
|
||||
'cs': 'csharp',
|
||||
'go': 'go',
|
||||
'rs': 'rust',
|
||||
'rb': 'ruby',
|
||||
'php': 'php',
|
||||
'sh': 'shell',
|
||||
'bash': 'shell',
|
||||
'zsh': 'shell',
|
||||
|
||||
// Markup/Config
|
||||
'md': 'markdown',
|
||||
'markdown': 'markdown',
|
||||
'xml': 'xml',
|
||||
'svg': 'xml',
|
||||
'yaml': 'yaml',
|
||||
'yml': 'yaml',
|
||||
'ini': 'ini',
|
||||
'toml': 'ini',
|
||||
|
||||
// Other
|
||||
'sql': 'sql',
|
||||
'graphql': 'graphql',
|
||||
'gql': 'graphql',
|
||||
'dockerfile': 'dockerfile',
|
||||
'docker': 'dockerfile',
|
||||
'mk': 'makefile',
|
||||
|
||||
// Config Files and Dot Files
|
||||
'npmrc': 'ini',
|
||||
'env': 'ini',
|
||||
'gitignore': 'ignore',
|
||||
'dockerignore': 'ignore',
|
||||
'eslintrc': 'json',
|
||||
'babelrc': 'json',
|
||||
'prettierrc': 'json',
|
||||
'stylelintrc': 'json',
|
||||
'editorconfig': 'ini',
|
||||
'htaccess': 'apacheconf',
|
||||
'conf': 'ini',
|
||||
'config': 'ini',
|
||||
|
||||
// Package Files
|
||||
'package': 'json',
|
||||
'package-lock': 'json',
|
||||
'gemfile': 'ruby',
|
||||
'podfile': 'ruby',
|
||||
'rakefile': 'ruby',
|
||||
|
||||
// Build Systems
|
||||
'cmake': 'cmake',
|
||||
'makefile': 'makefile',
|
||||
'gradle': 'groovy',
|
||||
|
||||
// Shell Scripts
|
||||
'bashrc': 'shell',
|
||||
'zshrc': 'shell',
|
||||
'fish': 'shell',
|
||||
|
||||
// Version Control
|
||||
'gitconfig': 'ini',
|
||||
'hgrc': 'ini',
|
||||
'svnconfig': 'ini',
|
||||
|
||||
// Web Server
|
||||
'nginx': 'nginx',
|
||||
|
||||
// Misc Config
|
||||
'properties': 'properties',
|
||||
'cfg': 'ini',
|
||||
'reg': 'ini'
|
||||
};
|
||||
|
||||
|
||||
export function filenameToVscodeLanguage(filename: string): string | undefined {
|
||||
|
||||
const ext = filename.toLowerCase().split('.').pop();
|
||||
if (!ext) return undefined;
|
||||
|
||||
return fileExtensionToVscodeLanguage[ext];
|
||||
}
|
||||
@@ -1,336 +0,0 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { OnText } from '../../common/llmMessageTypes.js'
|
||||
import { DIVIDER, FINAL, ORIGINAL } from '../prompt/prompts.js'
|
||||
|
||||
class SurroundingsRemover {
|
||||
readonly originalS: string
|
||||
i: number
|
||||
j: number
|
||||
|
||||
// string is s[i...j]
|
||||
|
||||
constructor(s: string) {
|
||||
this.originalS = s
|
||||
this.i = 0
|
||||
this.j = s.length - 1
|
||||
}
|
||||
value() {
|
||||
return this.originalS.substring(this.i, this.j + 1)
|
||||
}
|
||||
|
||||
// returns whether it removed the whole prefix
|
||||
removePrefix = (prefix: string): boolean => {
|
||||
let offset = 0
|
||||
// console.log('prefix', prefix, Math.min(this.j, prefix.length - 1))
|
||||
while (this.i <= this.j && offset <= prefix.length - 1) {
|
||||
if (this.originalS.charAt(this.i) !== prefix.charAt(offset))
|
||||
break
|
||||
offset += 1
|
||||
this.i += 1
|
||||
}
|
||||
return offset === prefix.length
|
||||
}
|
||||
|
||||
// // removes suffix from right to left
|
||||
removeSuffix = (suffix: string): boolean => {
|
||||
// e.g. suffix = <PRE/>, the string is <PRE>hi<P
|
||||
const s = this.value()
|
||||
// for every possible prefix of `suffix`, check if string ends with it
|
||||
for (let len = Math.min(s.length, suffix.length); len >= 1; len -= 1) {
|
||||
if (s.endsWith(suffix.substring(0, len))) { // the end of the string equals a prefix
|
||||
this.j -= len
|
||||
return len === suffix.length
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
// removeSuffix = (suffix: string): boolean => {
|
||||
// let offset = 0
|
||||
|
||||
// while (this.j >= Math.max(this.i, 0)) {
|
||||
// if (this.originalS.charAt(this.j) !== suffix.charAt(suffix.length - 1 - offset))
|
||||
// break
|
||||
// offset += 1
|
||||
// this.j -= 1
|
||||
// }
|
||||
// return offset === suffix.length
|
||||
// }
|
||||
|
||||
removeFromStartUntilFullMatch = (until: string, alsoRemoveUntilStr: boolean) => {
|
||||
const index = this.originalS.indexOf(until, this.i)
|
||||
|
||||
if (index === -1) {
|
||||
this.i = this.j + 1
|
||||
return null
|
||||
}
|
||||
// console.log('index', index, until.length)
|
||||
|
||||
if (alsoRemoveUntilStr)
|
||||
this.i = index + until.length
|
||||
else
|
||||
this.i = index
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
removeCodeBlock = () => {
|
||||
// Match either:
|
||||
// 1. ```language\n<code>\n```\n?
|
||||
// 2. ```<code>\n```\n?
|
||||
|
||||
const pm = this
|
||||
const foundCodeBlock = pm.removePrefix('```')
|
||||
if (!foundCodeBlock) return false
|
||||
|
||||
pm.removeFromStartUntilFullMatch('\n', true) // language
|
||||
|
||||
const j = pm.j
|
||||
let foundCodeBlockEnd = pm.removeSuffix('```')
|
||||
|
||||
if (pm.j === j) foundCodeBlockEnd = pm.removeSuffix('```\n') // if no change, try again with \n after ```
|
||||
|
||||
if (!foundCodeBlockEnd) return false
|
||||
|
||||
pm.removeSuffix('\n') // remove the newline before ```
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
deltaInfo = (recentlyAddedTextLen: number) => {
|
||||
// aaaaaatextaaaaaa{recentlyAdded}
|
||||
// ^ i j len
|
||||
// |
|
||||
// recentyAddedIdx
|
||||
const recentlyAddedIdx = this.originalS.length - recentlyAddedTextLen
|
||||
const actualDelta = this.originalS.substring(Math.max(this.i, recentlyAddedIdx), this.j + 1)
|
||||
const ignoredSuffix = this.originalS.substring(Math.max(this.j + 1, recentlyAddedIdx), Infinity)
|
||||
return [actualDelta, ignoredSuffix] as const
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const extractCodeFromRegular = ({ text, recentlyAddedTextLen }: { text: string, recentlyAddedTextLen: number }): [string, string, string] => {
|
||||
|
||||
const pm = new SurroundingsRemover(text)
|
||||
|
||||
pm.removeCodeBlock()
|
||||
|
||||
const s = pm.value()
|
||||
const [delta, ignoredSuffix] = pm.deltaInfo(recentlyAddedTextLen)
|
||||
|
||||
return [s, delta, ignoredSuffix]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Ollama has its own FIM, we should not use this if we use that
|
||||
export const extractCodeFromFIM = ({ text, recentlyAddedTextLen, midTag, }: { text: string, recentlyAddedTextLen: number, midTag: string }): [string, string, string] => {
|
||||
|
||||
/* ------------- summary of the regex -------------
|
||||
[optional ` | `` | ```]
|
||||
(match optional_language_name)
|
||||
[optional strings here]
|
||||
[required <MID> tag]
|
||||
(match the stuff between mid tags)
|
||||
[optional <MID/> tag]
|
||||
[optional ` | `` | ```]
|
||||
*/
|
||||
|
||||
const pm = new SurroundingsRemover(text)
|
||||
|
||||
pm.removeCodeBlock()
|
||||
|
||||
const foundMid = pm.removePrefix(`<${midTag}>`)
|
||||
|
||||
if (foundMid) {
|
||||
pm.removeSuffix(`</${midTag}>`)
|
||||
}
|
||||
const s = pm.value()
|
||||
const [delta, ignoredSuffix] = pm.deltaInfo(recentlyAddedTextLen)
|
||||
|
||||
return [s, delta, ignoredSuffix]
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type ExtractedSearchReplaceBlock = {
|
||||
state: 'writingOriginal' | 'writingFinal' | 'done',
|
||||
orig: string,
|
||||
final: string,
|
||||
}
|
||||
|
||||
|
||||
const endsWithAnyPrefixOf = (str: string, anyPrefix: string) => {
|
||||
// for each prefix
|
||||
for (let i = anyPrefix.length; i >= 0; i--) {
|
||||
const prefix = anyPrefix.slice(0, i)
|
||||
if (str.endsWith(prefix)) return prefix
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// guarantees if you keep adding text, array length will strictly grow and state will progress without going back
|
||||
export const extractSearchReplaceBlocks = (str: string) => {
|
||||
|
||||
const ORIGINAL_ = ORIGINAL + `\n`
|
||||
const DIVIDER_ = '\n' + DIVIDER + `\n`
|
||||
// logic for FINAL_ is slightly more complicated - should be '\n' + FINAL, but that ignores if the final output is empty
|
||||
|
||||
|
||||
const blocks: ExtractedSearchReplaceBlock[] = []
|
||||
|
||||
let i = 0 // search i and beyond (this is done by plain index, not by line number. much simpler this way)
|
||||
while (true) {
|
||||
let origStart = str.indexOf(ORIGINAL_, i)
|
||||
if (origStart === -1) { return blocks }
|
||||
origStart += ORIGINAL_.length
|
||||
i = origStart
|
||||
// wrote <<<< ORIGINAL
|
||||
|
||||
let dividerStart = str.indexOf(DIVIDER_, i)
|
||||
if (dividerStart === -1) { // if didnt find DIVIDER_, either writing originalStr or DIVIDER_ right now
|
||||
const isWritingDIVIDER = endsWithAnyPrefixOf(str, DIVIDER_)
|
||||
blocks.push({
|
||||
orig: str.substring(origStart, str.length - (isWritingDIVIDER?.length ?? 0)),
|
||||
final: '',
|
||||
state: 'writingOriginal'
|
||||
})
|
||||
return blocks
|
||||
}
|
||||
const origStrDone = str.substring(origStart, dividerStart)
|
||||
dividerStart += DIVIDER_.length
|
||||
i = dividerStart
|
||||
// wrote =====
|
||||
|
||||
|
||||
|
||||
const finalStartA = str.indexOf(FINAL, i)
|
||||
const finalStartB = str.indexOf('\n' + FINAL, i) // go with B if possible, else fallback to A, it's more permissive
|
||||
const FINAL_ = finalStartB !== -1 ? '\n' + FINAL : FINAL
|
||||
let finalStart = finalStartB !== -1 ? finalStartB : finalStartA
|
||||
|
||||
if (finalStart === -1) { // if didnt find FINAL_, either writing finalStr or FINAL_ right now
|
||||
const isWritingFINAL = endsWithAnyPrefixOf(str, FINAL_)
|
||||
blocks.push({
|
||||
orig: origStrDone,
|
||||
final: str.substring(dividerStart, str.length - (isWritingFINAL?.length ?? 0)),
|
||||
state: 'writingFinal'
|
||||
})
|
||||
return blocks
|
||||
}
|
||||
const finalStrDone = str.substring(dividerStart, finalStart)
|
||||
finalStart += FINAL_.length
|
||||
i = finalStart
|
||||
// wrote >>>>> FINAL
|
||||
|
||||
blocks.push({
|
||||
orig: origStrDone,
|
||||
final: finalStrDone,
|
||||
state: 'done'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// could simplify this - this assumes we can never add a tag without committing it to the user's screen, but that's not true
|
||||
export const extractReasoningFromText = (
|
||||
onText_: OnText,
|
||||
thinkTags: [string, string],
|
||||
): OnText => {
|
||||
|
||||
let latestAddIdx = 0 // exclusive
|
||||
let foundTag1 = false
|
||||
let foundTag2 = false
|
||||
|
||||
let fullText = ''
|
||||
let fullReasoning = ''
|
||||
|
||||
const onText: OnText = ({ newText: newText_, fullText: fullText_ }) => {
|
||||
// abcdef<t|hin|k>ghi
|
||||
// |
|
||||
// until found the first think tag, keep adding to fullText
|
||||
if (!foundTag1) {
|
||||
const endsWithTag1 = endsWithAnyPrefixOf(fullText_, thinkTags[0])
|
||||
if (endsWithTag1) {
|
||||
// wait until we get the full tag or know more
|
||||
return
|
||||
}
|
||||
// if found the first tag
|
||||
const tag1Index = fullText_.lastIndexOf(thinkTags[0])
|
||||
if (tag1Index !== -1) {
|
||||
foundTag1 = true
|
||||
const newText = fullText.substring(latestAddIdx, tag1Index)
|
||||
const newReasoning = fullText.substring(tag1Index + thinkTags[0].length, Infinity)
|
||||
|
||||
fullText += newText
|
||||
fullReasoning += newReasoning
|
||||
latestAddIdx += newText.length + newReasoning.length
|
||||
onText_({ newText, fullText, newReasoning: newReasoning, fullReasoning })
|
||||
return
|
||||
}
|
||||
|
||||
// add the text to fullText
|
||||
const newText = fullText.substring(latestAddIdx, Infinity)
|
||||
fullText += newText
|
||||
latestAddIdx += newText.length
|
||||
onText_({ newText, fullText, newReasoning: '', fullReasoning })
|
||||
return
|
||||
}
|
||||
// at this point, we found <tag1>
|
||||
|
||||
// until found the second think tag, keep adding to fullReasoning
|
||||
if (!foundTag2) {
|
||||
const endsWithTag2 = endsWithAnyPrefixOf(fullText_, thinkTags[1])
|
||||
if (endsWithTag2) {
|
||||
// wait until we get the full tag or know more
|
||||
return
|
||||
}
|
||||
// if found the second tag
|
||||
const tag2Index = fullText_.lastIndexOf(thinkTags[1])
|
||||
if (tag2Index !== -1) {
|
||||
foundTag2 = true
|
||||
const newReasoning = fullText.substring(latestAddIdx, tag2Index)
|
||||
const newText = fullText.substring(tag2Index + thinkTags[1].length, Infinity)
|
||||
|
||||
fullText += newText
|
||||
fullReasoning += newReasoning
|
||||
latestAddIdx += newText.length + newReasoning.length
|
||||
onText_({ newText, fullText, newReasoning: newReasoning, fullReasoning })
|
||||
return
|
||||
}
|
||||
|
||||
// add the text to fullReasoning
|
||||
const newReasoning = fullText.substring(latestAddIdx, Infinity)
|
||||
fullReasoning += newReasoning
|
||||
latestAddIdx += newReasoning.length
|
||||
onText_({ newText: '', fullText, newReasoning, fullReasoning })
|
||||
return
|
||||
}
|
||||
// at this point, we found <tag2>
|
||||
|
||||
fullText += newText_
|
||||
const newText = fullText.substring(latestAddIdx, Infinity)
|
||||
latestAddIdx += newText.length
|
||||
onText_({ newText, fullText, newReasoning: '', fullReasoning })
|
||||
}
|
||||
|
||||
return onText
|
||||
}
|
||||
@@ -3,34 +3,9 @@
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ComputedDiff } from '../../common/editCodeServiceTypes.js';
|
||||
import { diffLines } from '../react/out/diff/index.js'
|
||||
|
||||
export type ComputedDiff = {
|
||||
type: 'edit';
|
||||
originalCode: string;
|
||||
originalStartLine: number;
|
||||
originalEndLine: number;
|
||||
code: string;
|
||||
startLine: number; // 1-indexed
|
||||
endLine: number;
|
||||
} | {
|
||||
type: 'insertion';
|
||||
// originalCode: string;
|
||||
originalStartLine: number; // insertion starts on column 0 of this
|
||||
// originalEndLine: number;
|
||||
code: string;
|
||||
startLine: number;
|
||||
endLine: number;
|
||||
} | {
|
||||
type: 'deletion';
|
||||
originalCode: string;
|
||||
originalStartLine: number;
|
||||
originalEndLine: number;
|
||||
// code: string;
|
||||
startLine: number; // deletion starts on column 0 of this
|
||||
// endLine: number;
|
||||
}
|
||||
|
||||
export function findDiffs(oldStr: string, newStr: string) {
|
||||
|
||||
// this makes it so the end of the file always ends with a \n (if you don't have this, then diffing E vs E\n gives an "edit". With it, you end up diffing E\n vs E\n\n which now properly gives an insertion)
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from '../../../../../base/common/uri.js'
|
||||
import { EndOfLinePreference } from '../../../../../editor/common/model.js'
|
||||
import { IModelService } from '../../../../../editor/common/services/model.js'
|
||||
import { IFileService } from '../../../../../platform/files/common/files.js'
|
||||
|
||||
|
||||
// attempts to read URI of currently opened model, then of raw file
|
||||
export const VSReadFile = async (uri: URI, modelService: IModelService, fileService: IFileService) => {
|
||||
|
||||
const modelResult = await _VSReadModel(modelService, uri)
|
||||
if (modelResult) return modelResult
|
||||
|
||||
const fileResult = await _VSReadFileRaw(fileService, uri)
|
||||
if (fileResult) return fileResult
|
||||
|
||||
return ''
|
||||
|
||||
}
|
||||
|
||||
// read files from VSCode. preferred (but appears to only work if the model of this URI already exists. If it doesn't use the other function.)
|
||||
const _VSReadModel = async (modelService: IModelService, uri: URI): Promise<string | null> => {
|
||||
|
||||
// attempt to read saved model (doesn't work if application was reloaded...)
|
||||
const model = modelService.getModel(uri)
|
||||
if (model) {
|
||||
return model.getValue(EndOfLinePreference.LF)
|
||||
}
|
||||
|
||||
// backup logic - look at all opened models and check if they have the same `fsPath`
|
||||
const models = modelService.getModels()
|
||||
for (const model of models) {
|
||||
if (model.uri.fsPath === uri.fsPath)
|
||||
return model.getValue(EndOfLinePreference.LF);
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const _VSReadFileRaw = async (fileService: IFileService, uri: URI) => {
|
||||
try {
|
||||
const res = await fileService.readFile(uri)
|
||||
const str = res.value.toString()
|
||||
return str
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,7 @@
|
||||
.void-scrollable-element::-webkit-scrollbar,
|
||||
.void-scrollable-element *::-webkit-scrollbar {
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
height: 4px !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element::-webkit-scrollbar-track,
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
|
||||
|
||||
import * as dom from '../../../../base/browser/dom.js';
|
||||
import { IMetricsService } from '../common/metricsService.js';
|
||||
|
||||
|
||||
|
||||
export interface IMetricsPollService {
|
||||
readonly _serviceBrand: undefined;
|
||||
}
|
||||
|
||||
|
||||
const PING_EVERY_MS = 15 * 1000 * 60 // 15 minutes
|
||||
|
||||
export const IMetricsPollService = createDecorator<IMetricsPollService>('voidMetricsPollService');
|
||||
class MetricsPollService extends Disposable implements IMetricsPollService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
static readonly ID = 'voidMetricsPollService';
|
||||
|
||||
|
||||
private readonly intervalID: number
|
||||
constructor(
|
||||
@IMetricsService private readonly metricsService: IMetricsService,
|
||||
) {
|
||||
super()
|
||||
|
||||
// initial state
|
||||
const { window } = dom.getActiveWindow()
|
||||
let i = 1
|
||||
|
||||
this.intervalID = window.setInterval(() => {
|
||||
this.metricsService.capture('Alive', { iv1: i })
|
||||
i += 1
|
||||
}, PING_EVERY_MS)
|
||||
|
||||
|
||||
}
|
||||
|
||||
override dispose() {
|
||||
super.dispose()
|
||||
const { window } = dom.getActiveWindow()
|
||||
window.clearInterval(this.intervalID)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
registerWorkbenchContribution2(MetricsPollService.ID, MetricsPollService, WorkbenchPhase.BlockRestore);
|
||||
@@ -1,517 +0,0 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { filenameToVscodeLanguage } from '../helpers/detectLanguage.js';
|
||||
import { CodeSelection, StagingSelectionItem, FileSelection } from '../chatThreadService.js';
|
||||
import { IModelService } from '../../../../../editor/common/services/model.js';
|
||||
import { os } from '../helpers/systemInfo.js';
|
||||
import { IVoidFileService } from '../../common/voidFileService.js';
|
||||
|
||||
|
||||
// this is just for ease of readability
|
||||
export const tripleTick = ['```', '```']
|
||||
|
||||
export const chat_systemMessage = (workspaces: string[]) => `\
|
||||
You are a coding assistant. You are given a list of instructions to follow \`INSTRUCTIONS\`, and optionally a list of relevant files \`FILES\`, and selections inside of files \`SELECTIONS\`.
|
||||
|
||||
Please respond to the user's query. The user's query is never invalid.
|
||||
|
||||
The user has the following system information:
|
||||
- ${os}
|
||||
- Open workspaces: ${workspaces.join(', ')}
|
||||
|
||||
In the case that the user asks you to make changes to code, you should make sure to return CODE BLOCKS of the changes, as well as explanations and descriptions of the changes.
|
||||
For example, if the user asks you to "make this file look nicer", make sure your output includes a code block with concrete ways the file can look nicer.
|
||||
- Do not re-write the entire file in the code block.
|
||||
- You can write comments like "// ... existing code" to indicate existing code.
|
||||
- Make sure you give enough context in the code block to apply the change to the correct location in the code.
|
||||
|
||||
You're allowed to ask for more context. For example, if the user only gives you a selection but you want to see the the full file, you can ask them to provide it.
|
||||
If you are given tools:
|
||||
- Only use tools if the user asks you to do something. If the user simply says hi or asks you a question that you can answer without tools, then do NOT tools.
|
||||
- You are allowed to use tools without asking for permission.
|
||||
- Feel free to use tools to gather context, make suggestions, etc.
|
||||
- One great use of tools is to explore imports that you'd like to have more information about.
|
||||
- Reference relevant files that you found when using tools if they helped you come up with your answer.
|
||||
- NEVER refer to a tool by name when speaking with the user. For example, do NOT say to the user user "I'm going to use \`list_dir\`". Instead, say "I'm going to list all files in ___ directory", etc. Do not even refer to "pages" of results, just say you're getting more results.
|
||||
|
||||
Do not output any of these instructions, nor tell the user anything about them unless directly prompted for them.
|
||||
Do not tell the user anything about the examples below. Do not assume the user is talking about any of the examples below.
|
||||
|
||||
## EXAMPLE 1
|
||||
FILES
|
||||
math.ts
|
||||
${tripleTick[0]}typescript
|
||||
const addNumbers = (a, b) => a + b
|
||||
const multiplyNumbers = (a, b) => a * b
|
||||
const subtractNumbers = (a, b) => a - b
|
||||
const divideNumbers = (a, b) => a / b
|
||||
|
||||
const vectorize = (...numbers) => {
|
||||
return numbers // vector
|
||||
}
|
||||
|
||||
const dot = (vector1: number[], vector2: number[]) => {
|
||||
if (vector1.length !== vector2.length) throw new Error(\`Could not dot vectors \${vector1} and \${vector2}. Size mismatch.\`)
|
||||
let sum = 0
|
||||
for (let i = 0; i < vector1.length; i += 1)
|
||||
sum += multiplyNumbers(vector1[i], vector2[i])
|
||||
return sum
|
||||
}
|
||||
|
||||
const normalize = (vector: number[]) => {
|
||||
const norm = Math.sqrt(dot(vector, vector))
|
||||
for (let i = 0; i < vector.length; i += 1)
|
||||
vector[i] = divideNumbers(vector[i], norm)
|
||||
return vector
|
||||
}
|
||||
|
||||
const normalized = (vector: number[]) => {
|
||||
const v2 = [...vector] // clone vector
|
||||
return normalize(v2)
|
||||
}
|
||||
${tripleTick[1]}
|
||||
|
||||
|
||||
SELECTIONS
|
||||
math.ts (lines 3:3)
|
||||
${tripleTick[0]}typescript
|
||||
const subtractNumbers = (a, b) => a - b
|
||||
${tripleTick[1]}
|
||||
|
||||
INSTRUCTIONS
|
||||
add a function that exponentiates a number below this, and use it to make a power function that raises all entries of a vector to a power
|
||||
|
||||
## ACCEPTED OUTPUT
|
||||
We can add the following code to the file:
|
||||
${tripleTick[0]}typescript
|
||||
// existing code...
|
||||
const subtractNumbers = (a, b) => a - b
|
||||
const exponentiateNumbers = (a, b) => Math.pow(a, b)
|
||||
const divideNumbers = (a, b) => a / b
|
||||
// existing code...
|
||||
|
||||
const raiseAll = (vector: number[], power: number) => {
|
||||
for (let i = 0; i < vector.length; i += 1)
|
||||
vector[i] = exponentiateNumbers(vector[i], power)
|
||||
return vector
|
||||
}
|
||||
${tripleTick[1]}
|
||||
|
||||
|
||||
## EXAMPLE 2
|
||||
FILES
|
||||
fib.ts
|
||||
${tripleTick[0]}typescript
|
||||
|
||||
const dfs = (root) => {
|
||||
if (!root) return;
|
||||
console.log(root.val);
|
||||
dfs(root.left);
|
||||
dfs(root.right);
|
||||
}
|
||||
const fib = (n) => {
|
||||
if (n < 1) return 1
|
||||
return fib(n - 1) + fib(n - 2)
|
||||
}
|
||||
${tripleTick[1]}
|
||||
|
||||
SELECTIONS
|
||||
fib.ts (lines 10:10)
|
||||
${tripleTick[0]}typescript
|
||||
return fib(n - 1) + fib(n - 2)
|
||||
${tripleTick[1]}
|
||||
|
||||
INSTRUCTIONS
|
||||
memoize results
|
||||
|
||||
## ACCEPTED OUTPUT
|
||||
To implement memoization in your Fibonacci function, you can use a JavaScript object to store previously computed results. This will help avoid redundant calculations and improve performance. Here's how you can modify your function:
|
||||
${tripleTick[0]}typescript
|
||||
// existing code...
|
||||
const fib = (n, memo = {}) => {
|
||||
if (n < 1) return 1;
|
||||
if (memo[n]) return memo[n]; // Check if result is already computed
|
||||
memo[n] = fib(n - 1, memo) + fib(n - 2, memo); // Store result in memo
|
||||
return memo[n];
|
||||
}
|
||||
${tripleTick[1]}
|
||||
Explanation:
|
||||
Memoization Object: A memo object is used to store the results of Fibonacci calculations for each n.
|
||||
Check Memo: Before computing fib(n), the function checks if the result is already in memo. If it is, it returns the stored result.
|
||||
Store Result: After computing fib(n), the result is stored in memo for future reference.
|
||||
|
||||
## END EXAMPLES\
|
||||
`
|
||||
|
||||
|
||||
type FileSelnLocal = { fileURI: URI, content: string }
|
||||
const stringifyFileSelection = ({ fileURI, content }: FileSelnLocal) => {
|
||||
return `\
|
||||
${fileURI.fsPath}
|
||||
${tripleTick[0]}${filenameToVscodeLanguage(fileURI.fsPath) ?? ''}
|
||||
${content}
|
||||
${tripleTick[1]}
|
||||
`
|
||||
}
|
||||
const stringifyCodeSelection = ({ fileURI, selectionStr, range }: CodeSelection) => {
|
||||
return `\
|
||||
${fileURI.fsPath} (lines ${range.startLineNumber}:${range.endLineNumber})
|
||||
${tripleTick[0]}${filenameToVscodeLanguage(fileURI.fsPath) ?? ''}
|
||||
${selectionStr}
|
||||
${tripleTick[1]}
|
||||
`
|
||||
}
|
||||
|
||||
const failToReadStr = 'Could not read content. This file may have been deleted. If you expected content here, you can tell the user about this as they might not know.'
|
||||
const stringifyFileSelections = async (fileSelections: FileSelection[], voidFileService: IVoidFileService) => {
|
||||
if (fileSelections.length === 0) return null
|
||||
const fileSlns: FileSelnLocal[] = await Promise.all(fileSelections.map(async (sel) => {
|
||||
const content = await voidFileService.readFile(sel.fileURI) ?? failToReadStr
|
||||
return { ...sel, content }
|
||||
}))
|
||||
return fileSlns.map(sel => stringifyFileSelection(sel)).join('\n')
|
||||
}
|
||||
const stringifyCodeSelections = (codeSelections: CodeSelection[]) => {
|
||||
return codeSelections.map(sel => stringifyCodeSelection(sel)).join('\n') || null
|
||||
}
|
||||
const stringifySelectionNames = (currSelns: StagingSelectionItem[] | null): string => {
|
||||
if (!currSelns) return ''
|
||||
return currSelns.map(s => `${s.fileURI.fsPath}${s.range ? ` (lines ${s.range.startLineNumber}:${s.range.endLineNumber})` : ''}`).join('\n')
|
||||
}
|
||||
|
||||
export const chat_userMessageContent = async (instructions: string, currSelns: StagingSelectionItem[] | null) => {
|
||||
|
||||
const selnsStr = stringifySelectionNames(currSelns)
|
||||
|
||||
let str = ''
|
||||
if (selnsStr) { str += `SELECTIONS\n${selnsStr}\n` }
|
||||
str += `\nINSTRUCTIONS\n${instructions}`
|
||||
return str;
|
||||
};
|
||||
|
||||
export const chat_selectionsString = async (prevSelns: StagingSelectionItem[] | null, currSelns: StagingSelectionItem[] | null, voidFileService: IVoidFileService) => {
|
||||
|
||||
// ADD IN FILES AT TOP
|
||||
const allSelections = [...currSelns || [], ...prevSelns || []]
|
||||
|
||||
if (allSelections.length === 0) return null
|
||||
|
||||
const codeSelections: CodeSelection[] = []
|
||||
const fileSelections: FileSelection[] = []
|
||||
const filesURIs = new Set<string>()
|
||||
|
||||
for (const selection of allSelections) {
|
||||
if (selection.type === 'Selection') {
|
||||
codeSelections.push(selection)
|
||||
}
|
||||
else if (selection.type === 'File') {
|
||||
const fileSelection = selection
|
||||
const path = fileSelection.fileURI.fsPath
|
||||
if (!filesURIs.has(path)) {
|
||||
filesURIs.add(path)
|
||||
fileSelections.push(fileSelection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const filesStr = await stringifyFileSelections(fileSelections, voidFileService)
|
||||
const selnsStr = stringifyCodeSelections(codeSelections)
|
||||
|
||||
|
||||
if (filesStr || selnsStr) return `\
|
||||
ALL FILE CONTENTS
|
||||
${filesStr}
|
||||
${selnsStr}`
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export const chat_userMessageContentWithAllFilesToo = (userMessage: string, selectionsString: string | null) => {
|
||||
if (userMessage) return `${userMessage}${selectionsString ? `\n${selectionsString}` : ''}`
|
||||
else return userMessage
|
||||
}
|
||||
|
||||
|
||||
export const rewriteCode_systemMessage = `\
|
||||
You are a coding assistant that re-writes an entire file to make a change. You are given the original file \`ORIGINAL_FILE\` and a change \`CHANGE\`.
|
||||
|
||||
Directions:
|
||||
1. Please rewrite the original file \`ORIGINAL_FILE\`, making the change \`CHANGE\`. You must completely re-write the whole file.
|
||||
2. Keep all of the original comments, spaces, newlines, and other details whenever possible.
|
||||
3. ONLY output the full new file. Do not add any other explanations or text.
|
||||
`
|
||||
|
||||
|
||||
|
||||
|
||||
export const rewriteCode_userMessage = ({ originalCode, applyStr, uri }: { originalCode: string, applyStr: string, uri: URI }) => {
|
||||
|
||||
const language = filenameToVscodeLanguage(uri.fsPath) ?? ''
|
||||
|
||||
return `\
|
||||
ORIGINAL_FILE
|
||||
${tripleTick[0]}${language}
|
||||
${originalCode}
|
||||
${tripleTick[1]}
|
||||
|
||||
CHANGE
|
||||
${tripleTick[0]}
|
||||
${applyStr}
|
||||
${tripleTick[1]}
|
||||
|
||||
INSTRUCTIONS
|
||||
Please finish writing the new file by applying the change to the original file. Return ONLY the completion of the file, without any explanation.
|
||||
`
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const aiRegex_computeReplacementsForFile_systemMessage = `\
|
||||
You are a "search and replace" coding assistant.
|
||||
|
||||
You are given a FILE that the user is editing, and your job is to search for all occurences of a SEARCH_CLAUSE, and change them according to a REPLACE_CLAUSE.
|
||||
|
||||
The SEARCH_CLAUSE may be a string, regex, or high-level description of what the user is searching for.
|
||||
|
||||
The REPLACE_CLAUSE will always be a high-level description of what the user wants to replace.
|
||||
|
||||
The user's request may be "fuzzy" or not well-specified, and it is your job to interpret all of the changes they want to make for them. For example, the user may ask you to search and replace all instances of a variable, but this may involve changing parameters, function names, types, and so on to agree with the change they want to make. Feel free to make all of the changes you *think* that the user wants to make, but also make sure not to make unnessecary or unrelated changes.
|
||||
|
||||
## Instructions
|
||||
|
||||
1. If you do not want to make any changes, you should respond with the word "no".
|
||||
|
||||
2. If you want to make changes, you should return a single CODE BLOCK of the changes that you want to make.
|
||||
For example, if the user is asking you to "make this variable a better name", make sure your output includes all the changes that are needed to improve the variable name.
|
||||
- Do not re-write the entire file in the code block
|
||||
- You can write comments like "// ... existing code" to indicate existing code
|
||||
- Make sure you give enough context in the code block to apply the changes to the correct location in the code`
|
||||
|
||||
|
||||
export const aiRegex_computeReplacementsForFile_userMessage = async ({ searchClause, replaceClause, fileURI, voidFileService }: { searchClause: string, replaceClause: string, fileURI: URI, modelService: IModelService, voidFileService: IVoidFileService }) => {
|
||||
|
||||
// we may want to do this in batches
|
||||
const fileSelection: FileSelection = { type: 'File', fileURI, selectionStr: null, range: null }
|
||||
|
||||
const file = await stringifyFileSelections([fileSelection], voidFileService)
|
||||
|
||||
return `\
|
||||
## FILE
|
||||
${file}
|
||||
|
||||
## SEARCH_CLAUSE
|
||||
Here is what the user is searching for:
|
||||
${searchClause}
|
||||
|
||||
## REPLACE_CLAUSE
|
||||
Here is what the user wants to replace it with:
|
||||
${replaceClause}
|
||||
|
||||
## INSTRUCTIONS
|
||||
Please return the changes you want to make to the file in a codeblock, or return "no" if you do not want to make changes.`
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// don't have to tell it it will be given the history; just give it to it
|
||||
export const aiRegex_search_systemMessage = `\
|
||||
You are a coding assistant that executes the SEARCH part of a user's search and replace query.
|
||||
|
||||
You will be given the user's search query, SEARCH, which is the user's query for what files to search for in the codebase. You may also be given the user's REPLACE query for additional context.
|
||||
|
||||
Output
|
||||
- Regex query
|
||||
- Files to Include (optional)
|
||||
- Files to Exclude? (optional)
|
||||
|
||||
`
|
||||
|
||||
|
||||
|
||||
export const ORIGINAL = `<<<<<<< ORIGINAL`
|
||||
export const DIVIDER = `=======`
|
||||
export const FINAL = `>>>>>>> UPDATED`
|
||||
|
||||
export const searchReplace_systemMessage = `\
|
||||
You are a coding assistant that generates SEARCH/REPLACE code blocks that will be used to edit a file.
|
||||
|
||||
A SEARCH/REPLACE block describes the code before and after a change. Here is the format:
|
||||
${tripleTick[0]}
|
||||
${ORIGINAL}
|
||||
// ... original code goes here
|
||||
${DIVIDER}
|
||||
// ... final code goes here
|
||||
${FINAL}
|
||||
${tripleTick[1]}
|
||||
|
||||
You will be given the original file \`ORIGINAL_FILE\` and a description of a change \`CHANGE\` to make.
|
||||
Output SEARCH/REPLACE blocks to edit the file according to the desired change. You may output multiple SEARCH/REPLACE blocks.
|
||||
|
||||
Directions:
|
||||
1. Your OUTPUT should consist ONLY of SEARCH/REPLACE blocks. Do NOT output any text or explanations before or after this.
|
||||
2. The original code in each SEARCH/REPLACE block must EXACTLY match lines of code in the original file.
|
||||
3. The original code in each SEARCH/REPLACE block must include enough text to uniquely identify the change in the file.
|
||||
4. The original code in each SEARCH/REPLACE block must be disjoint from all other blocks.
|
||||
|
||||
The SEARCH/REPLACE blocks you generate will be applied immediately, and so they **MUST** produce a file that the user can run IMMEDIATELY.
|
||||
- Make sure you add all necessary imports.
|
||||
- Make sure the "final" code is complete and will not result in syntax/lint errors.
|
||||
|
||||
Follow coding conventions of the user (spaces, semilcolons, comments, etc). If the user spaces or formats things a certain way, CONTINUE formatting it that way, even if you prefer otherwise.
|
||||
|
||||
## EXAMPLE 1
|
||||
ORIGINAL_FILE
|
||||
${tripleTick[0]}
|
||||
let w = 5
|
||||
let x = 6
|
||||
let y = 7
|
||||
let z = 8
|
||||
${tripleTick[1]}
|
||||
|
||||
CHANGE
|
||||
Make x equal to 6.5, not 6.
|
||||
${tripleTick[0]}
|
||||
// ... existing code
|
||||
let x = 6.5
|
||||
// ... existing code
|
||||
${tripleTick[1]}
|
||||
|
||||
|
||||
## ACCEPTED OUTPUT
|
||||
${tripleTick[0]}
|
||||
${ORIGINAL}
|
||||
let x = 6
|
||||
${DIVIDER}
|
||||
let x = 6.5
|
||||
${FINAL}
|
||||
${tripleTick[1]}
|
||||
`
|
||||
|
||||
export const searchReplace_userMessage = ({ originalCode, applyStr }: { originalCode: string, applyStr: string }) => `\
|
||||
ORIGINAL_FILE
|
||||
${originalCode}
|
||||
|
||||
CHANGE
|
||||
${applyStr}
|
||||
|
||||
INSTRUCTIONS
|
||||
Please output SEARCH/REPLACE blocks to make the change. Return ONLY your suggested SEARCH/REPLACE blocks, without any explanation.
|
||||
`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const voidPrefixAndSuffix = ({ fullFileStr, startLine, endLine }: { fullFileStr: string, startLine: number, endLine: number }) => {
|
||||
|
||||
const fullFileLines = fullFileStr.split('\n')
|
||||
|
||||
// we can optimize this later
|
||||
const MAX_PREFIX_SUFFIX_CHARS = 20_000
|
||||
/*
|
||||
|
||||
a
|
||||
a
|
||||
a <-- final i (prefix = a\na\n)
|
||||
a
|
||||
|b <-- startLine-1 (middle = b\nc\nd\n) <-- initial i (moves up)
|
||||
c
|
||||
d| <-- endLine-1 <-- initial j (moves down)
|
||||
e
|
||||
e <-- final j (suffix = e\ne\n)
|
||||
e
|
||||
e
|
||||
*/
|
||||
|
||||
let prefix = ''
|
||||
let i = startLine - 1 // 0-indexed exclusive
|
||||
// we'll include fullFileLines[i...(startLine-1)-1].join('\n') in the prefix.
|
||||
while (i !== 0) {
|
||||
const newLine = fullFileLines[i - 1]
|
||||
if (newLine.length + 1 + prefix.length <= MAX_PREFIX_SUFFIX_CHARS) { // +1 to include the \n
|
||||
prefix = `${newLine}\n${prefix}`
|
||||
i -= 1
|
||||
}
|
||||
else break
|
||||
}
|
||||
|
||||
let suffix = ''
|
||||
let j = endLine - 1
|
||||
while (j !== fullFileLines.length - 1) {
|
||||
const newLine = fullFileLines[j + 1]
|
||||
if (newLine.length + 1 + suffix.length <= MAX_PREFIX_SUFFIX_CHARS) { // +1 to include the \n
|
||||
suffix = `${suffix}\n${newLine}`
|
||||
j += 1
|
||||
}
|
||||
else break
|
||||
}
|
||||
|
||||
return { prefix, suffix }
|
||||
|
||||
}
|
||||
|
||||
|
||||
export type QuickEditFimTagsType = {
|
||||
preTag: string,
|
||||
sufTag: string,
|
||||
midTag: string
|
||||
}
|
||||
export const defaultQuickEditFimTags: QuickEditFimTagsType = {
|
||||
preTag: 'ABOVE',
|
||||
sufTag: 'BELOW',
|
||||
midTag: 'SELECTION',
|
||||
}
|
||||
|
||||
// this should probably be longer
|
||||
export const ctrlKStream_systemMessage = ({ quickEditFIMTags: { preTag, midTag, sufTag } }: { quickEditFIMTags: QuickEditFimTagsType }) => {
|
||||
return `\
|
||||
You are a FIM (fill-in-the-middle) coding assistant. Your task is to fill in the middle SELECTION marked by <${midTag}> tags.
|
||||
|
||||
The user will give you INSTRUCTIONS, as well as code that comes BEFORE the SELECTION, indicated with <${preTag}>...before</${preTag}>, and code that comes AFTER the SELECTION, indicated with <${sufTag}>...after</${sufTag}>.
|
||||
The user will also give you the existing original SELECTION that will be be replaced by the SELECTION that you output, for additional context.
|
||||
|
||||
Instructions:
|
||||
1. Your OUTPUT should be a SINGLE PIECE OF CODE of the form <${midTag}>...new_code</${midTag}>. Do NOT output any text or explanations before or after this.
|
||||
2. You may ONLY CHANGE the original SELECTION, and NOT the content in the <${preTag}>...</${preTag}> or <${sufTag}>...</${sufTag}> tags.
|
||||
3. Make sure all brackets in the new selection are balanced the same as in the original selection.
|
||||
4. Be careful not to duplicate or remove variables, comments, or other syntax by mistake.
|
||||
`
|
||||
}
|
||||
|
||||
export const ctrlKStream_userMessage = ({ selection, prefix, suffix, instructions, fimTags, isOllamaFIM, language }: {
|
||||
selection: string, prefix: string, suffix: string, instructions: string, fimTags: QuickEditFimTagsType, language: string,
|
||||
isOllamaFIM: false, // we require this be false for clarity
|
||||
}) => {
|
||||
const { preTag, sufTag, midTag } = fimTags
|
||||
|
||||
// prompt the model artifically on how to do FIM
|
||||
// const preTag = 'BEFORE'
|
||||
// const sufTag = 'AFTER'
|
||||
// const midTag = 'SELECTION'
|
||||
return `\
|
||||
|
||||
CURRENT SELECTION
|
||||
${tripleTick[0]}${language}
|
||||
<${midTag}>${selection}</${midTag}>
|
||||
${tripleTick[1]}
|
||||
|
||||
INSTRUCTIONS
|
||||
${instructions}
|
||||
|
||||
<${preTag}>${prefix}</${preTag}>
|
||||
<${sufTag}>${suffix}</${sufTag}>
|
||||
|
||||
Return only the completion block of code (of the form ${tripleTick[0]}${language}
|
||||
<${midTag}>...new code</${midTag}>
|
||||
${tripleTick[1]}).`
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Action2, registerAction2 } from '../../../../platform/actions/common/ac
|
||||
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
|
||||
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
||||
import { IEditCodeService } from './editCodeService.js';
|
||||
import { IEditCodeService } from './editCodeServiceInterface.js';
|
||||
import { roundRangeToLines } from './sidebarActions.js';
|
||||
import { VOID_CTRL_K_ACTION_ID } from './actionIDs.js';
|
||||
import { localize2 } from '../../../../nls.js';
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { QuickEdit } from './quickEditActions.js';
|
||||
|
||||
|
||||
|
||||
// service that manages state
|
||||
export type VoidQuickEditState = {
|
||||
quickEditsOfDocument: { [uri: string]: QuickEdit }
|
||||
}
|
||||
|
||||
export interface IQuickEditStateService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly state: VoidQuickEditState; // readonly to the user
|
||||
setState(newState: Partial<VoidQuickEditState>): void;
|
||||
onDidChangeState: Event<void>;
|
||||
|
||||
onDidFocusChat: Event<void>;
|
||||
onDidBlurChat: Event<void>;
|
||||
fireFocusChat(): void;
|
||||
fireBlurChat(): void;
|
||||
|
||||
}
|
||||
|
||||
export const IQuickEditStateService = createDecorator<IQuickEditStateService>('voidQuickEditStateService');
|
||||
class VoidQuickEditStateService extends Disposable implements IQuickEditStateService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
static readonly ID = 'voidQuickEditStateService';
|
||||
|
||||
private readonly _onDidChangeState = new Emitter<void>();
|
||||
readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
|
||||
|
||||
private readonly _onFocusChat = new Emitter<void>();
|
||||
readonly onDidFocusChat: Event<void> = this._onFocusChat.event;
|
||||
|
||||
private readonly _onBlurChat = new Emitter<void>();
|
||||
readonly onDidBlurChat: Event<void> = this._onBlurChat.event;
|
||||
|
||||
|
||||
// state
|
||||
state: VoidQuickEditState
|
||||
|
||||
constructor(
|
||||
) {
|
||||
super()
|
||||
|
||||
// initial state
|
||||
this.state = { quickEditsOfDocument: {} }
|
||||
}
|
||||
|
||||
|
||||
setState(newState: Partial<VoidQuickEditState>) {
|
||||
|
||||
this.state = { ...this.state, ...newState }
|
||||
this._onDidChangeState.fire()
|
||||
}
|
||||
|
||||
fireFocusChat() {
|
||||
this._onFocusChat.fire()
|
||||
}
|
||||
|
||||
fireBlurChat() {
|
||||
this._onBlurChat.fire()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IQuickEditStateService, VoidQuickEditStateService, InstantiationType.Eager);
|
||||
@@ -74,7 +74,7 @@ function saveStylesFile() {
|
||||
} catch (err) {
|
||||
console.error('[scope-tailwind] Error saving styles.css:', err);
|
||||
}
|
||||
}, 3000);
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
+263
-84
@@ -1,9 +1,16 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { useAccessor, useURIStreamState, useSettingsState } from '../util/services.js'
|
||||
import { useRefState } from '../util/helpers.js'
|
||||
import { useAccessor, useCommandBarState, useCommandBarURIListener, useSettingsState } from '../util/services.js'
|
||||
import { usePromise, useRefState } from '../util/helpers.js'
|
||||
import { isFeatureNameDisabled } from '../../../../common/voidSettingsTypes.js'
|
||||
import { URI } from '../../../../../../../base/common/uri.js'
|
||||
import { IEditCodeService, URIStreamState } from '../../../editCodeService.js'
|
||||
import { FileSymlink, LucideIcon, RotateCw, Terminal } from 'lucide-react'
|
||||
import { Check, X, Square, Copy, Play, } from 'lucide-react'
|
||||
import { getBasename, ListableToolItem, ToolChildrenWrapper } from '../sidebar-tsx/SidebarChat.js'
|
||||
|
||||
enum CopyButtonText {
|
||||
Idle = 'Copy',
|
||||
@@ -11,9 +18,59 @@ enum CopyButtonText {
|
||||
Error = 'Could not copy',
|
||||
}
|
||||
|
||||
const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!'
|
||||
|
||||
const CopyButton = ({ codeStr }: { codeStr: string }) => {
|
||||
type IconButtonProps = {
|
||||
onClick: () => void;
|
||||
Icon: LucideIcon
|
||||
disabled?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const IconShell1 = ({ onClick, Icon, disabled, className }: IconButtonProps) => (
|
||||
<button
|
||||
disabled={disabled}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onClick?.();
|
||||
}}
|
||||
className={`
|
||||
size-[22px]
|
||||
p-[4px]
|
||||
flex items-center justify-center
|
||||
text-sm bg-void-bg-3 text-void-fg-1
|
||||
hover:brightness-110
|
||||
border border-void-border-1 rounded
|
||||
disabled:opacity-50 disabled:cursor-not-allowed
|
||||
${className}
|
||||
`}
|
||||
>
|
||||
<Icon />
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
// export const IconShell2 = ({ onClick, title, Icon, disabled, className }: IconButtonProps) => (
|
||||
// <button
|
||||
// title={title}
|
||||
// disabled={disabled}
|
||||
// onClick={onClick}
|
||||
// className={`
|
||||
// size-[24px]
|
||||
// flex items-center justify-center
|
||||
// text-sm
|
||||
// hover:opacity-80
|
||||
// disabled:opacity-50 disabled:cursor-not-allowed
|
||||
// ${className}
|
||||
// `}
|
||||
// >
|
||||
// <Icon size={16} />
|
||||
// </button>
|
||||
// )
|
||||
|
||||
const COPY_FEEDBACK_TIMEOUT = 1500 // amount of time to say 'Copied!'
|
||||
|
||||
export const CopyButton = ({ codeStr }: { codeStr: string }) => {
|
||||
const accessor = useAccessor()
|
||||
|
||||
const metricsService = accessor.get('IMetricsService')
|
||||
@@ -27,7 +84,6 @@ const CopyButton = ({ codeStr }: { codeStr: string }) => {
|
||||
}, COPY_FEEDBACK_TIMEOUT)
|
||||
}, [copyButtonText])
|
||||
|
||||
|
||||
const onCopy = useCallback(() => {
|
||||
clipboardService.writeText(codeStr)
|
||||
.then(() => { setCopyButtonText(CopyButtonText.Copied) })
|
||||
@@ -35,122 +91,245 @@ const CopyButton = ({ codeStr }: { codeStr: string }) => {
|
||||
metricsService.capture('Copy Code', { length: codeStr.length }) // capture the length only
|
||||
}, [metricsService, clipboardService, codeStr, setCopyButtonText])
|
||||
|
||||
const isSingleLine = !codeStr.includes('\n')
|
||||
|
||||
return <button
|
||||
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-1 text-void-fg-1 hover:brightness-110 border border-vscode-input-border rounded`}
|
||||
return <IconShell1
|
||||
Icon={copyButtonText === CopyButtonText.Copied ? Check : copyButtonText === CopyButtonText.Error ? X : Copy}
|
||||
onClick={onCopy}
|
||||
>
|
||||
{copyButtonText}
|
||||
</button>
|
||||
/>
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export const JumpToFileButton = ({ uri }: { uri: URI | 'current' }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
|
||||
const jumpToFileButton = uri !== 'current' && (
|
||||
<IconShell1
|
||||
Icon={FileSymlink}
|
||||
onClick={() => {
|
||||
commandService.executeCommand('vscode.open', uri, { preview: true })
|
||||
}}
|
||||
/>
|
||||
)
|
||||
return jumpToFileButton
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const JumpToTerminalButton = ({ onClick }: { onClick: () => void }) => {
|
||||
return (
|
||||
<IconShell1
|
||||
Icon={Terminal}
|
||||
onClick={onClick}
|
||||
className="text-void-fg-1"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// state persisted for duration of react only
|
||||
// TODO change this to use type `ChatThreads.applyBoxState[applyBoxId]`
|
||||
const applyingURIOfApplyBoxIdRef: { current: { [applyBoxId: string]: URI | undefined } } = { current: {} }
|
||||
|
||||
const getUriBeingApplied = (applyBoxId: string) => {
|
||||
return applyingURIOfApplyBoxIdRef.current[applyBoxId] ?? null
|
||||
}
|
||||
|
||||
|
||||
export const ApplyBlockHoverButtons = ({ codeStr, applyBoxId }: { codeStr: string, applyBoxId: string }) => {
|
||||
|
||||
console.log('applyboxid', applyBoxId, applyingURIOfApplyBoxIdRef)
|
||||
export const useApplyButtonState = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI | 'current' }) => {
|
||||
|
||||
const settingsState = useSettingsState()
|
||||
const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) || !applyBoxId
|
||||
|
||||
const accessor = useAccessor()
|
||||
const editCodeService = accessor.get('IEditCodeService')
|
||||
const metricsService = accessor.get('IMetricsService')
|
||||
const voidCommandBarService = accessor.get('IVoidCommandBarService')
|
||||
|
||||
const [_, rerender] = useState(0)
|
||||
|
||||
const applyingUri = useCallback(() => applyingURIOfApplyBoxIdRef.current[applyBoxId] ?? null, [applyBoxId])
|
||||
const streamState = useCallback(() => editCodeService.getURIStreamState({ uri: applyingUri() }), [editCodeService, applyingUri])
|
||||
const getStreamState = useCallback(() => {
|
||||
const uri = getUriBeingApplied(applyBoxId)
|
||||
if (!uri) return 'idle-no-changes'
|
||||
return voidCommandBarService.getStreamState(uri)
|
||||
}, [voidCommandBarService, applyBoxId])
|
||||
|
||||
// listen for stream updates
|
||||
useURIStreamState(
|
||||
useCallback((uri, newStreamState) => {
|
||||
const shouldUpdate = applyingUri()?.fsPath !== uri.fsPath
|
||||
if (shouldUpdate) return
|
||||
// listen for stream updates on this box
|
||||
useCommandBarURIListener(useCallback((uri_) => {
|
||||
const shouldUpdate = (
|
||||
getUriBeingApplied(applyBoxId)?.fsPath === uri_.fsPath
|
||||
|| (uri !== 'current' && uri.fsPath === uri_.fsPath)
|
||||
)
|
||||
if (shouldUpdate) {
|
||||
rerender(c => c + 1)
|
||||
}, [applyBoxId, editCodeService, applyingUri])
|
||||
)
|
||||
console.log('rerendering....')
|
||||
}
|
||||
}, [applyBoxId, applyBoxId, uri]))
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
const currStreamState = getStreamState()
|
||||
|
||||
return {
|
||||
getStreamState,
|
||||
isDisabled,
|
||||
currStreamState,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const StatusIndicatorHTML = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI | 'current' }) => {
|
||||
const { currStreamState } = useApplyButtonState({ applyBoxId, uri })
|
||||
|
||||
return <div className='flex flex-row items-center min-h-4 max-h-4 min-w-4 max-w-4'>
|
||||
<div
|
||||
className={` size-1.5 rounded-full border
|
||||
${currStreamState === 'idle-no-changes' ? 'bg-void-bg-3 border-void-border-1' :
|
||||
currStreamState === 'streaming' ? 'bg-orange-500 border-orange-500 shadow-[0_0_4px_0px_rgba(234,88,12,0.6)]' :
|
||||
currStreamState === 'idle-has-changes' ? 'bg-green-500 border-green-500 shadow-[0_0_4px_0px_rgba(22,163,74,0.6)]' :
|
||||
'bg-void-border-1 border-void-border-1'
|
||||
}`
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
export const ApplyButtonsHTML = ({ codeStr, applyBoxId, reapplyIcon, uri }: { codeStr: string, applyBoxId: string, reapplyIcon: boolean, uri: URI | 'current' }) => {
|
||||
const accessor = useAccessor()
|
||||
const editCodeService = accessor.get('IEditCodeService')
|
||||
const metricsService = accessor.get('IMetricsService')
|
||||
|
||||
const {
|
||||
currStreamState,
|
||||
isDisabled,
|
||||
getStreamState,
|
||||
} = useApplyButtonState({ applyBoxId, uri })
|
||||
|
||||
const onClickSubmit = useCallback(async () => {
|
||||
if (isDisabled) return
|
||||
if (streamState() === 'streaming') return
|
||||
const newApplyingUri = editCodeService.startApplying({
|
||||
if (getStreamState() === 'streaming') return
|
||||
const opts = {
|
||||
from: 'ClickApply',
|
||||
type: 'searchReplace',
|
||||
applyStr: codeStr,
|
||||
})
|
||||
uri: uri,
|
||||
startBehavior: 'reject-conflicts',
|
||||
} as const
|
||||
|
||||
await editCodeService.callBeforeStartApplying(opts)
|
||||
const [newApplyingUri, applyDonePromise] = editCodeService.startApplying(opts) ?? []
|
||||
|
||||
// catch any errors by interrupting the stream
|
||||
applyDonePromise?.catch(e => { if (newApplyingUri) editCodeService.interruptURIStreaming({ uri: newApplyingUri }) })
|
||||
|
||||
applyingURIOfApplyBoxIdRef.current[applyBoxId] = newApplyingUri ?? undefined
|
||||
rerender(c => c + 1)
|
||||
|
||||
// rerender(c => c + 1)
|
||||
metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only
|
||||
}, [isDisabled, streamState, editCodeService, codeStr, applyBoxId, metricsService])
|
||||
}, [isDisabled, getStreamState, editCodeService, codeStr, uri, applyBoxId, metricsService])
|
||||
|
||||
|
||||
const onInterrupt = useCallback(() => {
|
||||
if (streamState() !== 'streaming') return
|
||||
const uri = applyingUri()
|
||||
if (getStreamState() !== 'streaming') return
|
||||
const uri = getUriBeingApplied(applyBoxId)
|
||||
if (!uri) return
|
||||
|
||||
editCodeService.interruptURIStreaming({ uri })
|
||||
metricsService.capture('Stop Apply', {})
|
||||
}, [streamState, applyingUri, editCodeService, metricsService])
|
||||
}, [getStreamState, applyBoxId, editCodeService, metricsService])
|
||||
|
||||
const onAccept = useCallback(() => {
|
||||
const uri = getUriBeingApplied(applyBoxId)
|
||||
if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false })
|
||||
}, [applyBoxId, editCodeService])
|
||||
|
||||
const onReject = useCallback(() => {
|
||||
const uri = getUriBeingApplied(applyBoxId)
|
||||
if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false })
|
||||
}, [applyBoxId, editCodeService])
|
||||
|
||||
// const onReapply = useCallback(() => {
|
||||
// onReject()
|
||||
// onClickSubmit()
|
||||
// }, [onReject, onClickSubmit])
|
||||
|
||||
|
||||
const isSingleLine = !codeStr.includes('\n')
|
||||
if (currStreamState === 'streaming') {
|
||||
return <IconShell1 Icon={Square} onClick={onInterrupt} />
|
||||
}
|
||||
|
||||
const applyButton = <button
|
||||
// btn btn-secondary btn-sm border text-sm border-vscode-input-border rounded
|
||||
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-1 text-void-fg-1 hover:brightness-110 border border-vscode-input-border rounded`}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
if (currStreamState === 'idle-no-changes') {
|
||||
return <IconShell1 Icon={reapplyIcon ? RotateCw : Play} onClick={onClickSubmit} />
|
||||
}
|
||||
|
||||
const stopButton = <button
|
||||
// btn btn-secondary btn-sm border text-sm border-vscode-input-border rounded
|
||||
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-1 text-void-fg-1 hover:brightness-110 border border-vscode-input-border rounded`}
|
||||
onClick={onInterrupt}
|
||||
>
|
||||
Stop
|
||||
</button>
|
||||
if (currStreamState === 'idle-has-changes') {
|
||||
return <>
|
||||
{/* <IconShell1
|
||||
Icon={RotateCw}
|
||||
onClick={onReapply}
|
||||
/> */}
|
||||
<IconShell1
|
||||
Icon={X}
|
||||
onClick={onReject}
|
||||
className="text-red-600"
|
||||
/>
|
||||
<IconShell1
|
||||
Icon={Check}
|
||||
onClick={onAccept}
|
||||
className="text-green-600"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const BlockCodeApplyWrapper = ({
|
||||
children,
|
||||
initValue,
|
||||
applyBoxId,
|
||||
language,
|
||||
canApply,
|
||||
uri,
|
||||
}: {
|
||||
initValue: string;
|
||||
children: React.ReactNode;
|
||||
applyBoxId: string;
|
||||
canApply: boolean;
|
||||
language: string;
|
||||
uri: URI | 'current',
|
||||
}) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const { currStreamState } = useApplyButtonState({ applyBoxId, uri })
|
||||
|
||||
|
||||
const name = uri !== 'current' ?
|
||||
<ListableToolItem
|
||||
name={<span className='not-italic'>{getBasename(uri.fsPath)}</span>}
|
||||
isSmall={true}
|
||||
showDot={false}
|
||||
onClick={() => { commandService.executeCommand('vscode.open', uri, { preview: true }) }}
|
||||
/>
|
||||
: <span>{language}</span>
|
||||
|
||||
|
||||
return <div className='border border-void-border-3 rounded overflow-hidden bg-void-bg-3 my-1'>
|
||||
{/* header */}
|
||||
<div className=" select-none flex justify-between items-center py-1 px-2 border-b border-void-border-3 cursor-default">
|
||||
<div className="flex items-center">
|
||||
<StatusIndicatorHTML uri={uri} applyBoxId={applyBoxId} />
|
||||
<span className="text-[13px] font-light text-void-fg-3">
|
||||
{name}
|
||||
</span>
|
||||
</div>
|
||||
<div className={`${canApply ? '' : 'hidden'} flex items-center gap-1`}>
|
||||
<JumpToFileButton uri={uri} />
|
||||
{currStreamState === 'idle-no-changes' && <CopyButton codeStr={initValue} />}
|
||||
<ApplyButtonsHTML uri={uri} applyBoxId={applyBoxId} codeStr={initValue} reapplyIcon={false} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* contents */}
|
||||
<ToolChildrenWrapper>
|
||||
{children}
|
||||
</ToolChildrenWrapper>
|
||||
</div>
|
||||
|
||||
const acceptRejectButtons = <>
|
||||
<button
|
||||
// btn btn-secondary btn-sm border text-sm border-vscode-input-border rounded
|
||||
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-1 text-void-fg-1 hover:brightness-110 border border-vscode-input-border rounded`}
|
||||
onClick={() => {
|
||||
const uri = applyingUri()
|
||||
if (uri) editCodeService.removeDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false })
|
||||
}}
|
||||
>
|
||||
Accept
|
||||
</button>
|
||||
<button
|
||||
// btn btn-secondary btn-sm border text-sm border-vscode-input-border rounded
|
||||
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-1 text-void-fg-1 hover:brightness-110 border border-vscode-input-border rounded`}
|
||||
onClick={() => {
|
||||
const uri = applyingUri()
|
||||
if (uri) editCodeService.removeDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false })
|
||||
}}
|
||||
>
|
||||
Reject
|
||||
</button>
|
||||
</>
|
||||
|
||||
console.log('streamStateRef.current', streamState())
|
||||
|
||||
const currStreamState = streamState()
|
||||
return <>
|
||||
{currStreamState !== 'streaming' && <CopyButton codeStr={codeStr} />}
|
||||
{currStreamState === 'idle' && !isDisabled && applyButton}
|
||||
{currStreamState === 'streaming' && stopButton}
|
||||
{currStreamState === 'acceptRejectAll' && acceptRejectButtons}
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { VoidCodeEditor, VoidCodeEditorProps } from '../util/inputs.js';
|
||||
|
||||
|
||||
export const BlockCode = ({ buttonsOnHover, ...codeEditorProps }: { buttonsOnHover?: React.ReactNode } & VoidCodeEditorProps) => {
|
||||
const isSingleLine = !codeEditorProps.initValue.includes('\n')
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative group w-full overflow-hidden my-4">
|
||||
{buttonsOnHover === null ? null : (
|
||||
<div className={`z-[1] absolute top-0 right-0 opacity-0 group-hover:opacity-100 duration-200 ${isSingleLine ? 'h-full flex items-center' : ''}`}>
|
||||
<div className={`flex space-x-1 ${isSingleLine ? 'pr-2' : 'p-2'}`}>
|
||||
{buttonsOnHover}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<VoidCodeEditor {...codeEditorProps} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -3,86 +3,199 @@
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import React, { JSX } from 'react'
|
||||
import React, { JSX, useMemo, useState } from 'react'
|
||||
import { marked, MarkedToken, Token } from 'marked'
|
||||
import { BlockCode } from './BlockCode.js'
|
||||
import { ChatMessageLocation, } from '../../../aiRegexService.js'
|
||||
import { nameToVscodeLanguage } from '../../../helpers/detectLanguage.js'
|
||||
import { ApplyBlockHoverButtons } from './ApplyBlockHoverButtons.js'
|
||||
|
||||
import { convertToVscodeLang, detectLanguage } from '../../../../common/helpers/languageHelpers.js'
|
||||
import { BlockCodeApplyWrapper } from './ApplyBlockHoverButtons.js'
|
||||
import { useAccessor } from '../util/services.js'
|
||||
import { ScrollType } from '../../../../../../../editor/common/editorCommon.js'
|
||||
import { URI } from '../../../../../../../base/common/uri.js'
|
||||
import { isAbsolute } from '../../../../../../../base/common/path.js'
|
||||
import { separateOutFirstLine } from '../../../../common/helpers/util.js'
|
||||
import { BlockCode } from '../util/inputs.js'
|
||||
|
||||
|
||||
export type ChatMessageLocation = {
|
||||
threadId: string;
|
||||
messageIdx: number;
|
||||
}
|
||||
|
||||
type ApplyBoxLocation = ChatMessageLocation & { tokenIdx: string }
|
||||
|
||||
const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) => {
|
||||
export const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) => {
|
||||
return `${threadId}-${messageIdx}-${tokenIdx}`
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const CodeSpan = ({ children, className }: { children: React.ReactNode, className?: string }) => {
|
||||
return <code className={`
|
||||
bg-void-bg-1
|
||||
px-1
|
||||
rounded-sm
|
||||
font-mono font-medium
|
||||
break-all
|
||||
${className}
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
</code>
|
||||
function isValidUri(s: string): boolean {
|
||||
return s.length > 5 && isAbsolute(s) && !s.includes('//') && !s.includes('/*') // common case that is a false positive is comments like //
|
||||
}
|
||||
|
||||
const RenderToken = ({ token, nested, noSpace, chatMessageLocation, tokenIdx }: { token: Token | string, nested?: boolean, noSpace?: boolean, chatMessageLocation?: ChatMessageLocation, tokenIdx: string }): JSX.Element => {
|
||||
const Codespan = ({ text, className, onClick }: { text: string, className?: string, onClick?: () => void }) => {
|
||||
|
||||
// TODO compute this once for efficiency. we should use `labels.ts/shorten` to display duplicates properly
|
||||
|
||||
return <code
|
||||
className={`font-mono font-medium rounded-sm bg-void-bg-1 px-1 ${className}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{text}
|
||||
</code>
|
||||
|
||||
}
|
||||
|
||||
const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string, rawText: string, chatMessageLocation: ChatMessageLocation }) => {
|
||||
|
||||
const accessor = useAccessor()
|
||||
|
||||
const chatThreadService = accessor.get('IChatThreadService')
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const editorService = accessor.get('ICodeEditorService')
|
||||
|
||||
const { messageIdx, threadId } = chatMessageLocation
|
||||
|
||||
const [didComputeCodespanLink, setDidComputeCodespanLink] = useState<boolean>(false)
|
||||
|
||||
let link = undefined
|
||||
if (rawText.endsWith("`")) { // if codespan was completed
|
||||
|
||||
// get link from cache
|
||||
link = chatThreadService.getCodespanLink({ codespanStr: text, messageIdx, threadId })
|
||||
|
||||
if (link === undefined) {
|
||||
// if no link, generate link and add to cache
|
||||
(chatThreadService.generateCodespanLink({ codespanStr: text, threadId })
|
||||
.then(link => {
|
||||
chatThreadService.addCodespanLink({ newLinkText: text, newLinkLocation: link, messageIdx, threadId })
|
||||
setDidComputeCodespanLink(true) // rerender
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const onClick = () => {
|
||||
|
||||
if (!link) return;
|
||||
const selection = link.selection
|
||||
|
||||
// open the file
|
||||
commandService.executeCommand('vscode.open', link.uri).then(() => {
|
||||
|
||||
// select the text
|
||||
setTimeout(() => {
|
||||
if (!selection) return;
|
||||
|
||||
const editor = editorService.getActiveCodeEditor()
|
||||
if (!editor) return;
|
||||
|
||||
editor.setSelection(selection)
|
||||
editor.revealRange(selection, ScrollType.Immediate)
|
||||
|
||||
}, 50) // needed when document was just opened and needs to initialize
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return <Codespan
|
||||
// text={link?.displayText || text}
|
||||
text={link?.displayText || text}
|
||||
onClick={onClick}
|
||||
className={link ? 'underline hover:brightness-90 transition-all duration-200 cursor-pointer' : ''}
|
||||
/>
|
||||
}
|
||||
|
||||
|
||||
export type RenderTokenOptions = { isApplyEnabled?: boolean, isLinkDetectionEnabled?: boolean }
|
||||
const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ...options }: { token: Token | string, inPTag?: boolean, codeURI?: URI, chatMessageLocation?: ChatMessageLocation, tokenIdx: string, } & RenderTokenOptions): React.ReactNode => {
|
||||
const accessor = useAccessor()
|
||||
const languageService = accessor.get('ILanguageService')
|
||||
|
||||
// deal with built-in tokens first (assume marked token)
|
||||
const t = token as MarkedToken
|
||||
|
||||
if (t.raw.trim() === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (t.type === "space") {
|
||||
return <span>{t.raw}</span>
|
||||
}
|
||||
|
||||
if (t.type === "code") {
|
||||
const [firstLine, remainingContents] = separateOutFirstLine(t.text)
|
||||
const firstLineIsURI = isValidUri(firstLine) && !codeURI
|
||||
const contents = firstLineIsURI ? (remainingContents?.trimStart() || '') : t.text // exclude first-line URI from contents
|
||||
|
||||
const applyBoxId = chatMessageLocation ? getApplyBoxId({
|
||||
threadId: chatMessageLocation.threadId,
|
||||
messageIdx: chatMessageLocation.messageIdx,
|
||||
tokenIdx: tokenIdx,
|
||||
}) : null
|
||||
if (!contents) return null
|
||||
|
||||
// figure out langauge and URI
|
||||
let uri: URI | null
|
||||
let language: string
|
||||
if (codeURI) {
|
||||
uri = codeURI
|
||||
}
|
||||
else if (firstLineIsURI) { // get lang from the uri in the first line of the markdown
|
||||
uri = URI.file(firstLine)
|
||||
}
|
||||
else {
|
||||
uri = null
|
||||
}
|
||||
|
||||
if (t.lang) { // a language was provided. empty string is common so check truthy, not just undefined
|
||||
language = convertToVscodeLang(languageService, t.lang) // convert markdown language to language that vscode recognizes (eg markdown doesn't know bash but it does know shell)
|
||||
}
|
||||
else { // no language provided - fallback - get lang from the uri and contents
|
||||
language = detectLanguage(languageService, { uri, fileContents: contents })
|
||||
}
|
||||
|
||||
if (options.isApplyEnabled && chatMessageLocation) {
|
||||
const isCodeblockClosed = t.raw.trimEnd().endsWith('```') // user should only be able to Apply when the code has been closed (t.raw ends with "```")
|
||||
|
||||
const applyBoxId = getApplyBoxId({
|
||||
threadId: chatMessageLocation.threadId,
|
||||
messageIdx: chatMessageLocation.messageIdx,
|
||||
tokenIdx: tokenIdx,
|
||||
})
|
||||
return <BlockCodeApplyWrapper
|
||||
canApply={isCodeblockClosed}
|
||||
applyBoxId={applyBoxId}
|
||||
initValue={contents}
|
||||
language={language}
|
||||
uri={uri || 'current'}
|
||||
>
|
||||
<BlockCode
|
||||
initValue={contents}
|
||||
language={language}
|
||||
/>
|
||||
</BlockCodeApplyWrapper>
|
||||
}
|
||||
|
||||
return <BlockCode
|
||||
initValue={t.text}
|
||||
language={t.lang === undefined ? undefined : nameToVscodeLanguage[t.lang]}
|
||||
buttonsOnHover={applyBoxId && <ApplyBlockHoverButtons applyBoxId={applyBoxId} codeStr={t.text} />}
|
||||
initValue={contents}
|
||||
language={language}
|
||||
/>
|
||||
}
|
||||
|
||||
if (t.type === "heading") {
|
||||
|
||||
const HeadingTag = `h${t.depth}` as keyof JSX.IntrinsicElements
|
||||
const headingClasses: { [h: string]: string } = {
|
||||
h1: "text-4xl font-semibold mt-6 mb-4 pb-2 border-b border-void-bg-2",
|
||||
h2: "text-3xl font-semibold mt-6 mb-4 pb-2 border-b border-void-bg-2",
|
||||
h3: "text-2xl font-semibold mt-6 mb-4",
|
||||
h4: "text-xl font-semibold mt-6 mb-4",
|
||||
h5: "text-lg font-semibold mt-6 mb-4",
|
||||
h6: "text-base font-semibold mt-6 mb-4 text-gray-600"
|
||||
}
|
||||
return <HeadingTag className={headingClasses[HeadingTag]}>{t.text}</HeadingTag>
|
||||
|
||||
return <HeadingTag>
|
||||
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={t.text} inPTag={true} codeURI={codeURI} {...options} />
|
||||
</HeadingTag>
|
||||
}
|
||||
|
||||
if (t.type === "table") {
|
||||
return (
|
||||
<div className={`${noSpace ? '' : 'my-4'} overflow-x-auto`}>
|
||||
<table className="min-w-full border border-void-bg-2">
|
||||
<div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr className="bg-void-bg-1">
|
||||
<tr>
|
||||
{t.header.map((cell: any, index: number) => (
|
||||
<th
|
||||
key={index}
|
||||
className="px-4 py-2 border border-void-bg-2 font-semibold"
|
||||
style={{ textAlign: t.align[index] || "left" }}
|
||||
>
|
||||
<th key={index}>
|
||||
{cell.raw}
|
||||
</th>
|
||||
))}
|
||||
@@ -90,13 +203,9 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocation, tokenIdx }:
|
||||
</thead>
|
||||
<tbody>
|
||||
{t.rows.map((row: any[], rowIndex: number) => (
|
||||
<tr key={rowIndex} className={rowIndex % 2 === 0 ? 'bg-white' : 'bg-void-bg-1'}>
|
||||
<tr key={rowIndex}>
|
||||
{row.map((cell: any, cellIndex: number) => (
|
||||
<td
|
||||
key={cellIndex}
|
||||
className="px-4 py-2 border border-void-bg-2"
|
||||
style={{ textAlign: t.align[cellIndex] || "left" }}
|
||||
>
|
||||
<td key={cellIndex} >
|
||||
{cell.raw}
|
||||
</td>
|
||||
))}
|
||||
@@ -106,74 +215,99 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocation, tokenIdx }:
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
// return (
|
||||
// <div>
|
||||
// <table className={"min-w-full border border-void-bg-2"}>
|
||||
// <thead>
|
||||
// <tr className="bg-void-bg-1">
|
||||
// {t.header.map((cell: any, index: number) => (
|
||||
// <th
|
||||
// key={index}
|
||||
// className="px-4 py-2 border border-void-bg-2 font-semibold"
|
||||
// style={{ textAlign: t.align[index] || "left" }}
|
||||
// >
|
||||
// {cell.raw}
|
||||
// </th>
|
||||
// ))}
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// {t.rows.map((row: any[], rowIndex: number) => (
|
||||
// <tr key={rowIndex} className={rowIndex % 2 === 0 ? 'bg-white' : 'bg-void-bg-1'}>
|
||||
// {row.map((cell: any, cellIndex: number) => (
|
||||
// <td
|
||||
// key={cellIndex}
|
||||
// className={"px-4 py-2 border border-void-bg-2"}
|
||||
// style={{ textAlign: t.align[cellIndex] || "left" }}
|
||||
// >
|
||||
// {cell.raw}
|
||||
// </td>
|
||||
// ))}
|
||||
// </tr>
|
||||
// ))}
|
||||
// </tbody>
|
||||
// </table>
|
||||
// </div>
|
||||
// )
|
||||
}
|
||||
|
||||
if (t.type === "hr") {
|
||||
return <hr className="my-6 border-t border-void-bg-2" />
|
||||
return <hr />
|
||||
}
|
||||
|
||||
if (t.type === "blockquote") {
|
||||
return <blockquote className={`pl-4 border-l-4 border-void-bg-2 italic ${noSpace ? '' : 'my-4'}`}>{t.text}</blockquote>
|
||||
return <blockquote>{t.text}</blockquote>
|
||||
}
|
||||
|
||||
if (t.type === 'list_item') {
|
||||
return <li>
|
||||
<input type="checkbox" checked={t.checked} readOnly />
|
||||
<span>
|
||||
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={t.text} inPTag={true} codeURI={codeURI} {...options} />
|
||||
</span>
|
||||
</li>
|
||||
}
|
||||
|
||||
if (t.type === "list") {
|
||||
const ListTag = t.ordered ? "ol" : "ul"
|
||||
|
||||
return (
|
||||
<ListTag
|
||||
start={t.start ? t.start : undefined}
|
||||
className={`list-inside pl-2 ${noSpace ? '' : 'my-4'} ${t.ordered ? "list-decimal" : "list-disc"}`}
|
||||
>
|
||||
<ListTag start={t.start ? t.start : undefined}>
|
||||
{t.items.map((item, index) => (
|
||||
<li key={index} className={`${noSpace ? '' : 'mb-4'}`}>
|
||||
<li key={index}>
|
||||
{item.task && (
|
||||
<input type="checkbox" checked={item.checked} readOnly className="mr-2 form-checkbox" />
|
||||
<input type="checkbox" checked={item.checked} readOnly />
|
||||
)}
|
||||
<span className="ml-1">
|
||||
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={item.text} nested={true} />
|
||||
<span>
|
||||
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={item.text} inPTag={true} {...options} />
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ListTag>
|
||||
)
|
||||
// attempt at indentation
|
||||
// return (
|
||||
// <ListTag
|
||||
// start={t.start ? t.start : undefined}
|
||||
// className={`pl-2 ${noSpace ? '' : 'my-4'} ${t.ordered ? "list-decimal" : "list-disc"}`}
|
||||
// >
|
||||
// {t.items.map((item, index) => (
|
||||
// <li key={index} className={`${noSpace ? '' : 'mb-2'} ml-4`}>
|
||||
// {item.task && (
|
||||
// <input type="checkbox" className='mr-2 form-checkbox' checked={item.checked} readOnly />
|
||||
// )}
|
||||
// <span className-='inline-block pr-2'>
|
||||
// <ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={item.text} nested={true} />
|
||||
// </span>
|
||||
// </li>
|
||||
// ))}
|
||||
// </ListTag>
|
||||
// )
|
||||
}
|
||||
|
||||
if (t.type === "paragraph") {
|
||||
const contents = <>
|
||||
{t.tokens.map((token, index) => (
|
||||
<RenderToken key={index} token={token} tokenIdx={`${tokenIdx ? `${tokenIdx}-` : ''}${index}`} /> // assign a unique tokenId to nested components
|
||||
<RenderToken key={index}
|
||||
token={token}
|
||||
tokenIdx={`${tokenIdx ? `${tokenIdx}-` : ''}${index}`} // assign a unique tokenId to inPTag components
|
||||
chatMessageLocation={chatMessageLocation}
|
||||
inPTag={true}
|
||||
{...options}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
if (nested) return contents
|
||||
|
||||
return <p className={`${noSpace ? '' : 'my-4'}`}>
|
||||
{contents}
|
||||
</p>
|
||||
if (inPTag) return <span className='block'>{contents}</span>
|
||||
return <p>{contents}</p>
|
||||
}
|
||||
|
||||
if (t.type === "html") {
|
||||
return (
|
||||
<p className={`${noSpace ? '' : 'my-4'}`}>
|
||||
{t.raw}
|
||||
</p>
|
||||
)
|
||||
const contents = t.raw
|
||||
if (inPTag) return <span className='block'>{contents}</span>
|
||||
return <p>{contents}</p>
|
||||
}
|
||||
|
||||
if (t.type === "text" || t.type === "escape") {
|
||||
@@ -187,10 +321,10 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocation, tokenIdx }:
|
||||
if (t.type === "link") {
|
||||
return (
|
||||
<a
|
||||
className='underline'
|
||||
onClick={() => { window.open(t.href) }}
|
||||
href={t.href}
|
||||
title={t.title ?? undefined}
|
||||
className='underline cursor-pointer hover:brightness-90 transition-all duration-200'
|
||||
>
|
||||
{t.text}
|
||||
</a>
|
||||
@@ -202,25 +336,31 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocation, tokenIdx }:
|
||||
src={t.href}
|
||||
alt={t.text}
|
||||
title={t.title ?? undefined}
|
||||
className={`max4w-full h-auto rounded ${noSpace ? '' : 'my-4'}`}
|
||||
|
||||
/>
|
||||
}
|
||||
|
||||
if (t.type === "strong") {
|
||||
return <strong className="font-semibold">{t.text}</strong>
|
||||
return <strong>{t.text}</strong>
|
||||
}
|
||||
|
||||
if (t.type === "em") {
|
||||
return <em className="italic">{t.text}</em>
|
||||
return <em>{t.text}</em>
|
||||
}
|
||||
|
||||
// inline code
|
||||
if (t.type === "codespan") {
|
||||
return (
|
||||
<CodeSpan>
|
||||
{t.text}
|
||||
</CodeSpan>
|
||||
)
|
||||
|
||||
if (options.isLinkDetectionEnabled && chatMessageLocation) {
|
||||
return <CodespanWithLink
|
||||
text={t.text}
|
||||
rawText={t.raw}
|
||||
chatMessageLocation={chatMessageLocation}
|
||||
/>
|
||||
|
||||
}
|
||||
|
||||
return <Codespan text={t.text} />
|
||||
}
|
||||
|
||||
if (t.type === "br") {
|
||||
@@ -229,24 +369,23 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocation, tokenIdx }:
|
||||
|
||||
// strikethrough
|
||||
if (t.type === "del") {
|
||||
return <del className="line-through">{t.text}</del>
|
||||
return <del>{t.text}</del>
|
||||
}
|
||||
|
||||
// default
|
||||
return (
|
||||
<div className="bg-orange-50 rounded-sm overflow-hidden p-2">
|
||||
<span className="text-sm text-orange-500">Unknown type:</span>
|
||||
{t.raw}
|
||||
<span className="text-sm text-orange-500">Unknown token rendered...</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ChatMarkdownRender = ({ string, nested = false, noSpace, chatMessageLocation }: { string: string, nested?: boolean, noSpace?: boolean, chatMessageLocation?: ChatMessageLocation }) => {
|
||||
|
||||
export const ChatMarkdownRender = ({ string, inPTag = false, chatMessageLocation, ...options }: { string: string, inPTag?: boolean, codeURI?: URI, chatMessageLocation: ChatMessageLocation | undefined } & RenderTokenOptions) => {
|
||||
const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer
|
||||
return (
|
||||
<>
|
||||
{tokens.map((token, index) => (
|
||||
<RenderToken key={index} token={token} nested={nested} noSpace={noSpace} chatMessageLocation={chatMessageLocation} tokenIdx={index + ''} />
|
||||
<RenderToken key={index} token={token} inPTag={inPTag} chatMessageLocation={chatMessageLocation} tokenIdx={index + ''} {...options} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import React, { FormEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useSettingsState, useSidebarState, useChatThreadsState, useQuickEditState, useAccessor, useCtrlKZoneStreamingState } from '../util/services.js';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useSettingsState, useAccessor, useCtrlKZoneStreamingState } from '../util/services.js';
|
||||
import { TextAreaFns, VoidInputBox2 } from '../util/inputs.js';
|
||||
import { QuickEditPropsType } from '../../../quickEditActions.js';
|
||||
import { ButtonStop, ButtonSubmit, IconX, VoidChatArea } from '../sidebar-tsx/SidebarChat.js';
|
||||
import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js';
|
||||
import { VOID_CTRL_K_ACTION_ID } from '../../../actionIDs.js';
|
||||
import { useRefState } from '../util/helpers.js';
|
||||
import { useScrollbarStyles } from '../util/useScrollbarStyles.js';
|
||||
@@ -55,17 +54,27 @@ export const QuickEditChat = ({
|
||||
setIsStreamingRef(isStreaming)
|
||||
}, [diffareaid, setIsStreamingRef]))
|
||||
|
||||
const loadingIcon = <div
|
||||
className="@@codicon @@codicon-loading @@codicon-modifier-spin @@codicon-no-default-spin text-void-fg-3"
|
||||
/>
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
const onSubmit = useCallback(async () => {
|
||||
if (isDisabled) return
|
||||
if (isStreamingRef.current) return
|
||||
textAreaFnsRef.current?.disable()
|
||||
|
||||
editCodeService.startApplying({
|
||||
const opts = {
|
||||
from: 'QuickEdit',
|
||||
type: 'rewrite',
|
||||
diffareaid,
|
||||
})
|
||||
startBehavior: 'keep-conflicts',
|
||||
} as const
|
||||
|
||||
await editCodeService.callBeforeStartApplying(opts)
|
||||
const [newApplyingUri, applyDonePromise] = editCodeService.startApplying(opts) ?? []
|
||||
// catch any errors by interrupting the stream
|
||||
applyDonePromise?.catch(e => { if (newApplyingUri) editCodeService.interruptCtrlKStreaming({ diffareaid }) })
|
||||
|
||||
|
||||
}, [isStreamingRef, isDisabled, editCodeService, diffareaid])
|
||||
|
||||
const onInterrupt = useCallback(() => {
|
||||
@@ -87,14 +96,14 @@ export const QuickEditChat = ({
|
||||
const chatAreaRef = useRef<HTMLDivElement | null>(null)
|
||||
return <div ref={sizerRef} style={{ maxWidth: 450 }} className={`py-2 w-full`}>
|
||||
<VoidChatArea
|
||||
featureName='Ctrl+K'
|
||||
divRef={chatAreaRef}
|
||||
onSubmit={onSubmit}
|
||||
onAbort={onInterrupt}
|
||||
onClose={onX}
|
||||
isStreaming={isStreamingRef.current}
|
||||
loadingIcon={loadingIcon}
|
||||
isDisabled={isDisabled}
|
||||
featureName="Ctrl+K"
|
||||
className="py-2 w-full"
|
||||
onClickAnywhere={() => { textAreaRef.current?.focus() }}
|
||||
>
|
||||
<VoidInputBox2
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { AlertCircle, ChevronDown, ChevronUp, X } from 'lucide-react';
|
||||
import { errorDetails } from '../../../../../../../workbench/contrib/void/common/llmMessageTypes.js';
|
||||
import { useSettingsState } from '../util/services.js';
|
||||
import { errorDetails } from '../../../../common/sendLLMMessageTypes.js';
|
||||
|
||||
|
||||
export const ErrorDisplay = ({
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { mountFnGenerator } from '../util/mountFnGenerator.js'
|
||||
|
||||
// import { SidebarSettings } from './SidebarSettings.js';
|
||||
|
||||
|
||||
import { useIsDark, useSidebarState } from '../util/services.js';
|
||||
// import { SidebarThreadSelector } from './SidebarThreadSelector.js';
|
||||
@@ -20,9 +15,9 @@ export const Sidebar = ({ className }: { className: string }) => {
|
||||
const sidebarState = useSidebarState()
|
||||
const { currentTab: tab } = sidebarState
|
||||
|
||||
// const isDark = useIsDark()
|
||||
const isDark = useIsDark()
|
||||
return <div
|
||||
className={`@@void-scope`} // ${isDark ? 'dark' : ''}
|
||||
className={`@@void-scope ${isDark ? 'dark' : ''}`}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
>
|
||||
<div
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+11
-11
@@ -29,11 +29,11 @@ export const SidebarThreadSelector = () => {
|
||||
|
||||
// sorted by most recent to least recent
|
||||
const sortedThreadIds = Object.keys(allThreads ?? {})
|
||||
.sort((threadId1, threadId2) => allThreads![threadId1].lastModified > allThreads![threadId2].lastModified ? -1 : 1)
|
||||
.filter(threadId => allThreads![threadId].messages.length !== 0)
|
||||
.sort((threadId1, threadId2) => (allThreads[threadId1]?.lastModified ?? 0) > (allThreads[threadId2]?.lastModified ?? 0) ? -1 : 1)
|
||||
.filter(threadId => (allThreads![threadId]?.messages.length ?? 0) !== 0)
|
||||
|
||||
return (
|
||||
<div className="flex p-2 flex-col gap-y-1 max-h-[400px] overflow-y-auto">
|
||||
<div className="flex p-2 flex-col gap-y-1 max-h-[200px] overflow-y-auto">
|
||||
|
||||
<div className="w-full relative flex justify-center items-center">
|
||||
{/* title */}
|
||||
@@ -63,14 +63,16 @@ export const SidebarThreadSelector = () => {
|
||||
if (!allThreads) {
|
||||
return <li key="error" className="text-void-warning">{`Error accessing chat history.`}</li>;
|
||||
}
|
||||
|
||||
const pastThread = allThreads[threadId];
|
||||
if (!pastThread) {
|
||||
return <li key="error" className="text-void-warning">{`Error accessing chat history.`}</li>;
|
||||
}
|
||||
|
||||
|
||||
let firstMsg = null;
|
||||
// let secondMsg = null;
|
||||
|
||||
const firstUserMsgIdx = pastThread.messages.findIndex(
|
||||
(msg) => msg.role !== 'system' && msg.role !== 'tool' && !!msg.displayContent
|
||||
);
|
||||
const firstUserMsgIdx = pastThread.messages.findIndex((msg) => msg.role === 'user');
|
||||
|
||||
if (firstUserMsgIdx !== -1) {
|
||||
// firstMsg = truncate(pastThread.messages[firstMsgIdx].displayContent ?? '');
|
||||
@@ -88,9 +90,7 @@ export const SidebarThreadSelector = () => {
|
||||
// secondMsg = truncate(pastThread.messages[secondMsgIdx].displayContent ?? '');
|
||||
// }
|
||||
|
||||
const numMessages = pastThread.messages.filter(
|
||||
(msg) => msg.role !== 'system'
|
||||
).length;
|
||||
const numMessages = pastThread.messages.filter((msg) => msg.role !== 'tool_request').length;
|
||||
|
||||
return (
|
||||
<li key={pastThread.id}>
|
||||
@@ -106,7 +106,7 @@ export const SidebarThreadSelector = () => {
|
||||
`}
|
||||
onClick={() => chatThreadsService.switchToThread(pastThread.id)}
|
||||
onDoubleClick={() => sidebarStateService.setState({ isHistoryOpen: false })}
|
||||
title={new Date(pastThread.createdAt).toLocaleString()}
|
||||
title={new Date(pastThread.lastModified).toLocaleString()}
|
||||
>
|
||||
<div className='truncate'>{`${firstMsg}`}</div>
|
||||
<div>{`\u00A0(${numMessages})`}</div>
|
||||
|
||||
@@ -7,6 +7,30 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
& {
|
||||
--void-bg-1: var(--vscode-input-background);
|
||||
--void-bg-1-alt: var(--vscode-badge-background);
|
||||
--void-bg-2: var(--vscode-sideBar-background);
|
||||
--void-bg-2-alt: color-mix(in srgb, var(--vscode-sideBar-background) 30%, var(--vscode-editor-background) 70%);
|
||||
--void-bg-3: var(--vscode-editor-background);
|
||||
|
||||
--void-fg-0: color-mix(in srgb, var(--vscode-tab-activeForeground) 90%, black 10%);
|
||||
--void-fg-1: var(--vscode-editor-foreground);
|
||||
--void-fg-2: var(--vscode-input-foreground);
|
||||
--void-fg-3: var(--vscode-input-placeholderForeground);
|
||||
/* --void-fg-4: var(--vscode-tab-inactiveForeground); */
|
||||
--void-fg-4: var(--vscode-list-deemphasizedForeground);
|
||||
|
||||
--void-warning: var(--vscode-charts-yellow);
|
||||
|
||||
--void-border-1: var(--vscode-commandCenter-activeBorder);
|
||||
--void-border-2: var(--vscode-commandCenter-border);
|
||||
--void-border-3: var(--vscode-commandCenter-inactiveBorder);
|
||||
--void-border-4: var(--vscode-editorGroup-border);
|
||||
|
||||
--void-ring-color: #007FD4;
|
||||
--void-link-color: #007FD4;
|
||||
}
|
||||
|
||||
.select-child-restyle select {
|
||||
text-overflow: ellipsis;
|
||||
@@ -14,6 +38,10 @@
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
.void-force-child-placeholder-void-fg-1 ::placeholder {
|
||||
color: var(--void-fg-3);
|
||||
}
|
||||
|
||||
* {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
|
||||
|
||||
@@ -17,3 +17,12 @@ export const useRefState = <T,>(initVal: T): ReturnType<T> => {
|
||||
}, [])
|
||||
return [ref, setState]
|
||||
}
|
||||
|
||||
|
||||
export const usePromise = <T,>(promise: Promise<T>): T | undefined => {
|
||||
const [val, setVal] = useState<T | undefined>(undefined)
|
||||
useEffect(() => {
|
||||
promise.then((v) => setVal(v))
|
||||
}, [promise])
|
||||
return val
|
||||
}
|
||||
|
||||
@@ -152,6 +152,37 @@ export const VoidInputBox2 = forwardRef<HTMLTextAreaElement, InputBox2Props>(fun
|
||||
|
||||
})
|
||||
|
||||
|
||||
export const VoidSimpleInputBox = ({ value, onChangeValue, placeholder, className, disabled, passwordBlur, ...inputProps }: {
|
||||
value: string;
|
||||
onChangeValue: (value: string) => void;
|
||||
placeholder: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
passwordBlur?: boolean;
|
||||
} & React.InputHTMLAttributes<HTMLInputElement>) => {
|
||||
|
||||
return (
|
||||
<input
|
||||
value={value}
|
||||
onChange={(e) => onChangeValue(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
className={`w-full resize-none text-void-fg-1 placeholder:text-void-fg-3 px-2 py-1 rounded-sm
|
||||
${disabled ? 'opacity-50 cursor-not-allowed' : ''}
|
||||
${className}`}
|
||||
style={{
|
||||
...passwordBlur && { WebkitTextSecurity: 'disc' },
|
||||
background: asCssVariable(inputBackground),
|
||||
color: asCssVariable(inputForeground)
|
||||
}}
|
||||
{...inputProps}
|
||||
type={undefined} // VS Code is doing some annoyingness that breaks paste if this is defined
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, placeholder, isPasswordField, multiline }: {
|
||||
onChangeText: (value: string) => void;
|
||||
styles?: Partial<IInputBoxStyles>,
|
||||
@@ -169,7 +200,7 @@ export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, plac
|
||||
ctor={InputBox}
|
||||
className='
|
||||
bg-void-bg-1
|
||||
@@[&_::placeholder]:!void-text-void-fg-3
|
||||
@@void-force-child-placeholder-void-fg-1
|
||||
'
|
||||
propsFn={useCallback((container) => [
|
||||
container,
|
||||
@@ -214,27 +245,189 @@ export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, plac
|
||||
|
||||
|
||||
|
||||
|
||||
export const VoidSlider = ({
|
||||
value,
|
||||
onChange,
|
||||
size = 'md',
|
||||
disabled = false,
|
||||
min = 0,
|
||||
max = 7,
|
||||
step = 1,
|
||||
className = '',
|
||||
width = 200,
|
||||
}: {
|
||||
value: number;
|
||||
onChange: (value: number) => void;
|
||||
disabled?: boolean;
|
||||
size?: 'xxs' | 'xs' | 'sm' | 'sm+' | 'md';
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
className?: string;
|
||||
width?: number;
|
||||
}) => {
|
||||
// Calculate percentage for position
|
||||
const percentage = ((value - min) / (max - min)) * 100;
|
||||
|
||||
// Handle track click
|
||||
const handleTrackClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (disabled) return;
|
||||
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const clickPosition = e.clientX - rect.left;
|
||||
const trackWidth = rect.width;
|
||||
|
||||
// Calculate new value
|
||||
const newPercentage = Math.max(0, Math.min(1, clickPosition / trackWidth));
|
||||
const rawValue = min + newPercentage * (max - min);
|
||||
|
||||
// Special handling to ensure max value is always reachable
|
||||
if (rawValue >= max - step / 2) {
|
||||
onChange(max);
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal step calculation
|
||||
const steppedValue = Math.round((rawValue - min) / step) * step + min;
|
||||
const clampedValue = Math.max(min, Math.min(max, steppedValue));
|
||||
|
||||
onChange(clampedValue);
|
||||
};
|
||||
|
||||
// Helper function to handle thumb dragging that respects steps and max
|
||||
const handleThumbDrag = (moveEvent: MouseEvent, track: Element) => {
|
||||
if (!track) return;
|
||||
|
||||
const rect = (track as HTMLElement).getBoundingClientRect();
|
||||
const movePosition = moveEvent.clientX - rect.left;
|
||||
const trackWidth = rect.width;
|
||||
|
||||
// Calculate new value
|
||||
const newPercentage = Math.max(0, Math.min(1, movePosition / trackWidth));
|
||||
const rawValue = min + newPercentage * (max - min);
|
||||
|
||||
// Special handling to ensure max value is always reachable
|
||||
if (rawValue >= max - step / 2) {
|
||||
onChange(max);
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal step calculation
|
||||
const steppedValue = Math.round((rawValue - min) / step) * step + min;
|
||||
const clampedValue = Math.max(min, Math.min(max, steppedValue));
|
||||
|
||||
onChange(clampedValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`inline-flex items-center flex-shrink-0 ${className}`}>
|
||||
{/* Outer container with padding to account for thumb overhang */}
|
||||
<div className={`relative flex-shrink-0 ${disabled ? 'opacity-25' : ''}`}
|
||||
style={{
|
||||
width,
|
||||
// Add horizontal padding equal to half the thumb width
|
||||
// paddingLeft: thumbSizePx / 2,
|
||||
// paddingRight: thumbSizePx / 2
|
||||
}}>
|
||||
{/* Track container with adjusted width */}
|
||||
<div className="relative w-full">
|
||||
{/* Invisible wider clickable area that sits above the track */}
|
||||
<div
|
||||
className="absolute w-full cursor-pointer"
|
||||
style={{
|
||||
height: '16px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
zIndex: 1
|
||||
}}
|
||||
onClick={handleTrackClick}
|
||||
/>
|
||||
|
||||
{/* Track */}
|
||||
<div
|
||||
className={`relative ${size === 'xxs' ? 'h-0.5' :
|
||||
size === 'xs' ? 'h-1' :
|
||||
size === 'sm' ? 'h-1.5' :
|
||||
size === 'sm+' ? 'h-2' : 'h-2.5'
|
||||
} bg-void-bg-2 rounded-full cursor-pointer`}
|
||||
onClick={handleTrackClick}
|
||||
>
|
||||
{/* Filled part of track */}
|
||||
<div
|
||||
className={`absolute left-0 ${size === 'xxs' ? 'h-0.5' :
|
||||
size === 'xs' ? 'h-1' :
|
||||
size === 'sm' ? 'h-1.5' :
|
||||
size === 'sm+' ? 'h-2' : 'h-2.5'
|
||||
} bg-void-fg-1 rounded-full`}
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Thumb */}
|
||||
<div
|
||||
className={`absolute top-1/2 transform -translate-x-1/2 -translate-y-1/2
|
||||
${size === 'xxs' ? 'h-2 w-2' :
|
||||
size === 'xs' ? 'h-2.5 w-2.5' :
|
||||
size === 'sm' ? 'h-3 w-3' :
|
||||
size === 'sm+' ? 'h-3.5 w-3.5' : 'h-4 w-4'
|
||||
}
|
||||
bg-void-fg-1 rounded-full shadow-md ${disabled ? 'cursor-not-allowed' : 'cursor-grab active:cursor-grabbing'}
|
||||
border border-void-fg-1`}
|
||||
style={{ left: `${percentage}%`, zIndex: 2 }} // Ensure thumb is above the invisible clickable area
|
||||
onMouseDown={(e) => {
|
||||
if (disabled) return;
|
||||
|
||||
const track = e.currentTarget.previousElementSibling;
|
||||
|
||||
const handleMouseMove = (moveEvent: MouseEvent) => {
|
||||
handleThumbDrag(moveEvent, track as Element);
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
};
|
||||
|
||||
document.body.style.userSelect = 'none';
|
||||
document.body.style.cursor = 'grabbing';
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
|
||||
e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const VoidSwitch = ({
|
||||
value,
|
||||
onChange,
|
||||
size = 'md',
|
||||
label,
|
||||
disabled = false,
|
||||
}: {
|
||||
value: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
label?: string;
|
||||
disabled?: boolean;
|
||||
size?: 'xs' | 'sm' | 'sm+' | 'md';
|
||||
size?: 'xxs' | 'xs' | 'sm' | 'sm+' | 'md';
|
||||
}) => {
|
||||
return (
|
||||
<label className="inline-flex items-center cursor-pointer">
|
||||
<label className="inline-flex items-center">
|
||||
<div
|
||||
onClick={() => !disabled && onChange(!value)}
|
||||
className={`
|
||||
cursor-pointer
|
||||
relative inline-flex items-center rounded-full transition-colors duration-200 ease-in-out
|
||||
${value ? 'bg-gray-900 dark:bg-white' : 'bg-gray-200 dark:bg-gray-700'}
|
||||
${value ? 'bg-zinc-900 dark:bg-white' : 'bg-white dark:bg-zinc-600'}
|
||||
${disabled ? 'opacity-25' : ''}
|
||||
${size === 'xxs' ? 'h-3 w-5' : ''}
|
||||
${size === 'xs' ? 'h-4 w-7' : ''}
|
||||
${size === 'sm' ? 'h-5 w-9' : ''}
|
||||
${size === 'sm+' ? 'h-5 w-10' : ''}
|
||||
@@ -243,11 +436,13 @@ export const VoidSwitch = ({
|
||||
>
|
||||
<span
|
||||
className={`
|
||||
inline-block transform rounded-full bg-white dark:bg-gray-900 shadow transition-transform duration-200 ease-in-out
|
||||
inline-block transform rounded-full bg-white dark:bg-zinc-900 shadow transition-transform duration-200 ease-in-out
|
||||
${size === 'xxs' ? 'h-2 w-2' : ''}
|
||||
${size === 'xs' ? 'h-2.5 w-2.5' : ''}
|
||||
${size === 'sm' ? 'h-3 w-3' : ''}
|
||||
${size === 'sm+' ? 'h-3.5 w-3.5' : ''}
|
||||
${size === 'md' ? 'h-4 w-4' : ''}
|
||||
${size === 'xxs' ? (value ? 'translate-x-2.5' : 'translate-x-0.5') : ''}
|
||||
${size === 'xs' ? (value ? 'translate-x-3.5' : 'translate-x-0.5') : ''}
|
||||
${size === 'sm' ? (value ? 'translate-x-5' : 'translate-x-1') : ''}
|
||||
${size === 'sm+' ? (value ? 'translate-x-6' : 'translate-x-1') : ''}
|
||||
@@ -255,14 +450,6 @@ export const VoidSwitch = ({
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
{label && (
|
||||
<span className={`
|
||||
ml-3 font-medium text-gray-900 dark:text-gray-100
|
||||
${size === 'xs' ? 'text-xs' : 'text-sm'}
|
||||
`}>
|
||||
{label}
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
);
|
||||
};
|
||||
@@ -305,28 +492,32 @@ export const VoidCheckBox = ({ label, value, onClick, className }: { label: stri
|
||||
|
||||
|
||||
|
||||
export const VoidCustomDropdownBox = <T extends any>({
|
||||
export const VoidCustomDropdownBox = <T extends NonNullable<any>>({
|
||||
options,
|
||||
selectedOption,
|
||||
onChangeOption,
|
||||
getOptionDropdownName,
|
||||
getOptionDropdownDetail,
|
||||
getOptionDisplayName,
|
||||
getOptionsEqual,
|
||||
className,
|
||||
arrowTouchesText = true,
|
||||
matchInputWidth = false,
|
||||
gap = 0,
|
||||
gapPx = 0,
|
||||
offsetPx = -6,
|
||||
}: {
|
||||
options: T[];
|
||||
selectedOption: T | undefined;
|
||||
onChangeOption: (newValue: T) => void;
|
||||
getOptionDropdownName: (option: T) => string;
|
||||
getOptionDropdownDetail?: (option: T) => string;
|
||||
getOptionDisplayName: (option: T) => string;
|
||||
getOptionsEqual: (a: T, b: T) => boolean;
|
||||
className?: string;
|
||||
arrowTouchesText?: boolean;
|
||||
matchInputWidth?: boolean;
|
||||
gap?: number;
|
||||
gapPx?: number;
|
||||
offsetPx?: number;
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const measureRef = useRef<HTMLDivElement>(null);
|
||||
@@ -345,7 +536,7 @@ export const VoidCustomDropdownBox = <T extends any>({
|
||||
placement: 'bottom-start',
|
||||
|
||||
middleware: [
|
||||
offset(gap),
|
||||
offset({ mainAxis: gapPx, crossAxis: offsetPx }),
|
||||
flip({
|
||||
boundary: document.body,
|
||||
padding: 8
|
||||
@@ -380,7 +571,7 @@ export const VoidCustomDropdownBox = <T extends any>({
|
||||
// if the selected option is null, set the selection to the 0th option
|
||||
useEffect(() => {
|
||||
if (options.length === 0) return
|
||||
if (selectedOption) return
|
||||
if (selectedOption !== undefined) return
|
||||
onChangeOption(options[0])
|
||||
}, [selectedOption, onChangeOption, options])
|
||||
|
||||
@@ -409,7 +600,7 @@ export const VoidCustomDropdownBox = <T extends any>({
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, [isOpen, refs.floating, refs.reference]);
|
||||
|
||||
if (!selectedOption)
|
||||
if (selectedOption === undefined)
|
||||
return null
|
||||
|
||||
return (
|
||||
@@ -420,12 +611,21 @@ export const VoidCustomDropdownBox = <T extends any>({
|
||||
className="opacity-0 pointer-events-none absolute -left-[999999px] -top-[999999px] flex flex-col"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{options.map((option) => (
|
||||
<div key={getOptionDropdownName(option)} className="flex items-center whitespace-nowrap">
|
||||
<div className="w-4" />
|
||||
<span className="px-2">{getOptionDropdownName(option)}</span>
|
||||
</div>
|
||||
))}
|
||||
{options.map((option) => {
|
||||
const optionName = getOptionDropdownName(option);
|
||||
const optionDetail = getOptionDropdownDetail?.(option) || '';
|
||||
|
||||
return (
|
||||
<div key={optionName + optionDetail} className="flex items-center whitespace-nowrap">
|
||||
<div className="w-4" />
|
||||
<span className="flex justify-between w-full">
|
||||
<span>{optionName}</span>
|
||||
<span>{optionDetail}</span>
|
||||
<span>______</span>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Select Button */}
|
||||
@@ -473,6 +673,7 @@ export const VoidCustomDropdownBox = <T extends any>({
|
||||
{options.map((option) => {
|
||||
const thisOptionIsSelected = getOptionsEqual(option, selectedOption);
|
||||
const optionName = getOptionDropdownName(option);
|
||||
const optionDetail = getOptionDropdownDetail?.(option) || '';
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -500,7 +701,10 @@ export const VoidCustomDropdownBox = <T extends any>({
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
<span>{optionName}</span>
|
||||
<span className="flex justify-between w-full">
|
||||
<span>{optionName}</span>
|
||||
<span className='text-void-fg-4 opacity-60'>{optionDetail}</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -615,8 +819,8 @@ const normalizeIndentation = (code: string): string => {
|
||||
|
||||
|
||||
const modelOfEditorId: { [id: string]: ITextModel | undefined } = {}
|
||||
export type VoidCodeEditorProps = { initValue: string, language?: string, maxHeight?: number, showScrollbars?: boolean }
|
||||
export const VoidCodeEditor = ({ initValue, language, maxHeight, showScrollbars }: VoidCodeEditorProps) => {
|
||||
export type BlockCodeProps = { initValue: string, language?: string, maxHeight?: number, showScrollbars?: boolean }
|
||||
export const BlockCode = ({ initValue, language, maxHeight, showScrollbars }: BlockCodeProps) => {
|
||||
|
||||
initValue = normalizeIndentation(initValue)
|
||||
|
||||
@@ -631,7 +835,6 @@ export const VoidCodeEditor = ({ initValue, language, maxHeight, showScrollbars
|
||||
// const languageDetectionService = accessor.get('ILanguageDetectionService')
|
||||
const modelService = accessor.get('IModelService')
|
||||
|
||||
|
||||
const id = useId()
|
||||
|
||||
// these are used to pass to the model creation of modelRef
|
||||
@@ -712,9 +915,11 @@ export const VoidCodeEditor = ({ initValue, language, maxHeight, showScrollbars
|
||||
}, [instantiationService])}
|
||||
|
||||
onCreateInstance={useCallback((editor: CodeEditorWidget) => {
|
||||
const languageId = languageRef.current ? languageRef.current : 'plaintext'
|
||||
|
||||
const model = modelOfEditorId[id] ?? modelService.createModel(
|
||||
initValueRef.current + '\n', {
|
||||
languageId: languageRef.current ? languageRef.current : 'typescript',
|
||||
initValueRef.current, {
|
||||
languageId: languageId,
|
||||
onDidChange: (e) => { return { dispose: () => { } } } // no idea why they'd require this
|
||||
})
|
||||
modelRef.current = model
|
||||
@@ -751,7 +956,7 @@ export const VoidCodeEditor = ({ initValue, language, maxHeight, showScrollbars
|
||||
|
||||
export const VoidButton = ({ children, disabled, onClick }: { children: React.ReactNode; disabled?: boolean; onClick: () => void }) => {
|
||||
return <button disabled={disabled}
|
||||
className='px-3 py-1 bg-black/10 dark:bg-gray-200/10 rounded-sm overflow-hidden'
|
||||
className='px-3 py-1 bg-black/10 dark:bg-white/10 rounded-sm overflow-hidden whitespace-nowrap'
|
||||
onClick={onClick}
|
||||
>{children}</button>
|
||||
}
|
||||
|
||||
@@ -18,9 +18,21 @@ export const mountFnGenerator = (Component: (params: any) => React.ReactNode) =>
|
||||
|
||||
const disposables = _registerServices(accessor)
|
||||
|
||||
|
||||
const root = ReactDOM.createRoot(rootElement)
|
||||
root.render(<Component {...props} />); // tailwind dark theme indicator
|
||||
|
||||
return disposables
|
||||
const rerender = (props?: any) => {
|
||||
root.render(<Component {...props} />); // tailwind dark theme indicator
|
||||
}
|
||||
const dispose = () => {
|
||||
root.unmount();
|
||||
disposables.forEach(d => d.dispose());
|
||||
}
|
||||
|
||||
rerender(props)
|
||||
|
||||
const returnVal = {
|
||||
rerender,
|
||||
dispose,
|
||||
}
|
||||
return returnVal
|
||||
}
|
||||
|
||||
@@ -4,31 +4,26 @@
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import { ThreadStreamState, ThreadsState } from '../../../chatThreadService.js'
|
||||
import { RefreshableProviderName, SettingsOfProvider } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js'
|
||||
import { IDisposable } from '../../../../../../../base/common/lifecycle.js'
|
||||
import { VoidSidebarState } from '../../../sidebarStateService.js'
|
||||
import { VoidSettingsState } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js'
|
||||
import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js'
|
||||
import { VoidUriState } from '../../../voidUriStateService.js';
|
||||
import { VoidQuickEditState } from '../../../quickEditStateService.js'
|
||||
import { RefreshModelStateOfProvider } from '../../../../../../../workbench/contrib/void/common/refreshModelService.js'
|
||||
|
||||
import { ServicesAccessor } from '../../../../../../../editor/browser/editorExtensions.js';
|
||||
import { IExplorerService } from '../../../../../../../workbench/contrib/files/browser/files.js'
|
||||
import { IModelService } from '../../../../../../../editor/common/services/model.js';
|
||||
import { IClipboardService } from '../../../../../../../platform/clipboard/common/clipboardService.js';
|
||||
import { IContextViewService, IContextMenuService } from '../../../../../../../platform/contextview/browser/contextView.js';
|
||||
import { IFileService } from '../../../../../../../platform/files/common/files.js';
|
||||
import { IHoverService } from '../../../../../../../platform/hover/browser/hover.js';
|
||||
import { IThemeService } from '../../../../../../../platform/theme/common/themeService.js';
|
||||
import { ILLMMessageService } from '../../../../../../../workbench/contrib/void/common/llmMessageService.js';
|
||||
import { ILLMMessageService } from '../../../../common/sendLLMMessageService.js';
|
||||
import { IRefreshModelService } from '../../../../../../../workbench/contrib/void/common/refreshModelService.js';
|
||||
import { IVoidSettingsService } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js';
|
||||
import { IEditCodeService, URIStreamState } from '../../../editCodeService.js';
|
||||
import { IVoidUriStateService } from '../../../voidUriStateService.js';
|
||||
import { IQuickEditStateService } from '../../../quickEditStateService.js';
|
||||
|
||||
import { ISidebarStateService } from '../../../sidebarStateService.js';
|
||||
import { IChatThreadService } from '../../../chatThreadService.js';
|
||||
import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js'
|
||||
import { ICodeEditorService } from '../../../../../../../editor/browser/services/codeEditorService.js'
|
||||
import { ICommandService } from '../../../../../../../platform/commands/common/commands.js'
|
||||
@@ -44,18 +39,20 @@ import { IConfigurationService } from '../../../../../../../platform/configurati
|
||||
import { IPathService } from '../../../../../../../workbench/services/path/common/pathService.js'
|
||||
import { IMetricsService } from '../../../../../../../workbench/contrib/void/common/metricsService.js'
|
||||
import { URI } from '../../../../../../../base/common/uri.js'
|
||||
|
||||
import { IChatThreadService, ThreadsState, ThreadStreamState } from '../../../chatThreadService.js'
|
||||
import { ITerminalToolService } from '../../../terminalToolService.js'
|
||||
import { ILanguageService } from '../../../../../../../editor/common/languages/language.js'
|
||||
import { IVoidModelService } from '../../../../common/voidModelService.js'
|
||||
import { IWorkspaceContextService } from '../../../../../../../platform/workspace/common/workspace.js'
|
||||
import { IVoidCommandBarService } from '../../../voidCommandBarService.js'
|
||||
import { INativeHostService } from '../../../../../../../platform/native/common/native.js';
|
||||
import { IEditCodeService } from '../../../editCodeServiceInterface.js'
|
||||
|
||||
|
||||
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes
|
||||
|
||||
// even if React hasn't mounted yet, the variables are always updated to the latest state.
|
||||
// React listens by adding a setState function to these listeners.
|
||||
let uriState: VoidUriState
|
||||
const uriStateListeners: Set<(s: VoidUriState) => void> = new Set()
|
||||
|
||||
let quickEditState: VoidQuickEditState
|
||||
const quickEditStateListeners: Set<(s: VoidQuickEditState) => void> = new Set()
|
||||
|
||||
let sidebarState: VoidSidebarState
|
||||
const sidebarStateListeners: Set<(s: VoidSidebarState) => void> = new Set()
|
||||
@@ -77,54 +74,30 @@ let colorThemeState: ColorScheme
|
||||
const colorThemeStateListeners: Set<(s: ColorScheme) => void> = new Set()
|
||||
|
||||
const ctrlKZoneStreamingStateListeners: Set<(diffareaid: number, s: boolean) => void> = new Set()
|
||||
const uriStreamingStateListeners: Set<(uri: URI, s: URIStreamState) => void> = new Set()
|
||||
|
||||
const commandBarURIStateListeners: Set<(uri: URI) => void> = new Set();
|
||||
const activeURIListeners: Set<(uri: URI | null) => void> = new Set();
|
||||
|
||||
|
||||
// must call this before you can use any of the hooks below
|
||||
// this should only be called ONCE! this is the only place you don't need to dispose onDidChange. If you use state.onDidChange anywhere else, make sure to dispose it!
|
||||
let wasCalled = false
|
||||
export const _registerServices = (accessor: ServicesAccessor) => {
|
||||
|
||||
const disposables: IDisposable[] = []
|
||||
|
||||
// don't register services twice
|
||||
if (wasCalled) {
|
||||
return
|
||||
// console.error(`⚠️ Void _registerServices was called again! It should only be called once.`)
|
||||
}
|
||||
wasCalled = true
|
||||
|
||||
_registerAccessor(accessor)
|
||||
|
||||
const stateServices = {
|
||||
uriStateService: accessor.get(IVoidUriStateService),
|
||||
quickEditStateService: accessor.get(IQuickEditStateService),
|
||||
sidebarStateService: accessor.get(ISidebarStateService),
|
||||
chatThreadsStateService: accessor.get(IChatThreadService),
|
||||
settingsStateService: accessor.get(IVoidSettingsService),
|
||||
refreshModelService: accessor.get(IRefreshModelService),
|
||||
themeService: accessor.get(IThemeService),
|
||||
editCodeService: accessor.get(IEditCodeService),
|
||||
voidCommandBarService: accessor.get(IVoidCommandBarService),
|
||||
modelService: accessor.get(IModelService),
|
||||
}
|
||||
|
||||
const { uriStateService, sidebarStateService, quickEditStateService, settingsStateService, chatThreadsStateService, refreshModelService, themeService, editCodeService } = stateServices
|
||||
|
||||
uriState = uriStateService.state
|
||||
disposables.push(
|
||||
uriStateService.onDidChangeState(() => {
|
||||
uriState = uriStateService.state
|
||||
uriStateListeners.forEach(l => l(uriState))
|
||||
})
|
||||
)
|
||||
|
||||
quickEditState = quickEditStateService.state
|
||||
disposables.push(
|
||||
quickEditStateService.onDidChangeState(() => {
|
||||
quickEditState = quickEditStateService.state
|
||||
quickEditStateListeners.forEach(l => l(quickEditState))
|
||||
})
|
||||
)
|
||||
const { sidebarStateService, settingsStateService, chatThreadsStateService, refreshModelService, themeService, editCodeService, voidCommandBarService, modelService } = stateServices
|
||||
|
||||
sidebarState = sidebarStateService.state
|
||||
disposables.push(
|
||||
@@ -170,7 +143,7 @@ export const _registerServices = (accessor: ServicesAccessor) => {
|
||||
|
||||
colorThemeState = themeService.getColorTheme().type
|
||||
disposables.push(
|
||||
themeService.onDidColorThemeChange(theme => {
|
||||
themeService.onDidColorThemeChange(({ theme }) => {
|
||||
colorThemeState = theme.type
|
||||
colorThemeStateListeners.forEach(l => l(colorThemeState))
|
||||
})
|
||||
@@ -178,15 +151,21 @@ export const _registerServices = (accessor: ServicesAccessor) => {
|
||||
|
||||
// no state
|
||||
disposables.push(
|
||||
editCodeService.onDidChangeCtrlKZoneStreaming(({ diffareaid }) => {
|
||||
editCodeService.onDidChangeStreamingInCtrlKZone(({ diffareaid }) => {
|
||||
const isStreaming = editCodeService.isCtrlKZoneStreaming({ diffareaid })
|
||||
ctrlKZoneStreamingStateListeners.forEach(l => l(diffareaid, isStreaming))
|
||||
})
|
||||
)
|
||||
|
||||
disposables.push(
|
||||
editCodeService.onDidChangeURIStreamState(({ uri }) => {
|
||||
const isStreaming = editCodeService.getURIStreamState({ uri })
|
||||
uriStreamingStateListeners.forEach(l => l(uri, isStreaming))
|
||||
voidCommandBarService.onDidChangeState(({ uri }) => {
|
||||
commandBarURIStateListeners.forEach(l => l(uri));
|
||||
})
|
||||
)
|
||||
|
||||
disposables.push(
|
||||
voidCommandBarService.onDidChangeActiveURI(({ uri }) => {
|
||||
activeURIListeners.forEach(l => l(uri));
|
||||
})
|
||||
)
|
||||
|
||||
@@ -210,8 +189,6 @@ const getReactAccessor = (accessor: ServicesAccessor) => {
|
||||
IRefreshModelService: accessor.get(IRefreshModelService),
|
||||
IVoidSettingsService: accessor.get(IVoidSettingsService),
|
||||
IEditCodeService: accessor.get(IEditCodeService),
|
||||
IVoidUriStateService: accessor.get(IVoidUriStateService),
|
||||
IQuickEditStateService: accessor.get(IQuickEditStateService),
|
||||
ISidebarStateService: accessor.get(ISidebarStateService),
|
||||
IChatThreadService: accessor.get(IChatThreadService),
|
||||
|
||||
@@ -226,10 +203,18 @@ const getReactAccessor = (accessor: ServicesAccessor) => {
|
||||
ILanguageFeaturesService: accessor.get(ILanguageFeaturesService),
|
||||
IKeybindingService: accessor.get(IKeybindingService),
|
||||
|
||||
IExplorerService: accessor.get(IExplorerService),
|
||||
IEnvironmentService: accessor.get(IEnvironmentService),
|
||||
IConfigurationService: accessor.get(IConfigurationService),
|
||||
IPathService: accessor.get(IPathService),
|
||||
IMetricsService: accessor.get(IMetricsService),
|
||||
ITerminalToolService: accessor.get(ITerminalToolService),
|
||||
ILanguageService: accessor.get(ILanguageService),
|
||||
IVoidModelService: accessor.get(IVoidModelService),
|
||||
IWorkspaceContextService: accessor.get(IWorkspaceContextService),
|
||||
|
||||
IVoidCommandBarService: accessor.get(IVoidCommandBarService),
|
||||
INativeHostService: accessor.get(INativeHostService),
|
||||
|
||||
} as const
|
||||
return reactAccessor
|
||||
@@ -257,26 +242,6 @@ export const useAccessor = () => {
|
||||
|
||||
// -- state of services --
|
||||
|
||||
export const useUriState = () => {
|
||||
const [s, ss] = useState(uriState)
|
||||
useEffect(() => {
|
||||
ss(uriState)
|
||||
uriStateListeners.add(ss)
|
||||
return () => { uriStateListeners.delete(ss) }
|
||||
}, [ss])
|
||||
return s
|
||||
}
|
||||
|
||||
export const useQuickEditState = () => {
|
||||
const [s, ss] = useState(quickEditState)
|
||||
useEffect(() => {
|
||||
ss(quickEditState)
|
||||
quickEditStateListeners.add(ss)
|
||||
return () => { quickEditStateListeners.delete(ss) }
|
||||
}, [ss])
|
||||
return s
|
||||
}
|
||||
|
||||
export const useSidebarState = () => {
|
||||
const [s, ss] = useState(sidebarState)
|
||||
useEffect(() => {
|
||||
@@ -363,14 +328,6 @@ export const useCtrlKZoneStreamingState = (listener: (diffareaid: number, s: boo
|
||||
}, [listener, ctrlKZoneStreamingStateListeners])
|
||||
}
|
||||
|
||||
export const useURIStreamState = (listener: (uri: URI, s: URIStreamState) => void) => {
|
||||
useEffect(() => {
|
||||
uriStreamingStateListeners.add(listener)
|
||||
return () => { uriStreamingStateListeners.delete(listener) }
|
||||
}, [listener, uriStreamingStateListeners])
|
||||
}
|
||||
|
||||
|
||||
export const useIsDark = () => {
|
||||
const [s, ss] = useState(colorThemeState)
|
||||
useEffect(() => {
|
||||
@@ -382,6 +339,40 @@ export const useIsDark = () => {
|
||||
// s is the theme, return isDark instead of s
|
||||
const isDark = s === ColorScheme.DARK || s === ColorScheme.HIGH_CONTRAST_DARK
|
||||
return isDark
|
||||
|
||||
}
|
||||
|
||||
export const useCommandBarURIListener = (listener: (uri: URI) => void) => {
|
||||
useEffect(() => {
|
||||
commandBarURIStateListeners.add(listener);
|
||||
return () => { commandBarURIStateListeners.delete(listener) };
|
||||
}, [listener]);
|
||||
};
|
||||
export const useCommandBarState = () => {
|
||||
const accessor = useAccessor()
|
||||
const commandBarService = accessor.get('IVoidCommandBarService')
|
||||
const [s, ss] = useState({ state: commandBarService.stateOfURI, sortedURIs: commandBarService.sortedURIs });
|
||||
const listener = useCallback(() => {
|
||||
ss({ state: commandBarService.stateOfURI, sortedURIs: commandBarService.sortedURIs });
|
||||
}, [commandBarService])
|
||||
useCommandBarURIListener(listener)
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// roughly gets the active URI - this is used to get the history of recent URIs
|
||||
export const useActiveURI = () => {
|
||||
const accessor = useAccessor()
|
||||
const commandBarService = accessor.get('IVoidCommandBarService')
|
||||
const [s, ss] = useState(commandBarService.activeURI)
|
||||
useEffect(() => {
|
||||
const listener = () => { ss(commandBarService.activeURI) }
|
||||
activeURIListeners.add(listener);
|
||||
return () => { activeURIListeners.delete(listener) };
|
||||
}, [])
|
||||
return { uri: s }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import { useAccessor, useCommandBarState, useIsDark } from '../util/services.js';
|
||||
|
||||
import '../styles.css'
|
||||
import { useCallback, useEffect, useState, useRef } from 'react';
|
||||
import { ScrollType } from '../../../../../../../editor/common/editorCommon.js';
|
||||
import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBorder } from '../../../../common/helpers/colors.js';
|
||||
import { VoidCommandBarProps } from '../../../voidCommandBarService.js';
|
||||
|
||||
export const VoidCommandBarMain = ({ uri, editor }: VoidCommandBarProps) => {
|
||||
const isDark = useIsDark()
|
||||
|
||||
return <div
|
||||
className={`@@void-scope ${isDark ? 'dark' : ''}`}
|
||||
>
|
||||
<VoidCommandBar uri={uri} editor={editor} />
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
|
||||
const stepIdx = (currIdx: number | null, len: number, step: -1 | 1) => {
|
||||
if (len === 0) return null
|
||||
return ((currIdx ?? 0) + step + len) % len // for some reason, small negatives are kept negative. just add len to offset
|
||||
}
|
||||
|
||||
|
||||
|
||||
const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
|
||||
const accessor = useAccessor()
|
||||
const editCodeService = accessor.get('IEditCodeService')
|
||||
const editorService = accessor.get('ICodeEditorService')
|
||||
const metricsService = accessor.get('IMetricsService')
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const commandBarService = accessor.get('IVoidCommandBarService')
|
||||
const voidModelService = accessor.get('IVoidModelService')
|
||||
const { state: commandBarState, sortedURIs: sortedCommandBarURIs } = useCommandBarState()
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log('MOUNTING!!!')
|
||||
// }, [])
|
||||
|
||||
// latestUriIdx is used to remember place in leftRight
|
||||
const _latestValidUriIdxRef = useRef<number | null>(null)
|
||||
|
||||
// i is the current index of the URI in sortedCommandBarURIs
|
||||
const i_ = sortedCommandBarURIs.findIndex(e => e.fsPath === uri?.fsPath)
|
||||
const currFileIdx = i_ === -1 ? null : i_
|
||||
useEffect(() => {
|
||||
if (currFileIdx !== null) _latestValidUriIdxRef.current = currFileIdx
|
||||
}, [currFileIdx])
|
||||
|
||||
const uriIdxInStepper = currFileIdx !== null ? currFileIdx // use currFileIdx if it exists, else use latestNotNullUriIdxRef
|
||||
: _latestValidUriIdxRef.current === null ? null
|
||||
: _latestValidUriIdxRef.current < sortedCommandBarURIs.length ? _latestValidUriIdxRef.current
|
||||
: null
|
||||
|
||||
// when change URI, scroll to the proper spot
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
// check undefined
|
||||
if (!uri) return
|
||||
const s = commandBarService.stateOfURI[uri.fsPath]
|
||||
if (!s) return
|
||||
const { diffIdx } = s
|
||||
goToDiffIdx(diffIdx ?? 0)
|
||||
}, 50)
|
||||
}, [uri, commandBarService])
|
||||
|
||||
if (uri?.scheme !== 'file') return null // don't show in editors that we made, they must be files
|
||||
|
||||
const getNextDiffIdx = (step: 1 | -1) => {
|
||||
// check undefined
|
||||
if (!uri) return null
|
||||
const s = commandBarState[uri.fsPath]
|
||||
if (!s) return null
|
||||
const { diffIdx, sortedDiffIds } = s
|
||||
// get next idx
|
||||
const nextDiffIdx = stepIdx(diffIdx, sortedDiffIds.length, step)
|
||||
return nextDiffIdx
|
||||
}
|
||||
const goToDiffIdx = (idx: number | null) => {
|
||||
if (idx === null) return
|
||||
// check undefined
|
||||
if (!uri) return
|
||||
const s = commandBarState[uri.fsPath]
|
||||
if (!s) return
|
||||
const { sortedDiffIds } = s
|
||||
// reveal
|
||||
const diffid = sortedDiffIds[idx]
|
||||
if (diffid === undefined) return
|
||||
const diff = editCodeService.diffOfId[diffid]
|
||||
if (!diff) return
|
||||
editor.revealLineNearTop(diff.startLine - 1, ScrollType.Immediate)
|
||||
commandBarService.setDiffIdx(uri, idx)
|
||||
}
|
||||
const getNextUriIdx = (step: 1 | -1) => {
|
||||
return stepIdx(uriIdxInStepper, sortedCommandBarURIs.length, step)
|
||||
}
|
||||
const goToURIIdx = async (idx: number | null) => {
|
||||
if (idx === null) return
|
||||
const nextURI = sortedCommandBarURIs[idx]
|
||||
editCodeService.diffAreasOfURI
|
||||
const { model } = await voidModelService.getModelSafe(nextURI)
|
||||
if (model) {
|
||||
// switch to the URI
|
||||
editorService.openCodeEditor({ resource: nextURI, options: { revealIfVisible: true } }, editor)
|
||||
}
|
||||
}
|
||||
|
||||
const currDiffIdx = uri ? commandBarState[uri.fsPath]?.diffIdx ?? null : null
|
||||
const sortedDiffIds = uri ? commandBarState[uri.fsPath]?.sortedDiffIds ?? [] : []
|
||||
const sortedDiffZoneIds = uri ? commandBarState[uri.fsPath]?.sortedDiffZoneIds ?? [] : []
|
||||
|
||||
|
||||
const isADiffInThisFile = sortedDiffIds.length !== 0
|
||||
const isADiffZoneInThisFile = sortedDiffZoneIds.length !== 0
|
||||
const isADiffZoneInAnyFile = sortedCommandBarURIs.length !== 0
|
||||
|
||||
const streamState = uri ? commandBarService.getStreamState(uri) : null
|
||||
const showAcceptRejectAll = streamState === 'idle-has-changes'
|
||||
|
||||
const nextDiffIdx = getNextDiffIdx(1)
|
||||
const prevDiffIdx = getNextDiffIdx(-1)
|
||||
const nextURIIdx = getNextUriIdx(1)
|
||||
const prevURIIdx = getNextUriIdx(-1)
|
||||
|
||||
const upDownDisabled = prevDiffIdx === null || nextDiffIdx === null
|
||||
const leftRightDisabled = prevURIIdx === null || nextURIIdx === null // || (sortedCommandBarURIs.length === 1 && isADiffZoneInThisFile)
|
||||
|
||||
const upButton = <button
|
||||
className={`
|
||||
size-6 rounded cursor-default
|
||||
hover:bg-void-bg-1-alt
|
||||
`}// --border border-void-border-3 focus:border-void-border-1
|
||||
disabled={upDownDisabled}
|
||||
onClick={() => { goToDiffIdx(prevDiffIdx) }}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
goToDiffIdx(prevDiffIdx);
|
||||
}
|
||||
}}
|
||||
>↑</button>
|
||||
|
||||
const downButton = <button
|
||||
className={`
|
||||
size-6 rounded cursor-default
|
||||
hover:bg-void-bg-1-alt
|
||||
`}
|
||||
disabled={upDownDisabled}
|
||||
onClick={() => { goToDiffIdx(nextDiffIdx) }}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
goToDiffIdx(nextDiffIdx);
|
||||
}
|
||||
}}
|
||||
>↓</button>
|
||||
|
||||
const leftButton = <button
|
||||
className={`
|
||||
size-6 rounded cursor-default
|
||||
hover:bg-void-bg-1-alt
|
||||
`}
|
||||
disabled={leftRightDisabled}
|
||||
onClick={() => goToURIIdx(prevURIIdx)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
goToURIIdx(prevURIIdx);
|
||||
}
|
||||
}}
|
||||
>←</button>
|
||||
|
||||
const rightButton = <button
|
||||
className={`
|
||||
size-6 rounded cursor-default
|
||||
hover:bg-void-bg-1-alt
|
||||
`}
|
||||
disabled={leftRightDisabled}
|
||||
onClick={() => goToURIIdx(nextURIIdx)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
goToURIIdx(nextURIIdx);
|
||||
}
|
||||
}}
|
||||
>→</button>
|
||||
|
||||
|
||||
|
||||
// accept/reject if current URI has changes
|
||||
const onAcceptAll = () => {
|
||||
if (!uri) return
|
||||
editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false, _addToHistory: true })
|
||||
metricsService.capture('Accept All', {})
|
||||
}
|
||||
const onRejectAll = () => {
|
||||
if (!uri) return
|
||||
editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false, _addToHistory: true })
|
||||
metricsService.capture('Reject All', {})
|
||||
}
|
||||
|
||||
|
||||
if (!isADiffZoneInAnyFile) return null
|
||||
|
||||
const acceptAllButton = <button
|
||||
className='text-nowrap'
|
||||
onClick={onAcceptAll}
|
||||
style={{
|
||||
backgroundColor: acceptAllBg,
|
||||
border: acceptBorder,
|
||||
color: buttonTextColor,
|
||||
fontSize: buttonFontSize,
|
||||
padding: '2px 4px',
|
||||
borderRadius: '6px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Accept File
|
||||
</button>
|
||||
|
||||
|
||||
const rejectAllButton = <button
|
||||
className='text-nowrap'
|
||||
onClick={onRejectAll}
|
||||
style={{
|
||||
backgroundColor: rejectAllBg,
|
||||
border: rejectBorder,
|
||||
color: 'white',
|
||||
fontSize: buttonFontSize,
|
||||
padding: '2px 4px',
|
||||
borderRadius: '6px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Reject File
|
||||
</button>
|
||||
|
||||
const acceptRejectAllButtons = <div className="flex items-center gap-1 text-sm">
|
||||
{acceptAllButton}
|
||||
{rejectAllButton}
|
||||
</div>
|
||||
|
||||
// const closeCommandBar = useCallback(() => {
|
||||
// commandService.executeCommand('void.hideCommandBar');
|
||||
// }, [commandService]);
|
||||
|
||||
// const hideButton = <button
|
||||
// className='ml-auto pointer-events-auto'
|
||||
// onClick={closeCommandBar}
|
||||
// style={{
|
||||
// color: buttonTextColor,
|
||||
// fontSize: buttonFontSize,
|
||||
// padding: '2px 4px',
|
||||
// borderRadius: '6px',
|
||||
// cursor: 'pointer'
|
||||
// }}
|
||||
// title="Close command bar"
|
||||
// >x
|
||||
// </button>
|
||||
|
||||
const leftRightUpDownButtons = <div className='p-1 gap-1 flex flex-col items-center bg-void-bg-2 rounded shadow-md border border-void-border-2 w-full'>
|
||||
<div className="flex flex-col gap-1">
|
||||
{/* Changes in file */}
|
||||
<div className={`${!isADiffZoneInThisFile ? 'hidden' : ''} flex items-center ${upDownDisabled ? 'opacity-50' : ''}`}>
|
||||
{upButton}
|
||||
{downButton}
|
||||
<span className="min-w-16 px-2 text-xs">
|
||||
{isADiffInThisFile ?
|
||||
`Diff ${(currDiffIdx ?? 0) + 1} of ${sortedDiffIds.length}`
|
||||
: streamState === 'streaming' ?
|
||||
'No changes yet'
|
||||
: `No changes`
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Files */}
|
||||
<div className={`${!isADiffZoneInAnyFile ? 'hidden' : ''} flex items-center ${leftRightDisabled ? 'opacity-50' : ''}`}>
|
||||
{leftButton}
|
||||
{/* <div className="w-px h-3 bg-void-border-3 mx-0.5 shadow-sm"></div> */}
|
||||
{rightButton}
|
||||
{/* <div className="w-px h-3 bg-void-border-3 mx-0.5 shadow-sm"></div> */}
|
||||
<span className="min-w-16 px-2 text-xs">
|
||||
{currFileIdx !== null ?
|
||||
`File ${currFileIdx + 1} of ${sortedCommandBarURIs.length}`
|
||||
: `${sortedCommandBarURIs.length} file${sortedCommandBarURIs.length === 1 ? '' : 's'} changed`
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
return <div className={`flex flex-col items-center gap-y-2 mx-2 pointer-events-auto`}>
|
||||
{showAcceptRejectAll && acceptRejectAllButtons}
|
||||
{leftRightUpDownButtons}
|
||||
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { mountFnGenerator } from '../util/mountFnGenerator.js'
|
||||
import { VoidCommandBarMain } from './VoidCommandBar.js'
|
||||
|
||||
export const mountVoidCommandBar = mountFnGenerator(VoidCommandBarMain)
|
||||
+26
-14
@@ -10,7 +10,7 @@ import { _VoidSelectBox, VoidCustomDropdownBox } from '../util/inputs.js'
|
||||
import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js'
|
||||
import { IconWarning } from '../sidebar-tsx/SidebarChat.js'
|
||||
import { VOID_OPEN_SETTINGS_ACTION_ID, VOID_TOGGLE_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'
|
||||
import { ModelOption } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js'
|
||||
import { modelFilterOfFeatureName, ModelOption } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js'
|
||||
import { WarningBox } from './WarningBox.js'
|
||||
|
||||
const optionsEqual = (m1: ModelOption[], m2: ModelOption[]) => {
|
||||
@@ -21,7 +21,7 @@ const optionsEqual = (m1: ModelOption[], m2: ModelOption[]) => {
|
||||
return true
|
||||
}
|
||||
|
||||
const ModelSelectBox = ({ options, featureName }: { options: ModelOption[], featureName: FeatureName }) => {
|
||||
const ModelSelectBox = ({ options, featureName, className }: { options: ModelOption[], featureName: FeatureName, className: string }) => {
|
||||
const accessor = useAccessor()
|
||||
const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||
|
||||
@@ -37,9 +37,10 @@ const ModelSelectBox = ({ options, featureName }: { options: ModelOption[], feat
|
||||
selectedOption={selectedOption}
|
||||
onChangeOption={onChangeOption}
|
||||
getOptionDisplayName={(option) => option.selection.modelName}
|
||||
getOptionDropdownName={(option) => option.name}
|
||||
getOptionDropdownName={(option) => option.selection.modelName}
|
||||
getOptionDropdownDetail={(option) => option.selection.providerName}
|
||||
getOptionsEqual={(a, b) => optionsEqual([a], [b])}
|
||||
className='text-xs text-void-fg-3 px-1'
|
||||
className={className}
|
||||
matchInputWidth={false}
|
||||
/>
|
||||
}
|
||||
@@ -76,25 +77,32 @@ const ModelSelectBox = ({ options, featureName }: { options: ModelOption[], feat
|
||||
|
||||
|
||||
|
||||
const MemoizedModelDropdown = ({ featureName }: { featureName: FeatureName }) => {
|
||||
const MemoizedModelDropdown = ({ featureName, className }: { featureName: FeatureName, className: string }) => {
|
||||
const settingsState = useSettingsState()
|
||||
const oldOptionsRef = useRef<ModelOption[]>([])
|
||||
const [memoizedOptions, setMemoizedOptions] = useState(oldOptionsRef.current)
|
||||
|
||||
const { filter, emptyMessage } = modelFilterOfFeatureName[featureName]
|
||||
|
||||
useEffect(() => {
|
||||
const oldOptions = oldOptionsRef.current
|
||||
const newOptions = settingsState._modelOptions
|
||||
const newOptions = settingsState._modelOptions.filter((o) => filter(o.selection, { chatMode: settingsState.globalSettings.chatMode }))
|
||||
|
||||
if (!optionsEqual(oldOptions, newOptions)) {
|
||||
setMemoizedOptions(newOptions)
|
||||
}
|
||||
oldOptionsRef.current = newOptions
|
||||
}, [settingsState._modelOptions])
|
||||
}, [settingsState._modelOptions, filter])
|
||||
|
||||
return <ModelSelectBox featureName={featureName} options={memoizedOptions} />
|
||||
if (memoizedOptions.length === 0) { // Pretty sure this will never be reached unless filter is enabled
|
||||
return <WarningBox text={emptyMessage?.message || 'No models available'} />
|
||||
}
|
||||
|
||||
return <ModelSelectBox featureName={featureName} options={memoizedOptions} className={className} />
|
||||
|
||||
}
|
||||
|
||||
export const ModelDropdown = ({ featureName }: { featureName: FeatureName }) => {
|
||||
export const ModelDropdown = ({ featureName, className }: { featureName: FeatureName, className: string }) => {
|
||||
const settingsState = useSettingsState()
|
||||
|
||||
const accessor = useAccessor()
|
||||
@@ -102,14 +110,18 @@ export const ModelDropdown = ({ featureName }: { featureName: FeatureName }) =>
|
||||
|
||||
const openSettings = () => { commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID); };
|
||||
|
||||
|
||||
const { emptyMessage } = modelFilterOfFeatureName[featureName]
|
||||
|
||||
const isDisabled = isFeatureNameDisabled(featureName, settingsState)
|
||||
if (isDisabled)
|
||||
return <WarningBox onClick={openSettings} text={
|
||||
isDisabled === 'needToEnableModel' ? 'Enable a model'
|
||||
: isDisabled === 'addModel' ? 'Add a model'
|
||||
: (isDisabled === 'addProvider' || isDisabled === 'notFilledIn' || isDisabled === 'providerNotAutoDetected') ? 'Provider required'
|
||||
: 'Provider required'
|
||||
emptyMessage && emptyMessage.priority === 'always' ? emptyMessage.message :
|
||||
isDisabled === 'needToEnableModel' ? 'Enable a model'
|
||||
: isDisabled === 'addModel' ? 'Add a model'
|
||||
: (isDisabled === 'addProvider' || isDisabled === 'notFilledIn' || isDisabled === 'providerNotAutoDetected') ? 'Provider required'
|
||||
: 'Provider required'
|
||||
} />
|
||||
|
||||
return <MemoizedModelDropdown featureName={featureName} />
|
||||
return <MemoizedModelDropdown featureName={featureName} className={className} />
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'
|
||||
import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidModelInfo, globalSettingNames, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, defaultProviderSettings, nonlocalProviderNames, localProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName, isProviderNameDisabled, FeatureName } from '../../../../common/voidSettingsTypes.js'
|
||||
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
|
||||
import { VoidButton, VoidCheckBox, VoidCustomDropdownBox, VoidInputBox, VoidInputBox2, VoidSwitch } from '../util/inputs.js'
|
||||
import { VoidButton, VoidCheckBox, VoidCustomDropdownBox, VoidInputBox, VoidInputBox2, VoidSimpleInputBox, VoidSwitch } from '../util/inputs.js'
|
||||
import { useAccessor, useIsDark, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js'
|
||||
import { X, RefreshCw, Loader2, Check, MoveRight } from 'lucide-react'
|
||||
import { useScrollbarStyles } from '../util/useScrollbarStyles.js'
|
||||
@@ -17,14 +17,13 @@ import { env } from '../../../../../../../base/common/process.js'
|
||||
import { ModelDropdown } from './ModelDropdown.js'
|
||||
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js'
|
||||
import { WarningBox } from './WarningBox.js'
|
||||
import { os } from '../../../helpers/systemInfo.js'
|
||||
import { os } from '../../../../common/helpers/systemInfo.js'
|
||||
import { IconX } from '../sidebar-tsx/SidebarChat.js'
|
||||
|
||||
const SubtleButton = ({ onClick, text, icon, disabled }: { onClick: () => void, text: string, icon: React.ReactNode, disabled: boolean }) => {
|
||||
const ButtonLeftTextRightOption = ({ text, leftButton }: { text: string, leftButton?: React.ReactNode }) => {
|
||||
|
||||
return <div className='flex items-center text-void-fg-3 px-3 py-0.5 rounded-sm overflow-hidden gap-2 hover:bg-black/10 dark:hover:bg-gray-300/10'>
|
||||
<button className='flex items-center' disabled={disabled} onClick={onClick}>
|
||||
{icon}
|
||||
</button>
|
||||
{leftButton ? leftButton : null}
|
||||
<span>
|
||||
{text}
|
||||
</span>
|
||||
@@ -57,22 +56,28 @@ const RefreshModelButton = ({ providerName }: { providerName: RefreshableProvide
|
||||
const { state } = refreshModelState[providerName]
|
||||
|
||||
const { title: providerTitle } = displayInfoOfProviderName(providerName)
|
||||
return <SubtleButton
|
||||
onClick={() => {
|
||||
refreshModelService.startRefreshingModels(providerName, { enableProviderOnSuccess: false, doNotFire: false })
|
||||
metricsService.capture('Click', { providerName, action: 'Refresh Models' })
|
||||
}}
|
||||
text={justFinished === 'finished' ? `${providerTitle} Models are up-to-date!`
|
||||
: justFinished === 'error' ? `${providerTitle} not found!`
|
||||
: `Manually refresh ${providerTitle} models.`
|
||||
}
|
||||
icon={justFinished === 'finished' ? <Check className='stroke-green-500 size-3' />
|
||||
: justFinished === 'error' ? <X className='stroke-red-500 size-3' />
|
||||
: state === 'refreshing' ? <Loader2 className='size-3 animate-spin' />
|
||||
: <RefreshCw className='size-3' />
|
||||
|
||||
return <ButtonLeftTextRightOption
|
||||
|
||||
leftButton={
|
||||
<button
|
||||
className='flex items-center'
|
||||
disabled={state === 'refreshing' || justFinished !== null}
|
||||
onClick={() => {
|
||||
refreshModelService.startRefreshingModels(providerName, { enableProviderOnSuccess: false, doNotFire: false })
|
||||
metricsService.capture('Click', { providerName, action: 'Refresh Models' })
|
||||
}}
|
||||
>
|
||||
{justFinished === 'finished' ? <Check className='stroke-green-500 size-3' />
|
||||
: justFinished === 'error' ? <X className='stroke-red-500 size-3' />
|
||||
: state === 'refreshing' ? <Loader2 className='size-3 animate-spin' />
|
||||
: <RefreshCw className='size-3' />}
|
||||
</button>
|
||||
}
|
||||
|
||||
disabled={state === 'refreshing' || justFinished !== null}
|
||||
text={justFinished === 'finished' ? `${providerTitle} Models are up-to-date!`
|
||||
: justFinished === 'error' ? `${providerTitle} not found!`
|
||||
: `Manually refresh ${providerTitle} models.`}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -93,7 +98,7 @@ const RefreshableModels = () => {
|
||||
|
||||
|
||||
|
||||
const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
|
||||
const AddModelMenu = ({ onSubmit, onClose }: { onSubmit: () => void, onClose: () => void }) => {
|
||||
|
||||
const accessor = useAccessor()
|
||||
const settingsStateService = accessor.get('IVoidSettingsService')
|
||||
@@ -116,8 +121,8 @@ const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
|
||||
options={providerNames}
|
||||
selectedOption={providerName}
|
||||
onChangeOption={(pn) => setProviderName(pn)}
|
||||
getOptionDisplayName={(pn) => pn ? displayInfoOfProviderName(pn).title : '(null)'}
|
||||
getOptionDropdownName={(pn) => pn ? displayInfoOfProviderName(pn).title : '(null)'}
|
||||
getOptionDisplayName={(pn) => pn ? displayInfoOfProviderName(pn).title : 'Provider Name'}
|
||||
getOptionDropdownName={(pn) => pn ? displayInfoOfProviderName(pn).title : 'Provider Name'}
|
||||
getOptionsEqual={(a, b) => a === b}
|
||||
className={`max-w-44 w-full border border-void-border-2 bg-void-bg-1 text-void-fg-3 text-root
|
||||
py-[4px] px-[6px]
|
||||
@@ -141,8 +146,8 @@ const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
|
||||
</div>
|
||||
|
||||
{/* button */}
|
||||
<div className='max-w-40'>
|
||||
<VoidButton onClick={() => {
|
||||
<VoidButton
|
||||
onClick={() => {
|
||||
const modelName = modelNameRef.current?.value
|
||||
|
||||
if (providerName === null) {
|
||||
@@ -161,16 +166,16 @@ const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
|
||||
|
||||
settingsStateService.addModel(providerName, modelName)
|
||||
onSubmit()
|
||||
|
||||
}}
|
||||
>Add model</VoidButton>
|
||||
</div>
|
||||
>Add model</VoidButton>
|
||||
|
||||
{!errorString ? null : <div className='text-red-500 truncate whitespace-nowrap'>
|
||||
{errorString}
|
||||
</div>}
|
||||
<button onClick={onClose} className='ml-auto'><X className='size-4' /></button>
|
||||
</div>
|
||||
|
||||
{!errorString ? null : <div className='text-red-500 truncate whitespace-nowrap mt-1'>
|
||||
{errorString}
|
||||
</div>}
|
||||
|
||||
</>
|
||||
|
||||
}
|
||||
@@ -178,9 +183,9 @@ const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
|
||||
const AddModelMenuFull = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return <div className='hover:bg-black/10 dark:hover:bg-gray-300/10 py-1 my-4 pb-1 px-3 rounded-sm overflow-hidden '>
|
||||
return <div className='hover:bg-black/10 dark:hover:bg-gray-300/10 py-1 my-4 px-3 rounded-sm overflow-hidden'>
|
||||
{open ?
|
||||
<AddModelMenu onSubmit={() => { setOpen(false) }} />
|
||||
<AddModelMenu onSubmit={() => setOpen(false)} onClose={() => setOpen(false)} />
|
||||
: <VoidButton onClick={() => setOpen(true)}>Add Model</VoidButton>
|
||||
}
|
||||
</div>
|
||||
@@ -230,9 +235,7 @@ export const ModelDump = () => {
|
||||
|
||||
<VoidSwitch
|
||||
value={disabled ? false : !isHidden}
|
||||
onChange={() => {
|
||||
settingsStateService.toggleModelHidden(providerName, modelName)
|
||||
}}
|
||||
onChange={() => { settingsStateService.toggleModelHidden(providerName, modelName) }}
|
||||
disabled={disabled}
|
||||
size='sm'
|
||||
/>
|
||||
@@ -252,48 +255,31 @@ export const ModelDump = () => {
|
||||
|
||||
const ProviderSetting = ({ providerName, settingName }: { providerName: ProviderName, settingName: SettingName }) => {
|
||||
|
||||
|
||||
// const { title: providerTitle, } = displayInfoOfProviderName(providerName)
|
||||
|
||||
const { title: settingTitle, placeholder, isPasswordField, subTextMd } = displayInfoOfSettingName(providerName, settingName)
|
||||
|
||||
const accessor = useAccessor()
|
||||
const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||
const settingsState = useSettingsState()
|
||||
|
||||
let weChangedTextRef = false
|
||||
const settingValue = settingsState.settingsOfProvider[providerName][settingName] as string // this should always be a string in this component
|
||||
if (typeof settingValue !== 'string') {
|
||||
console.log('Error: Provider setting had a non-string value.')
|
||||
return
|
||||
}
|
||||
|
||||
return <ErrorBoundary>
|
||||
<div className='my-1'>
|
||||
<VoidInputBox
|
||||
// placeholder={`${providerTitle} ${settingTitle} (${placeholder})`}
|
||||
placeholder={`${settingTitle} (${placeholder})`}
|
||||
|
||||
onChangeText={useCallback((newVal) => {
|
||||
if (weChangedTextRef) return
|
||||
<VoidSimpleInputBox
|
||||
value={settingValue}
|
||||
onChangeValue={useCallback((newVal) => {
|
||||
voidSettingsService.setSettingOfProvider(providerName, settingName, newVal)
|
||||
}, [voidSettingsService, providerName, settingName])}
|
||||
|
||||
// we are responsible for setting the initial value. always sync the instance whenever there's a change to state.
|
||||
onCreateInstance={useCallback((instance: InputBox) => {
|
||||
const syncInstance = () => {
|
||||
const settingsAtProvider = voidSettingsService.state.settingsOfProvider[providerName];
|
||||
const stateVal = settingsAtProvider[settingName as SettingName]
|
||||
|
||||
// console.log('SYNCING TO', providerName, settingName, stateVal)
|
||||
weChangedTextRef = true
|
||||
instance.value = stateVal as string
|
||||
weChangedTextRef = false
|
||||
}
|
||||
|
||||
syncInstance()
|
||||
const disposable = voidSettingsService.onDidChangeState(syncInstance)
|
||||
return [disposable]
|
||||
}, [voidSettingsService, providerName, settingName])}
|
||||
multiline={false}
|
||||
isPasswordField={isPasswordField}
|
||||
// placeholder={`${providerTitle} ${settingTitle} (${placeholder})`}
|
||||
placeholder={`${settingTitle} (${placeholder})`}
|
||||
passwordBlur={isPasswordField}
|
||||
/>
|
||||
{subTextMd === undefined ? null : <div className='py-1 px-3 opacity-50 text-sm'>
|
||||
<ChatMarkdownRender noSpace string={subTextMd} />
|
||||
<ChatMarkdownRender string={subTextMd} chatMessageLocation={undefined} />
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
@@ -356,7 +342,7 @@ export const VoidProviderSettings = ({ providerNames }: { providerNames: Provide
|
||||
|
||||
|
||||
type TabName = 'models' | 'general'
|
||||
export const AutoRefreshToggle = () => {
|
||||
export const AutoDetectLocalModelsToggle = () => {
|
||||
const settingName: GlobalSettingName = 'autoRefreshModels'
|
||||
|
||||
const accessor = useAccessor()
|
||||
@@ -368,16 +354,19 @@ export const AutoRefreshToggle = () => {
|
||||
// right now this is just `enabled_autoRefreshModels`
|
||||
const enabled = voidSettingsState.globalSettings[settingName]
|
||||
|
||||
return <SubtleButton
|
||||
onClick={() => {
|
||||
voidSettingsService.setGlobalSetting(settingName, !enabled)
|
||||
metricsService.capture('Click', { action: 'Autorefresh Toggle', settingName, enabled: !enabled })
|
||||
}}
|
||||
return <ButtonLeftTextRightOption
|
||||
leftButton={<VoidSwitch
|
||||
size='xxs'
|
||||
value={enabled}
|
||||
onChange={(newVal) => {
|
||||
voidSettingsService.setGlobalSetting(settingName, newVal)
|
||||
metricsService.capture('Click', { action: 'Autorefresh Toggle', settingName, enabled: newVal })
|
||||
}}
|
||||
/>}
|
||||
text={`Automatically detect local providers and models (${refreshableProviderNames.map(providerName => displayInfoOfProviderName(providerName).title).join(', ')}).`}
|
||||
icon={enabled ? <Check className='stroke-green-500 size-3' /> : <X className='stroke-red-500 size-3' />}
|
||||
disabled={false}
|
||||
/>
|
||||
|
||||
|
||||
}
|
||||
|
||||
export const AIInstructionsBox = () => {
|
||||
@@ -395,15 +384,43 @@ export const AIInstructionsBox = () => {
|
||||
/>
|
||||
}
|
||||
|
||||
const FastApplyMethodDropdown = () => {
|
||||
const accessor = useAccessor()
|
||||
const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||
|
||||
const options = useMemo(() => [true, false], [])
|
||||
|
||||
const onChangeOption = useCallback((newVal: boolean) => {
|
||||
voidSettingsService.setGlobalSetting('enableFastApply', newVal)
|
||||
}, [voidSettingsService])
|
||||
|
||||
return <VoidCustomDropdownBox
|
||||
className='text-xs text-void-fg-3 bg-void-bg-1 border border-void-border-1 rounded p-0.5 px-1'
|
||||
options={options}
|
||||
selectedOption={voidSettingsService.state.globalSettings.enableFastApply}
|
||||
onChangeOption={onChangeOption}
|
||||
getOptionDisplayName={(val) => val ? 'Fast Apply' : 'Slow Apply'}
|
||||
getOptionDropdownName={(val) => val ? 'Fast Apply' : 'Slow Apply'}
|
||||
getOptionDropdownDetail={(val) => val ? 'Output Search/Replace blocks' : 'Rewrite whole files'}
|
||||
getOptionsEqual={(a, b) => a === b}
|
||||
/>
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const FeaturesTab = () => {
|
||||
const voidSettingsState = useSettingsState()
|
||||
const accessor = useAccessor()
|
||||
const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||
|
||||
|
||||
return <>
|
||||
<h2 className={`text-3xl mb-2`}>Models</h2>
|
||||
<ErrorBoundary>
|
||||
<AutoRefreshToggle />
|
||||
<RefreshableModels />
|
||||
<div className='py-2' />
|
||||
<ModelDump />
|
||||
<AddModelMenuFull />
|
||||
<AutoDetectLocalModelsToggle />
|
||||
<RefreshableModels />
|
||||
</ErrorBoundary>
|
||||
|
||||
|
||||
@@ -412,12 +429,12 @@ export const FeaturesTab = () => {
|
||||
{/* <h3 className={`opacity-50 mb-2`}>{`Instructions:`}</h3> */}
|
||||
{/* <h3 className={`mb-2`}>{`Void can access any model that you host locally. We automatically detect your local models by default.`}</h3> */}
|
||||
<h3 className={`text-void-fg-3 mb-2`}>{`Void can access any model that you host locally. We automatically detect your local models by default.`}</h3>
|
||||
<div className='pl-4 opacity-50'>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender noSpace string={`1. Download [Ollama](https://ollama.com/download).`} /></span>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender noSpace string={`2. Open your terminal.`} /></span>
|
||||
<span className={`text-sm mb-2 select-text`}><ChatMarkdownRender noSpace string={`3. Run \`ollama run llama3.1:8b\`. This installs Meta's llama3.1 model which is best for chat and inline edits. Requires 5GB of memory.`} /></span>
|
||||
<span className={`text-sm mb-2 select-text`}><ChatMarkdownRender noSpace string={`4. Run \`ollama run qwen2.5-coder:1.5b\`. This installs a faster autocomplete model. Requires 1GB of memory.`} /></span>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender noSpace string={`Void automatically detects locally running models and enables them.`} /></span>
|
||||
<div className='pl-4 prose-ol:list-decimal opacity-80'>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender string={`1. Download [Ollama](https://ollama.com/download).`} chatMessageLocation={undefined} /></span>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender string={`2. Open your terminal.`} chatMessageLocation={undefined} /></span>
|
||||
<span className={`text-sm mb-2 select-text`}><ChatMarkdownRender string={`3. Run \`ollama run llama3.1:8b\`. This installs Meta's llama3.1 model which is best for chat and inline edits. Requires 5GB of memory.`} chatMessageLocation={undefined} /></span>
|
||||
<span className={`text-sm mb-2 select-text`}><ChatMarkdownRender string={`4. Run \`ollama run qwen2.5-coder:1.5b\`. This installs a faster autocomplete model. Requires 1GB of memory.`} chatMessageLocation={undefined} /></span>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender string={`Void automatically detects locally running models and enables them.`} chatMessageLocation={undefined} /></span>
|
||||
{/* TODO we should create UI for downloading models without user going into terminal */}
|
||||
</div>
|
||||
|
||||
@@ -434,17 +451,96 @@ export const FeaturesTab = () => {
|
||||
|
||||
|
||||
|
||||
<h2 className={`text-3xl mb-2 mt-12`}>Feature Options</h2>
|
||||
<h2 className={`text-3xl mt-12`}>Feature Options</h2>
|
||||
<ErrorBoundary>
|
||||
{featureNames.map(featureName =>
|
||||
(['Ctrl+L', 'Ctrl+K'] as FeatureName[]).includes(featureName) ? null :
|
||||
<div key={featureName}
|
||||
className='mb-2'
|
||||
>
|
||||
<h4 className={`text-void-fg-3`}>{displayInfoOfFeatureName(featureName)}</h4>
|
||||
<ModelDropdown featureName={featureName} />
|
||||
{/* L1 */}
|
||||
<div className='flex items-start justify-around mt-4 my-4 gap-x-8'>
|
||||
{/* FIM */}
|
||||
<div className='w-full'>
|
||||
<h4 className={`text-base`}>{displayInfoOfFeatureName('Autocomplete')}</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>Experimental. Only works with models that support FIM.</div>
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Enable Switch */}
|
||||
<div className='flex items-center gap-x-2 my-2'>
|
||||
<VoidSwitch
|
||||
size='xs'
|
||||
value={voidSettingsState.globalSettings.enableAutocomplete}
|
||||
onChange={(newVal) => voidSettingsService.setGlobalSetting('enableAutocomplete', newVal)}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.enableAutocomplete ? 'Enabled' : 'Disabled'}</span>
|
||||
</div>
|
||||
{/* Model Dropdown */}
|
||||
<div className={`my-2 ${!voidSettingsState.globalSettings.enableAutocomplete ? 'hidden' : ''}`}>
|
||||
<ModelDropdown featureName={'Autocomplete'} className='text-xs text-void-fg-3 bg-void-bg-1 border border-void-border-1 rounded p-0.5 px-1' />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
{/* Apply */}
|
||||
<div className='w-full'>
|
||||
<h4 className={`text-base`}>{displayInfoOfFeatureName('Apply')}</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>Settings that control the behavior of the Apply button and the Edit tool.</div>
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Sync to Chat Switch */}
|
||||
<div className='flex items-center gap-x-2 my-2'>
|
||||
<VoidSwitch
|
||||
size='xs'
|
||||
value={voidSettingsState.globalSettings.syncApplyToChat}
|
||||
onChange={(newVal) => voidSettingsService.setGlobalSetting('syncApplyToChat', newVal)}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.syncApplyToChat ? 'Same as Chat model' : 'Different model'}</span>
|
||||
</div>
|
||||
|
||||
{/* Model Dropdown */}
|
||||
<div className={`my-2 ${voidSettingsState.globalSettings.syncApplyToChat ? 'hidden' : ''}`}>
|
||||
<ModelDropdown featureName={'Apply'} className='text-xs text-void-fg-3 bg-void-bg-1 border border-void-border-1 rounded p-0.5 px-1' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Fast Apply Method Dropdown */}
|
||||
<div className='flex items-center gap-x-2 my-2'>
|
||||
<FastApplyMethodDropdown />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* L2 */}
|
||||
<div className='flex items-start justify-around my-4 gap-x-8'>
|
||||
|
||||
{/* Tools Section */}
|
||||
<div className='w-full'>
|
||||
<h4 className={`text-base`}>Tools</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>{`Tools are functions that LLMs can call. Some tools require user approval.`}</div>
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Auto Accept Switch */}
|
||||
<div className='flex items-center gap-x-2 my-2'>
|
||||
<VoidSwitch
|
||||
size='xs'
|
||||
value={voidSettingsState.globalSettings.autoApprove}
|
||||
onChange={(newVal) => voidSettingsService.setGlobalSetting('autoApprove', newVal)}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.autoApprove ? 'Auto-approve' : 'Auto-approve'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='w-full'>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div className='py-8' />
|
||||
|
||||
</ErrorBoundary>
|
||||
|
||||
</>
|
||||
@@ -568,6 +664,8 @@ const OneClickSwitchButton = () => {
|
||||
const GeneralTab = () => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const environmentService = accessor.get('IEnvironmentService')
|
||||
const nativeHostService = accessor.get('INativeHostService')
|
||||
|
||||
return <>
|
||||
|
||||
@@ -599,6 +697,11 @@ const GeneralTab = () => {
|
||||
Theme Settings
|
||||
</VoidButton>
|
||||
</div>
|
||||
<div className='my-4'>
|
||||
<VoidButton onClick={() => { nativeHostService.showItemInFolder(environmentService.logsHome.fsPath) }}>
|
||||
Open Logs
|
||||
</VoidButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -649,7 +752,7 @@ export const Settings = () => {
|
||||
|
||||
|
||||
{/* content */}
|
||||
<div className='w-full min-w-[600px] overflow-auto'>
|
||||
<div className='w-full min-w-[550px]'>
|
||||
|
||||
<div className={`${tab !== 'models' ? 'hidden' : ''}`}>
|
||||
<FeaturesTab />
|
||||
|
||||
@@ -9,6 +9,28 @@ module.exports = {
|
||||
content: ['./src2/**/*.{jsx,tsx}'], // uses these files to decide how to transform the css file
|
||||
theme: {
|
||||
extend: {
|
||||
typography: theme => ({
|
||||
DEFAULT: {
|
||||
css: {
|
||||
'--tw-prose-body': 'var(--void-fg-1)',
|
||||
'--tw-prose-headings': 'var(--void-fg-1)',
|
||||
'--tw-prose-lead': 'var(--void-fg-2)',
|
||||
'--tw-prose-links': 'var(--void-link-color)',
|
||||
'--tw-prose-bold': 'var(--void-fg-1)',
|
||||
'--tw-prose-counters': 'var(--void-fg-3)',
|
||||
'--tw-prose-bullets': 'var(--void-fg-3)',
|
||||
'--tw-prose-hr': 'var(--void-border-4)',
|
||||
'--tw-prose-quotes': 'var(--void-fg-1)',
|
||||
'--tw-prose-quote-borders': 'var(--void-border-2)',
|
||||
'--tw-prose-captions': 'var(--void-fg-3)',
|
||||
'--tw-prose-code': 'var(--void-fg-0)',
|
||||
'--tw-prose-pre-code': 'var(--void-fg-0)',
|
||||
'--tw-prose-pre-bg': 'var(--void-bg-1)',
|
||||
'--tw-prose-th-borders': 'var(--void-border-4)',
|
||||
'--tw-prose-td-borders': 'var(--void-border-4)',
|
||||
},
|
||||
},
|
||||
}),
|
||||
fontSize: {
|
||||
xs: '10px',
|
||||
sm: '11px',
|
||||
@@ -27,146 +49,150 @@ module.exports = {
|
||||
// common colors to use, ordered light to dark
|
||||
|
||||
colors: {
|
||||
"void-bg-1": "var(--vscode-input-background)",
|
||||
"void-bg-1-alt": "var(--vscode-badge-background)",
|
||||
"void-bg-2": "var(--vscode-sideBar-background)",
|
||||
"void-bg-2-alt": "color-mix(in srgb, var(--vscode-sideBar-background) 30%, var(--vscode-editor-background) 70%)",
|
||||
"void-bg-3": "var(--vscode-editor-background)",
|
||||
'void-bg-1': 'var(--void-bg-1)',
|
||||
'void-bg-1-alt': 'var(--void-bg-1-alt)',
|
||||
'void-bg-2': 'var(--void-bg-2)',
|
||||
'void-bg-2-alt': 'var(--void-bg-2-alt)',
|
||||
'void-bg-3': 'var(--void-bg-3)',
|
||||
|
||||
|
||||
"void-fg-1": "var(--vscode-editor-foreground)",
|
||||
"void-fg-2": "var(--vscode-input-foreground)",
|
||||
"void-fg-3": "var(--vscode-input-placeholderForeground)",
|
||||
// "void-fg-4": "var(--vscode-tab-inactiveForeground)",
|
||||
"void-fg-4": "var(--vscode-list-deemphasizedForeground)",
|
||||
'void-fg-0': 'var(--void-fg-0)',
|
||||
'void-fg-1': 'var(--void-fg-1)',
|
||||
'void-fg-2': 'var(--void-fg-2)',
|
||||
'void-fg-3': 'var(--void-fg-3)',
|
||||
// 'void-fg-4': 'var(--vscode-tab-inactiveForeground)',
|
||||
'void-fg-4': 'var(--void-fg-4)',
|
||||
|
||||
'void-warning': 'var(--void-warning)',
|
||||
|
||||
"void-warning": "var(--vscode-charts-yellow)",
|
||||
|
||||
"void-border-1": "var(--vscode-commandCenter-activeBorder)",
|
||||
"void-border-2": "var(--vscode-commandCenter-border)",
|
||||
"void-border-3": "var(--vscode-commandCenter-inactiveBorder)",
|
||||
"void-border-3": "var(--vscode-settings-sashBorder)",
|
||||
'void-border-1': 'var(--void-border-1)',
|
||||
'void-border-2': 'var(--void-border-2)',
|
||||
'void-border-3': 'var(--void-border-3)',
|
||||
'void-border-4': 'var(--void-border-4)',
|
||||
|
||||
'void-ring-color': 'var(--void-ring-color)',
|
||||
'void-link-color': 'var(--void-link-color)',
|
||||
|
||||
vscode: {
|
||||
// see: https://code.visualstudio.com/api/extension-guides/webview#theming-webview-content
|
||||
|
||||
// base colors
|
||||
"fg": "var(--vscode-foreground)",
|
||||
"focus-border": "var(--vscode-focusBorder)",
|
||||
"disabled-fg": "var(--vscode-disabledForeground)",
|
||||
"widget-border": "var(--vscode-widget-border)",
|
||||
"widget-shadow": "var(--vscode-widget-shadow)",
|
||||
"selection-bg": "var(--vscode-selection-background)",
|
||||
"description-fg": "var(--vscode-descriptionForeground)",
|
||||
"error-fg": "var(--vscode-errorForeground)",
|
||||
"icon-fg": "var(--vscode-icon-foreground)",
|
||||
"sash-hover-border": "var(--vscode-sash-hoverBorder)",
|
||||
'fg': 'var(--vscode-foreground)',
|
||||
'focus-border': 'var(--vscode-focusBorder)',
|
||||
'disabled-fg': 'var(--vscode-disabledForeground)',
|
||||
'widget-border': 'var(--vscode-widget-border)',
|
||||
'widget-shadow': 'var(--vscode-widget-shadow)',
|
||||
'selection-bg': 'var(--vscode-selection-background)',
|
||||
'description-fg': 'var(--vscode-descriptionForeground)',
|
||||
'error-fg': 'var(--vscode-errorForeground)',
|
||||
'icon-fg': 'var(--vscode-icon-foreground)',
|
||||
'sash-hover-border': 'var(--vscode-sash-hoverBorder)',
|
||||
|
||||
// text colors
|
||||
"text-blockquote-bg": "var(--vscode-textBlockQuote-background)",
|
||||
"text-blockquote-border": "var(--vscode-textBlockQuote-border)",
|
||||
"text-codeblock-bg": "var(--vscode-textCodeBlock-background)",
|
||||
"text-link-active-fg": "var(--vscode-textLink-activeForeground)",
|
||||
"text-link-fg": "var(--vscode-textLink-foreground)",
|
||||
"text-preformat-fg": "var(--vscode-textPreformat-foreground)",
|
||||
"text-preformat-bg": "var(--vscode-textPreformat-background)",
|
||||
"text-separator-fg": "var(--vscode-textSeparator-foreground)",
|
||||
'text-blockquote-bg': 'var(--vscode-textBlockQuote-background)',
|
||||
'text-blockquote-border': 'var(--vscode-textBlockQuote-border)',
|
||||
'text-codeblock-bg': 'var(--vscode-textCodeBlock-background)',
|
||||
'text-link-active-fg': 'var(--vscode-textLink-activeForeground)',
|
||||
'text-link-fg': 'var(--vscode-textLink-foreground)',
|
||||
'text-preformat-fg': 'var(--vscode-textPreformat-foreground)',
|
||||
'text-preformat-bg': 'var(--vscode-textPreformat-background)',
|
||||
'text-separator-fg': 'var(--vscode-textSeparator-foreground)',
|
||||
|
||||
// input colors
|
||||
"input-bg": "var(--vscode-input-background)",
|
||||
"input-border": "var(--vscode-input-border)",
|
||||
"input-fg": "var(--vscode-input-foreground)",
|
||||
"input-placeholder-fg": "var(--vscode-input-placeholderForeground)",
|
||||
"input-active-bg": "var(--vscode-input-activeBackground)",
|
||||
"input-option-active-border": "var(--vscode-inputOption-activeBorder)",
|
||||
"input-option-active-fg": "var(--vscode-inputOption-activeForeground)",
|
||||
"input-option-hover-bg": "var(--vscode-inputOption-hoverBackground)",
|
||||
"input-validation-error-bg": "var(--vscode-inputValidation-errorBackground)",
|
||||
"input-validation-error-fg": "var(--vscode-inputValidation-errorForeground)",
|
||||
"input-validation-error-border": "var(--vscode-inputValidation-errorBorder)",
|
||||
"input-validation-info-bg": "var(--vscode-inputValidation-infoBackground)",
|
||||
"input-validation-info-fg": "var(--vscode-inputValidation-infoForeground)",
|
||||
"input-validation-info-border": "var(--vscode-inputValidation-infoBorder)",
|
||||
"input-validation-warning-bg": "var(--vscode-inputValidation-warningBackground)",
|
||||
"input-validation-warning-fg": "var(--vscode-inputValidation-warningForeground)",
|
||||
"input-validation-warning-border": "var(--vscode-inputValidation-warningBorder)",
|
||||
'input-bg': 'var(--vscode-input-background)',
|
||||
'input-border': 'var(--vscode-input-border)',
|
||||
'input-fg': 'var(--vscode-input-foreground)',
|
||||
'input-placeholder-fg': 'var(--vscode-input-placeholderForeground)',
|
||||
'input-active-bg': 'var(--vscode-input-activeBackground)',
|
||||
'input-option-active-border': 'var(--vscode-inputOption-activeBorder)',
|
||||
'input-option-active-fg': 'var(--vscode-inputOption-activeForeground)',
|
||||
'input-option-hover-bg': 'var(--vscode-inputOption-hoverBackground)',
|
||||
'input-validation-error-bg': 'var(--vscode-inputValidation-errorBackground)',
|
||||
'input-validation-error-fg': 'var(--vscode-inputValidation-errorForeground)',
|
||||
'input-validation-error-border': 'var(--vscode-inputValidation-errorBorder)',
|
||||
'input-validation-info-bg': 'var(--vscode-inputValidation-infoBackground)',
|
||||
'input-validation-info-fg': 'var(--vscode-inputValidation-infoForeground)',
|
||||
'input-validation-info-border': 'var(--vscode-inputValidation-infoBorder)',
|
||||
'input-validation-warning-bg': 'var(--vscode-inputValidation-warningBackground)',
|
||||
'input-validation-warning-fg': 'var(--vscode-inputValidation-warningForeground)',
|
||||
'input-validation-warning-border': 'var(--vscode-inputValidation-warningBorder)',
|
||||
|
||||
// command center colors (the top bar)
|
||||
"commandcenter-fg": "var(--vscode-commandCenter-foreground)",
|
||||
"commandcenter-active-fg": "var(--vscode-commandCenter-activeForeground)",
|
||||
"commandcenter-bg": "var(--vscode-commandCenter-background)",
|
||||
"commandcenter-active-bg": "var(--vscode-commandCenter-activeBackground)",
|
||||
"commandcenter-border": "var(--vscode-commandCenter-border)",
|
||||
"commandcenter-inactive-fg": "var(--vscode-commandCenter-inactiveForeground)",
|
||||
"commandcenter-inactive-border": "var(--vscode-commandCenter-inactiveBorder)",
|
||||
"commandcenter-active-border": "var(--vscode-commandCenter-activeBorder)",
|
||||
"commandcenter-debugging-bg": "var(--vscode-commandCenter-debuggingBackground)",
|
||||
'commandcenter-fg': 'var(--vscode-commandCenter-foreground)',
|
||||
'commandcenter-active-fg': 'var(--vscode-commandCenter-activeForeground)',
|
||||
'commandcenter-bg': 'var(--vscode-commandCenter-background)',
|
||||
'commandcenter-active-bg': 'var(--vscode-commandCenter-activeBackground)',
|
||||
'commandcenter-border': 'var(--vscode-commandCenter-border)',
|
||||
'commandcenter-inactive-fg': 'var(--vscode-commandCenter-inactiveForeground)',
|
||||
'commandcenter-inactive-border': 'var(--vscode-commandCenter-inactiveBorder)',
|
||||
'commandcenter-active-border': 'var(--vscode-commandCenter-activeBorder)',
|
||||
'commandcenter-debugging-bg': 'var(--vscode-commandCenter-debuggingBackground)',
|
||||
|
||||
// badge colors
|
||||
"badge-fg": "var(--vscode-badge-foreground)",
|
||||
"badge-bg": "var(--vscode-badge-background)",
|
||||
'badge-fg': 'var(--vscode-badge-foreground)',
|
||||
'badge-bg': 'var(--vscode-badge-background)',
|
||||
|
||||
// button colors
|
||||
"button-bg": "var(--vscode-button-background)",
|
||||
"button-fg": "var(--vscode-button-foreground)",
|
||||
"button-border": "var(--vscode-button-border)",
|
||||
"button-separator": "var(--vscode-button-separator)",
|
||||
"button-hover-bg": "var(--vscode-button-hoverBackground)",
|
||||
"button-secondary-fg": "var(--vscode-button-secondaryForeground)",
|
||||
"button-secondary-bg": "var(--vscode-button-secondaryBackground)",
|
||||
"button-secondary-hover-bg": "var(--vscode-button-secondaryHoverBackground)",
|
||||
'button-bg': 'var(--vscode-button-background)',
|
||||
'button-fg': 'var(--vscode-button-foreground)',
|
||||
'button-border': 'var(--vscode-button-border)',
|
||||
'button-separator': 'var(--vscode-button-separator)',
|
||||
'button-hover-bg': 'var(--vscode-button-hoverBackground)',
|
||||
'button-secondary-fg': 'var(--vscode-button-secondaryForeground)',
|
||||
'button-secondary-bg': 'var(--vscode-button-secondaryBackground)',
|
||||
'button-secondary-hover-bg': 'var(--vscode-button-secondaryHoverBackground)',
|
||||
|
||||
// checkbox colors
|
||||
"checkbox-bg": "var(--vscode-checkbox-background)",
|
||||
"checkbox-fg": "var(--vscode-checkbox-foreground)",
|
||||
"checkbox-border": "var(--vscode-checkbox-border)",
|
||||
"checkbox-select-bg": "var(--vscode-checkbox-selectBackground)",
|
||||
'checkbox-bg': 'var(--vscode-checkbox-background)',
|
||||
'checkbox-fg': 'var(--vscode-checkbox-foreground)',
|
||||
'checkbox-border': 'var(--vscode-checkbox-border)',
|
||||
'checkbox-select-bg': 'var(--vscode-checkbox-selectBackground)',
|
||||
|
||||
// sidebar colors
|
||||
"sidebar-bg": "var(--vscode-sideBar-background)",
|
||||
"sidebar-fg": "var(--vscode-sideBar-foreground)",
|
||||
"sidebar-border": "var(--vscode-sideBar-border)",
|
||||
"sidebar-drop-bg": "var(--vscode-sideBar-dropBackground)",
|
||||
"sidebar-title-fg": "var(--vscode-sideBarTitle-foreground)",
|
||||
"sidebar-header-bg": "var(--vscode-sideBarSectionHeader-background)",
|
||||
"sidebar-header-fg": "var(--vscode-sideBarSectionHeader-foreground)",
|
||||
"sidebar-header-border": "var(--vscode-sideBarSectionHeader-border)",
|
||||
"sidebar-activitybartop-border": "var(--vscode-sideBarActivityBarTop-border)",
|
||||
"sidebar-title-bg": "var(--vscode-sideBarTitle-background)",
|
||||
"sidebar-title-border": "var(--vscode-sideBarTitle-border)",
|
||||
"sidebar-stickyscroll-bg": "var(--vscode-sideBarStickyScroll-background)",
|
||||
"sidebar-stickyscroll-border": "var(--vscode-sideBarStickyScroll-border)",
|
||||
"sidebar-stickyscroll-shadow": "var(--vscode-sideBarStickyScroll-shadow)",
|
||||
'sidebar-bg': 'var(--vscode-sideBar-background)',
|
||||
'sidebar-fg': 'var(--vscode-sideBar-foreground)',
|
||||
'sidebar-border': 'var(--vscode-sideBar-border)',
|
||||
'sidebar-drop-bg': 'var(--vscode-sideBar-dropBackground)',
|
||||
'sidebar-title-fg': 'var(--vscode-sideBarTitle-foreground)',
|
||||
'sidebar-header-bg': 'var(--vscode-sideBarSectionHeader-background)',
|
||||
'sidebar-header-fg': 'var(--vscode-sideBarSectionHeader-foreground)',
|
||||
'sidebar-header-border': 'var(--vscode-sideBarSectionHeader-border)',
|
||||
'sidebar-activitybartop-border': 'var(--vscode-sideBarActivityBarTop-border)',
|
||||
'sidebar-title-bg': 'var(--vscode-sideBarTitle-background)',
|
||||
'sidebar-title-border': 'var(--vscode-sideBarTitle-border)',
|
||||
'sidebar-stickyscroll-bg': 'var(--vscode-sideBarStickyScroll-background)',
|
||||
'sidebar-stickyscroll-border': 'var(--vscode-sideBarStickyScroll-border)',
|
||||
'sidebar-stickyscroll-shadow': 'var(--vscode-sideBarStickyScroll-shadow)',
|
||||
|
||||
// other colors (these are partially complete)
|
||||
|
||||
// text formatting
|
||||
"text-preformat-bg": "var(--vscode-textPreformat-background)",
|
||||
"text-preformat-fg": "var(--vscode-textPreformat-foreground)",
|
||||
'text-preformat-bg': 'var(--vscode-textPreformat-background)',
|
||||
'text-preformat-fg': 'var(--vscode-textPreformat-foreground)',
|
||||
|
||||
// editor colors
|
||||
"editor-bg": "var(--vscode-editor-background)",
|
||||
"editor-fg": "var(--vscode-editor-foreground)",
|
||||
'editor-bg': 'var(--vscode-editor-background)',
|
||||
'editor-fg': 'var(--vscode-editor-foreground)',
|
||||
|
||||
|
||||
|
||||
// other
|
||||
"editorwidget-bg": "var(--vscode-editorWidget-background)",
|
||||
"toolbar-hover-bg": "var(--vscode-toolbar-hoverBackground)",
|
||||
"toolbar-foreground": "var(--vscode-editorActionList-foreground)",
|
||||
'editorwidget-bg': 'var(--vscode-editorWidget-background)',
|
||||
'toolbar-hover-bg': 'var(--vscode-toolbar-hoverBackground)',
|
||||
'toolbar-foreground': 'var(--vscode-editorActionList-foreground)',
|
||||
|
||||
"editorwidget-fg": "var(--vscode-editorWidget-foreground)",
|
||||
"editorwidget-border": "var(--vscode-editorWidget-border)",
|
||||
'editorwidget-fg': 'var(--vscode-editorWidget-foreground)',
|
||||
'editorwidget-border': 'var(--vscode-editorWidget-border)',
|
||||
|
||||
"charts-orange": "var(--vscode-charts-orange)",
|
||||
"charts-yellow": "var(--vscode-charts-yellow)",
|
||||
'charts-orange': 'var(--vscode-charts-orange)',
|
||||
'charts-yellow': 'var(--vscode-charts-yellow)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
plugins: [
|
||||
require('@tailwindcss/typography')
|
||||
],
|
||||
prefix: 'void-'
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig({
|
||||
entry: [
|
||||
'./src2/void-command-bar-tsx/index.tsx',
|
||||
'./src2/sidebar-tsx/index.tsx',
|
||||
'./src2/void-settings-tsx/index.tsx',
|
||||
'./src2/quick-edit-tsx/index.tsx',
|
||||
|
||||
@@ -7,8 +7,6 @@ import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { ILLMMessageService } from '../common/llmMessageService.js';
|
||||
import { ServiceSendLLMMessageParams } from '../common/llmMessageTypes.js';
|
||||
|
||||
|
||||
|
||||
@@ -17,29 +15,29 @@ export interface ISearchReplaceService {
|
||||
}
|
||||
|
||||
export const ISearchReplaceService = createDecorator<ISearchReplaceService>('SearchReplaceCacheService');
|
||||
class SearchReplaceService extends Disposable implements ISearchReplaceService {
|
||||
export class SearchReplaceService extends Disposable implements ISearchReplaceService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private readonly _onDidChangeState = new Emitter<void>();
|
||||
readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
|
||||
|
||||
constructor(
|
||||
@ILLMMessageService private readonly llmMessageService: ILLMMessageService,
|
||||
// @ILLMMessageService private readonly llmMessageService: ILLMMessageService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
send(params: Omit<ServiceSendLLMMessageParams, 'onText'> & { onText: (p: { newText: string, fullText: string }) => { retry: boolean } }) {
|
||||
this.llmMessageService.sendLLMMessage({
|
||||
...params as ServiceSendLLMMessageParams,
|
||||
onText: (p) => {
|
||||
const { retry } = params.onText(p)
|
||||
if (retry) {
|
||||
// send(params: ServiceSendLLMMessageParams & { onText: (p: { newText: string, fullText: string }) => { retry: boolean } }) {
|
||||
// this.llmMessageService.sendLLMMessage({
|
||||
// ...params as ServiceSendLLMMessageParams,
|
||||
// onText: (p) => {
|
||||
// const { retry } = params.onText(p)
|
||||
// if (retry) {
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -11,24 +11,20 @@ import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js
|
||||
|
||||
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
|
||||
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { StagingSelectionItem, IChatThreadService } from './chatThreadService.js';
|
||||
|
||||
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
||||
import { IRange } from '../../../../editor/common/core/range.js';
|
||||
import { ITextModel } from '../../../../editor/common/model.js';
|
||||
import { VOID_VIEW_CONTAINER_ID, VOID_VIEW_ID } from './sidebarPane.js';
|
||||
import { VOID_VIEW_ID } from './sidebarPane.js';
|
||||
import { IMetricsService } from '../common/metricsService.js';
|
||||
import { ISidebarStateService } from './sidebarStateService.js';
|
||||
import { ICommandService } from '../../../../platform/commands/common/commands.js';
|
||||
import { VOID_TOGGLE_SETTINGS_ACTION_ID } from './voidSettingsPane.js';
|
||||
import { VOID_CTRL_L_ACTION_ID } from './actionIDs.js';
|
||||
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
|
||||
import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { localize2 } from '../../../../nls.js';
|
||||
import { IViewsService } from '../../../services/views/common/viewsService.js';
|
||||
import { IVoidUriStateService } from './voidUriStateService.js';
|
||||
import { StagingSelectionItem } from '../common/chatThreadServiceTypes.js';
|
||||
import { IChatThreadService } from './chatThreadService.js';
|
||||
|
||||
// ---------- Register commands and keybindings ----------
|
||||
|
||||
@@ -56,23 +52,41 @@ export const roundRangeToLines = (range: IRange | null | undefined, options: { e
|
||||
return newRange
|
||||
}
|
||||
|
||||
const getContentInRange = (model: ITextModel, range: IRange | null) => {
|
||||
if (!range)
|
||||
return null
|
||||
const content = model.getValueInRange(range)
|
||||
const trimmedContent = content
|
||||
.replace(/^\s*\n/g, '') // trim pure whitespace lines from start
|
||||
.replace(/\n\s*$/g, '') // trim pure whitespace lines from end
|
||||
return trimmedContent
|
||||
}
|
||||
// const getContentInRange = (model: ITextModel, range: IRange | null) => {
|
||||
// if (!range)
|
||||
// return null
|
||||
// const content = model.getValueInRange(range)
|
||||
// const trimmedContent = content
|
||||
// .replace(/^\s*\n/g, '') // trim pure whitespace lines from start
|
||||
// .replace(/\n\s*$/g, '') // trim pure whitespace lines from end
|
||||
// return trimmedContent
|
||||
// }
|
||||
|
||||
|
||||
const findMatchingStagingIndex = (currentSelections: StagingSelectionItem[] | undefined, newSelection: StagingSelectionItem) => {
|
||||
return currentSelections?.findIndex(s =>
|
||||
s.fileURI.fsPath === newSelection.fileURI.fsPath
|
||||
&& s.range?.startLineNumber === newSelection.range?.startLineNumber
|
||||
&& s.range?.endLineNumber === newSelection.range?.endLineNumber
|
||||
)
|
||||
const findStagingItemToReplace = (currentSelections: StagingSelectionItem[] | undefined, newSelection: StagingSelectionItem): [number, StagingSelectionItem] | null => {
|
||||
if (!currentSelections) return null
|
||||
|
||||
for (let i = 0; i < currentSelections.length; i += 1) {
|
||||
const s = currentSelections[i]
|
||||
|
||||
if (s.uri.fsPath !== newSelection.uri.fsPath) continue
|
||||
|
||||
if (s.type === 'File' && newSelection.type === 'File') {
|
||||
return [i, s] as const
|
||||
}
|
||||
if (s.type === 'CodeSelection' && newSelection.type === 'CodeSelection') {
|
||||
if (s.uri.fsPath !== newSelection.uri.fsPath) continue
|
||||
// if there's any collision return true
|
||||
const [oldStart, oldEnd] = s.range
|
||||
const [newStart, newEnd] = newSelection.range
|
||||
if (oldStart !== newStart || oldEnd !== newEnd) continue
|
||||
return [i, s] as const
|
||||
}
|
||||
if (s.type === 'Folder' && newSelection.type === 'Folder') {
|
||||
return [i, s] as const
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const VOID_OPEN_SIDEBAR_ACTION_ID = 'void.sidebar.open'
|
||||
@@ -117,45 +131,49 @@ registerAction2(class extends Action2 {
|
||||
editor?.setSelection({ startLineNumber: selectionRange.startLineNumber, endLineNumber: selectionRange.endLineNumber, startColumn: 1, endColumn: Number.MAX_SAFE_INTEGER })
|
||||
}
|
||||
|
||||
const selectionStr = getContentInRange(model, selectionRange)
|
||||
|
||||
const selection: StagingSelectionItem = !selectionRange || !selectionStr || (selectionRange.startLineNumber > selectionRange.endLineNumber) ? {
|
||||
const selection: StagingSelectionItem = !selectionRange || (selectionRange.startLineNumber > selectionRange.endLineNumber) ? {
|
||||
type: 'File',
|
||||
fileURI: model.uri,
|
||||
selectionStr: null,
|
||||
range: null,
|
||||
uri: model.uri,
|
||||
language: model.getLanguageId(),
|
||||
state: { wasAddedAsCurrentFile: false }
|
||||
} : {
|
||||
type: 'Selection',
|
||||
fileURI: model.uri,
|
||||
selectionStr: selectionStr,
|
||||
range: selectionRange,
|
||||
type: 'CodeSelection',
|
||||
uri: model.uri,
|
||||
language: model.getLanguageId(),
|
||||
range: [selectionRange.startLineNumber, selectionRange.endLineNumber],
|
||||
state: { wasAddedAsCurrentFile: false }
|
||||
}
|
||||
|
||||
// update the staging selections
|
||||
const chatThreadService = accessor.get(IChatThreadService)
|
||||
|
||||
const focusedMessageIdx = chatThreadService.getFocusedMessageIdx()
|
||||
const focusedMessageIdx = chatThreadService.getCurrentFocusedMessageIdx()
|
||||
|
||||
// set the selections to the proper value
|
||||
let selections: StagingSelectionItem[] = []
|
||||
let setSelections = (s: StagingSelectionItem[]) => { }
|
||||
|
||||
if (focusedMessageIdx === undefined) {
|
||||
selections = chatThreadService.getCurrentThreadStagingSelections()
|
||||
setSelections = (s: StagingSelectionItem[]) => chatThreadService.setCurrentThreadStagingSelections(s)
|
||||
selections = chatThreadService.getCurrentThreadState().stagingSelections
|
||||
setSelections = (s: StagingSelectionItem[]) => chatThreadService.setCurrentThreadState({ stagingSelections: s })
|
||||
} else {
|
||||
selections = chatThreadService.getCurrentMessageState(focusedMessageIdx).stagingSelections
|
||||
setSelections = (s) => chatThreadService.setCurrentMessageState(focusedMessageIdx, { stagingSelections: s })
|
||||
}
|
||||
|
||||
// if matches with existing selection, overwrite (since text may change)
|
||||
const matchingStagingEltIdx = findMatchingStagingIndex(selections, selection)
|
||||
if (matchingStagingEltIdx !== undefined && matchingStagingEltIdx !== -1) {
|
||||
setSelections([
|
||||
...selections!.slice(0, matchingStagingEltIdx),
|
||||
selection,
|
||||
...selections!.slice(matchingStagingEltIdx + 1, Infinity)
|
||||
])
|
||||
const replaceRes = findStagingItemToReplace(selections, selection)
|
||||
if (replaceRes) {
|
||||
const [idx, newSel] = replaceRes
|
||||
|
||||
if (idx !== undefined && idx !== -1) {
|
||||
setSelections([
|
||||
...selections!.slice(0, idx),
|
||||
newSel,
|
||||
...selections!.slice(idx + 1, Infinity)
|
||||
])
|
||||
}
|
||||
}
|
||||
// if no match, add it
|
||||
else {
|
||||
@@ -196,7 +214,11 @@ registerAction2(class extends Action2 {
|
||||
id: 'void.newChatAction',
|
||||
title: 'New Chat',
|
||||
icon: { id: 'add' },
|
||||
menu: [{ id: MenuId.ViewTitle, group: 'navigation', when: ContextKeyExpr.equals('view', VOID_VIEW_ID), }]
|
||||
menu: [{ id: MenuId.ViewTitle, group: 'navigation', when: ContextKeyExpr.equals('view', VOID_VIEW_ID), }],
|
||||
keybinding: {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyL,
|
||||
weight: KeybindingWeight.VoidExtension,
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
@@ -278,43 +300,3 @@ export class TabSwitchListener extends Disposable {
|
||||
this._register(this._editorService.onCodeEditorAdd(editor => { initializeEditor(editor) }))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TabSwitchContribution extends Disposable implements IWorkbenchContribution {
|
||||
static readonly ID = 'workbench.contrib.void.tabswitch'
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IViewsService private readonly viewsService: IViewsService,
|
||||
@IVoidUriStateService private readonly uriStateService: IVoidUriStateService,
|
||||
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
|
||||
// @ICommandService private readonly commandService: ICommandService,
|
||||
) {
|
||||
super()
|
||||
|
||||
// sidebarIsVisible state
|
||||
let sidebarIsVisible = this.viewsService.isViewContainerVisible(VOID_VIEW_CONTAINER_ID)
|
||||
this._register(this.viewsService.onDidChangeViewVisibility(e => {
|
||||
sidebarIsVisible = e.visible
|
||||
}))
|
||||
|
||||
const onSwitchTab = () => { // update state
|
||||
if (sidebarIsVisible) {
|
||||
const currentUri = this.codeEditorService.getActiveCodeEditor()?.getModel()?.uri
|
||||
if (!currentUri) return;
|
||||
this.uriStateService.setState({ currentUri })
|
||||
// this.commandService.executeCommand(VOID_ADD_SELECTION_TO_SIDEBAR_ACTION_ID)
|
||||
}
|
||||
}
|
||||
|
||||
// when sidebar becomes visible, add current file
|
||||
this._register(this.viewsService.onDidChangeViewVisibility(e => { sidebarIsVisible = e.visible }))
|
||||
|
||||
// run on current tab if it exists, and listen for tab switches and visibility changes
|
||||
onSwitchTab()
|
||||
this._register(this.viewsService.onDidChangeViewVisibility(() => { onSwitchTab() }))
|
||||
this._register(this.instantiationService.createInstance(TabSwitchListener, () => { onSwitchTab() }))
|
||||
}
|
||||
}
|
||||
|
||||
registerWorkbenchContribution2(TabSwitchContribution.ID, TabSwitchContribution, WorkbenchPhase.BlockRestore);
|
||||
|
||||
@@ -38,7 +38,7 @@ import { mountSidebar } from './react/out/sidebar-tsx/index.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { Orientation } from '../../../../base/browser/ui/sash/sash.js';
|
||||
// import { IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
|
||||
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
|
||||
import { IViewsService } from '../../../services/views/common/viewsService.js';
|
||||
@@ -80,8 +80,8 @@ class SidebarViewPane extends ViewPane {
|
||||
// gets set immediately
|
||||
this.instantiationService.invokeFunction(accessor => {
|
||||
// mount react
|
||||
const disposables: IDisposable[] | undefined = mountSidebar(parent, accessor);
|
||||
disposables?.forEach(d => this._register(d))
|
||||
const disposeFn: (() => void) | undefined = mountSidebar(parent, accessor)?.dispose;
|
||||
this._register(toDisposable(() => disposeFn?.()))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { removeAnsiEscapeCodes } from '../../../../base/common/strings.js';
|
||||
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { TerminalLocation } from '../../../../platform/terminal/common/terminal.js';
|
||||
import { ITerminalService, ITerminalInstance } from '../../../../workbench/contrib/terminal/browser/terminal.js';
|
||||
import { TerminalResolveReason } from '../common/toolsServiceTypes.js';
|
||||
import { MAX_TERMINAL_CHARS_PAGE, TERMINAL_BG_WAIT_TIME, TERMINAL_TIMEOUT_TIME } from './toolsService.js';
|
||||
|
||||
|
||||
|
||||
export interface ITerminalToolService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
listTerminalIds(): string[];
|
||||
runCommand(command: string, proposedTerminalId: string, waitForCompletion: boolean): Promise<{ terminalId: string, didCreateTerminal: boolean, result: string, resolveReason: TerminalResolveReason }>;
|
||||
openTerminal(terminalId: string): Promise<void>
|
||||
terminalExists(terminalId: string): boolean
|
||||
}
|
||||
|
||||
export const ITerminalToolService = createDecorator<ITerminalToolService>('TerminalToolService');
|
||||
|
||||
|
||||
|
||||
function isCommandComplete(output: string) {
|
||||
// https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st
|
||||
const completionMatch = output.match(/\]633;D(?:;(\d+))?/)
|
||||
if (!completionMatch) { return false }
|
||||
if (completionMatch[1] !== undefined) return { exitCode: parseInt(completionMatch[1]) }
|
||||
return { exitCode: 0 }
|
||||
}
|
||||
|
||||
|
||||
const nameOfId = (id: string) => {
|
||||
if (id === '1') return 'Void Agent'
|
||||
return `Void Agent (${id})`
|
||||
}
|
||||
const idOfName = (name: string) => {
|
||||
if (name === 'Void Agent') return '1'
|
||||
|
||||
const match = name.match(/Void Agent \((\d+)\)/)
|
||||
if (!match) return null
|
||||
if (Number.isInteger(match[1]) && Number(match[1]) >= 1) return match[1]
|
||||
return null
|
||||
}
|
||||
|
||||
export class TerminalToolService extends Disposable implements ITerminalToolService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
private terminalInstanceOfId: Record<string, ITerminalInstance> = {}
|
||||
|
||||
constructor(
|
||||
@ITerminalService private readonly terminalService: ITerminalService,
|
||||
) {
|
||||
super();
|
||||
|
||||
// runs on ALL terminals for simplicity
|
||||
const initializeTerminal = (terminal: ITerminalInstance) => {
|
||||
// when exit, remove
|
||||
const d = terminal.onExit(() => {
|
||||
const terminalId = idOfName(terminal.title)
|
||||
if (terminalId !== null && (terminalId in this.terminalInstanceOfId)) delete this.terminalInstanceOfId[terminalId]
|
||||
d.dispose()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// initialize any terminals that are already open
|
||||
for (const terminal of terminalService.instances) {
|
||||
const proposedTerminalId = idOfName(terminal.title)
|
||||
if (proposedTerminalId) this.terminalInstanceOfId[proposedTerminalId] = terminal
|
||||
|
||||
initializeTerminal(terminal)
|
||||
}
|
||||
|
||||
this._register(
|
||||
terminalService.onDidCreateInstance(terminal => { initializeTerminal(terminal) })
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
listTerminalIds() {
|
||||
return Object.keys(this.terminalInstanceOfId)
|
||||
}
|
||||
|
||||
getValidNewTerminalId(): string {
|
||||
// {1 2 3} # size 3, new=4
|
||||
// {1 3 4} # size 3, new=2
|
||||
// 1 <= newTerminalId <= n + 1
|
||||
const n = Object.keys(this.terminalInstanceOfId).length;
|
||||
if (n === 0) return '1'
|
||||
|
||||
for (let i = 1; i <= n + 1; i++) {
|
||||
const potentialId = i + '';
|
||||
if (!(potentialId in this.terminalInstanceOfId)) return potentialId;
|
||||
}
|
||||
throw new Error('This should never be reached by pigeonhole principle');
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async _getOrCreateTerminal(proposedTerminalId: string) {
|
||||
// if terminal ID exists, return it
|
||||
if (proposedTerminalId in this.terminalInstanceOfId) return { terminalId: proposedTerminalId, didCreateTerminal: false }
|
||||
|
||||
// create new terminal and return its ID
|
||||
const terminalId = this.getValidNewTerminalId();
|
||||
const terminal = await this.terminalService.createTerminal({
|
||||
location: TerminalLocation.Panel,
|
||||
config: { name: nameOfId(terminalId), title: nameOfId(terminalId) },
|
||||
})
|
||||
|
||||
|
||||
// when a new terminal is created, there is an initial command that gets run which is empty, wait for it to end before returning
|
||||
const disposables: IDisposable[] = []
|
||||
const waitForMount = new Promise<void>(res => {
|
||||
let data = ''
|
||||
const d = terminal.onData(newData => {
|
||||
data += newData
|
||||
if (isCommandComplete(data)) { res() }
|
||||
})
|
||||
disposables.push(d)
|
||||
})
|
||||
const waitForTimeout = new Promise<void>(res => { setTimeout(() => { res() }, 1000) })
|
||||
|
||||
await Promise.any([waitForMount, waitForTimeout,])
|
||||
disposables.forEach(d => d.dispose())
|
||||
|
||||
this.terminalInstanceOfId[terminalId] = terminal
|
||||
return { terminalId, didCreateTerminal: true }
|
||||
}
|
||||
|
||||
terminalExists(terminalId: string): boolean {
|
||||
return terminalId in this.terminalInstanceOfId
|
||||
}
|
||||
|
||||
|
||||
openTerminal: ITerminalToolService['openTerminal'] = async (terminalId) => {
|
||||
if (!terminalId) return
|
||||
const terminal = this.terminalInstanceOfId[terminalId]
|
||||
if (!terminal) return // should never happen
|
||||
this.terminalService.setActiveInstance(terminal)
|
||||
await this.terminalService.focusActiveInstance()
|
||||
}
|
||||
|
||||
|
||||
|
||||
runCommand: ITerminalToolService['runCommand'] = async (command, proposedTerminalId, waitForCompletion) => {
|
||||
await this.terminalService.whenConnected;
|
||||
const { terminalId, didCreateTerminal } = await this._getOrCreateTerminal(proposedTerminalId)
|
||||
const terminal = this.terminalInstanceOfId[terminalId];
|
||||
if (!terminal) throw new Error(`Unexpected internal error: Terminal with ID ${terminalId} did not exist.`);
|
||||
|
||||
// focus the terminal about to run
|
||||
this.terminalService.setActiveInstance(terminal)
|
||||
await this.terminalService.focusActiveInstance()
|
||||
|
||||
let result: string = ''
|
||||
let resolveReason: TerminalResolveReason | undefined = undefined
|
||||
|
||||
const disposables: IDisposable[] = []
|
||||
|
||||
const waitUntilDone = new Promise<void>((res, rej) => {
|
||||
const d2 = terminal.onData(async newData => {
|
||||
if (resolveReason) return
|
||||
|
||||
result += newData
|
||||
|
||||
// onPageFull
|
||||
if (result.length > MAX_TERMINAL_CHARS_PAGE) {
|
||||
result = result.substring(0, MAX_TERMINAL_CHARS_PAGE)
|
||||
await terminal.sendText('\x03', true) // interrupt the terminal with Ctrl+C
|
||||
resolveReason = { type: 'toofull' }
|
||||
res()
|
||||
return
|
||||
}
|
||||
|
||||
// onDone
|
||||
const isDone = isCommandComplete(result)
|
||||
if (isDone) {
|
||||
resolveReason = { type: 'done', exitCode: isDone.exitCode }
|
||||
res()
|
||||
return
|
||||
}
|
||||
})
|
||||
disposables.push(d2)
|
||||
})
|
||||
|
||||
|
||||
// send the command here
|
||||
await terminal.sendText(command, true)
|
||||
|
||||
// timeout promise
|
||||
const waitUntilTimeout = new Promise<void>((res, rej) => {
|
||||
setTimeout(async () => {
|
||||
if (resolveReason) return
|
||||
await terminal.sendText('\x03', true) // interrupt the terminal with Ctrl+C
|
||||
resolveReason = { type: waitForCompletion ? 'timeout' : 'bgtask' }
|
||||
res()
|
||||
return
|
||||
}, (waitForCompletion ? TERMINAL_TIMEOUT_TIME : TERMINAL_BG_WAIT_TIME) * 1000)
|
||||
})
|
||||
|
||||
await Promise.any([
|
||||
waitUntilDone,
|
||||
waitUntilTimeout,
|
||||
])
|
||||
|
||||
disposables.forEach(d => d.dispose())
|
||||
|
||||
if (!resolveReason) throw new Error('Unexpected internal error: Promise.any should have resolved with a reason.')
|
||||
|
||||
|
||||
result = removeAnsiEscapeCodes(result)
|
||||
.split('\n').slice(1, -1) // remove first and last line (first = command, last = andrewpareles/void %)
|
||||
.join('\n')
|
||||
|
||||
return { terminalId, didCreateTerminal, result, resolveReason }
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(ITerminalToolService, TerminalToolService, InstantiationType.Delayed);
|
||||
@@ -0,0 +1,473 @@
|
||||
import { CancellationToken } from '../../../../base/common/cancellation.js'
|
||||
import { URI } from '../../../../base/common/uri.js'
|
||||
import { IFileService } from '../../../../platform/files/common/files.js'
|
||||
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'
|
||||
import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'
|
||||
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'
|
||||
import { QueryBuilder } from '../../../services/search/common/queryBuilder.js'
|
||||
import { ISearchService } from '../../../services/search/common/search.js'
|
||||
import { IEditCodeService } from './editCodeServiceInterface.js'
|
||||
import { ITerminalToolService } from './terminalToolService.js'
|
||||
import { ToolCallParams, ToolName, ToolResultType } from '../common/toolsServiceTypes.js'
|
||||
import { IVoidModelService } from '../common/voidModelService.js'
|
||||
import { EndOfLinePreference } from '../../../../editor/common/model.js'
|
||||
import { basename } from '../../../../base/common/path.js'
|
||||
import { IVoidCommandBarService } from './voidCommandBarService.js'
|
||||
import { computeDirectoryTree1Deep, IDirectoryStrService, stringifyDirectoryTree1Deep } from './directoryStrService.js'
|
||||
import { IMarkerService } from '../../../../platform/markers/common/markers.js'
|
||||
import { timeout } from '../../../../base/common/async.js'
|
||||
|
||||
|
||||
// tool use for AI
|
||||
|
||||
|
||||
|
||||
|
||||
type ValidateParams = { [T in ToolName]: (p: string) => Promise<ToolCallParams[T]> }
|
||||
type CallTool = { [T in ToolName]: (p: ToolCallParams[T]) => Promise<{ result: ToolResultType[T], interruptTool?: () => void }> }
|
||||
type ToolResultToString = { [T in ToolName]: (p: ToolCallParams[T], result: Awaited<ToolResultType[T]>) => string }
|
||||
|
||||
|
||||
|
||||
|
||||
// pagination info
|
||||
export const MAX_FILE_CHARS_PAGE = 50_000
|
||||
export const MAX_CHILDREN_URIs_PAGE = 500
|
||||
export const MAX_TERMINAL_CHARS_PAGE = 20_000
|
||||
export const TERMINAL_TIMEOUT_TIME = 15
|
||||
export const TERMINAL_BG_WAIT_TIME = 1
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const validateJSON = (s: string): { [s: string]: unknown } => {
|
||||
try {
|
||||
const o = JSON.parse(s)
|
||||
if (typeof o !== 'object') throw new Error()
|
||||
|
||||
if ('result' in o) { // openrouter sometimes wraps the result with { 'result': ... }
|
||||
return o.result
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Invalid LLM output format: Tool parameter was not a string of a valid JSON: "${s}".`)
|
||||
}
|
||||
}
|
||||
|
||||
const isFalsy = (u: unknown) => {
|
||||
return !u || u === 'null' || u === 'undefined'
|
||||
}
|
||||
|
||||
const validateStr = (argName: string, value: unknown) => {
|
||||
if (typeof value !== 'string') throw new Error(`Invalid LLM output format: ${argName} must be a string.`)
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
// We are NOT checking to make sure in workspace
|
||||
// TODO!!!! check to make sure folder/file exists
|
||||
const validateURI = (uriStr: unknown) => {
|
||||
if (typeof uriStr !== 'string') throw new Error('Invalid LLM output format: Provided uri must be a string.')
|
||||
const uri = URI.file(uriStr)
|
||||
return uri
|
||||
}
|
||||
|
||||
const validateOptionalURI = (uriStr: unknown) => {
|
||||
if (isFalsy(uriStr)) return null
|
||||
return validateURI(uriStr)
|
||||
}
|
||||
|
||||
const validateOptionalStr = (argName: string, str: unknown) => {
|
||||
if (isFalsy(str)) return null
|
||||
return validateStr(argName, str)
|
||||
}
|
||||
|
||||
|
||||
const validatePageNum = (pageNumberUnknown: unknown) => {
|
||||
if (!pageNumberUnknown) return 1
|
||||
const parsedInt = Number.parseInt(pageNumberUnknown + '')
|
||||
if (!Number.isInteger(parsedInt)) throw new Error(`Page number was not an integer: "${pageNumberUnknown}".`)
|
||||
if (parsedInt < 1) throw new Error(`Invalid LLM output format: Specified page number must be 1 or greater: "${pageNumberUnknown}".`)
|
||||
return parsedInt
|
||||
}
|
||||
|
||||
const validateNumber = (numStr: unknown, opts: { default: number | null }) => {
|
||||
if (typeof numStr === 'number')
|
||||
return numStr
|
||||
if (isFalsy(numStr)) return opts.default
|
||||
|
||||
if (typeof numStr === 'string') {
|
||||
const parsedInt = Number.parseInt(numStr + '')
|
||||
if (!Number.isInteger(parsedInt)) return opts.default
|
||||
return parsedInt
|
||||
}
|
||||
|
||||
return opts.default
|
||||
}
|
||||
|
||||
const validateRecursiveParamStr = (paramsUnknown: unknown) => {
|
||||
if (typeof paramsUnknown !== 'string') throw new Error('Invalid LLM output format: Error calling tool: provided params must be a string.')
|
||||
const params = paramsUnknown
|
||||
const isRecursive = params.includes('r')
|
||||
return isRecursive
|
||||
}
|
||||
|
||||
const validateProposedTerminalId = (terminalIdUnknown: unknown) => {
|
||||
if (!terminalIdUnknown) return '1'
|
||||
const terminalId = terminalIdUnknown + ''
|
||||
return terminalId
|
||||
}
|
||||
|
||||
const validateBoolean = (b: unknown, opts: { default: boolean }) => {
|
||||
if (typeof b === 'string') {
|
||||
if (b === 'true') return true
|
||||
if (b === 'false') return false
|
||||
}
|
||||
if (typeof b === 'boolean') {
|
||||
return b
|
||||
}
|
||||
return opts.default
|
||||
}
|
||||
|
||||
|
||||
const checkIfIsFolder = (uriStr: string) => {
|
||||
uriStr = uriStr.trim()
|
||||
if (uriStr.endsWith('/') || uriStr.endsWith('\\')) return true
|
||||
return false
|
||||
}
|
||||
|
||||
export interface IToolsService {
|
||||
readonly _serviceBrand: undefined;
|
||||
validateParams: ValidateParams;
|
||||
callTool: CallTool;
|
||||
stringOfResult: ToolResultToString;
|
||||
}
|
||||
|
||||
export const IToolsService = createDecorator<IToolsService>('ToolsService');
|
||||
|
||||
export class ToolsService implements IToolsService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
public validateParams: ValidateParams;
|
||||
public callTool: CallTool;
|
||||
public stringOfResult: ToolResultToString;
|
||||
|
||||
constructor(
|
||||
@IFileService fileService: IFileService,
|
||||
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
|
||||
@ISearchService searchService: ISearchService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IVoidModelService voidModelService: IVoidModelService,
|
||||
@IEditCodeService editCodeService: IEditCodeService,
|
||||
@ITerminalToolService private readonly terminalToolService: ITerminalToolService,
|
||||
@IVoidCommandBarService private readonly commandBarService: IVoidCommandBarService,
|
||||
@IDirectoryStrService private readonly directoryStrService: IDirectoryStrService,
|
||||
@IMarkerService private readonly markerService: IMarkerService,
|
||||
) {
|
||||
|
||||
const queryBuilder = instantiationService.createInstance(QueryBuilder);
|
||||
|
||||
this.validateParams = {
|
||||
read_file: async (params: string) => {
|
||||
const o = validateJSON(params)
|
||||
const { uri: uriStr, startLine: startLineUnknown, endLine: endLineUnknown, pageNumber: pageNumberUnknown } = o
|
||||
|
||||
const uri = validateURI(uriStr)
|
||||
const pageNumber = validatePageNum(pageNumberUnknown)
|
||||
|
||||
const startLine = validateNumber(startLineUnknown, { default: null })
|
||||
const endLine = validateNumber(endLineUnknown, { default: null })
|
||||
|
||||
return { uri, startLine, endLine, pageNumber }
|
||||
},
|
||||
ls_dir: async (params: string) => {
|
||||
const o = validateJSON(params)
|
||||
const { uri: uriStr, pageNumber: pageNumberUnknown } = o
|
||||
|
||||
const uri = validateURI(uriStr)
|
||||
const pageNumber = validatePageNum(pageNumberUnknown)
|
||||
return { rootURI: uri, pageNumber }
|
||||
},
|
||||
get_dir_structure: async (params: string) => {
|
||||
const o = validateJSON(params)
|
||||
const { uri: uriStr, } = o
|
||||
const uri = validateURI(uriStr)
|
||||
return { rootURI: uri }
|
||||
},
|
||||
search_pathnames_only: async (params: string) => {
|
||||
const o = validateJSON(params)
|
||||
const {
|
||||
query: queryUnknown,
|
||||
include: includeUnknown,
|
||||
pageNumber: pageNumberUnknown
|
||||
} = o
|
||||
|
||||
const queryStr = validateStr('query', queryUnknown)
|
||||
const pageNumber = validatePageNum(pageNumberUnknown)
|
||||
const include = validateOptionalStr('include', includeUnknown)
|
||||
|
||||
return { queryStr, include, pageNumber }
|
||||
|
||||
},
|
||||
search_files: async (params: string) => {
|
||||
const o = validateJSON(params)
|
||||
const {
|
||||
query: queryUnknown,
|
||||
searchInFolder: searchInFolderUnknown,
|
||||
isRegex: isRegexUnknown,
|
||||
pageNumber: pageNumberUnknown
|
||||
} = o
|
||||
|
||||
const queryStr = validateStr('query', queryUnknown)
|
||||
const pageNumber = validatePageNum(pageNumberUnknown)
|
||||
|
||||
const searchInFolder = validateOptionalURI(searchInFolderUnknown)
|
||||
const isRegex = validateBoolean(isRegexUnknown, { default: false })
|
||||
|
||||
return { queryStr, searchInFolder, isRegex, pageNumber }
|
||||
},
|
||||
|
||||
// ---
|
||||
|
||||
create_file_or_folder: async (params: string) => {
|
||||
const o = validateJSON(params)
|
||||
const { uri: uriUnknown } = o
|
||||
const uri = validateURI(uriUnknown)
|
||||
const uriStr = validateStr('uri', uriUnknown)
|
||||
const isFolder = checkIfIsFolder(uriStr)
|
||||
return { uri, isFolder }
|
||||
},
|
||||
|
||||
delete_file_or_folder: async (params: string) => {
|
||||
const o = validateJSON(params)
|
||||
const { uri: uriUnknown, params: paramsStr } = o
|
||||
const uri = validateURI(uriUnknown)
|
||||
const isRecursive = validateRecursiveParamStr(paramsStr)
|
||||
const uriStr = validateStr('uri', uriUnknown)
|
||||
const isFolder = checkIfIsFolder(uriStr)
|
||||
return { uri, isRecursive, isFolder }
|
||||
},
|
||||
|
||||
edit_file: async (params: string) => {
|
||||
const o = validateJSON(params)
|
||||
const { uri: uriStr, changeDescription: changeDescriptionUnknown } = o
|
||||
const uri = validateURI(uriStr)
|
||||
const changeDescription = validateStr('changeDescription', changeDescriptionUnknown)
|
||||
return { uri, changeDescription }
|
||||
},
|
||||
|
||||
run_terminal_command: async (s: string) => {
|
||||
const o = validateJSON(s)
|
||||
const { command: commandUnknown, terminalId: terminalIdUnknown, waitForCompletion: waitForCompletionUnknown } = o
|
||||
const command = validateStr('command', commandUnknown)
|
||||
const proposedTerminalId = validateProposedTerminalId(terminalIdUnknown)
|
||||
const waitForCompletion = validateBoolean(waitForCompletionUnknown, { default: true })
|
||||
return { command, proposedTerminalId, waitForCompletion }
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
|
||||
this.callTool = {
|
||||
read_file: async ({ uri, startLine, endLine, pageNumber }) => {
|
||||
await voidModelService.initializeModel(uri)
|
||||
const { model } = await voidModelService.getModelSafe(uri)
|
||||
if (model === null) { throw new Error(`Contents were empty. There may have been an error, or the file may not exist.`) }
|
||||
|
||||
let contents: string
|
||||
if (startLine === null && endLine === null) {
|
||||
contents = model.getValue(EndOfLinePreference.LF)
|
||||
}
|
||||
else {
|
||||
const startLineNumber = startLine === null ? 1 : startLine
|
||||
const endLineNumber = endLine === null ? model.getLineCount() : endLine
|
||||
contents = model.getValueInRange({ startLineNumber, startColumn: 1, endLineNumber, endColumn: Number.MAX_SAFE_INTEGER }, EndOfLinePreference.LF)
|
||||
}
|
||||
|
||||
const fromIdx = MAX_FILE_CHARS_PAGE * (pageNumber - 1)
|
||||
const toIdx = MAX_FILE_CHARS_PAGE * pageNumber - 1
|
||||
const fileContents = contents.slice(fromIdx, toIdx + 1) // paginate
|
||||
const hasNextPage = (contents.length - 1) - toIdx >= 1
|
||||
|
||||
return { result: { fileContents, hasNextPage } }
|
||||
},
|
||||
|
||||
ls_dir: async ({ rootURI, pageNumber }) => {
|
||||
const dirResult = await computeDirectoryTree1Deep(fileService, rootURI, pageNumber)
|
||||
return { result: dirResult }
|
||||
},
|
||||
|
||||
get_dir_structure: async ({ rootURI }) => {
|
||||
const result = await this.directoryStrService.getDirectoryStrTool(rootURI)
|
||||
let str = result.str
|
||||
if (result.wasCutOff) str += '\n(Result was truncated)'
|
||||
return { result: { str } }
|
||||
},
|
||||
|
||||
search_pathnames_only: async ({ queryStr, include, pageNumber }) => {
|
||||
|
||||
const query = queryBuilder.file(workspaceContextService.getWorkspace().folders.map(f => f.uri), {
|
||||
filePattern: queryStr,
|
||||
includePattern: include ?? undefined,
|
||||
})
|
||||
const data = await searchService.fileSearch(query, CancellationToken.None)
|
||||
|
||||
const fromIdx = MAX_CHILDREN_URIs_PAGE * (pageNumber - 1)
|
||||
const toIdx = MAX_CHILDREN_URIs_PAGE * pageNumber - 1
|
||||
const uris = data.results
|
||||
.slice(fromIdx, toIdx + 1) // paginate
|
||||
.map(({ resource, results }) => resource)
|
||||
|
||||
const hasNextPage = (data.results.length - 1) - toIdx >= 1
|
||||
return { result: { uris, hasNextPage } }
|
||||
},
|
||||
|
||||
search_files: async ({ queryStr, isRegex, searchInFolder, pageNumber }) => {
|
||||
const searchFolders = searchInFolder === null ?
|
||||
workspaceContextService.getWorkspace().folders.map(f => f.uri)
|
||||
: [searchInFolder]
|
||||
|
||||
const query = queryBuilder.text({
|
||||
pattern: queryStr,
|
||||
isRegExp: isRegex,
|
||||
}, searchFolders)
|
||||
|
||||
const data = await searchService.textSearch(query, CancellationToken.None)
|
||||
|
||||
const fromIdx = MAX_CHILDREN_URIs_PAGE * (pageNumber - 1)
|
||||
const toIdx = MAX_CHILDREN_URIs_PAGE * pageNumber - 1
|
||||
const uris = data.results
|
||||
.slice(fromIdx, toIdx + 1) // paginate
|
||||
.map(({ resource, results }) => resource)
|
||||
|
||||
const hasNextPage = (data.results.length - 1) - toIdx >= 1
|
||||
return { result: { queryStr, uris, hasNextPage } }
|
||||
},
|
||||
|
||||
// ---
|
||||
|
||||
create_file_or_folder: async ({ uri, isFolder }) => {
|
||||
if (isFolder)
|
||||
await fileService.createFolder(uri)
|
||||
else {
|
||||
await fileService.createFile(uri)
|
||||
}
|
||||
return { result: {} }
|
||||
},
|
||||
|
||||
delete_file_or_folder: async ({ uri, isRecursive }) => {
|
||||
await fileService.del(uri, { recursive: isRecursive })
|
||||
return { result: {} }
|
||||
},
|
||||
|
||||
edit_file: async ({ uri, changeDescription }) => {
|
||||
await voidModelService.initializeModel(uri)
|
||||
if (this.commandBarService.getStreamState(uri) === 'streaming') {
|
||||
throw new Error(`Another LLM is currently making changes to this file. Please stop streaming for now and resume later.`)
|
||||
}
|
||||
const opts = {
|
||||
uri,
|
||||
applyStr: changeDescription,
|
||||
from: 'ClickApply',
|
||||
startBehavior: 'keep-conflicts',
|
||||
} as const
|
||||
|
||||
await editCodeService.callBeforeStartApplying(opts)
|
||||
const res = editCodeService.startApplying(opts)
|
||||
if (!res) throw new Error(`The Apply model did not start running on ${basename(uri.fsPath)}. Please try again.`)
|
||||
const [diffZoneURI, applyDonePromise] = res
|
||||
|
||||
const interruptTool = () => { // must reject the applyPromiseDone promise
|
||||
editCodeService.interruptURIStreaming({ uri: diffZoneURI })
|
||||
}
|
||||
|
||||
const lintErrorsPromise = applyDonePromise.then(async () => {
|
||||
await timeout(500)
|
||||
const lintErrorsStr = this.markerService
|
||||
.read({ resource: uri })
|
||||
.map(l => l.message)
|
||||
.join('\n')
|
||||
|
||||
if (!lintErrorsStr) return { lintErrorsStr: null }
|
||||
return { lintErrorsStr }
|
||||
})
|
||||
|
||||
return { result: lintErrorsPromise, interruptTool }
|
||||
},
|
||||
run_terminal_command: async ({ command, proposedTerminalId, waitForCompletion }) => {
|
||||
const { terminalId, didCreateTerminal, result, resolveReason } = await this.terminalToolService.runCommand(command, proposedTerminalId, waitForCompletion)
|
||||
return { result: { terminalId, didCreateTerminal, result, resolveReason } }
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
const nextPageStr = (hasNextPage: boolean) => hasNextPage ? '\n\n(more on next page...)' : ''
|
||||
|
||||
// given to the LLM after the call
|
||||
this.stringOfResult = {
|
||||
read_file: (params, result) => {
|
||||
return result.fileContents + nextPageStr(result.hasNextPage)
|
||||
},
|
||||
ls_dir: (params, result) => {
|
||||
const dirTreeStr = stringifyDirectoryTree1Deep(params, result)
|
||||
return dirTreeStr // + nextPageStr(result.hasNextPage) // already handles num results remaining
|
||||
},
|
||||
get_dir_structure: (params, result) => {
|
||||
return result.str
|
||||
},
|
||||
search_pathnames_only: (params, result) => {
|
||||
return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage)
|
||||
},
|
||||
search_files: (params, result) => {
|
||||
return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage)
|
||||
},
|
||||
// ---
|
||||
create_file_or_folder: (params, result) => {
|
||||
return `URI ${params.uri.fsPath} successfully created.`
|
||||
},
|
||||
delete_file_or_folder: (params, result) => {
|
||||
return `URI ${params.uri.fsPath} successfully deleted.`
|
||||
},
|
||||
edit_file: (params, result) => {
|
||||
const additionalStr = result.lintErrorsStr ? `Lint errors found after change:\n${result.lintErrorsStr}.\nIf this is related to a change made while calling this tool, you might want to fix the error.` : `No lint errors found.`
|
||||
return `Change successfully made to ${params.uri.fsPath}. ${additionalStr}`
|
||||
},
|
||||
run_terminal_command: (params, result) => {
|
||||
const {
|
||||
terminalId,
|
||||
didCreateTerminal,
|
||||
resolveReason,
|
||||
result: result_,
|
||||
} = result
|
||||
|
||||
const terminalDesc = `terminal ${terminalId}${didCreateTerminal ? ` (a newly-created terminal)` : ''}`
|
||||
|
||||
if (resolveReason.type === 'timeout') {
|
||||
return `Terminal command ran in ${terminalDesc}, but timed out after ${TERMINAL_TIMEOUT_TIME} seconds. Result:\n${result_}`
|
||||
}
|
||||
else if (resolveReason.type === 'bgtask') {
|
||||
return `Terminal command is running in the background in ${terminalDesc}. Here were the outputs after ${TERMINAL_BG_WAIT_TIME} seconds:\n${result_}`
|
||||
}
|
||||
else if (resolveReason.type === 'toofull') {
|
||||
return `Terminal command executed in terminal ${terminalDesc}. Command was interrupted because output was too long. Result:\n${result_}`
|
||||
}
|
||||
else if (resolveReason.type === 'done') {
|
||||
return `Terminal command executed in terminal ${terminalDesc}. Result (exit code ${resolveReason.exitCode}):\n${result_}`
|
||||
}
|
||||
throw new Error(`Unexpected internal error: Terminal command did not resolve with a valid reason.`)
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IToolsService, ToolsService, InstantiationType.Eager);
|
||||
@@ -15,8 +15,6 @@ import './sidebarStateService.js'
|
||||
// register quick edit (Ctrl+K)
|
||||
import './quickEditActions.js'
|
||||
|
||||
// register Thread History
|
||||
import './chatThreadService.js'
|
||||
|
||||
// register Autocomplete
|
||||
import './autocompleteService.js'
|
||||
@@ -35,12 +33,23 @@ import './media/void.css'
|
||||
import './voidUpdateActions.js'
|
||||
|
||||
|
||||
// tools
|
||||
import './toolsService.js'
|
||||
import './terminalToolService.js'
|
||||
|
||||
// register Thread History
|
||||
import './chatThreadService.js'
|
||||
|
||||
// ping
|
||||
import './metricsPollService.js'
|
||||
|
||||
// helper services
|
||||
import './helperServices/consistentItemService.js'
|
||||
|
||||
// ---------- common (unclear if these actually need to be imported, because they're already imported wherever they're used) ----------
|
||||
|
||||
// llmMessage
|
||||
import '../common/llmMessageService.js'
|
||||
import '../common/sendLLMMessageService.js'
|
||||
|
||||
// voidSettings
|
||||
import '../common/voidSettingsService.js'
|
||||
@@ -54,5 +63,5 @@ import '../common/metricsService.js'
|
||||
// updates
|
||||
import '../common/voidUpdateService.js'
|
||||
|
||||
// tools
|
||||
import '../common/toolsService.js'
|
||||
// model service
|
||||
import '../common/voidModelService.js'
|
||||
|
||||
@@ -0,0 +1,443 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import * as dom from '../../../../base/browser/dom.js';
|
||||
import { Widget } from '../../../../base/browser/ui/widget.js';
|
||||
import { IOverlayWidget, ICodeEditor, OverlayWidgetPositionPreference } from '../../../../editor/browser/editorBrowser.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
||||
import { mountVoidCommandBar } from './react/out/void-command-bar-tsx/index.js'
|
||||
import { deepClone } from '../../../../base/common/objects.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { IEditCodeService } from './editCodeServiceInterface.js';
|
||||
import { ITextModel } from '../../../../editor/common/model.js';
|
||||
import { IModelService } from '../../../../editor/common/services/model.js';
|
||||
import { generateUuid } from '../../../../base/common/uuid.js';
|
||||
|
||||
|
||||
|
||||
export interface IVoidCommandBarService {
|
||||
readonly _serviceBrand: undefined;
|
||||
stateOfURI: { [uri: string]: CommandBarStateType };
|
||||
sortedURIs: URI[];
|
||||
activeURI: URI | null;
|
||||
|
||||
onDidChangeState: Event<{ uri: URI }>;
|
||||
onDidChangeActiveURI: Event<{ uri: URI | null }>;
|
||||
|
||||
getStreamState: (uri: URI) => 'streaming' | 'idle-has-changes' | 'idle-no-changes';
|
||||
setDiffIdx(uri: URI, newIdx: number | null): void;
|
||||
|
||||
acceptOrRejectAllFiles(opts: { behavior: 'reject' | 'accept' }): void;
|
||||
anyFileIsStreaming(): boolean;
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const IVoidCommandBarService = createDecorator<IVoidCommandBarService>('VoidCommandBarService');
|
||||
|
||||
|
||||
export type CommandBarStateType = undefined | {
|
||||
sortedDiffZoneIds: string[]; // sorted by line number
|
||||
sortedDiffIds: string[]; // sorted by line number (computed)
|
||||
isStreaming: boolean; // is any diffZone streaming in this URI
|
||||
|
||||
diffIdx: number | null; // must refresh whenever sortedDiffIds does so it's valid
|
||||
}
|
||||
|
||||
|
||||
|
||||
const defaultState: NonNullable<CommandBarStateType> = {
|
||||
sortedDiffZoneIds: [],
|
||||
sortedDiffIds: [],
|
||||
isStreaming: false,
|
||||
diffIdx: null,
|
||||
}
|
||||
|
||||
|
||||
export class VoidCommandBarService extends Disposable implements IVoidCommandBarService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
static readonly ID: 'void.VoidCommandBarService'
|
||||
|
||||
// depends on uri -> diffZone -> {streaming, diffs}
|
||||
public stateOfURI: { [uri: string]: CommandBarStateType } = {}
|
||||
public sortedURIs: URI[] = [] // keys of state (depends on diffZones in the uri)
|
||||
private readonly _listenToTheseURIs = new Set<URI>() // uriFsPaths
|
||||
|
||||
// Emits when a URI's stream state changes between idle, streaming, and acceptRejectAll
|
||||
private readonly _onDidChangeState = new Emitter<{ uri: URI }>();
|
||||
readonly onDidChangeState = this._onDidChangeState.event;
|
||||
|
||||
|
||||
// active URI
|
||||
activeURI: URI | null = null;
|
||||
private readonly _onDidChangeActiveURI = new Emitter<{ uri: URI | null }>();
|
||||
readonly onDidChangeActiveURI = this._onDidChangeActiveURI.event;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
|
||||
@IModelService private readonly _modelService: IModelService,
|
||||
@IEditCodeService private readonly _editCodeService: IEditCodeService,
|
||||
) {
|
||||
super();
|
||||
|
||||
|
||||
const registeredModelURIs = new Set<string>()
|
||||
const initializeModel = async (model: ITextModel) => {
|
||||
// do not add listeners to the same model twice - important, or will see duplicates
|
||||
if (registeredModelURIs.has(model.uri.fsPath)) return
|
||||
registeredModelURIs.add(model.uri.fsPath)
|
||||
this._listenToTheseURIs.add(model.uri)
|
||||
}
|
||||
// initialize all existing models + initialize when a new model mounts
|
||||
this._modelService.getModels().forEach(model => { initializeModel(model) })
|
||||
this._register(this._modelService.onModelAdded(model => { initializeModel(model) }));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// for every new editor, add the floating widget and update active URI
|
||||
const disposablesOfEditorId: { [editorId: string]: IDisposable[] } = {};
|
||||
const onCodeEditorAdd = (editor: ICodeEditor) => {
|
||||
const id = editor.getId();
|
||||
disposablesOfEditorId[id] = [];
|
||||
|
||||
// mount the command bar
|
||||
const d1 = this._instantiationService.createInstance(AcceptRejectAllFloatingWidget, { editor });
|
||||
disposablesOfEditorId[id].push(d1);
|
||||
const d2 = editor.onDidChangeModel((e) => {
|
||||
if (e.newModelUrl?.scheme !== 'file') return
|
||||
this.activeURI = e.newModelUrl;
|
||||
this._onDidChangeActiveURI.fire({ uri: e.newModelUrl })
|
||||
})
|
||||
disposablesOfEditorId[id].push(d2);
|
||||
}
|
||||
const onCodeEditorRemove = (editor: ICodeEditor) => {
|
||||
const id = editor.getId();
|
||||
if (disposablesOfEditorId[id]) {
|
||||
disposablesOfEditorId[id].forEach(d => d.dispose());
|
||||
delete disposablesOfEditorId[id];
|
||||
}
|
||||
}
|
||||
this._register(this._codeEditorService.onCodeEditorAdd((editor) => { onCodeEditorAdd(editor) }))
|
||||
this._register(this._codeEditorService.onCodeEditorRemove((editor) => { onCodeEditorRemove(editor) }))
|
||||
this._codeEditorService.listCodeEditors().forEach(editor => { onCodeEditorAdd(editor) })
|
||||
|
||||
// state updaters
|
||||
this._register(this._editCodeService.onDidAddOrDeleteDiffZones(e => {
|
||||
for (const uri of this._listenToTheseURIs) {
|
||||
if (e.uri.fsPath !== uri.fsPath) continue
|
||||
// --- sortedURIs: delete if empty, add if not in state yet
|
||||
const diffZones = this._getDiffZonesOnURI(uri)
|
||||
if (diffZones.length === 0) {
|
||||
this._deleteURIEntryFromState(uri)
|
||||
this._onDidChangeState.fire({ uri })
|
||||
continue // deleted, so done
|
||||
}
|
||||
if (!this.sortedURIs.find(uri2 => uri2.fsPath === uri.fsPath)) {
|
||||
this._addURIEntryToState(uri)
|
||||
}
|
||||
|
||||
const currState = this.stateOfURI[uri.fsPath]
|
||||
if (!currState) continue // should never happen
|
||||
// update state of the diffZones on this URI
|
||||
const oldDiffZones = currState.sortedDiffZoneIds
|
||||
const currentDiffZones = this._editCodeService.diffAreasOfURI[uri.fsPath] || [] // a Set
|
||||
const { addedDiffZones, deletedDiffZones } = this._getDiffZoneChanges(oldDiffZones, currentDiffZones || [])
|
||||
|
||||
const diffZonesWithoutDeleted = oldDiffZones.filter(olddiffareaid => !deletedDiffZones.has(olddiffareaid))
|
||||
|
||||
// --- new state:
|
||||
const newSortedDiffZoneIds = [
|
||||
...diffZonesWithoutDeleted,
|
||||
...addedDiffZones,
|
||||
]
|
||||
const newSortedDiffIds = this._computeSortedDiffs(newSortedDiffZoneIds)
|
||||
const isStreaming = this._isAnyDiffZoneStreaming(currentDiffZones)
|
||||
|
||||
this._setState(uri, {
|
||||
sortedDiffZoneIds: newSortedDiffZoneIds,
|
||||
sortedDiffIds: newSortedDiffIds,
|
||||
isStreaming: isStreaming
|
||||
})
|
||||
this._onDidChangeState.fire({ uri })
|
||||
}
|
||||
|
||||
}))
|
||||
this._register(this._editCodeService.onDidChangeDiffsInDiffZoneNotStreaming(e => {
|
||||
for (const uri of this._listenToTheseURIs) {
|
||||
if (e.uri.fsPath !== uri.fsPath) continue
|
||||
// --- sortedURIs: no change
|
||||
// --- state:
|
||||
// sortedDiffIds gets a change to it, so gets recomputed
|
||||
const currState = this.stateOfURI[uri.fsPath]
|
||||
if (!currState) continue // should never happen
|
||||
const { sortedDiffZoneIds } = currState
|
||||
const newSortedDiffIds = this._computeSortedDiffs(sortedDiffZoneIds)
|
||||
this._setState(uri, {
|
||||
sortedDiffIds: newSortedDiffIds,
|
||||
// sortedDiffZoneIds, // no change
|
||||
// isStreaming, // no change
|
||||
})
|
||||
this._onDidChangeState.fire({ uri })
|
||||
}
|
||||
}))
|
||||
this._register(this._editCodeService.onDidChangeStreamingInDiffZone(e => {
|
||||
for (const uri of this._listenToTheseURIs) {
|
||||
if (e.uri.fsPath !== uri.fsPath) continue
|
||||
// --- sortedURIs: no change
|
||||
// --- state:
|
||||
const currState = this.stateOfURI[uri.fsPath]
|
||||
if (!currState) continue // should never happen
|
||||
const { sortedDiffZoneIds } = currState
|
||||
this._setState(uri, {
|
||||
isStreaming: this._isAnyDiffZoneStreaming(sortedDiffZoneIds),
|
||||
// sortedDiffIds, // no change
|
||||
// sortedDiffZoneIds, // no change
|
||||
})
|
||||
this._onDidChangeState.fire({ uri })
|
||||
}
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
|
||||
setDiffIdx(uri: URI, newIdx: number | null): void {
|
||||
this._setState(uri, { diffIdx: newIdx });
|
||||
this._onDidChangeState.fire({ uri });
|
||||
}
|
||||
|
||||
|
||||
getStreamState(uri: URI) {
|
||||
const { isStreaming, sortedDiffZoneIds } = this.stateOfURI[uri.fsPath] ?? {}
|
||||
if (isStreaming) {
|
||||
return 'streaming'
|
||||
}
|
||||
if ((sortedDiffZoneIds?.length ?? 0) > 0) {
|
||||
return 'idle-has-changes'
|
||||
}
|
||||
return 'idle-no-changes'
|
||||
}
|
||||
|
||||
|
||||
_computeSortedDiffs(diffareaids: string[]) {
|
||||
const sortedDiffIds = [];
|
||||
for (const diffareaid of diffareaids) {
|
||||
const diffZone = this._editCodeService.diffAreaOfId[diffareaid];
|
||||
if (!diffZone || diffZone.type !== 'DiffZone') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add all diff ids from this diffzone
|
||||
const diffIds = Object.keys(diffZone._diffOfId);
|
||||
sortedDiffIds.push(...diffIds);
|
||||
}
|
||||
|
||||
return sortedDiffIds;
|
||||
}
|
||||
|
||||
_getDiffZoneChanges(oldDiffZones: Iterable<string>, currentDiffZones: Iterable<string>) {
|
||||
// Find the added or deleted diffZones by comparing diffareaids
|
||||
const addedDiffZoneIds = new Set<string>();
|
||||
const deletedDiffZoneIds = new Set<string>();
|
||||
|
||||
// Convert the current diffZones to a set of ids for easy lookup
|
||||
const currentDiffZoneIdSet = new Set(currentDiffZones);
|
||||
|
||||
// Find deleted diffZones (in old but not in current)
|
||||
for (const oldDiffZoneId of oldDiffZones) {
|
||||
if (!currentDiffZoneIdSet.has(oldDiffZoneId)) {
|
||||
const diffZone = this._editCodeService.diffAreaOfId[oldDiffZoneId];
|
||||
if (diffZone && diffZone.type === 'DiffZone') {
|
||||
deletedDiffZoneIds.add(oldDiffZoneId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find added diffZones (in current but not in old)
|
||||
const oldDiffZoneIdSet = new Set(oldDiffZones);
|
||||
for (const currentDiffZoneId of currentDiffZones) {
|
||||
if (!oldDiffZoneIdSet.has(currentDiffZoneId)) {
|
||||
const diffZone = this._editCodeService.diffAreaOfId[currentDiffZoneId];
|
||||
if (diffZone && diffZone.type === 'DiffZone') {
|
||||
addedDiffZoneIds.add(currentDiffZoneId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { addedDiffZones: addedDiffZoneIds, deletedDiffZones: deletedDiffZoneIds }
|
||||
}
|
||||
|
||||
_isAnyDiffZoneStreaming(diffareaids: Iterable<string>) {
|
||||
for (const diffareaid of diffareaids) {
|
||||
const diffZone = this._editCodeService.diffAreaOfId[diffareaid];
|
||||
if (!diffZone || diffZone.type !== 'DiffZone') {
|
||||
continue;
|
||||
}
|
||||
if (diffZone._streamState.isStreaming) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
_setState(uri: URI, opts: Partial<CommandBarStateType>) {
|
||||
const newState = {
|
||||
...this.stateOfURI[uri.fsPath] ?? deepClone(defaultState),
|
||||
...opts
|
||||
}
|
||||
|
||||
// make sure diffIdx is always correct
|
||||
if (newState.diffIdx && newState.diffIdx > newState.sortedDiffIds.length) {
|
||||
newState.diffIdx = newState.sortedDiffIds.length
|
||||
if (newState.diffIdx < 0) newState.diffIdx = null
|
||||
}
|
||||
|
||||
this.stateOfURI = {
|
||||
...this.stateOfURI,
|
||||
[uri.fsPath]: newState
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_addURIEntryToState(uri: URI) {
|
||||
// add to sortedURIs
|
||||
this.sortedURIs = [
|
||||
...this.sortedURIs,
|
||||
uri
|
||||
]
|
||||
|
||||
// add to state
|
||||
this.stateOfURI[uri.fsPath] = deepClone(defaultState)
|
||||
}
|
||||
|
||||
_deleteURIEntryFromState(uri: URI) {
|
||||
// delete this from sortedURIs
|
||||
const i = this.sortedURIs.findIndex(uri2 => uri2.fsPath === uri.fsPath)
|
||||
if (i === -1) return
|
||||
this.sortedURIs = [
|
||||
...this.sortedURIs.slice(0, i),
|
||||
...this.sortedURIs.slice(i + 1, Infinity),
|
||||
]
|
||||
// delete from state
|
||||
delete this.stateOfURI[uri.fsPath]
|
||||
}
|
||||
|
||||
|
||||
|
||||
private _getDiffZonesOnURI(uri: URI) {
|
||||
const diffZones = [...this._editCodeService.diffAreasOfURI[uri.fsPath]?.values() ?? []]
|
||||
.map(diffareaid => this._editCodeService.diffAreaOfId[diffareaid])
|
||||
.filter(diffArea => !!diffArea && diffArea.type === 'DiffZone')
|
||||
return diffZones
|
||||
}
|
||||
|
||||
|
||||
anyFileIsStreaming() {
|
||||
return this.sortedURIs.some(uri => this.getStreamState(uri) === 'streaming')
|
||||
}
|
||||
|
||||
acceptOrRejectAllFiles(opts: { behavior: 'reject' | 'accept' }) {
|
||||
const { behavior } = opts
|
||||
// if anything is streaming, do nothing
|
||||
const anyIsStreaming = this.anyFileIsStreaming()
|
||||
if (anyIsStreaming) return
|
||||
for (const uri of this.sortedURIs) {
|
||||
this._editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior, removeCtrlKs: false })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IVoidCommandBarService, VoidCommandBarService, InstantiationType.Delayed); // delayed is needed here :(
|
||||
|
||||
// registerWorkbenchContribution2(VoidCommandBarService.ID, VoidCommandBarService, WorkbenchPhase.BlockRestore);
|
||||
|
||||
|
||||
export type VoidCommandBarProps = {
|
||||
uri: URI | null;
|
||||
editor: ICodeEditor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
class AcceptRejectAllFloatingWidget extends Widget implements IOverlayWidget {
|
||||
private readonly _domNode: HTMLElement;
|
||||
private readonly editor: ICodeEditor;
|
||||
private readonly ID: string;
|
||||
|
||||
_height = 0
|
||||
|
||||
constructor({ editor }: { editor: ICodeEditor, },
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.ID = generateUuid();
|
||||
this.editor = editor;
|
||||
// Create container div
|
||||
const { root } = dom.h('div@root');
|
||||
|
||||
// Style the container
|
||||
// root.style.backgroundColor = 'rgb(248 113 113)';
|
||||
root.style.height = '16rem'; // make a fixed size, and all contents go on the bottom right. this fixes annoying VS Code mounting issues
|
||||
root.style.width = '16rem';
|
||||
root.style.flexDirection = 'column';
|
||||
root.style.justifyContent = 'flex-end';
|
||||
root.style.alignItems = 'flex-end';
|
||||
root.style.zIndex = '2';
|
||||
root.style.padding = '4px';
|
||||
root.style.pointerEvents = 'none';
|
||||
root.style.display = 'flex';
|
||||
root.style.overflow = 'hidden';
|
||||
|
||||
|
||||
this._domNode = root;
|
||||
editor.addOverlayWidget(this);
|
||||
|
||||
this.instantiationService.invokeFunction(accessor => {
|
||||
const uri = editor.getModel()?.uri || null
|
||||
const res = mountVoidCommandBar(root, accessor, { uri, editor } satisfies VoidCommandBarProps)
|
||||
if (!res) return
|
||||
this._register(toDisposable(() => res.dispose?.()))
|
||||
this._register(editor.onWillChangeModel((model) => {
|
||||
const uri = model.newModelUrl
|
||||
res.rerender({ uri, editor } satisfies VoidCommandBarProps)
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
public getId(): string {
|
||||
return this.ID;
|
||||
}
|
||||
|
||||
public getDomNode(): HTMLElement {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
public getPosition() {
|
||||
return {
|
||||
preference: OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER
|
||||
}
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
this.editor.removeOverlayWidget(this);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextke
|
||||
|
||||
import { mountVoidSettings } from './react/out/void-settings-tsx/index.js'
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
|
||||
|
||||
// refer to preferences.contribution.ts keybindings editor
|
||||
@@ -90,12 +90,12 @@ class VoidSettingsPane extends EditorPane {
|
||||
|
||||
// Mount React into the scrollable content
|
||||
this.instantiationService.invokeFunction(accessor => {
|
||||
const disposables: IDisposable[] | undefined = mountVoidSettings(settingsElt, accessor);
|
||||
const disposeFn = mountVoidSettings(settingsElt, accessor)?.dispose;
|
||||
this._register(toDisposable(() => disposeFn?.()))
|
||||
|
||||
// setTimeout(() => { // this is a complete hack and I don't really understand how scrollbar works here
|
||||
// this._scrollbar?.scanDomNode();
|
||||
// }, 1000)
|
||||
disposables?.forEach(d => this._register(d));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,41 @@ import * as dom from '../../../../base/browser/dom.js';
|
||||
|
||||
|
||||
|
||||
const notifyYesUpdate = (notifService: INotificationService, msg?: string) => {
|
||||
const message = msg || 'This is a very old version of void, please download the latest version! [Void Editor](https://voideditor.com/download-beta)!'
|
||||
notifService.notify({
|
||||
const notifyYesUpdate = (notifService: INotificationService, res: { message?: string } = {}) => {
|
||||
const message = res?.message || 'This is a very old version of Void, please download the latest version! [Void Editor](https://voideditor.com/download-beta)!'
|
||||
const notifController = notifService.notify({
|
||||
severity: Severity.Info,
|
||||
message: message,
|
||||
sticky: true,
|
||||
progress: { worked: 0, total: 100 },
|
||||
actions: {
|
||||
primary: [{
|
||||
id: 'void.updater.update',
|
||||
enabled: true,
|
||||
label: `Reinstall`,
|
||||
tooltip: '',
|
||||
class: undefined,
|
||||
run: () => {
|
||||
const { window } = dom.getActiveWindow()
|
||||
window.open('https://voideditor.com/download-beta')
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'void.updater.site',
|
||||
enabled: true,
|
||||
label: `Void Site`,
|
||||
tooltip: '',
|
||||
class: undefined,
|
||||
run: () => {
|
||||
const { window } = dom.getActiveWindow()
|
||||
window.open('https://voideditor.com/')
|
||||
}
|
||||
}]
|
||||
},
|
||||
})
|
||||
const d = notifController.onDidClose(() => {
|
||||
notifyYesUpdate(notifService, res)
|
||||
d.dispose()
|
||||
})
|
||||
}
|
||||
const notifyNoUpdate = (notifService: INotificationService) => {
|
||||
@@ -35,6 +65,7 @@ const notifyErrChecking = (notifService: INotificationService) => {
|
||||
notifService.notify({
|
||||
severity: Severity.Info,
|
||||
message: message,
|
||||
sticky: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -57,7 +88,7 @@ registerAction2(class extends Action2 {
|
||||
metricsService.capture('Void Update Manual: Checking...', {})
|
||||
const res = await voidUpdateService.check()
|
||||
if (!res) { notifyErrChecking(notifService); metricsService.capture('Void Update Manual: Error', { res }) }
|
||||
else if (res.hasUpdate) { notifyYesUpdate(notifService, res.message); metricsService.capture('Void Update Manual: Yes', { res }) }
|
||||
else if (res.hasUpdate) { notifyYesUpdate(notifService, res); metricsService.capture('Void Update Manual: Yes', { res }) }
|
||||
else if (!res.hasUpdate) { notifyNoUpdate(notifService); metricsService.capture('Void Update Manual: No', { res }) }
|
||||
}
|
||||
})
|
||||
@@ -75,19 +106,19 @@ class VoidUpdateWorkbenchContribution extends Disposable implements IWorkbenchCo
|
||||
this.metricsService.capture('Void Update Startup: Checking...', {})
|
||||
const res = await this.voidUpdateService.check()
|
||||
if (!res) { notifyErrChecking(this.notifService); this.metricsService.capture('Void Update Startup: Error', { res }) }
|
||||
else if (res.hasUpdate) { notifyYesUpdate(this.notifService, res.message); this.metricsService.capture('Void Update Startup: Yes', { res }) }
|
||||
else if (res.hasUpdate) { notifyYesUpdate(this.notifService, res); this.metricsService.capture('Void Update Startup: Yes', { res }) }
|
||||
else if (!res.hasUpdate) { this.metricsService.capture('Void Update Startup: No', { res }) } // display nothing if up to date
|
||||
}
|
||||
|
||||
// check once 5 seconds after mount
|
||||
|
||||
const initId = setTimeout(() => autoCheck(), 5 * 1000)
|
||||
this._register({ dispose: () => clearTimeout(initId) })
|
||||
|
||||
// check every 3 hours
|
||||
const { window } = dom.getActiveWindow()
|
||||
|
||||
const intervalId = window.setInterval(() => autoCheck(), 3 * 60 * 60 * 1000)
|
||||
const initId = window.setTimeout(() => autoCheck(), 5 * 1000)
|
||||
this._register({ dispose: () => window.clearTimeout(initId) })
|
||||
|
||||
|
||||
const intervalId = window.setInterval(() => autoCheck(), 3 * 60 * 60 * 1000) // every 3 hrs
|
||||
this._register({ dispose: () => window.clearInterval(intervalId) })
|
||||
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
|
||||
|
||||
|
||||
// service that manages state
|
||||
export type VoidUriState = {
|
||||
currentUri?: URI
|
||||
}
|
||||
|
||||
export interface IVoidUriStateService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly state: VoidUriState; // readonly to the user
|
||||
setState(newState: Partial<VoidUriState>): void;
|
||||
onDidChangeState: Event<void>;
|
||||
}
|
||||
|
||||
export const IVoidUriStateService = createDecorator<IVoidUriStateService>('voidUriStateService');
|
||||
class VoidUriStateService extends Disposable implements IVoidUriStateService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
static readonly ID = 'voidUriStateService';
|
||||
|
||||
private readonly _onDidChangeState = new Emitter<void>();
|
||||
readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
|
||||
|
||||
|
||||
// state
|
||||
state: VoidUriState
|
||||
|
||||
constructor(
|
||||
) {
|
||||
super()
|
||||
|
||||
// initial state
|
||||
this.state = { currentUri: undefined }
|
||||
}
|
||||
|
||||
setState(newState: Partial<VoidUriState>) {
|
||||
|
||||
this.state = { ...this.state, ...newState }
|
||||
this._onDidChangeState.fire()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IVoidUriStateService, VoidUriStateService, InstantiationType.Eager);
|
||||
@@ -0,0 +1,108 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { VoidFileSnapshot } from './editCodeServiceTypes.js';
|
||||
import { AnthropicReasoning } from './sendLLMMessageTypes.js';
|
||||
import { ToolName, ToolCallParams, ToolResultType } from './toolsServiceTypes.js';
|
||||
|
||||
export type ToolMessage<T extends ToolName> = {
|
||||
role: 'tool';
|
||||
paramsStr: string; // internal use
|
||||
id: string; // apis require this tool use id
|
||||
content: string; // give this result to LLM (string of value)
|
||||
} & (
|
||||
// in order of events:
|
||||
| { type: 'invalid_params', result: null, params: null, name: string }
|
||||
|
||||
| { type: 'tool_request', result: null, name: T, params: ToolCallParams[T], } // params were validated, awaiting user
|
||||
|
||||
| { type: 'running_now', result: null, name: T, params: ToolCallParams[T], }
|
||||
|
||||
| { type: 'tool_error', result: string, name: T, params: ToolCallParams[T], } // error when tool was running
|
||||
| { type: 'success', result: ToolResultType[T], name: T, params: ToolCallParams[T], }
|
||||
| { type: 'rejected', result: null, name: T, params: ToolCallParams[T], }
|
||||
) // user rejected
|
||||
|
||||
export type DecorativeCanceledTool = {
|
||||
role: 'decorative_canceled_tool';
|
||||
name: string;
|
||||
}
|
||||
|
||||
// export type ToolRequestApproval<T extends ToolName> = {
|
||||
// role: 'tool_request';
|
||||
// name: T; // internal use
|
||||
// params: ToolCallParams[T]; // internal use
|
||||
// paramsStr: string; // internal use - this is what the LLM outputted, not necessarily JSON.stringify(params)
|
||||
// id: string; // proposed tool's id
|
||||
// }
|
||||
|
||||
|
||||
// checkpoints
|
||||
export type CheckpointEntry = {
|
||||
role: 'checkpoint';
|
||||
type: 'user_edit' | 'tool_edit';
|
||||
voidFileSnapshotOfURI: { [fsPath: string]: VoidFileSnapshot | undefined };
|
||||
|
||||
userModifications: {
|
||||
voidFileSnapshotOfURI: { [fsPath: string]: VoidFileSnapshot | undefined };
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// WARNING: changing this format is a big deal!!!!!! need to migrate old format to new format on users' computers so people don't get errors.
|
||||
export type ChatMessage =
|
||||
| {
|
||||
role: 'user';
|
||||
content: string; // content displayed to the LLM on future calls - allowed to be '', will be replaced with (empty)
|
||||
displayContent: string; // content displayed to user - allowed to be '', will be ignored
|
||||
selections: StagingSelectionItem[] | null; // the user's selection
|
||||
state: {
|
||||
stagingSelections: StagingSelectionItem[];
|
||||
isBeingEdited: boolean;
|
||||
}
|
||||
} | {
|
||||
role: 'assistant';
|
||||
content: string; // content received from LLM - allowed to be '', will be replaced with (empty)
|
||||
reasoning: string; // reasoning from the LLM, used for step-by-step thinking
|
||||
|
||||
anthropicReasoning: AnthropicReasoning[] | null; // anthropic reasoning
|
||||
}
|
||||
| ToolMessage<ToolName>
|
||||
| DecorativeCanceledTool
|
||||
| CheckpointEntry
|
||||
|
||||
|
||||
// one of the square items that indicates a selection in a chat bubble
|
||||
export type StagingSelectionItem = {
|
||||
type: 'File';
|
||||
uri: URI;
|
||||
language: string;
|
||||
state: { wasAddedAsCurrentFile: boolean; };
|
||||
} | {
|
||||
type: 'CodeSelection';
|
||||
range: [number, number];
|
||||
uri: URI;
|
||||
language: string;
|
||||
state: { wasAddedAsCurrentFile: boolean; };
|
||||
} | {
|
||||
type: 'Folder';
|
||||
uri: URI;
|
||||
language?: undefined;
|
||||
state?: undefined;
|
||||
}
|
||||
|
||||
|
||||
// a link to a symbol (an underlined link to a piece of code)
|
||||
export type CodespanLocationLink = {
|
||||
uri: URI, // we handle serialization for this
|
||||
displayText: string,
|
||||
selection?: { // store as JSON so dont have to worry about serialization
|
||||
startLineNumber: number
|
||||
startColumn: number,
|
||||
endLineNumber: number
|
||||
endColumn: number,
|
||||
} | undefined
|
||||
} | null
|
||||
@@ -0,0 +1,10 @@
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
|
||||
export type VoidDirectoryItem = {
|
||||
uri: URI;
|
||||
name: string;
|
||||
isSymbolicLink: boolean;
|
||||
children: VoidDirectoryItem[] | null;
|
||||
isDirectory: boolean;
|
||||
isGitIgnoredDirectory: false | { numChildren: number }; // if directory is gitignored, we ignore children
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
|
||||
export type ComputedDiff = {
|
||||
type: 'edit';
|
||||
originalCode: string;
|
||||
originalStartLine: number;
|
||||
originalEndLine: number;
|
||||
code: string;
|
||||
startLine: number; // 1-indexed
|
||||
endLine: number;
|
||||
} | {
|
||||
type: 'insertion';
|
||||
// originalCode: string;
|
||||
originalStartLine: number; // insertion starts on column 0 of this
|
||||
// originalEndLine: number;
|
||||
code: string;
|
||||
startLine: number;
|
||||
endLine: number;
|
||||
} | {
|
||||
type: 'deletion';
|
||||
originalCode: string;
|
||||
originalStartLine: number;
|
||||
originalEndLine: number;
|
||||
// code: string;
|
||||
startLine: number; // deletion starts on column 0 of this
|
||||
// endLine: number;
|
||||
}
|
||||
|
||||
// ---------- Diff types ----------
|
||||
|
||||
export type CommonZoneProps = {
|
||||
diffareaid: number;
|
||||
startLine: number;
|
||||
endLine: number;
|
||||
|
||||
_URI: URI; // typically we get the URI from model
|
||||
|
||||
}
|
||||
|
||||
|
||||
export type CtrlKZone = {
|
||||
type: 'CtrlKZone';
|
||||
originalCode?: undefined;
|
||||
|
||||
editorId: string; // the editor the input lives on
|
||||
|
||||
// _ means anything we don't include if we clone it
|
||||
_mountInfo: null | {
|
||||
textAreaRef: { current: HTMLTextAreaElement | null }
|
||||
dispose: () => void;
|
||||
refresh: () => void;
|
||||
}
|
||||
_linkedStreamingDiffZone: number | null; // diffareaid of the diffZone currently streaming here
|
||||
_removeStylesFns: Set<Function> // these don't remove diffs or this diffArea, only their styles
|
||||
} & CommonZoneProps
|
||||
|
||||
|
||||
export type TrackingZone<T> = {
|
||||
type: 'TrackingZone';
|
||||
metadata: T;
|
||||
originalCode?: undefined;
|
||||
editorId?: undefined;
|
||||
_removeStylesFns?: undefined;
|
||||
} & CommonZoneProps
|
||||
|
||||
|
||||
// called DiffArea for historical purposes, we can rename to something like TextRegion if we want
|
||||
export type DiffArea = CtrlKZone | DiffZone | TrackingZone<any>
|
||||
|
||||
|
||||
export type Diff = {
|
||||
diffid: number;
|
||||
diffareaid: number; // the diff area this diff belongs to, "computed"
|
||||
} & ComputedDiff
|
||||
|
||||
|
||||
export type DiffZone = {
|
||||
type: 'DiffZone',
|
||||
originalCode: string;
|
||||
_diffOfId: Record<string, Diff>; // diffid -> diff in this DiffArea
|
||||
_streamState: {
|
||||
isStreaming: true;
|
||||
streamRequestIdRef: { current: string | null };
|
||||
line: number;
|
||||
} | {
|
||||
isStreaming: false;
|
||||
streamRequestIdRef?: undefined;
|
||||
line?: undefined;
|
||||
};
|
||||
editorId?: undefined;
|
||||
linkedStreamingDiffZone?: undefined;
|
||||
_removeStylesFns: Set<Function> // these don't remove diffs or this diffArea, only their styles
|
||||
} & CommonZoneProps
|
||||
|
||||
|
||||
export const diffAreaSnapshotKeys = [
|
||||
'type',
|
||||
'diffareaid',
|
||||
'originalCode',
|
||||
'startLine',
|
||||
'endLine',
|
||||
'editorId',
|
||||
|
||||
] as const satisfies (keyof DiffArea)[]
|
||||
|
||||
|
||||
|
||||
export type DiffAreaSnapshotEntry<DiffAreaType extends DiffArea = DiffArea> = Pick<DiffAreaType, typeof diffAreaSnapshotKeys[number]>
|
||||
|
||||
export type VoidFileSnapshot = {
|
||||
snapshottedDiffAreaOfId: Record<string, DiffAreaSnapshotEntry>;
|
||||
entireFileCode: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export const acceptBg = '#1a7431'
|
||||
export const acceptAllBg = '#1e8538'
|
||||
export const acceptBorder = '1px solid #145626'
|
||||
export const rejectBg = '#b42331'
|
||||
export const rejectAllBg = '#cf2838'
|
||||
export const rejectBorder = '1px solid #8e1c27'
|
||||
export const buttonFontSize = '11px'
|
||||
export const buttonTextColor = 'white'
|
||||
@@ -0,0 +1,576 @@
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { OnText } from '../sendLLMMessageTypes.js'
|
||||
import { DIVIDER, FINAL, ORIGINAL } from '../prompt/prompts.js'
|
||||
|
||||
class SurroundingsRemover {
|
||||
readonly originalS: string
|
||||
i: number
|
||||
j: number
|
||||
|
||||
// string is s[i...j]
|
||||
|
||||
constructor(s: string) {
|
||||
this.originalS = s
|
||||
this.i = 0
|
||||
this.j = s.length - 1
|
||||
}
|
||||
value() {
|
||||
return this.originalS.substring(this.i, this.j + 1)
|
||||
}
|
||||
|
||||
// returns whether it removed the whole prefix
|
||||
removePrefix = (prefix: string): boolean => {
|
||||
let offset = 0
|
||||
// console.log('prefix', prefix, Math.min(this.j, prefix.length - 1))
|
||||
while (this.i <= this.j && offset <= prefix.length - 1) {
|
||||
if (this.originalS.charAt(this.i) !== prefix.charAt(offset))
|
||||
break
|
||||
offset += 1
|
||||
this.i += 1
|
||||
}
|
||||
return offset === prefix.length
|
||||
}
|
||||
|
||||
// // removes suffix from right to left
|
||||
removeSuffix = (suffix: string): boolean => {
|
||||
// e.g. suffix = <PRE/>, the string is <PRE>hi<P
|
||||
const s = this.value()
|
||||
// for every possible prefix of `suffix`, check if string ends with it
|
||||
for (let len = Math.min(s.length, suffix.length); len >= 1; len -= 1) {
|
||||
if (s.endsWith(suffix.substring(0, len))) { // the end of the string equals a prefix
|
||||
this.j -= len
|
||||
return len === suffix.length
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
// removeSuffix = (suffix: string): boolean => {
|
||||
// let offset = 0
|
||||
|
||||
// while (this.j >= Math.max(this.i, 0)) {
|
||||
// if (this.originalS.charAt(this.j) !== suffix.charAt(suffix.length - 1 - offset))
|
||||
// break
|
||||
// offset += 1
|
||||
// this.j -= 1
|
||||
// }
|
||||
// return offset === suffix.length
|
||||
// }
|
||||
|
||||
removeFromStartUntilFullMatch = (until: string, alsoRemoveUntilStr: boolean) => {
|
||||
const index = this.originalS.indexOf(until, this.i)
|
||||
|
||||
if (index === -1) {
|
||||
this.i = this.j + 1
|
||||
return null
|
||||
}
|
||||
// console.log('index', index, until.length)
|
||||
|
||||
if (alsoRemoveUntilStr)
|
||||
this.i = index + until.length
|
||||
else
|
||||
this.i = index
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
removeCodeBlock = () => {
|
||||
// Match either:
|
||||
// 1. ```language\n<code>\n```\n?
|
||||
// 2. ```<code>\n```\n?
|
||||
|
||||
const pm = this
|
||||
const foundCodeBlock = pm.removePrefix('```')
|
||||
if (!foundCodeBlock) return false
|
||||
|
||||
pm.removeFromStartUntilFullMatch('\n', true) // language
|
||||
|
||||
const j = pm.j
|
||||
let foundCodeBlockEnd = pm.removeSuffix('```')
|
||||
|
||||
if (pm.j === j) foundCodeBlockEnd = pm.removeSuffix('```\n') // if no change, try again with \n after ```
|
||||
|
||||
if (!foundCodeBlockEnd) return false
|
||||
|
||||
pm.removeSuffix('\n') // remove the newline before ```
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
deltaInfo = (recentlyAddedTextLen: number) => {
|
||||
// aaaaaatextaaaaaa{recentlyAdded}
|
||||
// ^ i j len
|
||||
// |
|
||||
// recentyAddedIdx
|
||||
const recentlyAddedIdx = this.originalS.length - recentlyAddedTextLen
|
||||
const actualDelta = this.originalS.substring(Math.max(this.i, recentlyAddedIdx), this.j + 1)
|
||||
const ignoredSuffix = this.originalS.substring(Math.max(this.j + 1, recentlyAddedIdx), Infinity)
|
||||
return [actualDelta, ignoredSuffix] as const
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const extractCodeFromRegular = ({ text, recentlyAddedTextLen }: { text: string, recentlyAddedTextLen: number }): [string, string, string] => {
|
||||
|
||||
const pm = new SurroundingsRemover(text)
|
||||
|
||||
pm.removeCodeBlock()
|
||||
|
||||
const s = pm.value()
|
||||
const [delta, ignoredSuffix] = pm.deltaInfo(recentlyAddedTextLen)
|
||||
|
||||
return [s, delta, ignoredSuffix]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Ollama has its own FIM, we should not use this if we use that
|
||||
export const extractCodeFromFIM = ({ text, recentlyAddedTextLen, midTag, }: { text: string, recentlyAddedTextLen: number, midTag: string }): [string, string, string] => {
|
||||
|
||||
/* ------------- summary of the regex -------------
|
||||
[optional ` | `` | ```]
|
||||
(match optional_language_name)
|
||||
[optional strings here]
|
||||
[required <MID> tag]
|
||||
(match the stuff between mid tags)
|
||||
[optional <MID/> tag]
|
||||
[optional ` | `` | ```]
|
||||
*/
|
||||
|
||||
const pm = new SurroundingsRemover(text)
|
||||
|
||||
pm.removeCodeBlock()
|
||||
|
||||
const foundMid = pm.removePrefix(`<${midTag}>`)
|
||||
|
||||
if (foundMid) {
|
||||
pm.removeSuffix(`</${midTag}>`)
|
||||
}
|
||||
const s = pm.value()
|
||||
const [delta, ignoredSuffix] = pm.deltaInfo(recentlyAddedTextLen)
|
||||
|
||||
return [s, delta, ignoredSuffix]
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type ExtractedSearchReplaceBlock = {
|
||||
state: 'writingOriginal' | 'writingFinal' | 'done',
|
||||
orig: string,
|
||||
final: string,
|
||||
}
|
||||
|
||||
|
||||
// JS substring swaps indices, so "ab".substr(1,0) will NOT be '', it will be 'a'!
|
||||
const voidSubstr = (str: string, start: number, end: number) => end < start ? '' : str.substring(start, end)
|
||||
|
||||
const endsWithAnyPrefixOf = (str: string, anyPrefix: string) => {
|
||||
// for each prefix
|
||||
for (let i = anyPrefix.length; i >= 1; i--) { // i >= 1 because must not be empty string
|
||||
const prefix = anyPrefix.slice(0, i)
|
||||
if (str.endsWith(prefix)) return prefix
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// guarantees if you keep adding text, array length will strictly grow and state will progress without going back
|
||||
export const extractSearchReplaceBlocks = (str: string) => {
|
||||
|
||||
const ORIGINAL_ = ORIGINAL + `\n`
|
||||
const DIVIDER_ = '\n' + DIVIDER + `\n`
|
||||
// logic for FINAL_ is slightly more complicated - should be '\n' + FINAL, but that ignores if the final output is empty
|
||||
|
||||
const blocks: ExtractedSearchReplaceBlock[] = []
|
||||
|
||||
let i = 0 // search i and beyond (this is done by plain index, not by line number. much simpler this way)
|
||||
while (true) {
|
||||
let origStart = str.indexOf(ORIGINAL_, i)
|
||||
if (origStart === -1) { return blocks }
|
||||
origStart += ORIGINAL_.length
|
||||
i = origStart
|
||||
// wrote <<<< ORIGINAL\n
|
||||
|
||||
let dividerStart = str.indexOf(DIVIDER_, i)
|
||||
if (dividerStart === -1) { // if didnt find DIVIDER_, either writing originalStr or DIVIDER_ right now
|
||||
const writingDIVIDERlen = endsWithAnyPrefixOf(str, DIVIDER_)?.length ?? 0
|
||||
blocks.push({
|
||||
orig: voidSubstr(str, origStart, str.length - writingDIVIDERlen),
|
||||
final: '',
|
||||
state: 'writingOriginal'
|
||||
})
|
||||
return blocks
|
||||
}
|
||||
const origStrDone = voidSubstr(str, origStart, dividerStart)
|
||||
dividerStart += DIVIDER_.length
|
||||
i = dividerStart
|
||||
// wrote \n=====\n
|
||||
|
||||
const fullFINALStart = str.indexOf(FINAL, i)
|
||||
const fullFINALStart_ = str.indexOf('\n' + FINAL, i) // go with B if possible, else fallback to A, it's more permissive
|
||||
const matchedFullFINAL_ = fullFINALStart_ !== -1 && fullFINALStart === fullFINALStart_ + 1 // this logic is really important, otherwise we might look for FINAL_ at a much later part of the string
|
||||
|
||||
let finalStart = matchedFullFINAL_ ? fullFINALStart_ : fullFINALStart
|
||||
if (finalStart === -1) { // if didnt find FINAL_, either writing finalStr or FINAL or FINAL_ right now
|
||||
const writingFINALlen = endsWithAnyPrefixOf(str, FINAL)?.length ?? 0
|
||||
const writingFINALlen_ = endsWithAnyPrefixOf(str, '\n' + FINAL)?.length ?? 0 // this gets priority
|
||||
const usingWritingFINALlen = Math.max(writingFINALlen, writingFINALlen_)
|
||||
blocks.push({
|
||||
orig: origStrDone,
|
||||
final: voidSubstr(str, dividerStart, str.length - usingWritingFINALlen),
|
||||
state: 'writingFinal'
|
||||
})
|
||||
return blocks
|
||||
}
|
||||
const usingFINAL = matchedFullFINAL_ ? '\n' + FINAL : FINAL
|
||||
const finalStrDone = voidSubstr(str, dividerStart, finalStart)
|
||||
finalStart += usingFINAL.length
|
||||
i = finalStart
|
||||
// wrote >>>>> FINAL
|
||||
|
||||
blocks.push({
|
||||
orig: origStrDone,
|
||||
final: finalStrDone,
|
||||
state: 'done'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// could simplify this - this assumes we can never add a tag without committing it to the user's screen, but that's not true
|
||||
export const extractReasoningOnTextWrapper = (onText: OnText, thinkTags: [string, string]): OnText => {
|
||||
let latestAddIdx = 0 // exclusive index in fullText_
|
||||
let foundTag1 = false
|
||||
let foundTag2 = false
|
||||
|
||||
let fullTextSoFar = ''
|
||||
let fullReasoningSoFar = ''
|
||||
|
||||
let onText_ = onText
|
||||
onText = (params) => {
|
||||
onText_(params)
|
||||
}
|
||||
|
||||
const newOnText: OnText = ({ fullText: fullText_, ...p }) => {
|
||||
// until found the first think tag, keep adding to fullText
|
||||
if (!foundTag1) {
|
||||
const endsWithTag1 = endsWithAnyPrefixOf(fullText_, thinkTags[0])
|
||||
if (endsWithTag1) {
|
||||
// console.log('endswith1', { fullTextSoFar, fullReasoningSoFar, fullText_ })
|
||||
// wait until we get the full tag or know more
|
||||
return
|
||||
}
|
||||
// if found the first tag
|
||||
const tag1Index = fullText_.indexOf(thinkTags[0])
|
||||
if (tag1Index !== -1) {
|
||||
// console.log('tag1Index !==1', { tag1Index, fullTextSoFar, fullReasoningSoFar, thinkTags, fullText_ })
|
||||
foundTag1 = true
|
||||
// Add text before the tag to fullTextSoFar
|
||||
fullTextSoFar += fullText_.substring(0, tag1Index)
|
||||
// Update latestAddIdx to after the first tag
|
||||
latestAddIdx = tag1Index + thinkTags[0].length
|
||||
onText({ ...p, fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
return
|
||||
}
|
||||
|
||||
// console.log('adding to text A', { fullTextSoFar, fullReasoningSoFar })
|
||||
// add the text to fullText
|
||||
fullTextSoFar = fullText_
|
||||
latestAddIdx = fullText_.length
|
||||
onText({ ...p, fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
return
|
||||
}
|
||||
|
||||
// at this point, we found <tag1>
|
||||
|
||||
// until found the second think tag, keep adding to fullReasoning
|
||||
if (!foundTag2) {
|
||||
const endsWithTag2 = endsWithAnyPrefixOf(fullText_, thinkTags[1])
|
||||
if (endsWithTag2) {
|
||||
// console.log('endsWith2', { fullTextSoFar, fullReasoningSoFar })
|
||||
// wait until we get the full tag or know more
|
||||
return
|
||||
}
|
||||
|
||||
// if found the second tag
|
||||
const tag2Index = fullText_.indexOf(thinkTags[1], latestAddIdx)
|
||||
if (tag2Index !== -1) {
|
||||
// console.log('tag2Index !== -1', { fullTextSoFar, fullReasoningSoFar })
|
||||
foundTag2 = true
|
||||
// Add everything between first and second tag to reasoning
|
||||
fullReasoningSoFar += fullText_.substring(latestAddIdx, tag2Index)
|
||||
// Update latestAddIdx to after the second tag
|
||||
latestAddIdx = tag2Index + thinkTags[1].length
|
||||
onText({ ...p, fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
return
|
||||
}
|
||||
|
||||
// add the text to fullReasoning (content after first tag but before second tag)
|
||||
// console.log('adding to text B', { fullTextSoFar, fullReasoningSoFar })
|
||||
|
||||
// If we have more text than we've processed, add it to reasoning
|
||||
if (fullText_.length > latestAddIdx) {
|
||||
fullReasoningSoFar += fullText_.substring(latestAddIdx)
|
||||
latestAddIdx = fullText_.length
|
||||
}
|
||||
|
||||
onText({ ...p, fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
return
|
||||
}
|
||||
|
||||
// at this point, we found <tag2> - content after the second tag is normal text
|
||||
// console.log('adding to text C', { fullTextSoFar, fullReasoningSoFar })
|
||||
|
||||
// Add any new text after the closing tag to fullTextSoFar
|
||||
if (fullText_.length > latestAddIdx) {
|
||||
fullTextSoFar += fullText_.substring(latestAddIdx)
|
||||
latestAddIdx = fullText_.length
|
||||
}
|
||||
|
||||
onText({ ...p, fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||
}
|
||||
|
||||
return newOnText
|
||||
}
|
||||
|
||||
|
||||
export const extractReasoningOnFinalMessage = (fullText_: string, thinkTags: [string, string]): { fullText: string, fullReasoning: string } => {
|
||||
const tag1Idx = fullText_.indexOf(thinkTags[0])
|
||||
const tag2Idx = fullText_.indexOf(thinkTags[1])
|
||||
if (tag1Idx === -1) return { fullText: fullText_, fullReasoning: '' } // never started reasoning
|
||||
if (tag2Idx === -1) return { fullText: '', fullReasoning: fullText_ } // never stopped reasoning
|
||||
|
||||
const fullReasoning = fullText_.substring(tag1Idx + thinkTags[0].length, tag2Idx)
|
||||
const fullText = fullText_.substring(0, tag1Idx) + fullText_.substring(tag2Idx + thinkTags[1].length, Infinity)
|
||||
return { fullText, fullReasoning }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// const tests: [string, { shape: Partial<ExtractedSearchReplaceBlock>[] }][] = [[
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINA`, { shape: [] }
|
||||
// ], [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL`, { shape: [], }
|
||||
// ], [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A`, { shape: [{ state: 'writingOriginal', orig: 'A' }], }
|
||||
// ], [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A
|
||||
// B`, { shape: [{ state: 'writingOriginal', orig: 'A\nB' }], }
|
||||
// ], [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A
|
||||
// B
|
||||
// `, { shape: [{ state: 'writingOriginal', orig: 'A\nB' }], }
|
||||
// ], [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A
|
||||
// B
|
||||
// ===`, { shape: [{ state: 'writingOriginal', orig: 'A\nB' }], }
|
||||
// ], [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A
|
||||
// B
|
||||
// ======`, { shape: [{ state: 'writingOriginal', orig: 'A\nB' }], }
|
||||
// ], [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A
|
||||
// B
|
||||
// =======`, { shape: [{ state: 'writingOriginal', orig: 'A\nB' }], }
|
||||
// ], [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A
|
||||
// B
|
||||
// =======
|
||||
// `, { shape: [{ state: 'writingFinal', orig: 'A\nB', final: '' }], }
|
||||
// ], [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A
|
||||
// B
|
||||
// =======
|
||||
// >>>>>>> UPDAT`, { shape: [{ state: 'writingFinal', orig: 'A\nB', final: '' }], }
|
||||
// ], [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A
|
||||
// B
|
||||
// =======
|
||||
// >>>>>>> UPDATED`, { shape: [{ state: 'done', orig: 'A\nB', final: '' }], }
|
||||
// ], [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A
|
||||
// B
|
||||
// =======
|
||||
// >>>>>>> UPDATED
|
||||
// \`\`\``, { shape: [{ state: 'done', orig: 'A\nB', final: '' }], }
|
||||
// ],
|
||||
|
||||
|
||||
// // alternatively
|
||||
// [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A
|
||||
// B
|
||||
// =======
|
||||
// X`, { shape: [{ state: 'writingFinal', orig: 'A\nB', final: 'X' }], }
|
||||
// ],
|
||||
// [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A
|
||||
// B
|
||||
// =======
|
||||
// X
|
||||
// Y`, { shape: [{ state: 'writingFinal', orig: 'A\nB', final: 'X\nY' }], }
|
||||
// ],
|
||||
// [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A
|
||||
// B
|
||||
// =======
|
||||
// X
|
||||
// Y
|
||||
// `, { shape: [{ state: 'writingFinal', orig: 'A\nB', final: 'X\nY' }], }
|
||||
// ],
|
||||
// [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A
|
||||
// B
|
||||
// =======
|
||||
// X
|
||||
// Y
|
||||
// >>>>>>> UPDAT`, { shape: [{ state: 'writingFinal', orig: 'A\nB', final: 'X\nY' }], }
|
||||
// ], [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A
|
||||
// B
|
||||
// =======
|
||||
// X
|
||||
// Y
|
||||
// >>>>>>> UPDATED`, { shape: [{ state: 'done', orig: 'A\nB', final: 'X\nY' }], }
|
||||
// ], [
|
||||
// `\
|
||||
// \`\`\`
|
||||
// <<<<<<< ORIGINAL
|
||||
// A
|
||||
// B
|
||||
// =======
|
||||
// X
|
||||
// Y
|
||||
// >>>>>>> UPDATED
|
||||
// \`\`\``, { shape: [{ state: 'done', orig: 'A\nB', final: 'X\nY' }], }
|
||||
// ]]
|
||||
|
||||
|
||||
|
||||
|
||||
// function runTests() {
|
||||
|
||||
|
||||
// let passedTests = 0;
|
||||
// let failedTests = 0;
|
||||
|
||||
// for (let i = 0; i < tests.length; i++) {
|
||||
// const [input, expected] = tests[i];
|
||||
// const result = extractSearchReplaceBlocks(input);
|
||||
|
||||
// // Compare result with expected shape
|
||||
// let passed = true;
|
||||
// if (result.length !== expected.shape.length) {
|
||||
// passed = false;
|
||||
// } else {
|
||||
// for (let j = 0; j < result.length; j++) { // block
|
||||
// const expectedItem = expected.shape[j];
|
||||
// const resultItem = result[j];
|
||||
|
||||
// if ((expectedItem.state !== undefined) && (expectedItem.state !== resultItem.state) ||
|
||||
// (expectedItem.orig !== undefined) && (expectedItem.orig !== resultItem.orig) ||
|
||||
// (expectedItem.final !== undefined) && (expectedItem.final !== resultItem.final)) {
|
||||
// passed = false;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (passed) {
|
||||
// passedTests++;
|
||||
// console.log(`Test ${i + 1} passed`);
|
||||
// } else {
|
||||
// failedTests++;
|
||||
// console.log(`Test ${i + 1} failed`);
|
||||
// console.log('Input:', input)
|
||||
// console.log(`Expected:`, expected.shape);
|
||||
// console.log(`Got:`, result);
|
||||
// }
|
||||
// }
|
||||
|
||||
// console.log(`Total: ${tests.length}, Passed: ${passedTests}, Failed: ${failedTests}`);
|
||||
// return failedTests === 0;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// runTests()
|
||||
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
// /*--------------------------------------------------------------------------------------
|
||||
// * Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
// * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
// *--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { ILanguageService } from '../../../../../editor/common/languages/language.js';
|
||||
import { separateOutFirstLine } from './util.js';
|
||||
|
||||
|
||||
// this works better than model.getLanguageId()
|
||||
export function detectLanguage(languageService: ILanguageService, opts: { uri: URI | null, fileContents: string | undefined }) {
|
||||
const firstLine = opts.fileContents ? separateOutFirstLine(opts.fileContents)?.[0] : undefined
|
||||
const fullLang = languageService.createByFilepathOrFirstLine(opts.uri, firstLine)
|
||||
return fullLang.languageId || 'plaintext'
|
||||
}
|
||||
|
||||
// --- conversions
|
||||
export const convertToVscodeLang = (languageService: ILanguageService, markdownLang: string) => {
|
||||
if (markdownLang in markdownLangToVscodeLang)
|
||||
return markdownLangToVscodeLang[markdownLang]
|
||||
|
||||
const { languageId } = languageService.createById(markdownLang)
|
||||
return languageId
|
||||
}
|
||||
|
||||
|
||||
// // eg "bash" -> "shell"
|
||||
const markdownLangToVscodeLang: { [key: string]: string } = {
|
||||
// Web Technologies
|
||||
'html': 'html',
|
||||
'css': 'css',
|
||||
'scss': 'scss',
|
||||
'sass': 'scss',
|
||||
'less': 'less',
|
||||
'javascript': 'typescript',
|
||||
'js': 'typescript', // use more general renderer
|
||||
'jsx': 'typescriptreact',
|
||||
'typescript': 'typescript',
|
||||
'ts': 'typescript',
|
||||
'tsx': 'typescriptreact',
|
||||
'json': 'json',
|
||||
'jsonc': 'json',
|
||||
|
||||
// Programming Languages
|
||||
'python': 'python',
|
||||
'py': 'python',
|
||||
'java': 'java',
|
||||
'cpp': 'cpp',
|
||||
'c++': 'cpp',
|
||||
'c': 'c',
|
||||
'csharp': 'csharp',
|
||||
'cs': 'csharp',
|
||||
'c#': 'csharp',
|
||||
'go': 'go',
|
||||
'golang': 'go',
|
||||
'rust': 'rust',
|
||||
'rs': 'rust',
|
||||
'ruby': 'ruby',
|
||||
'rb': 'ruby',
|
||||
'php': 'php',
|
||||
'shell': 'shellscript', // this is important
|
||||
'bash': 'shellscript',
|
||||
'sh': 'shellscript',
|
||||
'zsh': 'shellscript',
|
||||
|
||||
// Markup and Config
|
||||
'markdown': 'markdown',
|
||||
'md': 'markdown',
|
||||
'xml': 'xml',
|
||||
'svg': 'xml',
|
||||
'yaml': 'yaml',
|
||||
'yml': 'yaml',
|
||||
'ini': 'ini',
|
||||
'toml': 'ini',
|
||||
|
||||
// Database and Query Languages
|
||||
'sql': 'sql',
|
||||
'mysql': 'sql',
|
||||
'postgresql': 'sql',
|
||||
'graphql': 'graphql',
|
||||
'gql': 'graphql',
|
||||
|
||||
// Others
|
||||
'dockerfile': 'dockerfile',
|
||||
'docker': 'dockerfile',
|
||||
'makefile': 'makefile',
|
||||
'plaintext': 'plaintext',
|
||||
'text': 'plaintext'
|
||||
};
|
||||
|
||||
// // eg ".ts" -> "typescript"
|
||||
// const fileExtensionToVscodeLanguage: { [key: string]: string } = {
|
||||
// // Web
|
||||
// 'html': 'html',
|
||||
// 'htm': 'html',
|
||||
// 'css': 'css',
|
||||
// 'scss': 'scss',
|
||||
// 'less': 'less',
|
||||
// 'js': 'javascript',
|
||||
// 'jsx': 'javascript',
|
||||
// 'ts': 'typescript',
|
||||
// 'tsx': 'typescript',
|
||||
// 'json': 'json',
|
||||
// 'jsonc': 'json',
|
||||
|
||||
// // Programming Languages
|
||||
// 'py': 'python',
|
||||
// 'java': 'java',
|
||||
// 'cpp': 'cpp',
|
||||
// 'cc': 'cpp',
|
||||
// 'c': 'c',
|
||||
// 'h': 'cpp',
|
||||
// 'hpp': 'cpp',
|
||||
// 'cs': 'csharp',
|
||||
// 'go': 'go',
|
||||
// 'rs': 'rust',
|
||||
// 'rb': 'ruby',
|
||||
// 'php': 'php',
|
||||
// 'sh': 'shell',
|
||||
// 'bash': 'shell',
|
||||
// 'zsh': 'shell',
|
||||
|
||||
// // Markup/Config
|
||||
// 'md': 'markdown',
|
||||
// 'markdown': 'markdown',
|
||||
// 'xml': 'xml',
|
||||
// 'svg': 'xml',
|
||||
// 'yaml': 'yaml',
|
||||
// 'yml': 'yaml',
|
||||
// 'ini': 'ini',
|
||||
// 'toml': 'ini',
|
||||
|
||||
// // Other
|
||||
// 'sql': 'sql',
|
||||
// 'graphql': 'graphql',
|
||||
// 'gql': 'graphql',
|
||||
// 'dockerfile': 'dockerfile',
|
||||
// 'docker': 'dockerfile',
|
||||
// 'mk': 'makefile',
|
||||
|
||||
// // Config Files and Dot Files
|
||||
// 'npmrc': 'ini',
|
||||
// 'env': 'ini',
|
||||
// 'gitignore': 'ignore',
|
||||
// 'dockerignore': 'ignore',
|
||||
// 'eslintrc': 'json',
|
||||
// 'babelrc': 'json',
|
||||
// 'prettierrc': 'json',
|
||||
// 'stylelintrc': 'json',
|
||||
// 'editorconfig': 'ini',
|
||||
// 'htaccess': 'apacheconf',
|
||||
// 'conf': 'ini',
|
||||
// 'config': 'ini',
|
||||
|
||||
// // Package Files
|
||||
// 'package': 'json',
|
||||
// 'package-lock': 'json',
|
||||
// 'gemfile': 'ruby',
|
||||
// 'podfile': 'ruby',
|
||||
// 'rakefile': 'ruby',
|
||||
|
||||
// // Build Systems
|
||||
// 'cmake': 'cmake',
|
||||
// 'makefile': 'makefile',
|
||||
// 'gradle': 'groovy',
|
||||
|
||||
// // Shell Scripts
|
||||
// 'bashrc': 'shell',
|
||||
// 'zshrc': 'shell',
|
||||
// 'fish': 'shell',
|
||||
|
||||
// // Version Control
|
||||
// 'gitconfig': 'ini',
|
||||
// 'hgrc': 'ini',
|
||||
// 'svnconfig': 'ini',
|
||||
|
||||
// // Web Server
|
||||
// 'nginx': 'nginx',
|
||||
|
||||
// // Misc Config
|
||||
// 'properties': 'properties',
|
||||
// 'cfg': 'ini',
|
||||
// 'reg': 'ini'
|
||||
// };
|
||||
|
||||
|
||||
// export function filenameToVscodeLanguage(filename: string): string | undefined {
|
||||
|
||||
|
||||
|
||||
|
||||
// const ext = filename.toLowerCase().split('.').pop();
|
||||
// if (!ext) return undefined;
|
||||
|
||||
// return fileExtensionToVscodeLanguage[ext];
|
||||
// }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user