Compare commits

...

341 Commits

Author SHA1 Message Date
Andrew Pareles d5dc411e4c Merge remote-tracking branch 'origin/main' into tools-in-plaintext 2025-04-09 08:16:51 -07:00
Andrew Pareles e14fdd6678 awaited 2025-04-09 08:16:43 -07:00
Andrew Pareles 8b5dfa66d5 Merge remote-tracking branch 'origin/model-selection' into tools-in-plaintext 2025-04-09 08:14:01 -07:00
Andrew Pareles aef606dfb4 better update checking 2025-04-09 08:08:47 -07:00
Mathew Pareles 89b29ac97b settings page is decent for update 2025-04-09 07:56:17 -07:00
Andrew Pareles 908de32b8f Merge pull request #382 from voideditor/revert-374-main
Revert "Merge changes from vscode disabling mangling"
2025-04-09 07:08:46 -07:00
Andrew Pareles 4787400865 Revert "Merge changes from vscode disabling mangling" 2025-04-09 07:08:30 -07:00
Mathew Pareles 8b5e7dc25b revert package.json bug 2025-04-09 06:22:41 -07:00
Andrew Pareles 7289f5f15a Merge remote-tracking branch 'origin/main' into tools-in-plaintext 2025-04-09 06:09:16 -07:00
Mathew Pareles 3128000cf7 fix ux 2025-04-09 04:39:32 -07:00
Mathew Pareles 99901d1e20 prepare to add linterror ui (incomplete) 2025-04-09 04:31:06 -07:00
Andrew Pareles 858faed92b 5s 2025-04-09 04:18:46 -07:00
Andrew Pareles a4f94087df supportsTools is back 2025-04-09 04:01:39 -07:00
Andrew Pareles 4236d920d9 Merge remote-tracking branch 'origin/model-selection' into tools-in-plaintext 2025-04-09 04:01:30 -07:00
Andrew Pareles 56619c7b17 rm copilot head 2025-04-09 03:31:34 -07:00
Andrew Pareles ea65903b3f fix running_tool state and double undo on click stop in Apply 2025-04-09 03:25:09 -07:00
Andrew Pareles 78671db5b8 checkpoint state improvements 2025-04-09 02:45:34 -07:00
Mathew Pareles 7f38c1c9c3 ux 2025-04-09 01:23:31 -07:00
Mathew Pareles eefa9648c4 ui changes 2025-04-09 01:10:32 -07:00
Andrew Pareles 8c26fd2081 params 2025-04-08 23:59:07 -07:00
Andrew Pareles 49fbb62259 proper handling of tool call result, and improved prompting 2025-04-08 23:55:57 -07:00
Andrew Pareles ad5a77dc55 rm sax 2025-04-08 15:43:06 -07:00
Andrew Pareles 052a50f9b0 misc fixes + clarify displayContent 2025-04-08 03:20:34 -07:00
Andrew Pareles e331fb4eed trim whitespace + fix onFinalMessage last chunk error 2025-04-08 02:44:05 -07:00
Andrew Pareles 4e197f4659 debug 2025-04-08 02:04:08 -07:00
Andrew Pareles 39bf2283cc GPTify sax library 2025-04-08 02:03:25 -07:00
Mathew Pareles c87d1886cd tooltips work 2025-04-08 02:00:42 -07:00
Andrew Pareles 6f693c4d0a progress 2025-04-07 23:05:03 -07:00
Andrew Pareles 1c5adb96d3 tool calls via plaintext initial draft 2025-04-07 22:26:31 -07:00
Andrew Pareles 8f8fa8548d + 2025-04-07 20:28:39 -07:00
Mathew Pareles bbe7575321 fix styles that were broken by someone 2025-04-07 18:36:51 -07:00
Andrew Pareles dae5d054e7 Merge pull request #371 from bjoaquinc/remote-wsl-extension
Added open-remote-wsl to /extensions
2025-04-07 16:49:56 -07:00
Andrew Pareles 0638cef79b Merge pull request #374 from GhostDog98/main
Merge changes from vscode disabling mangling
2025-04-07 16:49:12 -07:00
Mathew Pareles 8e2e8c58c9 onboarding draft and lots of misc changes 2025-04-07 16:38:15 -07:00
Mathew Pareles 4b4dde71b3 bug 2025-04-07 16:38:15 -07:00
Mathew Pareles d59b99a2a0 make helper close when Cmd+L or Cmd+K pressed 2025-04-07 16:38:15 -07:00
Mathew Pareles df32898523 remove dropdown max w 2025-04-07 16:38:15 -07:00
Mathew Pareles 2bd8e61706 fn 2025-04-07 16:38:15 -07:00
Mathew Pareles 35724f36cd smartselect draft (need to add setting) 2025-04-07 16:38:15 -07:00
Mathew Pareles 7fba51b59f final changes to selection helper 2025-04-07 16:38:15 -07:00
Mathew Pareles 599d80dcc3 minor change 2025-04-07 16:38:15 -07:00
Mathew Pareles 3c186031be minor positional change 2025-04-07 16:38:15 -07:00
Mathew Pareles 51c84df5a5 better helperwidget positioning 2025-04-07 16:38:15 -07:00
Andrew Pareles 77c031abc5 300ms 2025-04-07 16:13:18 -07:00
Andrew Pareles e81e71c81f hover delay 300ms 2025-04-07 16:12:37 -07:00
Andrew Pareles be07ecec2e Merge remote-tracking branch 'origin/main' into model-selection 2025-04-07 04:00:57 -07:00
Andrew Pareles 3aa0bf11e6 Merge pull request #337 from Dylan-86/main
Added more models for agents from openRouter
2025-04-07 03:46:40 -07:00
Andrew Pareles 329e68ccb1 merge 2025-04-07 03:46:12 -07:00
Andrew Pareles f3da41ba46 Merge pull request #376 from GhostDog98/patch-2
Update CONTRIBUTING.md
2025-04-07 03:39:10 -07:00
Andrew Pareles 78384b3acf Merge pull request #378 from voideditor/add-checkpoints
Add checkpoints
2025-04-07 03:38:31 -07:00
Andrew Pareles 2912d5cd89 rm logs 2025-04-07 03:37:43 -07:00
Andrew Pareles c699577183 only add current file if no message was sent 2025-04-07 02:51:46 -07:00
Andrew Pareles 707fe9e0a3 add links for any file the LLM has read 2025-04-07 02:40:27 -07:00
Andrew Pareles 71dd603c3d context weights 2025-04-07 01:59:45 -07:00
Andrew Pareles 7aa5f1e4ec context trimming 2025-04-07 01:46:07 -07:00
Andrew Pareles 7c4e92a030 pass lint errors!! 2025-04-06 22:47:57 -07:00
Andrew Pareles 76125cc83e feed lint errors 2025-04-06 22:46:30 -07:00
Andrew Pareles 3fc0fd0bc0 ghost 2025-04-06 22:16:32 -07:00
Andrew Pareles 5b0d8f4418 tool use + checkpoint state updates 2025-04-06 22:08:00 -07:00
ghostoverflow256 d71f377ac1 Update CONTRIBUTING.md
also this
2025-04-07 11:23:13 +10:00
ghostoverflow256 dbd63229c8 Update CONTRIBUTING.md
Also linux ia32 doesn't exist as a build target in the gulp files...
2025-04-07 11:21:37 +10:00
ghostoverflow256 1e39cdaba4 Update CONTRIBUTING.md
`vscode-linux-arm` does not exist as a defined build option. `vscode-linux-arm64` *does* however.
2025-04-07 11:13:43 +10:00
Andrew Pareles 5f76fa8d7d turn off reasoning for everything except chat by default 2025-04-06 17:49:32 -07:00
Andrew Pareles f6358e319b model capabilities progress 2025-04-06 17:31:11 -07:00
Andrew Pareles 5a52689d91 Merge pull request #375 from GhostDog98/patch-1
Update CONTRIBUTING.md
2025-04-06 17:04:11 -07:00
ghostoverflow256 5187f0e96c Update CONTRIBUTING.md
Added fix for macos compilation
2025-04-07 09:29:29 +10:00
João Moreno 086e471dd5 disable mangling when building vscode locally (#243856)
* disable mangling when building vscode locally

* local build: ensure mangling runs when minification is on
2025-04-07 09:06:17 +10:00
Andrew Pareles 033de587f2 improve checkpoint logic + UI 2025-04-06 03:29:45 -07:00
Andrew Pareles bd0db41f67 compare by ignoring whitespace 2025-04-05 21:13:03 -07:00
Andrew Pareles a5ac79c6a4 gemma 2025-04-05 21:06:27 -07:00
Andrew Pareles 67a253164e + 2025-04-05 20:56:28 -07:00
Andrew Pareles 3c93e0a414 separate out thinking by feature 2025-04-05 20:56:08 -07:00
Andrew Pareles 1d9e0faaa3 make chats by reference 2025-04-05 20:40:59 -07:00
Andrew Pareles b440c8732f search tools now support regex, reading line numbers, etc 2025-04-05 16:59:03 -07:00
Andrew Pareles 2aae01f3b6 rename tools 2025-04-05 15:59:43 -07:00
Andrew Pareles c1ffca04a7 recursive folder tool call and context 2025-04-05 00:26:20 -07:00
Andrew Pareles de703e82cb add new models, more info in prompt, 2025-04-04 02:39:11 -07:00
Andrew Pareles a2a9cdba60 checkpoints revert/restore diffareas!!! 2025-04-03 17:12:18 -07:00
Andrew Pareles bde51106a1 checkpoints 2025-04-03 01:03:03 -07:00
Andrew Pareles 3ac9dcf0c0 checkpoint progress 2025-04-02 19:13:51 -07:00
Andrew Pareles 7e8af9c2ef improve Apply state and misc other improvements for Apply 2025-04-02 00:25:23 -07:00
Andrew Pareles a94ce5d474 checkpoints work! need to add user_temp_modifications now and deal with diffzones 2025-03-31 22:56:47 -07:00
Joaquin Coromina eceaa6a59b Updated DEFAULT_DOWNLOAD_URL_TEMPLATE to also point at the void binaries repo 2025-04-01 00:03:02 -04:00
Joaquin Coromina 103dd68b3c Updated config to point at voidVersion instead of the base vscode version 2025-04-01 00:00:34 -04:00
Andrew Pareles 7decc8e146 add checkpoints 2025-03-31 20:35:40 -07:00
Joaquin Coromina c58210ba80 Updated the package.json default url to point to the void binaries repo properly 2025-03-31 23:16:06 -04:00
Joaquin Coromina c68a497e77 Merge branch 'voideditor:main' into remote-wsl-extension 2025-03-31 23:11:16 -04:00
Andrew Pareles 7c0ba71314 scaffolding for checkpoints 2025-03-30 21:30:01 -07:00
Andrew Pareles 878a439acd rm tool test thread 2025-03-30 20:53:07 -07:00
Andrew Pareles f047ef9c55 add dummy contrib 2025-03-30 20:52:00 -07:00
Andrew Pareles db6ebeb9f2 Merge pull request #364 from bjoaquinc/patch-2
Updated version argument to get voidVersion
2025-03-30 20:47:06 -07:00
Andrew Pareles 7631b41b27 comment 2025-03-30 20:45:42 -07:00
Andrew Pareles b2d8ca3023 Merge pull request #365 from voideditor/minor-changes-mar
Minor changes mar
2025-03-30 20:44:53 -07:00
Andrew Pareles f11ff3475b revert 2025-03-30 20:44:18 -07:00
Andrew Pareles e74cc15d71 minor UI improvements 2025-03-30 20:44:06 -07:00
Joaquin Coromina 0294cb3b98 Removed the fall back, restored the original code and commented it out 2025-03-30 22:51:40 -04:00
Joaquin Coromina 88dc32a43e Merge branch 'main' into patch-2 2025-03-30 22:46:27 -04:00
Joaquin Coromina 42e1277bb9 Merge branch 'voideditor:main' into remote-wsl-extension 2025-03-30 20:30:43 -04:00
Andrew Pareles 2dcd95cf2a Merge pull request #363 from bjoaquinc/patch-1
Fixed default url in package.json since this is used by app
2025-03-30 17:24:37 -07:00
Joaquin Coromina 499abdf26c Fixed serverConfig.ts to pull voidVersion instead of version 2025-03-30 18:40:41 -04:00
Joaquin Coromina 52e92ac3f9 Update ${NAME_OF_REPO} to binaries repo for accessing the remote host tarballs 2025-03-30 10:50:40 -04:00
Andrew Pareles 885ebc8528 Merge pull request #360 from bjoaquinc/updated-remote-ssh-for-binaries
Fixed the open-remote-ssh extension to point at binaries repo
2025-03-30 03:50:07 -07:00
Andrew Pareles 4f4ef694ff Merge pull request #362 from voideditor/minor-changes-mar
1.0.4 test
2025-03-29 22:01:10 -07:00
Andrew Pareles 8cc68989dd 1.0.4 test 2025-03-29 21:50:06 -07:00
Andrew Pareles 57795b3210 Merge pull request #361 from voideditor/minor-changes-mar
Minor changes mar
2025-03-29 21:21:46 -07:00
Andrew Pareles 68be4dcf57 minor UI 2025-03-29 21:21:31 -07:00
Andrew Pareles eb4d2a6c7b version=34.3.2 2025-03-29 21:21:24 -07:00
Joaquin Coromina 36277eaed8 updated the downlaod url to point at void reh 2025-03-29 17:43:12 -04:00
Joaquin Coromina ba4cac3fb6 Updated the DEFAULT_DOWNLOAD_URL_TEMPLATE to point at the new void binaries repo and made the string dynamic 2025-03-29 17:34:29 -04:00
Andrew Pareles 3ce2911e3d 1.0.3 2025-03-29 03:39:48 -07:00
Andrew Pareles 89dc3c8805 1.0.2 2025-03-29 03:38:19 -07:00
Andrew Pareles 1bbd6f59fe Merge pull request #358 from voideditor/minor-changes-mar
version -> voidVersion
2025-03-29 03:34:36 -07:00
Andrew Pareles bf6cb764ec version -> voidVersion 2025-03-29 03:34:20 -07:00
Mathew Pareles 7fdde7af3e inline suggestions finished 2025-03-29 03:16:30 -07:00
Andrew Pareles a2a265423f 1.0.3 2025-03-29 03:07:05 -07:00
Andrew Pareles 5f39e314b6 1.0.2 2025-03-29 02:42:59 -07:00
Andrew Pareles b7e87b4af8 1.0.3 2025-03-29 02:04:31 -07:00
Andrew Pareles b34b2f3d7b 1.0.2 2025-03-29 01:57:06 -07:00
Andrew Pareles 5b6bf42576 Merge pull request #357 from voideditor/minor-changes-mar
bump to 1.0.3 to test auto-updates
2025-03-29 01:18:11 -07:00
Andrew Pareles a72ca7868e 1.0.3 2025-03-29 01:14:31 -07:00
Andrew Pareles a0f43c90c4 Merge pull request #356 from voideditor/minor-changes-mar
revert to 1.0.2 for testing
2025-03-29 00:30:06 -07:00
Andrew Pareles 41b85052f0 revert to 1.0.2 for testing 2025-03-29 00:29:27 -07:00
Andrew Pareles 4120031e0d Merge pull request #355 from voideditor/minor-changes-mar
test update to 1.0.3
2025-03-28 23:38:56 -07:00
Andrew Pareles a499fa19fb test update to 1.0.3 2025-03-28 23:38:31 -07:00
Andrew Pareles 6867dabf09 Merge pull request #354 from voideditor/github-workflow
enable auto updates
2025-03-28 17:37:00 -07:00
Andrew Pareles 93875818f5 enable auto updates 2025-03-28 16:21:11 -07:00
Andrew Pareles d09223c362 Merge pull request #353 from voideditor/minor-changes-mar
dummy commit
2025-03-28 02:04:53 -07:00
Andrew Pareles fb5ae96a44 dummy commit 2025-03-28 02:03:47 -07:00
Andrew Pareles dbe62e82e1 Merge pull request #351 from voideditor/minor-changes-mar
new code.ico
2025-03-27 17:14:24 -07:00
Andrew Pareles e5c72ac13e new code.ico 2025-03-27 17:13:31 -07:00
Andrew Pareles dc143e7cb5 Merge pull request #350 from voideditor/minor-changes-mar
xpm
2025-03-27 17:04:36 -07:00
Andrew Pareles 80443892f3 xpm 2025-03-27 17:03:31 -07:00
Andrew Pareles 70e89c8f20 Merge pull request #349 from voideditor/github-workflow
fix vscodium patches
2025-03-27 15:56:10 -07:00
Andrew Pareles 57e8ef5463 fix vscodium patches 2025-03-26 23:14:43 -07:00
Andrew Pareles d5581b1bb4 Merge pull request #345 from voideditor/github-workflow
tslib
2025-03-26 21:43:04 -07:00
Andrew Pareles 1482ebba64 tslib 2025-03-26 21:42:26 -07:00
Andrew Pareles 6d4253dff9 Merge pull request #344 from voideditor/github-workflow
More vscodium patches
2025-03-26 21:01:50 -07:00
Andrew Pareles cc4c863c82 version-0 and version-1 2025-03-26 21:01:21 -07:00
Andrew Pareles 4618e81d20 Merge pull request #343 from voideditor/github-workflow
Vscodium patches
2025-03-26 19:58:53 -07:00
Andrew Pareles 7fa971cbcd Merge pull request #342 from voideditor/minor-changes-mar
Minor changes mar
2025-03-26 19:56:22 -07:00
Andrew Pareles 2267757c41 apply some patches 2025-03-26 19:53:02 -07:00
Andrew Pareles e07727393b rm workflows, move to its own dir 2025-03-26 14:55:08 -07:00
Mathew Pareles 6a73930f4b small styling change 2025-03-26 04:13:07 -07:00
Mathew Pareles 6c382be737 styles 2025-03-26 03:49:25 -07:00
Mathew Pareles c43a234788 commandbar (needs styling for accept/reject) 2025-03-26 03:47:45 -07:00
Andrew Pareles 4e56321eb9 update 2025-03-25 20:11:35 -07:00
Andrew Pareles 9e177c73ca + 2025-03-25 20:08:02 -07:00
Andrew Pareles ccbe1911d2 change build 2025-03-25 20:04:19 -07:00
Dylan-86 24e5b78583 Added more models for agents from openRouter 2025-03-25 12:09:39 +01:00
Andrew Pareles ed89858fbc leading-1 2025-03-25 01:26:41 -07:00
Andrew Pareles a6d5a5f325 universal macOS 2025-03-24 22:53:54 -07:00
Andrew Pareles e566db8396 Merge remote-tracking branch 'origin/model-selection' into github-workflow 2025-03-24 19:49:22 -07:00
Andrew Pareles b52cf80bd5 update 2025-03-24 19:48:11 -07:00
Andrew Pareles f1a31246b2 b2 2025-03-24 19:46:19 -07:00
Andrew Pareles e33b3ceae1 add refetch 2025-03-24 19:43:14 -07:00
Andrew Pareles c4e78cd3ba size 2025-03-24 18:36:20 -07:00
Andrew Pareles fa1e74acf2 deps 2025-03-24 18:35:41 -07:00
Andrew Pareles 112a770f2c leading 1 2025-03-24 18:31:49 -07:00
Andrew Pareles b1bcac5a6d deps 2025-03-24 18:29:40 -07:00
Andrew Pareles ae79229c00 more retry options 2025-03-24 08:43:52 -07:00
Andrew Pareles 28ac708904 error message 2025-03-24 08:36:01 -07:00
Andrew Pareles 161b446bb2 test3 2025-03-24 08:26:57 -07:00
Andrew Pareles 072484dd5d v4 2025-03-24 07:58:31 -07:00
Andrew Pareles a86d3dc82b large 2025-03-24 00:47:29 -07:00
Andrew Pareles b141b2edfe revert 3.7 with large macos 2025-03-24 00:47:07 -07:00
Mathew Pareles cf9482bd76 add back dropdown animation which was removed 2025-03-23 23:41:28 -07:00
Andrew Pareles 4be2c3b6fd +? 2025-03-23 23:29:53 -07:00
Mathew Pareles 69a75f5c47 fix paste 2025-03-23 23:24:08 -07:00
Andrew Pareles 0ab4f8513b fix? 2025-03-23 23:23:48 -07:00
Andrew Pareles 93f8597dd4 rm ci 2025-03-23 23:21:10 -07:00
Andrew Pareles 0f0e56a263 + 2025-03-23 23:16:37 -07:00
Andrew Pareles 91a23bb2d3 asd 2025-03-23 23:15:17 -07:00
Andrew Pareles 19503d92b4 fix? 2025-03-23 23:13:47 -07:00
Andrew Pareles e2648bba3e fix? 2025-03-23 23:12:45 -07:00
Andrew Pareles 090eaba3aa test 3 2025-03-23 23:07:14 -07:00
Mathew Pareles 1f98e75a4f terminal better 2025-03-23 22:46:25 -07:00
Andrew Pareles a38b4c8ee0 revert to 3.7 again 2025-03-23 22:44:06 -07:00
Andrew Pareles 1def205b28 test2 2025-03-23 22:28:59 -07:00
Mathew Pareles 32bee63b50 fix prompting that made void use pathname_search over regular search 2025-03-23 22:26:09 -07:00
Mathew Pareles 4798bbdc86 prompt 2025-03-23 22:03:26 -07:00
Andrew Pareles a854a341d3 revert to 3.7 2025-03-23 20:56:47 -07:00
Andrew Pareles 1a4623589c rm. ci 2025-03-23 20:54:30 -07:00
Andrew Pareles 51b19a6831 update 2025-03-23 20:53:37 -07:00
Andrew Pareles b58ad5f876 update 2025-03-23 20:48:35 -07:00
Andrew Pareles 4951116a6c test 2025-03-23 20:46:02 -07:00
Andrew Pareles 46ca49e02b + 2025-03-23 20:43:57 -07:00
Andrew Pareles 9133d6ecf5 test 2025-03-23 20:42:33 -07:00
Andrew Pareles ad736edd9e 3.7 edits 2025-03-23 20:29:59 -07:00
Mathew Pareles 1c7494861d rename for better accuracy 2025-03-23 20:26:47 -07:00
Andrew Pareles 31163e8516 npm i 2025-03-23 20:20:41 -07:00
Andrew Pareles 853a26ae8e disable ci 2025-03-23 20:08:34 -07:00
Andrew Pareles 16c7aaf599 v4? 2025-03-23 20:04:35 -07:00
Mathew Pareles d6df2514e1 fix ugly colors 2025-03-23 20:01:05 -07:00
Andrew Pareles 2b33294a85 + 2025-03-23 19:44:54 -07:00
Andrew Pareles 3ecdae6ab6 rename 2025-03-23 19:43:39 -07:00
Mathew Pareles fd1c840618 fix ugly blue color 2025-03-23 19:35:26 -07:00
Mathew Pareles 71f8315e06 fix paste issue 2025-03-23 19:30:32 -07:00
Andrew Pareles 633f7ffee5 test new workflow 2025-03-23 19:30:22 -07:00
Mathew Pareles 35f53e66dd definition 2025-03-23 18:47:34 -07:00
Joaquin Coromina 016d59e81a Merge branch 'voideditor:main' into remote-wsl-extension 2025-03-23 20:03:22 -04:00
Andrew Pareles 82851d519d slice 2025-03-23 00:31:38 -07:00
Andrew Pareles cfc18827bb Merge remote-tracking branch 'origin/main' into model-selection 2025-03-22 19:12:22 -07:00
Andrew Pareles 04e8a204a3 add trusted domain 2025-03-22 18:14:55 -07:00
Andrew Pareles 6fd514230c Merge pull request #326 from voideditor/model-selection
Small (or empty) file editing
2025-03-22 00:40:17 -07:00
Mathew Pareles f75b4f34d8 fixes 2025-03-22 00:39:22 -07:00
Mathew Pareles 6ecf7be826 fix search+replace on empty files 2025-03-22 00:02:06 -07:00
Andrew Pareles 47998e4ee5 Merge pull request #325 from voideditor/v1.0.2
V1.0.2
2025-03-21 22:41:28 -07:00
Andrew Pareles 995f39b61f No X button 2025-03-21 22:38:26 -07:00
Andrew Pareles ec2ae0e190 more persistent update message 2025-03-21 22:31:24 -07:00
Andrew Pareles 2bef719754 Merge pull request #324 from voideditor/model-selection
Command Bar for Changes + Tool UI
2025-03-21 21:57:02 -07:00
Andrew Pareles c6376eb1db 1 2025-03-21 21:55:20 -07:00
Andrew Pareles db2c60db82 rm 2025-03-21 21:53:42 -07:00
Andrew Pareles a6f1b1e0ad window 2025-03-21 21:52:53 -07:00
Andrew Pareles 9edca589a0 1.0.2 2025-03-21 21:49:08 -07:00
Andrew Pareles da6710a079 current file fix will vs did 2025-03-21 21:45:17 -07:00
Andrew Pareles f60d5cad2f void agent 2025-03-21 21:22:21 -07:00
Andrew Pareles 75f1a2d79b format 2025-03-21 21:20:57 -07:00
Andrew Pareles 22dd42ba70 misc improvements 2025-03-21 21:20:41 -07:00
Mathew Pareles 1675879fa4 ux for commandbar 2025-03-21 20:14:55 -07:00
Andrew Pareles d6a7921526 Merge remote-tracking branch 'origin/main' into model-selection 2025-03-21 19:58:41 -07:00
Andrew Pareles 00734ce327 update commandbar 2025-03-21 19:58:41 -07:00
Mathew Pareles 3e584d01a3 focus on chatmessage edit 2025-03-21 19:53:12 -07:00
Mathew Pareles 847b57c406 bug 2025-03-21 19:40:59 -07:00
Andrew Pareles d4e9dbb82a rm titles and add .fsPaths 2025-03-21 19:33:03 -07:00
Andrew Pareles f3451d5077 command bar state + accept/reject all 2025-03-21 19:23:06 -07:00
Mathew Pareles 159d52a716 styles 2025-03-21 19:21:13 -07:00
Mathew Pareles 29f8fa6e1d improve selections 2025-03-21 19:09:49 -07:00
Andrew Pareles 9dc7632d9b Merge pull request #279 from jcommaret/voidVersion-from-tag
⚙️ - Void version from : tag (semver) commit and date
2025-03-21 15:25:49 -07:00
Andrew Pareles cf9eca0564 copy 2025-03-21 14:47:17 -07:00
Mathew Pareles 2eafda9132 fix opacity 2025-03-21 06:44:17 -07:00
Andrew Pareles c889acadc0 interruption handling 2025-03-21 06:44:05 -07:00
Mathew Pareles b3522ad73c better styling for commandbar 2025-03-21 06:42:07 -07:00
Andrew Pareles 96867326ae prompt 2025-03-21 05:59:49 -07:00
Andrew Pareles 4946d3c2a0 copy 2025-03-21 05:56:18 -07:00
Andrew Pareles 6a6cb56d87 add visual feedback for tool that's being loaded (eg edit tool) 2025-03-21 05:50:31 -07:00
Andrew Pareles eed71d9582 minor changes 2025-03-21 04:18:25 -07:00
Andrew Pareles 90a282a1f7 minor prompt fix 2025-03-21 02:54:52 -07:00
Andrew Pareles 8511681710 fixed events and interrupts during agent mode 2025-03-21 02:54:52 -07:00
Mathew Pareles 4dff3e216a command bar decent 2025-03-21 00:22:53 -07:00
Mathew Pareles e894be803b command bar draft 2025-03-21 00:22:53 -07:00
Andrew Pareles 07e995fc2f strict:true 2025-03-20 07:24:57 -07:00
Andrew Pareles cffe053226 merge 2025-03-20 06:03:37 -07:00
Andrew Pareles dafc509fa1 always add tool_request 2025-03-20 05:56:56 -07:00
Mathew Pareles 794c4593e6 styles 2025-03-20 04:44:10 -07:00
Mathew Pareles 5d825ad970 styles better 2025-03-20 04:43:06 -07:00
Andrew Pareles cc4115c8c4 misc fixes 2025-03-20 04:19:33 -07:00
Andrew Pareles b86356d2af misc bug fixes with oai-compat and agent 2025-03-20 04:03:37 -07:00
Andrew Pareles 8dab3ac854 tool error styles 2025-03-20 02:52:44 -07:00
Andrew Pareles 1326f19a5e add openrouter claude thinking 2025-03-20 02:37:04 -07:00
Andrew Pareles 69697542ba misc fixes 2025-03-20 00:46:10 -07:00
Andrew Pareles a7728a6030 show current tool running + misc style fixes + chat interrupt stops tool too 2025-03-19 23:00:26 -07:00
Andrew Pareles 9ee14e6a71 tense 2025-03-19 23:00:26 -07:00
Mathew Pareles 2ae098ee96 improvements 2025-03-19 22:19:51 -07:00
Mathew Pareles 0dae638be7 add automatic file adding 2025-03-19 21:58:14 -07:00
Andrew Pareles dd321121e3 prompt 2025-03-19 18:55:13 -07:00
Andrew Pareles 932b4974e9 prompt 2025-03-19 18:30:42 -07:00
Andrew Pareles 20d89cd07c prompt update 2025-03-19 18:28:38 -07:00
Andrew Pareles f75dee39b3 fix 2025-03-19 17:44:53 -07:00
Andrew Pareles acd711f93b fix 2025-03-19 16:32:36 -07:00
Andrew Pareles 744d387fe1 editCodeService and voidCommandBar service!!! + VoidCommandBar.tsx 2025-03-19 16:15:09 -07:00
Mathew Pareles ec505b653b debug fastapply mutiple changes (unsolved) 2025-03-18 22:35:57 -07:00
Mathew Pareles ad471a3015 fixes 2025-03-18 21:22:13 -07:00
Andrew Pareles 86fddaa0e0 text_search 2025-03-18 04:56:59 -07:00
Andrew Pareles b9405a76cd auto-approve 2025-03-18 04:51:23 -07:00
Mathew Pareles 730758cbec small changes.. 2025-03-18 03:43:12 -07:00
Andrew Pareles b26b62998d settings page 2025-03-18 03:42:53 -07:00
Andrew Pareles 6856e23e2a just revert all SEARCH/REPLACE on error + history reorder 2025-03-18 03:35:54 -07:00
Andrew Pareles b305fc5034 keep-conflicts on apply 2025-03-18 02:41:45 -07:00
Andrew Pareles 9ab3ec3c11 add autoapply 2025-03-18 00:33:40 -07:00
Andrew Pareles 5b3471a7d7 changes + potentially fix error finding SEARCH block 2025-03-17 23:47:35 -07:00
Mathew Pareles 6bb0966eea draft 2025-03-17 22:58:32 -07:00
Andrew Pareles 5d0a4c8157 misc 2025-03-17 22:51:23 -07:00
Andrew Pareles a1c7b254d3 flex instead of observer 2025-03-17 22:38:45 -07:00
Andrew Pareles 4cf79f7a3b styles 2025-03-17 22:25:41 -07:00
Andrew Pareles b1e50798d8 apply improvements 2025-03-17 20:55:37 -07:00
Andrew Pareles a320323aa1 promise resolves when abort to free space 2025-03-17 18:39:14 -07:00
Andrew Pareles ad2e5f2616 misc 2025-03-17 16:26:33 -07:00
Andrew Pareles f64f6b54b4 styles + fixes 2025-03-17 16:18:26 -07:00
Andrew Pareles f2caa9876c isRunning: tool | message 2025-03-17 04:33:00 -07:00
Andrew Pareles 4a06d79170 type fix 2025-03-17 04:09:31 -07:00
Andrew Pareles 9a8814cc50 jump to file for edit request 2025-03-17 04:00:18 -07:00
Andrew Pareles d6327f2b03 explicit dependence on threadId in agentloop 2025-03-17 03:47:44 -07:00
Andrew Pareles 4528270937 add Void in file menu 2025-03-17 03:00:56 -07:00
Andrew Pareles 8ef693b218 comment 2025-03-17 02:47:11 -07:00
Andrew Pareles 46db2c6e95 + 2025-03-17 02:46:38 -07:00
Andrew Pareles 6b897a6f71 state simplify 2025-03-17 02:44:49 -07:00
Andrew Pareles 7aecbe25cd minor change 2025-03-17 02:39:55 -07:00
Andrew Pareles f79d438084 update styles 2025-03-17 02:19:51 -07:00
Andrew Pareles 35eb93d829 finally bg edits seem to actually work 2025-03-17 01:06:37 -07:00
Andrew Pareles 9350c0dcdf misc state fixes and fix model background (add voidModelService, delete voidFileService) 2025-03-16 23:19:22 -07:00
Mathew Pareles 39e989d3cc codespan link 2025-03-16 22:48:17 -07:00
Mathew Pareles a3cfd0d023 fix codespan links 2025-03-16 22:29:17 -07:00
Andrew Pareles 97b44a94e4 redesign how tool use loop works 2025-03-16 02:55:47 -07:00
Mathew Pareles 4a9af17736 commandbar draft (broken) 2025-03-15 21:55:14 -07:00
Mathew Pareles a33160f33e loading 2025-03-15 21:55:14 -07:00
Mathew Pareles 4bd6f4f8ac loading symbol Cmd+K 2025-03-15 21:54:48 -07:00
Mathew Pareles 71dc1ab93e change default setting for tailwind autosuggestions 2025-03-15 21:54:48 -07:00
Mathew Pareles 0ec9e4a54e codespan shorten paths 2025-03-15 21:54:48 -07:00
Mathew Pareles 4284461b06 fix height issue 2025-03-15 21:54:22 -07:00
Mathew Pareles 3f7f2fb40c commandbar draft 2025-03-15 21:54:22 -07:00
Andrew Pareles 47e93d1cf3 fix up settings 2025-03-15 17:00:21 -07:00
Andrew Pareles a99d1c99a5 agent mode must use tool models only 2025-03-15 16:23:14 -07:00
Andrew Pareles 7b76df235a reasoning component when streaming 2025-03-15 16:00:03 -07:00
Andrew Pareles aca5deb814 desc 2025-03-15 04:24:39 -07:00
Andrew Pareles 824de11f21 setting 2025-03-15 04:21:01 -07:00
Andrew Pareles a4d93ac95a chatMode and agent mode UI 2025-03-15 04:19:30 -07:00
Andrew Pareles c1e83e458b header uri name 2025-03-15 03:42:21 -07:00
Andrew Pareles 7ea5870a11 alive ping 2025-03-15 03:38:36 -07:00
Andrew Pareles cf6cef8e0e massively improve styles 2025-03-15 03:24:48 -07:00
Andrew Pareles bf2843b1bd edit state and apply state should work, edit UI 2025-03-15 02:59:32 -07:00
Andrew Pareles 8a6b75b6bb style updates progress 2025-03-15 01:17:13 -07:00
Andrew Pareles dbdd40bd3d add better settings 2025-03-14 02:23:58 -07:00
Andrew Pareles 8f3a752562 sync fast apply to chat 2025-03-14 00:41:17 -07:00
Andrew Pareles fa43a9f937 language detection 2025-03-14 00:20:03 -07:00
Andrew Pareles c222a801a8 fix CtrlK model selection 2025-03-13 21:29:45 -07:00
Andrew Pareles def78affb4 ensureModelExists (forceModel) and fix quick edit state 2025-03-13 21:05:44 -07:00
Andrew Pareles edefe841d5 changes in the BG work!! + autosave 2025-03-13 16:54:31 -07:00
Mathew Pareles cd512402d9 collab 2025-03-12 14:34:58 -07:00
Andrew Pareles 744f0ed852 style 2025-03-12 05:33:38 -07:00
Andrew Pareles 1b6d81f4e0 approve tool works even if closed void and reopened 2025-03-12 05:16:42 -07:00
Mathew Pareles bb370b390c logic 2025-03-12 05:06:16 -07:00
Mathew Pareles 378ef493b5 fix gratuitous error 2025-03-12 04:35:12 -07:00
Mathew Pareles a70f606362 fix faulty changes 2025-03-12 03:41:08 -07:00
Andrew Pareles f66fbcd911 agentLoop improvements 2025-03-12 03:19:24 -07:00
Mathew Pareles 1c2e8c0c59 minh 2025-03-12 03:09:00 -07:00
Mathew Pareles 09ba9387bc fix refactor 2025-03-12 02:54:36 -07:00
Andrew Pareles b97c5089ce update approve/reject state 2025-03-12 02:26:55 -07:00
Andrew Pareles 7563f595f3 misc 2025-03-12 02:06:30 -07:00
Andrew Pareles 79ef6b5773 fix stream state 2025-03-12 02:02:25 -07:00
Andrew Pareles 11d376325e tool UI progress + terminal fix + misc fixes 2025-03-12 01:24:21 -07:00
Andrew Pareles 1ef8011bb0 tool UI step 1 (BROKEN) 2025-03-12 00:51:17 -07:00
Mathew Pareles fa1f407b76 fix naming for tool response 2025-03-12 00:37:30 -07:00
Mathew Pareles 094e05070a size 2025-03-12 00:20:21 -07:00
Mathew Pareles 6d5d4c34d7 size 2025-03-12 00:11:14 -07:00
Mathew Pareles 967f7dc85e make apply actually work 2025-03-12 00:06:28 -07:00
Andrew Pareles d6d5f77183 terminal progress + misc 2025-03-11 06:53:54 -07:00
Joaquin Coromina 9fce7ce812 Merge branch 'voideditor:main' into remote-wsl-extension 2025-03-08 19:20:59 +07:00
Joaquin Coromina 85009ab5b3 Added open-remote-wsl output folder to the exclude list for hygiene checks 2025-03-08 05:48:15 -06:00
Joaquin Coromina fe01285d58 Added open-remote-wsl to the npm install build command 2025-03-08 05:42:20 -06:00
Joaquin Coromina d1fa4d3439 Added open-remote-wsl to the gulp build pipeline 2025-03-08 05:39:43 -06:00
Joaquin Coromina d0921f899c Added open-remote-wsl to extensions 2025-03-08 00:23:54 -06:00
144 changed files with 13777 additions and 5807 deletions
+1 -1
View File
@@ -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"
+3 -4
View File
@@ -90,7 +90,7 @@ Alternatively, if you want to build Void from the terminal, instead of pressing
- 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.
- If you get errors like `npm error libtool: error: unrecognised option: '-static'`, make sure you have GNU libtool instead of BSD libtool (BSD is the default in macos)
## Packaging
@@ -104,12 +104,11 @@ We don't usually recommend packaging. Instead, you should probably just build. I
### Windows
- `npm run gulp vscode-win32-x64` - most common
- `npm run gulp vscode-win32-ia32`
- `npm run gulp vscode-win32-arm64`
### Linux
- `npm run gulp vscode-linux-x64` - most common
- `npm run gulp vscode-linux-arm`
- `npm run gulp vscode-linux-ia32`
- `npm run gulp vscode-linux-arm64`
### Output
+75 -75
View File
@@ -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
+3 -1
View File
@@ -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 = {
+3 -1
View File
@@ -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';
}
};
+1
View File
@@ -139,6 +139,7 @@ module.exports.indentationFilter = [
'!extensions/simple-browser/media/*.js',
'!extensions/open-remote-ssh/out/*.js', // Void added this
'!extensions/open-remote-wsl/out/*.js', // Void added this
];
module.exports.copyrightFilter = [
+1
View File
@@ -73,6 +73,7 @@ const compilations = [
'.vscode/extensions/vscode-selfhost-import-aid/tsconfig.json',
'extensions/open-remote-ssh/tsconfig.json', // Void added this
'extensions/open-remote-wsl/tsconfig.json', // Void added this
];
+16 -4
View File
@@ -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);
+4 -3
View 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);
}
+3 -5
View File
@@ -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'));
+2 -2
View File
@@ -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,
+1 -4
View File
@@ -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}`));
+1 -3
View File
@@ -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);
}
+1
View File
@@ -55,6 +55,7 @@ const dirs = [
'.vscode/extensions/vscode-selfhost-import-aid',
'.vscode/extensions/vscode-selfhost-test-provider',
'extensions/open-remote-ssh', // Void added this
'extensions/open-remote-wsl', // Void added this
];
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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';
+3
View File
@@ -0,0 +1,3 @@
# Remote - WSL Support
Inherited for Void from [Open Remote - WSL](https://github.com/jeanp413/open-remote-wsl).
@@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const withBrowserDefaults = require('../shared.webpack.config').browser;
module.exports = withBrowserDefaults({
context: __dirname,
entry: {
extension: './src/extension.ts'
}
});
@@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const withDefaults = require('../shared.webpack.config');
module.exports = withDefaults({
context: __dirname,
resolve: {
mainFields: ['module', 'main']
},
entry: {
extension: './src/extension.ts',
}
});
+15
View File
@@ -0,0 +1,15 @@
{
"name": "open-remote-wsl",
"version": "0.0.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "open-remote-wsl",
"version": "0.0.4",
"engines": {
"vscode": "^1.70.2"
}
}
}
}
+281
View File
@@ -0,0 +1,281 @@
{
"name": "open-remote-wsl",
"displayName": "Remote - WSL",
"description": "Open any folder in the Windows Subsystem for Linux (WSL).",
"version": "0.0.4",
"icon": "resources/icon.png",
"engines": {
"vscode": "^1.70.2"
},
"extensionKind": [
"ui"
],
"enabledApiProposals": [
"resolvers",
"contribViewsRemote"
],
"keywords": [
"remote development",
"remote",
"wsl"
],
"api": "none",
"activationEvents": [
"onCommand:openremotewsl.connect",
"onCommand:openremotewsl.connectInNewWindow",
"onCommand:openremotewsl.connectUsingDistro",
"onCommand:openremotewsl.connectUsingDistroInNewWindow",
"onCommand:openremotewsl.showLog",
"onResolveRemoteAuthority:wsl",
"onView:wslTargets"
],
"main": "./out/extension.js",
"contributes": {
"configuration": {
"title": "WSL",
"properties": {
"remote.WSL.serverDownloadUrlTemplate": {
"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/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz"
}
}
},
"views": {
"remote": [
{
"id": "wslTargets",
"name": "WSL Targets",
"group": "targets@1",
"when": "(isWindows && !isWeb)",
"remoteName": "wsl"
}
]
},
"commands": [
{
"command": "openremotewsl.connect",
"title": "Connect to WSL",
"category": "Remote-WSL"
},
{
"command": "openremotewsl.connectInNewWindow",
"title": "Connect to WSL in New Window",
"category": "Remote-WSL"
},
{
"command": "openremotewsl.connectUsingDistro",
"title": "Connect to WSL using Distro...",
"category": "Remote-WSL"
},
{
"command": "openremotewsl.connectUsingDistroInNewWindow",
"title": "Connect to WSL using Distro in New Window...",
"category": "Remote-WSL"
},
{
"command": "openremotewsl.showLog",
"title": "Show Log",
"category": "Remote-WSL"
},
{
"command": "openremotewsl.explorer.emptyWindowInNewWindow",
"title": "Connect in New Window",
"icon": "$(empty-window)"
},
{
"command": "openremotewsl.explorer.emptyWindowInCurrentWindow",
"title": "Connect in Current Window"
},
{
"command": "openremotewsl.explorer.reopenFolderInCurrentWindow",
"title": "Open in Current Window"
},
{
"command": "openremotewsl.explorer.reopenFolderInNewWindow",
"title": "Open in New Window",
"icon": "$(folder-opened)"
},
{
"command": "openremotewsl.explorer.deleteFolderHistoryItem",
"title": "Remove From Recent List",
"icon": "$(x)"
},
{
"command": "openremotewsl.explorer.refresh",
"title": "Refresh",
"icon": "$(refresh)"
},
{
"command": "openremotewsl.explorer.addDistro",
"title": "Add a Distro",
"icon": "$(plus)"
},
{
"command": "openremotewsl.explorer.setDefaultDistro",
"title": "Set as Default Distro"
},
{
"command": "openremotewsl.explorer.deleteDistro",
"title": "Delete Distro"
}
],
"resourceLabelFormatters": [
{
"scheme": "vscode-remote",
"authority": "wsl+*",
"formatting": {
"label": "${path}",
"separator": "/",
"tildify": true,
"workspaceSuffix": "WSL"
}
}
],
"menus": {
"statusBar/remoteIndicator": [
{
"command": "openremotewsl.connect",
"when": "(isWindows && !isWeb)",
"group": "remote_20_wsl_1general@1"
},
{
"command": "openremotewsl.connectUsingDistro",
"when": "(isWindows && !isWeb)",
"group": "remote_20_wsl_1general@2"
},
{
"command": "openremotewsl.showLog",
"when": "remoteName =~ /^wsl$/",
"group": "remote_20_wsl_1general@4"
}
],
"commandPalette": [
{
"command": "openremotewsl.connect",
"when": "(isWindows && !isWeb)"
},
{
"command": "openremotewsl.connectInNewWindow",
"when": "(isWindows && !isWeb)"
},
{
"command": "openremotewsl.connectUsingDistro",
"when": "(isWindows && !isWeb)"
},
{
"command": "openremotewsl.connectUsingDistroInNewWindow",
"when": "(isWindows && !isWeb)"
},
{
"command": "openremotewsl.explorer.refresh",
"when": "false"
},
{
"command": "openremotewsl.explorer.addDistro",
"when": "false"
},
{
"command": "openremotewsl.explorer.emptyWindowInNewWindow",
"when": "false"
},
{
"command": "openremotewsl.explorer.emptyWindowInCurrentWindow",
"when": "false"
},
{
"command": "openremotewsl.explorer.reopenFolderInCurrentWindow",
"when": "false"
},
{
"command": "openremotewsl.explorer.reopenFolderInNewWindow",
"when": "false"
},
{
"command": "openremotewsl.explorer.deleteFolderHistoryItem",
"when": "false"
},
{
"command": "openremotewsl.explorer.setDefaultDistro",
"when": "false"
},
{
"command": "openremotewsl.explorer.deleteDistro",
"when": "false"
}
],
"view/title": [
{
"command": "openremotewsl.explorer.addDistro",
"when": "view == wslTargets",
"group": "navigation"
},
{
"command": "openremotewsl.explorer.refresh",
"when": "view == wslTargets",
"group": "navigation"
}
],
"view/item/context": [
{
"command": "openremotewsl.explorer.emptyWindowInNewWindow",
"when": "viewItem =~ /^openremotewsl.explorer.distro$/",
"group": "inline@1"
},
{
"command": "openremotewsl.explorer.emptyWindowInNewWindow",
"when": "viewItem =~ /^openremotewsl.explorer.distro$/",
"group": "navigation@2"
},
{
"command": "openremotewsl.explorer.emptyWindowInCurrentWindow",
"when": "viewItem =~ /^openremotewsl.explorer.distro$/",
"group": "navigation@1"
},
{
"command": "openremotewsl.explorer.setDefaultDistro",
"when": "viewItem =~ /^openremotewsl.explorer.distro$/",
"group": "management@1"
},
{
"command": "openremotewsl.explorer.deleteDistro",
"when": "viewItem =~ /^openremotewsl.explorer.distro$/",
"group": "management@2"
},
{
"command": "openremotewsl.explorer.reopenFolderInNewWindow",
"when": "viewItem == openremotewsl.explorer.folder",
"group": "inline@1"
},
{
"command": "openremotewsl.explorer.reopenFolderInNewWindow",
"when": "viewItem == openremotewsl.explorer.folder",
"group": "navigation@2"
},
{
"command": "openremotewsl.explorer.reopenFolderInCurrentWindow",
"when": "viewItem == openremotewsl.explorer.folder",
"group": "navigation@1"
},
{
"command": "openremotewsl.explorer.deleteFolderHistoryItem",
"when": "viewItem =~ /^openremotewsl.explorer.folder/",
"group": "navigation@3"
},
{
"command": "openremotewsl.explorer.deleteFolderHistoryItem",
"when": "viewItem =~ /^openremotewsl.explorer.folder/",
"group": "inline@2"
}
]
}
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "gulp compile-extension:open-remote-wsl",
"compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none",
"watch": "gulp watch-extension:open-remote-wsl",
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
}
}
@@ -0,0 +1,121 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import Log from './common/logger';
import { installCodeServer, ServerInstallError } from './serverSetup';
import { WSLManager } from './wsl/wslManager';
export const REMOTE_WSL_AUTHORITY = 'wsl';
export function getRemoteAuthority(distro: string) {
return `${REMOTE_WSL_AUTHORITY}+${distro}`;
}
class Tunnel implements vscode.Tunnel {
private _onDidDisposeEmitter = new vscode.EventEmitter<void>();
readonly onDidDispose = this._onDidDisposeEmitter.event;
constructor(
readonly remoteAddress: { port: number; host: string },
readonly localAddress: { port: number; host: string }
) {
// If ipv6 localhost 0:0:0:0:0:0:0:1 or [::1] replace with localhost
if (localAddress.host !== 'localhost' && localAddress.host !== '127.0.0.1') {
localAddress.host = 'localhost';
}
}
dispose() {
this._onDidDisposeEmitter.fire();
}
}
export class RemoteWSLResolver implements vscode.RemoteAuthorityResolver, vscode.Disposable {
private labelFormatterDisposable: vscode.Disposable | undefined;
constructor(
private readonly wslManager: WSLManager,
private readonly logger: Log
) {
}
resolve(authority: string, context: vscode.RemoteAuthorityResolverContext): Thenable<vscode.ResolverResult> {
const [type, distroName] = authority.split('+');
if (type !== REMOTE_WSL_AUTHORITY) {
throw new Error(`Invalid authority type for WSL resolver: ${type}`);
}
this.logger.info(`Resolving wsl remote authority '${authority}' (attemp #${context.resolveAttempt})`);
// It looks like default values are not loaded yet when resolving a remote,
// so let's hardcode the default values here
const remoteSSHconfig = vscode.workspace.getConfiguration('remote.WSL');
const serverDownloadUrlTemplate = remoteSSHconfig.get<string>('serverDownloadUrlTemplate');
return vscode.window.withProgress({
title: `Setting up WSL Distro: ${distroName}`,
location: vscode.ProgressLocation.Notification,
cancellable: false
}, async () => {
try {
const installResult = await installCodeServer(this.wslManager, distroName, serverDownloadUrlTemplate, [], [], this.logger);
this.labelFormatterDisposable?.dispose();
this.labelFormatterDisposable = vscode.workspace.registerResourceLabelFormatter({
scheme: 'vscode-remote',
authority: `${REMOTE_WSL_AUTHORITY}+*`,
formatting: {
label: '${path}',
separator: '/',
tildify: true,
workspaceSuffix: `WSL: ${distroName}`,
workspaceTooltip: `Running in ${distroName}`
}
});
return new vscode.ResolvedAuthority('127.0.0.1', installResult.listeningOn, installResult.connectionToken);
} catch (e: unknown) {
this.logger.error(`Error resolving authority`, e);
// Initial connection
if (context.resolveAttempt === 1) {
this.logger.show();
const closeRemote = 'Close Remote';
const retry = 'Retry';
const result = await vscode.window.showErrorMessage(`Could not establish connection to WSL distro "${distroName}"`, { modal: true }, closeRemote, retry);
if (result === closeRemote) {
await vscode.commands.executeCommand('workbench.action.remote.close');
} else if (result === retry) {
await vscode.commands.executeCommand('workbench.action.reloadWindow');
}
}
if (e instanceof ServerInstallError || !(e instanceof Error)) {
throw vscode.RemoteAuthorityResolverError.NotAvailable(e instanceof Error ? e.message : String(e));
} else {
throw vscode.RemoteAuthorityResolverError.TemporarilyNotAvailable(e.message);
}
}
});
}
async tunnelFactory(tunnelOptions: vscode.TunnelOptions) {
return new Tunnel(
tunnelOptions.remoteAddress,
{
host: tunnelOptions.remoteAddress.host,
port: tunnelOptions.localAddressPort ?? tunnelOptions.remoteAddress.port
}
);
}
dispose() {
this.labelFormatterDisposable?.dispose();
}
}
@@ -0,0 +1,86 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getRemoteAuthority } from './authResolver';
import { WSLDistro, WSLManager, WSLOnlineDistro } from './wsl/wslManager';
import wslTerminal from './wsl/wslTerminal';
async function showDistrosPicker(wslManager: WSLManager, placeHolder: string): Promise<WSLDistro | undefined> {
const pickItemsPromise = wslManager.listDistros()
.then(distros => distros.map(distroData => {
return {
...distroData,
label: `${distroData.name}`,
detail: distroData.isDefault ? 'default distro' : undefined,
};
}));
const picked = await vscode.window.showQuickPick(pickItemsPromise, { canPickMany: false, placeHolder });
return picked;
}
async function showOnlineDistrosPicker(wslManager: WSLManager, placeHolder: string): Promise<WSLOnlineDistro | undefined> {
const pickItemsPromise = Promise.all([wslManager.listOnlineDistros(), wslManager.listDistros()])
.then(([onlineDistros, localDistros]) => {
const distroToInstall = onlineDistros.filter(d => !localDistros.some(l => l.name === d.name));
return distroToInstall.map(distroData => {
return {
...distroData,
label: `${distroData.friendlyName}`,
};
});
});
const picked = await vscode.window.showQuickPick(pickItemsPromise, { canPickMany: false, placeHolder });
return picked;
}
export async function promptOpenRemoteWSLWindow(wslManager: WSLManager, useDefault: boolean, reuseWindow: boolean) {
let distroName: string | undefined;
if (useDefault) {
const distros = await wslManager.listDistros();
distroName = distros.find(distro => distro.isDefault)?.name;
} else {
distroName = (await showDistrosPicker(wslManager, 'Select WSL distro'))?.name;
}
if (!distroName) {
return;
}
openRemoteWSLWindow(distroName, reuseWindow);
}
export async function promptInstallNewWSLDistro(wslManager: WSLManager) {
const distroName = (await showOnlineDistrosPicker(wslManager, 'Select the WSL distro to install'))?.name;
if (!distroName) {
return;
}
wslTerminal.runCommand(`wsl.exe --install -d ${distroName}`);
}
export function openRemoteWSLWindow(distro: string, reuseWindow: boolean) {
vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: getRemoteAuthority(distro), reuseWindow });
}
export function openRemoteWSLLocationWindow(distro: string, path: string, reuseWindow: boolean) {
vscode.commands.executeCommand('vscode.openFolder', vscode.Uri.from({ scheme: 'vscode-remote', authority: getRemoteAuthority(distro), path }), { forceNewWindow: !reuseWindow });
}
export async function setDefaultWSLDistro(wslManager: WSLManager, distroName: string) {
await wslManager.setDefaultDistro(distroName);
}
export async function deleteWSLDistro(wslManager: WSLManager, distroName: string) {
const deleteAction = 'Delete';
const resp = await vscode.window.showInformationMessage(`Are you sure you want to permanently delete the distro "${distroName}" including all its data?`, { modal: true }, deleteAction);
if (resp === deleteAction) {
await wslManager.deleteDistro(distroName);
return true;
}
return false;
}
@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function timeout(millis: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, millis));
}
export interface ITask<T> {
(): T;
}
export async function retry<T>(task: ITask<Promise<T>>, delay: number, retries: number): Promise<T> {
let lastError: Error | undefined;
for (let i = 0; i < retries; i++) {
try {
return await task();
} catch (error) {
lastError = error;
await timeout(delay);
}
}
throw lastError;
}
@@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
export function disposeAll(disposables: vscode.Disposable[]): void {
while (disposables.length) {
const item = disposables.pop();
if (item) {
item.dispose();
}
}
}
export abstract class Disposable {
private _isDisposed = false;
protected _disposables: vscode.Disposable[] = [];
public dispose(): any {
if (this._isDisposed) {
return;
}
this._isDisposed = true;
disposeAll(this._disposables);
}
protected _register<T extends vscode.Disposable>(value: T): T {
if (this._isDisposed) {
value.dispose();
} else {
this._disposables.push(value);
}
return value;
}
protected get isDisposed(): boolean {
return this._isDisposed;
}
}
@@ -0,0 +1,142 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface IDisposable {
dispose(): void;
}
export interface Event<T> {
(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable;
}
/**
* Returns a promise that resolves when the event fires, or when cancellation
* is requested, whichever happens first.
*/
export function toPromise<T>(event: Event<T>): Promise<T>;
export function toPromise<T>(event: Event<T>, signal: AbortSignal): Promise<T | undefined>;
export function toPromise<T>(event: Event<T>, signal?: AbortSignal): Promise<T | undefined> {
if (!signal) {
return new Promise<T>((resolve) => once(event, resolve));
}
if (signal.aborted) {
return Promise.resolve(undefined);
}
return new Promise((resolve) => {
const d2 = once(event, (data) => {
(signal as any).removeEventListener('abort', d1);
resolve(data);
});
const d1 = () => {
d2.dispose();
(signal as any).removeEventListener('abort', d1);
resolve(undefined);
};
(signal as any).addEventListener('abort', d1);
});
}
/**
* Adds a handler that handles one event on the emitter, then disposes itself.
*/
export const once = <T>(event: Event<T>, listener: (data: T) => void): IDisposable => {
const disposable = event((value) => {
listener(value);
disposable.dispose();
});
return disposable;
};
/**
* Base event emitter. Calls listeners when data is emitted.
*/
export class EventEmitter<T> {
private listeners?: Array<(data: T) => void> | ((data: T) => void);
/**
* Event<T> function.
*/
public readonly event: Event<T> = (listener, thisArgs, disposables) => {
const d = this.add(thisArgs ? listener.bind(thisArgs) : listener);
disposables?.push(d);
return d;
};
/**
* Gets the number of event listeners.
*/
public get size() {
if (!this.listeners) {
return 0;
} else if (typeof this.listeners === 'function') {
return 1;
} else {
return this.listeners.length;
}
}
/**
* Emits event data.
*/
public fire(value: T) {
if (!this.listeners) {
// no-op
} else if (typeof this.listeners === 'function') {
this.listeners(value);
} else {
for (const listener of this.listeners) {
listener(value);
}
}
}
/**
* Disposes of the emitter.
*/
public dispose() {
this.listeners = undefined;
}
private add(listener: (data: T) => void): IDisposable {
if (!this.listeners) {
this.listeners = listener;
} else if (typeof this.listeners === 'function') {
this.listeners = [this.listeners, listener];
} else {
this.listeners.push(listener);
}
return { dispose: () => this.rm(listener) };
}
private rm(listener: (data: T) => void) {
if (!this.listeners) {
return;
}
if (typeof this.listeners === 'function') {
if (this.listeners === listener) {
this.listeners = undefined;
}
return;
}
const index = this.listeners.indexOf(listener);
if (index === -1) {
return;
}
if (this.listeners.length === 2) {
this.listeners = index === 0 ? this.listeners[1] : this.listeners[0];
} else {
this.listeners = this.listeners.slice(0, index).concat(this.listeners.slice(index + 1));
}
}
}
@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as os from 'os';
const homeDir = os.homedir();
export async function exists(path: string) {
try {
await fs.promises.access(path);
return true;
} catch {
return false;
}
}
export function untildify(path: string) {
return path.replace(/^~(?=$|\/|\\)/, homeDir);
}
export function normalizeToSlash(path: string) {
return path.replace(/\\/g, '/');
}
@@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
type LogLevel = 'Trace' | 'Info' | 'Error';
export default class Log {
private output: vscode.OutputChannel;
constructor(name: string) {
this.output = vscode.window.createOutputChannel(name);
}
private data2String(data: any): string {
if (data instanceof Error) {
return data.stack || data.message;
}
if (data.success === false && data.message) {
return data.message;
}
return data.toString();
}
public trace(message: string, data?: any): void {
this.logLevel('Trace', message, data);
}
public info(message: string, data?: any): void {
this.logLevel('Info', message, data);
}
public error(message: string, data?: any): void {
this.logLevel('Error', message, data);
}
public logLevel(level: LogLevel, message: string, data?: any): void {
this.output.appendLine(`[${level} - ${this.now()}] ${message}`);
if (data) {
this.output.appendLine(this.data2String(data));
}
}
private now(): string {
const now = new Date();
return padLeft(now.getUTCHours() + '', 2, '0')
+ ':' + padLeft(now.getMinutes() + '', 2, '0')
+ ':' + padLeft(now.getUTCSeconds() + '', 2, '0') + '.' + now.getMilliseconds();
}
public show() {
this.output.show();
}
public dispose() {
this.output.dispose();
}
}
function padLeft(s: string, n: number, pad = ' ') {
return pad.repeat(Math.max(0, n - s.length)) + s;
}
@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const isWindows = process.platform === 'win32';
export const isMacintosh = process.platform === 'darwin';
export const isLinux = process.platform === 'linux';
@@ -0,0 +1,134 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as net from 'net';
/**
* Finds a random unused port assigned by the operating system. Will reject in case no free port can be found.
*/
export function findRandomPort(): Promise<number> {
return new Promise((resolve, reject) => {
const server = net.createServer({ pauseOnConnect: true });
server.on('error', reject);
server.on('listening', () => {
const port = (server.address() as net.AddressInfo).port;
server.close(() => resolve(port));
});
server.listen(0, '127.0.0.1');
});
}
/**
* Given a start point and a max number of retries, will find a port that
* is openable. Will return 0 in case no free port can be found.
*/
export function findFreePort(startPort: number, giveUpAfter: number, timeout: number, stride = 1): Promise<number> {
let done = false;
return new Promise(resolve => {
const timeoutHandle = setTimeout(() => {
if (!done) {
done = true;
return resolve(0);
}
}, timeout);
doFindFreePort(startPort, giveUpAfter, stride, (port) => {
if (!done) {
done = true;
clearTimeout(timeoutHandle);
return resolve(port);
}
});
});
}
function doFindFreePort(startPort: number, giveUpAfter: number, stride: number, clb: (port: number) => void): void {
if (giveUpAfter === 0) {
return clb(0);
}
const client = new net.Socket();
// If we can connect to the port it means the port is already taken so we continue searching
client.once('connect', () => {
dispose(client);
return doFindFreePort(startPort + stride, giveUpAfter - 1, stride, clb);
});
client.once('data', () => {
// this listener is required since node.js 8.x
});
client.once('error', (err: Error & { code?: string }) => {
dispose(client);
// If we receive any non ECONNREFUSED error, it means the port is used but we cannot connect
if (err.code !== 'ECONNREFUSED') {
return doFindFreePort(startPort + stride, giveUpAfter - 1, stride, clb);
}
// Otherwise it means the port is free to use!
return clb(startPort);
});
client.connect(startPort, '127.0.0.1');
}
/**
* Uses listen instead of connect. Is faster, but if there is another listener on 0.0.0.0 then this will take 127.0.0.1 from that listener.
*/
export function findFreePortFaster(startPort: number, giveUpAfter: number, timeout: number): Promise<number> {
let resolved = false;
let timeoutHandle: NodeJS.Timeout | undefined = undefined;
let countTried = 1;
const server = net.createServer({ pauseOnConnect: true });
function doResolve(port: number, resolve: (port: number) => void) {
if (!resolved) {
resolved = true;
server.removeAllListeners();
server.close();
if (timeoutHandle) {
clearTimeout(timeoutHandle);
}
resolve(port);
}
}
return new Promise<number>(resolve => {
timeoutHandle = setTimeout(() => {
doResolve(0, resolve);
}, timeout);
server.on('listening', () => {
doResolve(startPort, resolve);
});
server.on('error', err => {
if (err && ((<any>err).code === 'EADDRINUSE' || (<any>err).code === 'EACCES') && (countTried < giveUpAfter)) {
startPort++;
countTried++;
server.listen(startPort, '127.0.0.1');
} else {
doResolve(0, resolve);
}
});
server.on('close', () => {
doResolve(0, resolve);
});
server.listen(startPort, '127.0.0.1');
});
}
function dispose(socket: net.Socket): void {
try {
socket.removeAllListeners('connect');
socket.removeAllListeners('error');
socket.end();
socket.destroy();
socket.unref();
} catch (error) {
console.error(error); // otherwise this error would get lost in the callback chain
}
}
@@ -0,0 +1,109 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as path from 'path';
import { RemoteLocationHistory } from './remoteLocationHistory';
import { Disposable } from './common/disposable';
import { openRemoteWSLWindow, openRemoteWSLLocationWindow, promptInstallNewWSLDistro, deleteWSLDistro, setDefaultWSLDistro } from './commands';
import { WSLManager } from './wsl/wslManager';
class DistroItem {
constructor(
public name: string,
public isDefault: boolean,
public locations: string[]
) {
}
}
class DistroLocationItem {
constructor(
public path: string,
public name: string
) {
}
}
type DataTreeItem = DistroItem | DistroLocationItem;
export class DistroTreeDataProvider extends Disposable implements vscode.TreeDataProvider<DataTreeItem> {
private readonly _onDidChangeTreeData = this._register(new vscode.EventEmitter<DataTreeItem | DataTreeItem[] | void>());
public readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
constructor(
private readonly locationHistory: RemoteLocationHistory,
private readonly wslManager: WSLManager
) {
super();
this._register(vscode.commands.registerCommand('openremotewsl.explorer.addDistro', () => promptInstallNewWSLDistro(wslManager)));
this._register(vscode.commands.registerCommand('openremotewsl.explorer.refresh', () => this.refresh()));
this._register(vscode.commands.registerCommand('openremotewsl.explorer.emptyWindowInNewWindow', e => this.openRemoteWSLWindow(e, false)));
this._register(vscode.commands.registerCommand('openremotewsl.explorer.emptyWindowInCurrentWindow', e => this.openRemoteWSLWindow(e, true)));
this._register(vscode.commands.registerCommand('openremotewsl.explorer.reopenFolderInNewWindow', e => this.openRemoteWSLocationWindow(e, false)));
this._register(vscode.commands.registerCommand('openremotewsl.explorer.reopenFolderInCurrentWindow', e => this.openRemoteWSLocationWindow(e, true)));
this._register(vscode.commands.registerCommand('openremotewsl.explorer.deleteFolderHistoryItem', e => this.deleteDistroLocation(e)));
this._register(vscode.commands.registerCommand('openremotewsl.explorer.setDefaultDistro', e => this.setDefaultDistro(e)));
this._register(vscode.commands.registerCommand('openremotewsl.explorer.deleteDistro', e => this.deleteDistro(e)));
}
getTreeItem(element: DataTreeItem): vscode.TreeItem {
if (element instanceof DistroLocationItem) {
const label = path.posix.basename(element.path).replace(/\.code-workspace$/, ' (Workspace)');
const treeItem = new vscode.TreeItem(label);
treeItem.description = path.posix.dirname(element.path);
treeItem.iconPath = new vscode.ThemeIcon('folder');
treeItem.contextValue = 'openremotewsl.explorer.folder';
return treeItem;
}
const treeItem = new vscode.TreeItem(element.name);
treeItem.description = element.isDefault ? 'default distro' : undefined;
treeItem.collapsibleState = element.locations.length ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None;
treeItem.iconPath = new vscode.ThemeIcon('vm');
treeItem.contextValue = 'openremotewsl.explorer.distro';
return treeItem;
}
async getChildren(element?: DistroItem): Promise<DataTreeItem[]> {
if (!element) {
const distros = await this.wslManager.listDistros();
return distros.map(distro => new DistroItem(distro.name, distro.isDefault, this.locationHistory.getHistory(distro.name)));
}
if (element instanceof DistroItem) {
return element.locations.map(location => new DistroLocationItem(location, element.name));
}
return [];
}
private refresh() {
this._onDidChangeTreeData.fire();
}
private async deleteDistroLocation(element: DistroLocationItem) {
await this.locationHistory.removeLocation(element.name, element.path);
this.refresh();
}
private async openRemoteWSLWindow(element: DistroItem, reuseWindow: boolean) {
openRemoteWSLWindow(element.name, reuseWindow);
}
private async openRemoteWSLocationWindow(element: DistroLocationItem, reuseWindow: boolean) {
openRemoteWSLLocationWindow(element.name, element.path, reuseWindow);
}
private async setDefaultDistro(element: DistroItem) {
await setDefaultWSLDistro(this.wslManager, element.name);
this.refresh();
}
private async deleteDistro(element: DistroItem) {
await deleteWSLDistro(this.wslManager, element.name);
this.refresh();
}
}
@@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import Log from './common/logger';
import { RemoteWSLResolver, REMOTE_WSL_AUTHORITY } from './authResolver';
import { promptOpenRemoteWSLWindow } from './commands';
import { DistroTreeDataProvider } from './distroTreeView';
import { getRemoteWorkspaceLocationData, RemoteLocationHistory } from './remoteLocationHistory';
import { WSLManager } from './wsl/wslManager';
import { isWindows } from './common/platform';
export async function activate(context: vscode.ExtensionContext) {
if (!isWindows) {
return;
}
const logger = new Log('Remote - WSL');
context.subscriptions.push(logger);
const wslManager = new WSLManager(logger);
const remoteWSLResolver = new RemoteWSLResolver(wslManager, logger);
context.subscriptions.push(vscode.workspace.registerRemoteAuthorityResolver(REMOTE_WSL_AUTHORITY, remoteWSLResolver));
context.subscriptions.push(remoteWSLResolver);
const locationHistory = new RemoteLocationHistory(context);
const locationData = getRemoteWorkspaceLocationData();
if (locationData) {
await locationHistory.addLocation(locationData[0], locationData[1]);
}
const distroTreeDataProvider = new DistroTreeDataProvider(locationHistory, wslManager);
context.subscriptions.push(vscode.window.createTreeView('wslTargets', { treeDataProvider: distroTreeDataProvider }));
context.subscriptions.push(distroTreeDataProvider);
context.subscriptions.push(vscode.commands.registerCommand('openremotewsl.connect', () => promptOpenRemoteWSLWindow(wslManager, true, true)));
context.subscriptions.push(vscode.commands.registerCommand('openremotewsl.connectInNewWindow', () => promptOpenRemoteWSLWindow(wslManager, true, false)));
context.subscriptions.push(vscode.commands.registerCommand('openremotewsl.connectUsingDistro', () => promptOpenRemoteWSLWindow(wslManager, false, true)));
context.subscriptions.push(vscode.commands.registerCommand('openremotewsl.connectUsingDistroInNewWindow', () => promptOpenRemoteWSLWindow(wslManager, false, false)));
context.subscriptions.push(vscode.commands.registerCommand('openremotewsl.showLog', () => logger.show()));
}
export function deactivate() {
}
@@ -0,0 +1,56 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { REMOTE_WSL_AUTHORITY } from './authResolver';
export class RemoteLocationHistory {
private static STORAGE_KEY = 'remoteLocationHistory_v0';
private remoteLocationHistory: Record<string, string[]> = {};
constructor(private context: vscode.ExtensionContext) {
// context.globalState.update(RemoteLocationHistory.STORAGE_KEY, undefined);
this.remoteLocationHistory = context.globalState.get(RemoteLocationHistory.STORAGE_KEY) || {};
}
getHistory(host: string): string[] {
return this.remoteLocationHistory[host] || [];
}
async addLocation(host: string, path: string) {
const hostLocations = this.remoteLocationHistory[host] || [];
if (!hostLocations.includes(path)) {
hostLocations.unshift(path);
this.remoteLocationHistory[host] = hostLocations;
await this.context.globalState.update(RemoteLocationHistory.STORAGE_KEY, this.remoteLocationHistory);
}
}
async removeLocation(host: string, path: string) {
let hostLocations = this.remoteLocationHistory[host] || [];
hostLocations = hostLocations.filter(l => l !== path);
this.remoteLocationHistory[host] = hostLocations;
await this.context.globalState.update(RemoteLocationHistory.STORAGE_KEY, this.remoteLocationHistory);
}
}
export function getRemoteWorkspaceLocationData(): [string, string] | undefined {
let location = vscode.workspace.workspaceFile;
if (location && location.scheme === 'vscode-remote' && location.authority.startsWith(REMOTE_WSL_AUTHORITY) && location.path.endsWith('.code-workspace')) {
const [, distroName] = location.authority.split('+');
return [distroName, location.path];
}
location = vscode.workspace.workspaceFolders?.[0].uri;
if (location && location.scheme === 'vscode-remote' && location.authority.startsWith(REMOTE_WSL_AUTHORITY)) {
const [, distroName] = location.authority.split('+');
return [distroName, location.path];
}
return undefined;
}
@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
let vscodeProductJson: any;
async function getVSCodeProductJson() {
if (!vscodeProductJson) {
const productJsonStr = await fs.promises.readFile(path.join(vscode.env.appRoot, 'product.json'), 'utf8');
vscodeProductJson = JSON.parse(productJsonStr);
}
return vscodeProductJson;
}
export interface IServerConfig {
version: string;
commit: string;
quality: string;
release?: string; // void-like specific
serverApplicationName: string;
serverDataFolderName: string;
serverDownloadUrlTemplate?: string; // void-like specific
}
export async function getVSCodeServerConfig(): Promise<IServerConfig> {
const productJson = await getVSCodeProductJson();
return {
// version: vscode.version.replace('-insider', ''),
commit: productJson.commit,
quality: productJson.quality,
release: productJson.release,
serverApplicationName: productJson.serverApplicationName,
serverDataFolderName: productJson.serverDataFolderName,
serverDownloadUrlTemplate: productJson.serverDownloadUrlTemplate,
// Void changed this
version: productJson.voidVersion
};
}
@@ -0,0 +1,353 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as crypto from 'crypto';
import Log from './common/logger';
import { getVSCodeServerConfig } from './serverConfig';
import { WSLManager } from './wsl/wslManager';
export interface ServerInstallOptions {
id: string;
quality: string;
commit: string;
version: string;
release?: string; // void specific
extensionIds: string[];
envVariables: string[];
serverApplicationName: string;
serverDataFolderName: string;
serverDownloadUrlTemplate: string;
}
export interface ServerInstallResult {
exitCode: number;
listeningOn: number;
connectionToken: string;
logFile: string;
osReleaseId: string;
arch: string;
platform: string;
tmpDir: string;
[key: string]: any;
}
export class ServerInstallError extends Error {
constructor(message: string) {
super(message);
}
}
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(wslManager: WSLManager, distroName: string, serverDownloadUrlTemplate: string | undefined, extensionIds: string[], envVariables: string[], logger: Log): Promise<ServerInstallResult> {
const scriptId = crypto.randomBytes(12).toString('hex');
const vscodeServerConfig = await getVSCodeServerConfig();
const installOptions: ServerInstallOptions = {
id: scriptId,
version: vscodeServerConfig.version,
commit: vscodeServerConfig.commit,
quality: vscodeServerConfig.quality,
release: vscodeServerConfig.release,
extensionIds,
envVariables,
serverApplicationName: vscodeServerConfig.serverApplicationName,
serverDataFolderName: vscodeServerConfig.serverDataFolderName,
serverDownloadUrlTemplate: serverDownloadUrlTemplate ?? vscodeServerConfig.serverDownloadUrlTemplate ?? DEFAULT_DOWNLOAD_URL_TEMPLATE,
};
const installServerScript = generateBashInstallScript(installOptions);
// Fish shell does not support heredoc so let's workaround it using -c option,
// also replace single quotes (') within the script with ('\'') as there's no quoting within single quotes, see https://unix.stackexchange.com/a/24676
const resp = await wslManager.exec('bash', ['-c', `'${installServerScript.replace(/'/g, `'\\''`)}'`], distroName);
const endScriptRegex = new RegExp(`${scriptId}: Server installation script done`, 'm');
const commandOutput = await Promise.race([
resp.exitPromise.then(result => ({ stdout: resp.stdout, stderr: resp.stderr, exitCode: result.exitCode })),
new Promise<{ stdout: string; stderr: string; exitCode: number }>((resolve) => {
resp.onStdoutData(buffer => {
if (endScriptRegex.test(buffer.toString('utf8'))) {
resolve({ stdout: resp.stdout, stderr: resp.stderr, exitCode: 0 });
}
});
})
]);
if (commandOutput.exitCode) {
logger.trace('Server install command stderr:', commandOutput.stderr);
}
logger.trace('Server install command stdout:', commandOutput.stdout);
const resultMap = parseServerInstallOutput(commandOutput.stdout, scriptId);
if (!resultMap) {
throw new ServerInstallError(`Failed parsing install script output`);
}
const exitCode = parseInt(resultMap.exitCode, 10);
if (exitCode !== 0) {
throw new ServerInstallError(`Couldn't install void server on remote server, install script returned non-zero exit status`);
}
const listeningOn = parseInt(resultMap.listeningOn, 10);
const remoteEnvVars = Object.fromEntries(Object.entries(resultMap).filter(([key,]) => envVariables.includes(key)));
return {
exitCode,
listeningOn,
connectionToken: resultMap.connectionToken,
logFile: resultMap.logFile,
osReleaseId: resultMap.osReleaseId,
arch: resultMap.arch,
platform: resultMap.platform,
tmpDir: resultMap.tmpDir,
...remoteEnvVars
};
}
function parseServerInstallOutput(str: string, scriptId: string): { [k: string]: string } | undefined {
const startResultStr = `${scriptId}: start`;
const endResultStr = `${scriptId}: end`;
const startResultIdx = str.indexOf(startResultStr);
if (startResultIdx < 0) {
return undefined;
}
const endResultIdx = str.indexOf(endResultStr, startResultIdx + startResultStr.length);
if (endResultIdx < 0) {
return undefined;
}
const installResult = str.substring(startResultIdx + startResultStr.length, endResultIdx);
const resultMap: { [k: string]: string } = {};
const resultArr = installResult.split(/\r?\n/);
for (const line of resultArr) {
const [key, value] = line.split('==');
resultMap[key] = value;
}
return resultMap;
}
function generateBashInstallScript({ id, quality, version, commit, release, extensionIds, envVariables, serverApplicationName, serverDataFolderName, serverDownloadUrlTemplate }: ServerInstallOptions) {
const extensions = extensionIds.map(id => '--install-extension ' + id).join(' ');
return `
# Server installation script
TMP_DIR="\${XDG_RUNTIME_DIR:-"/tmp"}"
DISTRO_VERSION="${version}"
DISTRO_COMMIT="${commit}"
DISTRO_QUALITY="${quality}"
DISTRO_VSCODIUM_RELEASE="${release ?? ''}"
SERVER_APP_NAME="${serverApplicationName}"
SERVER_INITIAL_EXTENSIONS="${extensions}"
SERVER_LISTEN_FLAG="--port=0"
SERVER_DATA_DIR="$HOME/${serverDataFolderName}"
SERVER_DIR="$SERVER_DATA_DIR/bin/$DISTRO_COMMIT"
SERVER_SCRIPT="$SERVER_DIR/bin/$SERVER_APP_NAME"
SERVER_LOGFILE="$SERVER_DATA_DIR/.$DISTRO_COMMIT.log"
SERVER_PIDFILE="$SERVER_DATA_DIR/.$DISTRO_COMMIT.pid"
SERVER_TOKENFILE="$SERVER_DATA_DIR/.$DISTRO_COMMIT.token"
SERVER_OS=
SERVER_ARCH=
SERVER_CONNECTION_TOKEN=
SERVER_DOWNLOAD_URL=
LISTENING_ON=
OS_RELEASE_ID=
ARCH=
PLATFORM=
# Mimic output from logs of remote-ssh extension
print_install_results_and_exit() {
echo "${id}: start"
echo "exitCode==$1=="
echo "listeningOn==$LISTENING_ON=="
echo "connectionToken==$SERVER_CONNECTION_TOKEN=="
echo "logFile==$SERVER_LOGFILE=="
echo "osReleaseId==$OS_RELEASE_ID=="
echo "arch==$ARCH=="
echo "platform==$PLATFORM=="
echo "tmpDir==$TMP_DIR=="
${envVariables.map(envVar => `echo "${envVar}==$${envVar}=="`).join('\n')}
echo "${id}: end"
exit 0
}
# Check if platform is supported
PLATFORM="$(uname -s)"
case $PLATFORM in
Linux)
SERVER_OS="linux"
;;
*)
echo "Error platform not supported: $PLATFORM"
print_install_results_and_exit 1
;;
esac
# Check machine architecture
ARCH="$(uname -m)"
case $ARCH in
x86_64 | amd64)
SERVER_ARCH="x64"
;;
armv7l | armv8l)
SERVER_ARCH="armhf"
;;
arm64 | aarch64)
SERVER_ARCH="arm64"
;;
*)
echo "Error architecture not supported: $ARCH"
print_install_results_and_exit 1
;;
esac
# https://www.freedesktop.org/software/systemd/man/os-release.html
OS_RELEASE_ID="$(grep -i '^ID=' /etc/os-release 2>/dev/null | sed 's/^ID=//gi' | sed 's/"//g')"
if [[ -z $OS_RELEASE_ID ]]; then
OS_RELEASE_ID="$(grep -i '^ID=' /usr/lib/os-release 2>/dev/null | sed 's/^ID=//gi' | sed 's/"//g')"
if [[ -z $OS_RELEASE_ID ]]; then
OS_RELEASE_ID="unknown"
fi
fi
# Create installation folder
if [[ ! -d $SERVER_DIR ]]; then
mkdir -p $SERVER_DIR
if (( $? > 0 )); then
echo "Error creating server install directory"
print_install_results_and_exit 1
fi
fi
SERVER_DOWNLOAD_URL="$(echo "${serverDownloadUrlTemplate.replace(/\$\{/g, '\\${')}" | sed "s/\\\${quality}/$DISTRO_QUALITY/g" | sed "s/\\\${version}/$DISTRO_VERSION/g" | sed "s/\\\${commit}/$DISTRO_COMMIT/g" | sed "s/\\\${os}/$SERVER_OS/g" | sed "s/\\\${arch}/$SERVER_ARCH/g" | sed "s/\\\${release}/$DISTRO_VSCODIUM_RELEASE/g")"
# Check if server script is already installed
if [[ ! -f $SERVER_SCRIPT ]]; then
if [[ "$SERVER_OS" = "dragonfly" ]] || [[ "$SERVER_OS" = "freebsd" ]]; then
echo "Error "$SERVER_OS" needs manual installation of remote extension host"
print_install_results_and_exit 1
fi
pushd $SERVER_DIR > /dev/null
if [[ ! -z $(which wget) ]]; then
wget --tries=3 --timeout=10 --continue --no-verbose -O vscode-server.tar.gz $SERVER_DOWNLOAD_URL
elif [[ ! -z $(which curl) ]]; then
curl --retry 3 --connect-timeout 10 --location --show-error --silent --output vscode-server.tar.gz $SERVER_DOWNLOAD_URL
else
echo "Error no tool to download server binary"
print_install_results_and_exit 1
fi
if (( $? > 0 )); then
echo "Error downloading server from $SERVER_DOWNLOAD_URL"
print_install_results_and_exit 1
fi
tar -xf vscode-server.tar.gz --strip-components 1
if (( $? > 0 )); then
echo "Error while extracting server contents"
print_install_results_and_exit 1
fi
if [[ ! -f $SERVER_SCRIPT ]]; then
echo "Error server contents are corrupted"
print_install_results_and_exit 1
fi
rm -f vscode-server.tar.gz
popd > /dev/null
else
echo "Server script already installed in $SERVER_SCRIPT"
fi
# Try to find if server is already running
if [[ -f $SERVER_PIDFILE ]]; then
SERVER_PID="$(cat $SERVER_PIDFILE)"
SERVER_RUNNING_PROCESS="$(ps -o pid,args -p $SERVER_PID | grep $SERVER_SCRIPT)"
else
SERVER_RUNNING_PROCESS="$(ps -o pid,args -A | grep $SERVER_SCRIPT | grep -v grep)"
fi
if [[ -z $SERVER_RUNNING_PROCESS ]]; then
if [[ -f $SERVER_LOGFILE ]]; then
rm $SERVER_LOGFILE
fi
if [[ -f $SERVER_TOKENFILE ]]; then
rm $SERVER_TOKENFILE
fi
touch $SERVER_TOKENFILE
chmod 600 $SERVER_TOKENFILE
SERVER_CONNECTION_TOKEN="${crypto.randomUUID()}"
echo $SERVER_CONNECTION_TOKEN > $SERVER_TOKENFILE
$SERVER_SCRIPT --start-server --host=127.0.0.1 $SERVER_LISTEN_FLAG $SERVER_INITIAL_EXTENSIONS --connection-token-file $SERVER_TOKENFILE --telemetry-level off --use-host-proxy --disable-websocket-compression --without-browser-env-var --enable-remote-auto-shutdown --accept-server-license-terms &> $SERVER_LOGFILE &
echo $! > $SERVER_PIDFILE
else
echo "Server script is already running $SERVER_SCRIPT"
fi
if [[ -f $SERVER_TOKENFILE ]]; then
SERVER_CONNECTION_TOKEN="$(cat $SERVER_TOKENFILE)"
else
echo "Error server token file not found $SERVER_TOKENFILE"
print_install_results_and_exit 1
fi
if [[ -f $SERVER_LOGFILE ]]; then
for i in {1..5}; do
LISTENING_ON="$(cat $SERVER_LOGFILE | grep -E 'Extension host agent listening on .+' | sed 's/Extension host agent listening on //')"
if [[ -n $LISTENING_ON ]]; then
break
fi
sleep 0.5
done
if [[ -z $LISTENING_ON ]]; then
echo "Error server did not start sucessfully"
print_install_results_and_exit 1
fi
else
echo "Error server log file not found $SERVER_LOGFILE"
print_install_results_and_exit 1
fi
# Finish server setup and keep script running
if [[ -z $SERVER_RUNNING_PROCESS ]]; then
echo "${id}: start"
echo "exitCode==0=="
echo "listeningOn==$LISTENING_ON=="
echo "connectionToken==$SERVER_CONNECTION_TOKEN=="
echo "logFile==$SERVER_LOGFILE=="
echo "osReleaseId==$OS_RELEASE_ID=="
echo "arch==$ARCH=="
echo "platform==$PLATFORM=="
echo "tmpDir==$TMP_DIR=="
${envVariables.map(envVar => `echo "${envVar}==$${envVar}=="`).join('\n')}
echo "${id}: end"
echo "${id}: Server installation script done"
SERVER_PID="$(cat $SERVER_PIDFILE)"
SERVER_RUNNING_PROCESS="$(ps -o pid,args -p $SERVER_PID | grep $SERVER_SCRIPT)"
while [[ -n $SERVER_RUNNING_PROCESS ]]; do
sleep 300;
SERVER_RUNNING_PROCESS="$(ps -o pid,args -p $SERVER_PID | grep $SERVER_SCRIPT)"
done
else
print_install_results_and_exit 0
fi
`;
}
@@ -0,0 +1,150 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as cp from 'child_process';
import Log from '../common/logger';
import { EventEmitter } from '../common/event';
const wslBinary = 'wsl.exe';
export interface WSLDistro {
isDefault: boolean;
name: string;
state: string;
version: string;
}
export interface WSLOnlineDistro {
name: string;
friendlyName: string;
}
export class WSLManager {
constructor(private readonly logger: Log) {
}
async listDistros() {
const resp = this._runWSLCommand(['--list', '--verbose'], 'utf16le');
const { exitCode } = await resp.exitPromise;
const { stdout, stderr } = resp;
if (exitCode) {
this.logger.trace(`Command wsl listDistros exited with code ${exitCode}`, stdout + '\n\n' + stderr);
throw new Error(`Command wsl listDistros exited with code ${exitCode}`);
}
const regex = /(?<default>\*|\s)\s+(?<name>[\w\.-]+)\s+(?<state>[\w]+)\s+(?<version>\d)/;
const distros: WSLDistro[] = [];
for (const line of stdout.split(/\r?\n/)) {
const matches = line.match(regex);
if (matches && matches.groups) {
distros.push({
isDefault: matches.groups.default === '*',
name: matches.groups.name,
state: matches.groups.state,
version: matches.groups.version,
});
}
}
return distros;
}
async listOnlineDistros() {
const resp = this._runWSLCommand(['--list', '--online'], 'utf16le');
const { exitCode } = await resp.exitPromise;
const { stdout, stderr } = resp;
if (exitCode) {
this.logger.trace(`Command wsl listOnlineDistros exited with code ${exitCode}`, stdout + '\n\n' + stderr);
throw new Error(`Command wsl listOnlineDistros exited with code ${exitCode}`);
}
let lines = stdout.split(/\r?\n/);
const idx = lines.findIndex(l => /\s*NAME\s+FRIENDLY NAME\s*/.test(l));
lines = lines.slice(idx + 1);
const regex = /(?<name>[\w\.-]+)\s+(?<friendlyName>\w.+\w)/;
const distros: WSLOnlineDistro[] = [];
for (const line of lines) {
const matches = line.match(regex);
if (matches && matches.groups) {
distros.push({
name: matches.groups.name,
friendlyName: matches.groups.friendlyName,
});
}
}
return distros;
}
async setDefaultDistro(distroName: string) {
const resp = this._runWSLCommand(['--set-default', distroName], 'utf16le');
const { exitCode } = await resp.exitPromise;
const { stdout, stderr } = resp;
if (exitCode) {
this.logger.trace(`Command wsl setDefaultDistro exited with code ${exitCode}`, stdout + '\n\n' + stderr);
throw new Error(`Command wsl setDefaultDistro exited with code ${exitCode}`);
}
}
async deleteDistro(distroName: string) {
const resp = this._runWSLCommand(['--unregister', distroName], 'utf16le');
const { exitCode } = await resp.exitPromise;
const { stdout, stderr } = resp;
if (exitCode) {
this.logger.trace(`Command wsl deleteDistro exited with code ${exitCode}`, stdout + '\n\n' + stderr);
throw new Error(`Command wsl deleteDistro exited with code ${exitCode}`);
}
}
async exec(cmd: string, args: string[], distro: string) {
return this._runWSLCommand(['--distribution', distro, '--', cmd, ...args], 'utf8');
}
private _runWSLCommand(args: string[], encoding: 'utf8' | 'utf16le') {
this.logger.trace(`Running WSL command: ${wslBinary} ${args.join(' ')}`);
const cmd = cp.spawn(wslBinary, args, { windowsHide: true, windowsVerbatimArguments: true });
const stdoutDataEmitter = new EventEmitter<Buffer>();
const stdoutData: Buffer[] = [];
const stderrDataEmitter = new EventEmitter<Buffer>();
const stderrData: Buffer[] = [];
cmd.stdout.on('data', (data: Buffer) => {
stdoutData.push(data);
stdoutDataEmitter.fire(data);
});
cmd.stderr.on('data', (data: Buffer) => {
stderrData.push(data);
stderrDataEmitter.fire(data);
});
const exitPromise = new Promise<{ exitCode: number }>((resolve, reject) => {
cmd.on('error', (err) => {
this.logger.error(`Error running WSL command: ${wslBinary} ${args.join(' ')}`, err);
reject(err);
});
cmd.on('exit', (code, _signal) => {
resolve({ exitCode: code ?? 0 });
});
});
return {
get stdout() {
return Buffer.concat(stdoutData).toString(encoding);
},
get stderr() {
return Buffer.concat(stderrData).toString(encoding);
},
get onStdoutData() {
return stdoutDataEmitter.event;
},
get onStderrData() {
return stderrDataEmitter.event;
},
exitPromise
};
}
}
@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
class WSLTerminal {
static NAME = 'WSL';
private getTerminal() {
const wslTerminal = vscode.window.terminals.find(t => t.name === WSLTerminal.NAME);
if (wslTerminal) {
return wslTerminal;
}
return vscode.window.createTerminal(WSLTerminal.NAME);
}
runCommand(command: string) {
const wslTerminal = this.getTerminal();
wslTerminal.show(false);
wslTerminal.sendText(command, true);
}
}
export default new WSLTerminal();
+12
View File
@@ -0,0 +1,12 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "./out",
},
"include": [
"src/**/*",
"../../src/vscode-dts/vscode.d.ts",
"../../src/vscode-dts/vscode.proposed.resolvers.d.ts",
"../../src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts",
]
}
+44 -14
View File
@@ -62,6 +62,7 @@
"posthog-node": "^4.8.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-tooltip": "^5.28.1",
"tas-client-umd": "0.2.0",
"v8-inspect-profiler": "^0.1.1",
"vscode-html-languageservice": "^5.3.1",
@@ -117,7 +118,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",
@@ -3587,6 +3588,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",
@@ -6614,6 +6622,12 @@
"node": ">=0.10.0"
}
},
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
"license": "MIT"
},
"node_modules/cli-cursor": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
@@ -8001,9 +8015,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",
@@ -15087,10 +15101,11 @@
}
},
"node_modules/nan": {
"version": "2.14.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz",
"integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/nanoid": {
@@ -17992,6 +18007,20 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/react-tooltip": {
"version": "5.28.1",
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.28.1.tgz",
"integrity": "sha512-ZA4oHwoIIK09TS7PvSLFcRlje1wGZaxw6xHvfrzn6T82UcMEfEmHVCad16Gnr4NDNDh93HyN037VK4HDi5odfQ==",
"license": "MIT",
"dependencies": {
"@floating-ui/dom": "^1.6.1",
"classnames": "^2.3.0"
},
"peerDependencies": {
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -18800,10 +18829,11 @@
}
},
"node_modules/sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
"dev": true
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"dev": true,
"license": "ISC"
},
"node_modules/scheduler": {
"version": "0.25.0",
@@ -21443,9 +21473,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"
},
+3 -2
View File
@@ -69,7 +69,7 @@
"extensions-ci-pr": "node ./node_modules/gulp/bin/gulp.js extensions-ci-pr",
"perf": "node scripts/code-perf.js",
"update-build-ts-version": "npm install typescript@next && tsc -p ./build/tsconfig.build.json"
},
},
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
"@floating-ui/react": "^0.27.5",
@@ -123,6 +123,7 @@
"posthog-node": "^4.8.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-tooltip": "^5.28.1",
"tas-client-umd": "0.2.0",
"v8-inspect-profiler": "^0.1.1",
"vscode-html-languageservice": "^5.3.1",
@@ -178,7 +179,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",
+5 -1
View File
@@ -35,5 +35,9 @@
"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
View File
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

+3
View File
@@ -3,6 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// This bootstrap-fork module handles the initialization of a forked process in VS Code.
// It sets up logging, exception handling, and loads the ESM module system.
import * as performance from './vs/base/common/performance.js';
import { removeGlobalNodeJsModuleLookupPaths, devInjectNodeModuleLookupPath } from './bootstrap-node.js';
import { bootstrapESM } from './bootstrap-esm.js';
+14
View File
@@ -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;
+2 -1
View File
@@ -56,7 +56,8 @@ export type ExtensionVirtualWorkspaceSupport = {
export interface IProductConfiguration {
readonly version: string;
readonly voidVersion?: string; // Void added this
readonly voidVersion: string; // Void added this
readonly release: string; // VSCodium added this
readonly date?: string;
readonly quality?: string;
readonly commit?: string;
+1 -1
View File
@@ -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' },
@@ -121,6 +121,93 @@ export class SmartSelectController implements IEditorContribution {
}
this._state = this._state.map(state => state.mov(forward));
const newSelections = this._state.map(state => Selection.fromPositions(state.ranges[state.index].getStartPosition(), state.ranges[state.index].getEndPosition()));
// Void changed this to skip over added whitespace when using smartSelect
// // Store the original selections for comparison
// const originalSelections = selections;
// // Keep skipping while we're only adding/removing whitespace
// let keepSkipping = true;
// let skipCount = 0;
// const MAX_SKIPS = 5; // Avoid infinite loops by setting a reasonable limit
// while (keepSkipping && skipCount < MAX_SKIPS) {
// keepSkipping = false; // Reset for each iteration
// // Check if all selections only added/removed whitespace
// if (originalSelections.length === newSelections.length) {
// for (let i = 0; i < originalSelections.length; i++) {
// const oldSel = originalSelections[i];
// const newSel = newSelections[i];
// if (forward) { // For expanding (^+Shift+Right)
// // Skip if only whitespace was added
// const oldText = model.getValueInRange(oldSel).trim();
// const newText = model.getValueInRange(newSel).trim();
// const onlyWhitespaceAdded = oldText === newText && oldText.length > 0;
// if (onlyWhitespaceAdded) {
// console.log(`SMART SELECT - SKIPPING (EXPAND) [${skipCount + 1}]:`, {
// reason: 'only whitespace added',
// oldText: model.getValueInRange(oldSel),
// newText: model.getValueInRange(newSel)
// });
// keepSkipping = true;
// break;
// }
// } else { // For shrinking (^+Shift+Left)
// // Skip if only whitespace was removed
// const oldText = model.getValueInRange(oldSel).trim();
// const newText = model.getValueInRange(newSel).trim();
// const onlyWhitespaceRemoved = oldText === newText && newText.length > 0;
// if (onlyWhitespaceRemoved) {
// console.log(`SMART SELECT - SKIPPING (SHRINK) [${skipCount + 1}]:`, {
// reason: 'only whitespace removed',
// oldText: model.getValueInRange(oldSel),
// newText: model.getValueInRange(newSel)
// });
// keepSkipping = true;
// break;
// }
// }
// }
// }
// // If we need to skip, move one more time
// if (keepSkipping) {
// skipCount++;
// // Try to move to the next range
// const prevState = this._state;
// this._state = this._state.map(state => state.mov(forward));
// // Check if we've reached the end of available ranges
// const stateUnchanged = this._state.every((state, idx) =>
// state.index === prevState[idx].index
// );
// if (stateUnchanged) {
// // We can't move any further, so stop skipping
// keepSkipping = false;
// } else {
// // Update selections for the next iteration
// newSelections = this._state.map(state => Selection.fromPositions(
// state.ranges[state.index].getStartPosition(),
// state.ranges[state.index].getEndPosition()
// ));
// }
// }
// }
// // Print AFTER selection (before actually setting it)
// console.log('SMART SELECT - AFTER:', newSelections.map(s => {
// return {
// range: `(${s.startLineNumber},${s.startColumn}) -> (${s.endLineNumber},${s.endColumn})`,
// text: model.getValueInRange(s)
// };
// }));
this._ignoreSelection = true;
try {
this._editor.setSelections(newSelections);
@@ -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');
+3 -2
View File
@@ -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
});
}
}
+40 -1
View File
@@ -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
)
}
@@ -612,7 +612,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
'description': localize('workbench.hover.delay', "Controls the delay in milliseconds after which the hover is shown for workbench items (ex. some extension provided tree view items). Already visible items may require a refresh before reflecting this setting change."),
// Testing has indicated that on Windows and Linux 500 ms matches the native hovers most closely.
// On Mac, the delay is 1500.
'default': isMacintosh ? 1500 : 500,
'default': 300, // Void changed this from isMacintosh ? 1500 : 500,
'minimum': 0
},
'workbench.reduceMotion': {
+15
View File
@@ -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;
}
@@ -648,8 +648,9 @@ const defaultChat = {
providerSetting: product.defaultChatAgent?.providerSetting ?? '',
};
// Void commented this out - copilot head
// Add next to the command center if command center is disabled
MenuRegistry.appendMenuItem(MenuId.CommandCenter, {
/* MenuRegistry.appendMenuItem(MenuId.CommandCenter, {
submenu: MenuId.ChatTitleBarMenu,
title: localize('title4', "Copilot"),
icon: Codicon.copilot,
@@ -672,7 +673,7 @@ MenuRegistry.appendMenuItem(MenuId.TitleBar, {
ContextKeyExpr.has('config.window.commandCenter').negate(),
),
order: 1
});
}); */
registerAction2(class ToggleCopilotControl extends ToggleTitleBarConfigAction {
constructor() {
@@ -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);
@@ -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));
// }
// };
@@ -34,7 +34,7 @@
// // const result = await new Promise((res, rej) => {
// // sendLLMMessage({
// // messages,
// // tools: ['search'],
// // tools: ['search_files'],
// // onFinalMessage: ({ result: r, }) => {
// // res(r)
// // },
@@ -73,7 +73,7 @@
// // const result = new Promise((res, rej) => {
// // sendLLMMessage({
// // messages,
// // tools: ['search'],
// // tools: ['search_files'],
// // onResult: (r) => {
// // res(r)
// // }
@@ -8,8 +8,7 @@ import { ILanguageFeaturesService } from '../../../../editor/common/services/lan
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { ITextModel } from '../../../../editor/common/model.js';
import { Position } from '../../../../editor/common/core/position.js';
import { InlineCompletion, InlineCompletionContext, } from '../../../../editor/common/languages.js';
import { CancellationToken } from '../../../../base/common/cancellation.js';
import { InlineCompletion, } from '../../../../editor/common/languages.js';
import { Range } from '../../../../editor/common/core/range.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js';
@@ -633,13 +632,14 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
async _provideInlineCompletionItems(
model: ITextModel,
position: Position,
context: InlineCompletionContext,
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
@@ -790,12 +790,11 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
const featureName: FeatureName = 'Autocomplete'
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[modelSelection.providerName]?.[modelSelection.modelName] : undefined
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
const isEnabled = this._settingsService.state.globalSettings.enableAutocomplete
// set parameters of `newAutocompletion` appropriately
newAutocompletion.llmPromise = isEnabled ? new Promise((resolve, reject) => reject('Autocomplete is disabled')) : new Promise((resolve, reject) => {
newAutocompletion.llmPromise = new Promise((resolve, reject) => {
const requestId = this._llmMessageService.sendLLMMessage({
messagesType: 'FIMMessage',
@@ -850,6 +849,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
newAutocompletion.status = 'error'
reject(message)
},
onAbort: () => { reject('Aborted autocomplete') },
})
newAutocompletion.requestId = requestId
@@ -894,9 +894,9 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
) {
super()
this._langFeatureService.inlineCompletionsProvider.register('*', {
this._register(this._langFeatureService.inlineCompletionsProvider.register('*', {
provideInlineCompletions: async (model, position, context, token) => {
const items = await this._provideInlineCompletionItems(model, position, context, token)
const items = await this._provideInlineCompletionItems(model, position)
// console.log('item: ', items?.[0]?.insertText)
return { items: items, }
@@ -913,7 +913,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)
@@ -933,7 +933,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
});
},
})
}))
}
@@ -942,4 +942,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,321 @@
/*--------------------------------------------------------------------------------------
* 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';
import { MAX_DIRSTR_CHARS_TOTAL_BEGINNING, MAX_DIRSTR_CHARS_TOTAL_TOOL } from '../common/prompt/prompts.js';
// const MAX_FILES_TOTAL = 200
export interface IDirectoryStrService {
readonly _serviceBrand: undefined;
getDirectoryStrTool(uri: URI): Promise<string>
getAllDirectoriesStr(opts: { cutOffMessage: string }): Promise<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_DIRSTR_CHARS_TOTAL_TOOL);
let c = content.substring(0, MAX_DIRSTR_CHARS_TOTAL_TOOL)
c = `Directory of ${uri.fsPath}:\n${content}`
if (wasCutOff) c = `${c}\n...Result was truncated...`
return c
}
async getAllDirectoriesStr({ cutOffMessage }: { cutOffMessage: string }) {
let str: string = '';
let cutOff = false;
const folders = this.workspaceContextService.getWorkspace().folders;
if (folders.length === 0)
return '(NO WORKSPACE OPEN)';
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);
const { content, wasCutOff } = stringifyDirectoryTree(dirTree, MAX_DIRSTR_CHARS_TOTAL_BEGINNING - str.length);
str += content;
if (wasCutOff) {
cutOff = true;
break;
}
}
if (cutOff) {
return `${str}\n${cutOffMessage}`
}
return str
}
}
registerSingleton(IDirectoryStrService, DirectoryStrService, InstantiationType.Delayed);
File diff suppressed because it is too large Load Diff
@@ -7,22 +7,29 @@ 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 StartApplyingOpts = ({
export type CallBeforeStartApplyingOpts = {
from: 'QuickEdit';
type: 'rewrite';
diffareaid: number; // id of the CtrlK area (contains text selection)
} | {
from: 'ClickApply';
type: 'searchReplace' | 'rewrite';
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,
@@ -30,28 +37,36 @@ export type AddCtrlKOpts = {
editor: ICodeEditor,
}
export type URIStreamState = 'idle' | 'acceptRejectAll' | 'streaming'
export const IEditCodeService = createDecorator<IEditCodeService>('editCodeService');
export interface IEditCodeService {
readonly _serviceBrand: undefined;
startApplying(opts: StartApplyingOpts): [URI, Promise<void>] | null;
callBeforeStartApplying(opts: CallBeforeStartApplyingOpts): Promise<void>;
startApplying(opts: StartApplyingOpts): [URI, Promise<void>] | null;
addCtrlKZone(opts: AddCtrlKOpts): number | undefined;
removeCtrlKZone(opts: { diffareaid: number }): void;
removeDiffAreas(opts: { uri: URI, removeCtrlKs: boolean, behavior: 'reject' | 'accept' }): 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;
onDidChangeCtrlKZoneStreaming: Event<{ uri: URI; diffareaid: number }>;
// // DiffZone codeBoxId streaming state
getURIStreamState(opts: { uri: URI | null }): URIStreamState;
interruptURIStreaming(opts: { uri: URI }): void;
onDidChangeURIStreamState: Event<{ uri: URI; state: URIStreamState }>;
// 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
@@ -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
}
}
@@ -76,93 +76,107 @@
opacity: 80%;
}
/* styles for all containers used by void */
.void-scope {
--scrollbar-vertical-width: 8px;
--scrollbar-horizontal-height: 6px;
}
/* Target both void-scope and all its descendants with scrollbars */
.void-scope,
.void-scope * {
scrollbar-width: thin !important;
scrollbar-color: var(--void-bg-1) var(--void-bg-3) !important; /* For Firefox */
}
.void-scope::-webkit-scrollbar,
.void-scope *::-webkit-scrollbar {
width: var(--scrollbar-vertical-width) !important;
height: var(--scrollbar-horizontal-height) !important;
background-color: var(--void-bg-3) !important;
}
.void-scope::-webkit-scrollbar-thumb,
.void-scope *::-webkit-scrollbar-thumb {
background-color: var(--void-bg-1) !important;
border-radius: 4px !important;
border: none !important;
-webkit-box-shadow: none !important;
box-shadow: none !important;
}
.void-scope::-webkit-scrollbar-thumb:hover,
.void-scope *::-webkit-scrollbar-thumb:hover {
background-color: var(--void-bg-1) !important;
filter: brightness(1.1) !important;
}
.void-scope::-webkit-scrollbar-thumb:active,
.void-scope *::-webkit-scrollbar-thumb:active {
background-color: var(--void-bg-1) !important;
filter: brightness(1.2) !important;
}
.void-scope::-webkit-scrollbar-track,
.void-scope *::-webkit-scrollbar-track {
background-color: var(--void-bg-3) !important;
border: none !important;
}
.void-scope::-webkit-scrollbar-corner,
.void-scope *::-webkit-scrollbar-corner {
background-color: var(--void-bg-3) !important;
}
/* Add void-scrollable-element styles to match */
.void-scrollable-element {
background-color: var(--vscode-editor-background);
--scrollbar-vertical-width: 14px;
--scrollbar-horizontal-height: 6px;
overflow: auto; /* Ensure scrollbars are shown when needed */
}
.void-scrollable-element,
.void-scrollable-element * {
scrollbar-width: thin !important; /* For Firefox */
scrollbar-color: var(--void-bg-1) var(--void-bg-3) !important; /* For Firefox */
}
.void-scrollable-element::-webkit-scrollbar,
.void-scrollable-element *::-webkit-scrollbar {
width: 14px !important;
height: 14px !important;
}
.void-scrollable-element::-webkit-scrollbar-track,
.void-scrollable-element *::-webkit-scrollbar-track {
background: transparent !important;
width: var(--scrollbar-vertical-width) !important;
height: var(--scrollbar-horizontal-height) !important;
background-color: var(--void-bg-3) !important;
}
.void-scrollable-element::-webkit-scrollbar-thumb,
.void-scrollable-element *::-webkit-scrollbar-thumb {
background-color: transparent !important;
border-radius: 0px !important;
background-color: var(--void-bg-1) !important;
border-radius: 4px !important;
border: none !important;
-webkit-box-shadow: none !important;
box-shadow: none !important;
}
.void-scrollable-element::-webkit-scrollbar-thumb:hover,
.void-scrollable-element *::-webkit-scrollbar-thumb:hover {
background-color: var(--vscode-scrollbarSlider-hoverBackground) !important;
background-color: var(--void-bg-1) !important;
filter: brightness(1.1) !important;
}
.void-scrollable-element::-webkit-scrollbar-thumb:active,
.void-scrollable-element *::-webkit-scrollbar-thumb:active {
background-color: var(--vscode-scrollbarSlider-activeBackground) !important;
background-color: var(--void-bg-1) !important;
filter: brightness(1.2) !important;
}
.void-scrollable-element::-webkit-scrollbar-track,
.void-scrollable-element *::-webkit-scrollbar-track {
background-color: var(--void-bg-3) !important;
border: none !important;
}
.void-scrollable-element::-webkit-scrollbar-corner,
.void-scrollable-element *::-webkit-scrollbar-corner {
background-color: transparent !important;
}
.void-scrollable-element.show-scrollbar-0::-webkit-scrollbar-thumb,
.void-scrollable-element.show-scrollbar-0 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 0%, transparent) !important;
}
.void-scrollable-element.show-scrollbar-1::-webkit-scrollbar-thumb,
.void-scrollable-element.show-scrollbar-1 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 10%, transparent) !important;
}
.void-scrollable-element.show-scrollbar-2::-webkit-scrollbar-thumb,
.void-scrollable-element.show-scrollbar-2 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 20%, transparent) !important;
}
.void-scrollable-element.show-scrollbar-3::-webkit-scrollbar-thumb,
.void-scrollable-element.show-scrollbar-3 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 30%, transparent) !important;
}
.void-scrollable-element.show-scrollbar-4::-webkit-scrollbar-thumb,
.void-scrollable-element.show-scrollbar-4 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 40%, transparent) !important;
}
.void-scrollable-element.show-scrollbar-5::-webkit-scrollbar-thumb,
.void-scrollable-element.show-scrollbar-5 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 50%, transparent) !important;
}
.void-scrollable-element.show-scrollbar-6::-webkit-scrollbar-thumb,
.void-scrollable-element.show-scrollbar-6 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 60%, transparent) !important;
}
.void-scrollable-element.show-scrollbar-7::-webkit-scrollbar-thumb,
.void-scrollable-element.show-scrollbar-7 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 70%, transparent) !important;
}
.void-scrollable-element.show-scrollbar-8::-webkit-scrollbar-thumb,
.void-scrollable-element.show-scrollbar-8 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 80%, transparent) !important;
}
.void-scrollable-element.show-scrollbar-9::-webkit-scrollbar-thumb,
.void-scrollable-element.show-scrollbar-9 *::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 90%, transparent) !important;
}
.void-scrollable-element.show-scrollbar-10::-webkit-scrollbar-thumb,
.void-scrollable-element.show-scrollbar-10 *::-webkit-scrollbar-thumb {
background-color: var(--vscode-scrollbarSlider-background) !important;
background-color: var(--void-bg-3) !important;
}
@@ -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,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);
}
}, 4000);
}, 6000);
}
const args = process.argv.slice(2);
@@ -1,8 +1,17 @@
/*--------------------------------------------------------------------------------------
* 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 { 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'
import { PlacesType, VariantType } from 'react-tooltip'
enum CopyButtonText {
Idle = 'Copy',
@@ -10,9 +19,57 @@ enum CopyButtonText {
Error = 'Could not copy',
}
const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!'
const CopyButton = ({ codeStr }: { codeStr: string }) => {
type IconButtonProps = {
Icon: LucideIcon
}
export const IconShell1 = ({ onClick, Icon, disabled, className, ...props }: IconButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>) => (
<button
disabled={disabled}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onClick?.(e);
}}
// border border-void-border-1 rounded
className={`
size-[18px]
p-[2px]
flex items-center justify-center
text-sm bg-void-bg-3 text-void-fg-3
hover:brightness-110
disabled:opacity-50 disabled:cursor-not-allowed
${className}
`}
{...props}
>
<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')
@@ -26,7 +83,6 @@ const CopyButton = ({ codeStr }: { codeStr: string }) => {
}, COPY_FEEDBACK_TIMEOUT)
}, [copyButtonText])
const onCopy = useCallback(() => {
clipboardService.writeText(codeStr)
.then(() => { setCopyButtonText(CopyButtonText.Copied) })
@@ -34,115 +90,300 @@ const CopyButton = ({ codeStr }: { codeStr: string }) => {
metricsService.capture('Copy Code', { length: codeStr.length }) // capture the length only
}, [metricsService, clipboardService, codeStr, setCopyButtonText])
const isSingleLine = false //!codeStr.includes('\n')
return <button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-2 rounded`}
return <IconShell1
Icon={copyButtonText === CopyButtonText.Copied ? Check : copyButtonText === CopyButtonText.Error ? X : Copy}
onClick={onCopy}
>
{copyButtonText}
</button>
{...tooltipPropsForApplyBlock({ tooltipName: 'Copy' })}
/>
}
export const JumpToFileButton = ({ uri, ...props }: { uri: URI | 'current' } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const jumpToFileButton = uri !== 'current' && (
<IconShell1
Icon={FileSymlink}
onClick={() => {
commandService.executeCommand('vscode.open', uri, { preview: true })
}}
{...tooltipPropsForApplyBlock({ tooltipName: 'Go to file' })}
{...props}
/>
)
return jumpToFileButton
}
export const JumpToTerminalButton = ({ onClick }: { onClick: () => void }) => {
return (
<IconShell1
Icon={Terminal}
onClick={onClick}
/>
)
}
// 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 }) => {
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, uri]))
const currStreamState = getStreamState()
return {
getStreamState,
isDisabled,
currStreamState,
}
}
type IndicatorColor = 'green' | 'orange' | 'dark' | 'yellow' | null
export const StatusIndicator = ({ indicatorColor, title, className, ...props }: { indicatorColor: IndicatorColor, title?: React.ReactNode, className?: string } & React.HTMLAttributes<HTMLDivElement>) => {
return (
<div className={`flex flex-row text-void-fg-3 text-xs items-center gap-1.5 ${className}`} {...props}>
{title && <span className='opacity-80'>{title}</span>}
<div
className={` size-1.5 rounded-full border
${indicatorColor === 'dark' ? 'bg-void-bg-3 border-void-border-1' :
indicatorColor === 'orange' ? 'bg-orange-500 border-orange-500 shadow-[0_0_4px_0px_rgba(234,88,12,0.6)]' :
indicatorColor === 'green' ? 'bg-green-500 border-green-500 shadow-[0_0_4px_0px_rgba(22,163,74,0.6)]' :
indicatorColor === 'yellow' ? 'bg-yellow-500 border-yellow-500 shadow-[0_0_4px_0px_rgba(22,163,74,0.6)]' :
'bg-void-border-1 border-void-border-1'
}
`}
/>
</div>
);
};
const tooltipPropsForApplyBlock = ({ tooltipName, color = undefined, position = 'top', offset = undefined }: { tooltipName: string, color?: IndicatorColor, position?: PlacesType, offset?: number }) => ({
'data-tooltip-id': color === 'orange' ? `void-tooltip-orange` : color === 'green' ? 'void-tooltip-green' : 'void-tooltip',
'data-tooltip-place': position as PlacesType,
'data-tooltip-content': `${tooltipName}`,
'data-tooltip-offset': offset,
})
export const StatusIndicatorForApplyButton = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI | 'current' } & React.HTMLAttributes<HTMLDivElement>) => {
const { currStreamState } = useApplyButtonState({ applyBoxId, uri })
const color = (
currStreamState === 'idle-no-changes' ? 'dark' :
currStreamState === 'streaming' ? 'orange' :
currStreamState === 'idle-has-changes' ? 'green' :
null
)
const onSubmit = useCallback(() => {
const tooltipName = (
currStreamState === 'idle-no-changes' ? 'Done' :
currStreamState === 'streaming' ? 'Applying' :
currStreamState === 'idle-has-changes' ? 'Done' : // also 'Done'? 'Applied' looked bad
''
)
const statusIndicatorHTML = <StatusIndicator
key={currStreamState}
className='mx-2'
indicatorColor={color}
{...tooltipPropsForApplyBlock({ tooltipName, color, position: 'top', offset: 12 })}
/>
return statusIndicatorHTML
}
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: 'current',
}) ?? []
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 => {
const uri = getUriBeingApplied(applyBoxId)
if (uri) editCodeService.interruptURIStreaming({ uri: uri })
})
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 = false //!codeStr.includes('\n')
if (currStreamState === 'streaming') {
return <IconShell1
const applyButton = <button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-2 rounded`}
onClick={onSubmit}
>
Apply
</button>
Icon={Square}
onClick={onInterrupt}
const stopButton = <button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-2 rounded`}
onClick={onInterrupt}
>
Stop
</button>
{...tooltipPropsForApplyBlock({ tooltipName: 'Stop' })}
/>
}
const acceptRejectButtons = <>
<button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-2 rounded`}
onClick={() => {
const uri = applyingUri()
if (uri) editCodeService.removeDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false })
}}
>
Accept
</button>
<button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-2 rounded`}
onClick={() => {
const uri = applyingUri()
if (uri) editCodeService.removeDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false })
}}
>
Reject
</button>
</>
if (currStreamState === 'idle-no-changes') {
return <IconShell1
Icon={reapplyIcon ? RotateCw : Play}
onClick={onClickSubmit}
{...tooltipPropsForApplyBlock({ tooltipName: reapplyIcon ? 'Reapply' : 'Apply' })}
/>
}
if (currStreamState === 'idle-has-changes') {
return <>
{/* <IconShell1
Icon={RotateCw}
onClick={onReapply}
/> */}
<IconShell1
Icon={X}
onClick={onReject}
{...tooltipPropsForApplyBlock({ tooltipName: 'Reject file' })}
/>
<IconShell1
Icon={Check}
onClick={onAccept}
{...tooltipPropsForApplyBlock({ tooltipName: 'Accept file' })}
/>
</>
}
}
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">
<StatusIndicatorForApplyButton 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 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">
{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,15 +3,17 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import React, { JSX, useState } from 'react'
import React, { JSX, useMemo, useState } from 'react'
import { marked, MarkedToken, Token } from 'marked'
import { BlockCode } from './BlockCode.js'
import { nameToVscodeLanguage } from '../../../../common/helpers/detectLanguage.js'
import { ApplyBlockHoverButtons } from './ApplyBlockHoverButtons.js'
import { useAccessor, useChatThreadsState } from '../util/services.js'
import { Range } from '../../../../../../services/search/common/searchExtTypes.js'
import { IRange } from '../../../../../../../base/common/range.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 = {
@@ -21,13 +23,18 @@ export type ChatMessageLocation = {
type ApplyBoxLocation = ChatMessageLocation & { tokenIdx: string }
const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) => {
export const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) => {
return `${threadId}-${messageIdx}-${tokenIdx}`
}
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 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}
@@ -42,7 +49,7 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
const accessor = useAccessor()
const chatThreadService = accessor.get('IChatThreadService')
const commandSerivce = accessor.get('ICommandService')
const commandService = accessor.get('ICommandService')
const editorService = accessor.get('ICodeEditorService')
const { messageIdx, threadId } = chatMessageLocation
@@ -56,8 +63,8 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
link = chatThreadService.getCodespanLink({ codespanStr: text, messageIdx, threadId })
if (link === undefined) {
// generate link and add to cache
(chatThreadService.generateCodespanLink(text)
// 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
@@ -74,7 +81,7 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
const selection = link.selection
// open the file
commandSerivce.executeCommand('vscode.open', link.uri).then(() => {
commandService.executeCommand('vscode.open', link.uri).then(() => {
// select the text
setTimeout(() => {
@@ -93,19 +100,24 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
}
return <Codespan
text={text}
// text={link?.displayText || text}
text={link?.displayText || text}
onClick={onClick}
className={link ? 'underline hover:brightness-90 transition-all duration-200 cursor-pointer' : ''}
/>
}
const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token: Token | string, nested?: boolean, chatMessageLocation?: ChatMessageLocation, tokenIdx: string }): JSX.Element => {
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 <></>;
return null;
}
if (t.type === "space") {
@@ -113,29 +125,67 @@ const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token:
}
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
// TODO user should only be able to apply this when the code has been closed (t.raw ends with "```")
// 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
}
return <div>
<BlockCode
initValue={t.text}
language={t.lang === undefined ? undefined : nameToVscodeLanguage[t.lang]}
buttonsOnHover={applyBoxId && <ApplyBlockHoverButtons applyBoxId={applyBoxId} codeStr={t.text} />}
/>
</div>
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={contents}
language={language}
/>
}
if (t.type === "heading") {
const HeadingTag = `h${t.depth}` as keyof JSX.IntrinsicElements
return <HeadingTag>{t.text}</HeadingTag>
return <HeadingTag>
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={t.text} inPTag={true} codeURI={codeURI} {...options} />
</HeadingTag>
}
if (t.type === "table") {
@@ -213,7 +263,7 @@ const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token:
return <li>
<input type="checkbox" checked={t.checked} readOnly />
<span>
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={t.text} nested={true} />
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={t.text} inPTag={true} codeURI={codeURI} {...options} />
</span>
</li>
}
@@ -229,7 +279,7 @@ const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token:
<input type="checkbox" checked={item.checked} readOnly />
)}
<span>
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={item.text} nested={true} />
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={item.text} inPTag={true} {...options} />
</span>
</li>
))}
@@ -242,25 +292,22 @@ const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token:
{t.tokens.map((token, index) => (
<RenderToken key={index}
token={token}
tokenIdx={`${tokenIdx ? `${tokenIdx}-` : ''}${index}`} // assign a unique tokenId to nested components
tokenIdx={`${tokenIdx ? `${tokenIdx}-` : ''}${index}`} // assign a unique tokenId to inPTag components
chatMessageLocation={chatMessageLocation}
inPTag={true}
{...options}
/>
))}
</>
if (nested) return contents
return <p>
{contents}
</p>
if (inPTag) return <span className='block'>{contents}</span>
return <p>{contents}</p>
}
if (t.type === "html") {
return (
<p>
{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") {
@@ -304,12 +351,13 @@ const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token:
// inline code
if (t.type === "codespan") {
if (chatMessageLocation) {
if (options.isLinkDetectionEnabled && chatMessageLocation) {
return <CodespanWithLink
text={t.text}
rawText={t.raw}
chatMessageLocation={chatMessageLocation}
/>
}
return <Codespan text={t.text} />
@@ -331,12 +379,13 @@ const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token:
)
}
export const ChatMarkdownRender = ({ string, nested = false, chatMessageLocation }: { string: string, nested?: boolean, chatMessageLocation: ChatMessageLocation | undefined }) => {
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} chatMessageLocation={chatMessageLocation} tokenIdx={index + ''} />
<RenderToken key={index} token={token} inPTag={inPTag} chatMessageLocation={chatMessageLocation} tokenIdx={index + ''} {...options} />
))}
</>
)
@@ -3,15 +3,13 @@
* 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';
import { isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js';
export const QuickEditChat = ({
@@ -55,17 +53,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(() => {
@@ -80,20 +88,19 @@ export const QuickEditChat = ({
editCodeService.removeCtrlKZone({ diffareaid })
}, [editCodeService, diffareaid])
useScrollbarStyles(sizerRef)
const keybindingString = accessor.get('IKeybindingService').lookupKeybinding(VOID_CTRL_K_ACTION_ID)?.getLabel()
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}
className="py-2 w-full"
onClickAnywhere={() => { textAreaRef.current?.focus() }}
>
<VoidInputBox2
File diff suppressed because it is too large Load Diff
@@ -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,12 +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 !== 'tool' && msg.role !== 'tool_request');
const firstUserMsgIdx = pastThread.messages.findIndex((msg) => msg.role === 'user');
if (firstUserMsgIdx !== -1) {
// firstMsg = truncate(pastThread.messages[firstMsgIdx].displayContent ?? '');
@@ -86,7 +90,7 @@ export const SidebarThreadSelector = () => {
// secondMsg = truncate(pastThread.messages[secondMsgIdx].displayContent ?? '');
// }
const numMessages = pastThread.messages.filter((msg) => msg.role !== 'tool_request').length;
const numMessages = pastThread.messages.filter((msg) => msg.role === 'assistant' || msg.role === 'user').length;
return (
<li key={pastThread.id}>
@@ -102,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>
@@ -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
}
@@ -106,6 +106,7 @@ export const VoidInputBox2 = forwardRef<HTMLTextAreaElement, InputBox2Props>(fun
return (
<textarea
autoFocus={false}
ref={useCallback((r: HTMLTextAreaElement | null) => {
if (fnsRef)
fnsRef.current = fns
@@ -152,6 +153,42 @@ export const VoidInputBox2 = forwardRef<HTMLTextAreaElement, InputBox2Props>(fun
})
export const VoidSimpleInputBox = ({ value, onChangeValue, placeholder, className, disabled, passwordBlur, compact, ...inputProps }: {
value: string;
onChangeValue: (value: string) => void;
placeholder: string;
className?: string;
disabled?: boolean;
compact?: boolean;
passwordBlur?: boolean;
} & React.InputHTMLAttributes<HTMLInputElement>) => {
return (
<input
value={value}
onChange={(e) => onChangeValue(e.target.value)}
placeholder={placeholder}
disabled={disabled}
// className='max-w-44 w-full border border-void-border-2 bg-void-bg-1 text-void-fg-3 text-root'
// className={`w-full resize-none text-void-fg-1 placeholder:text-void-fg-3 px-2 py-1 rounded-sm
className={`w-full resize-none bg-void-bg-1 text-void-fg-1 placeholder:text-void-fg-3 border border-void-border-2 focus:border-void-border-1
${compact ? 'py-1 px-2' : 'py-2 px-4 '}
rounded
${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>,
@@ -316,24 +353,24 @@ export const VoidSlider = ({
{/* 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-gray-200 dark:bg-gray-700 rounded-full cursor-pointer`}
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-gray-900 dark:bg-white rounded-full`}
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 with sizes matching VoidSwitch */}
{/* Thumb */}
<div
className={`absolute top-1/2 transform -translate-x-1/2 -translate-y-1/2
${size === 'xxs' ? 'h-2 w-2' :
@@ -341,7 +378,8 @@ export const VoidSlider = ({
size === 'sm' ? 'h-3 w-3' :
size === 'sm+' ? 'h-3.5 w-3.5' : 'h-4 w-4'
}
bg-white dark:bg-gray-900 rounded-full shadow-md ${disabled ? 'cursor-not-allowed' : 'cursor-grab active:cursor-grabbing'}`}
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;
@@ -393,7 +431,7 @@ export const VoidSwitch = ({
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' : ''}
@@ -404,7 +442,7 @@ 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' : ''}
@@ -460,7 +498,7 @@ 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,
@@ -471,7 +509,8 @@ export const VoidCustomDropdownBox = <T extends any>({
className,
arrowTouchesText = true,
matchInputWidth = false,
gap = 0,
gapPx = 0,
offsetPx = -6,
}: {
options: T[];
selectedOption: T | undefined;
@@ -483,7 +522,8 @@ export const VoidCustomDropdownBox = <T extends any>({
className?: string;
arrowTouchesText?: boolean;
matchInputWidth?: boolean;
gap?: number;
gapPx?: number;
offsetPx?: number;
}) => {
const [isOpen, setIsOpen] = useState(false);
const measureRef = useRef<HTMLDivElement>(null);
@@ -502,7 +542,7 @@ export const VoidCustomDropdownBox = <T extends any>({
placement: 'bottom-start',
middleware: [
offset(gap),
offset({ mainAxis: gapPx, crossAxis: offsetPx }),
flip({
boundary: document.body,
padding: 8
@@ -537,7 +577,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])
@@ -566,7 +606,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 (
@@ -601,7 +641,7 @@ export const VoidCustomDropdownBox = <T extends any>({
className="flex items-center h-4 bg-transparent whitespace-nowrap hover:brightness-90 w-full"
onClick={() => setIsOpen(!isOpen)}
>
<span className={`max-w-[120px] truncate ${arrowTouchesText ? 'mr-1' : ''}`}>
<span className={`truncate ${arrowTouchesText ? 'mr-1' : ''}`}>
{getOptionDisplayName(selectedOption)}
</span>
<svg
@@ -785,8 +825,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)
@@ -801,7 +841,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
@@ -882,9 +921,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
@@ -919,9 +960,9 @@ export const VoidCodeEditor = ({ initValue, language, maxHeight, showScrollbars
}
export const VoidButton = ({ children, disabled, onClick }: { children: React.ReactNode; disabled?: boolean; onClick: () => void }) => {
export const VoidButtonBgDarken = ({ children, disabled, onClick, className }: { children: React.ReactNode; disabled?: boolean; onClick: () => void; className?: string }) => {
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 flex items-center justify-center ${className || ''}`}
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
}
@@ -9,8 +9,6 @@ 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';
@@ -24,10 +22,7 @@ import { IThemeService } from '../../../../../../../platform/theme/common/themeS
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 '../../../editCodeServiceInterface.js'
import { IVoidUriStateService } from '../../../voidUriStateService.js';
import { IQuickEditStateService } from '../../../quickEditStateService.js';
import { ISidebarStateService } from '../../../sidebarStateService.js';
import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js'
import { ICodeEditorService } from '../../../../../../../editor/browser/services/codeEditorService.js'
@@ -45,18 +40,19 @@ import { IPathService } from '../../../../../../../workbench/services/path/commo
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()
@@ -78,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(
@@ -179,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));
})
)
@@ -211,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),
@@ -232,6 +208,13 @@ const getReactAccessor = (accessor: ServicesAccessor) => {
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
@@ -259,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(() => {
@@ -365,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(() => {
@@ -384,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({ stateOfURI: commandBarService.stateOfURI, sortedURIs: commandBarService.sortedURIs });
const listener = useCallback(() => {
ss({ stateOfURI: 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 }
}
@@ -1,128 +1,130 @@
import { useEffect } from 'react';
// Get rid of this as it was causing lag
export const useScrollbarStyles = (containerRef: React.MutableRefObject<HTMLDivElement | null>) => {
useEffect(() => {
if (!containerRef.current) return;
// import { useEffect } from 'react';
// Create selector for specific overflow classes
const overflowSelector = [
'[class*="overflow-auto"]',
'[class*="overflow-x-auto"]',
'[class*="overflow-y-auto"]'
].join(',');
// export const useScrollbarStyles = (containerRef: React.RefObject<HTMLDivElement | null>) => {
// useEffect(() => {
// if (!containerRef.current) return;
// Function to initialize scrollbar styles for elements
const initializeScrollbarStyles = () => {
// Get all matching elements within the container, including the container itself
const scrollElements = [
...(containerRef.current?.matches(overflowSelector) ? [containerRef.current] : []),
...Array.from(containerRef.current?.querySelectorAll(overflowSelector) || [])
];
// // Create selector for specific overflow classes
// const overflowSelector = [
// '[class*="overflow-auto"]',
// '[class*="overflow-x-auto"]',
// '[class*="overflow-y-auto"]'
// ].join(',');
// Apply basic styling to all elements
scrollElements.forEach(element => {
element.classList.add('void-scrollable-element');
});
// // Function to initialize scrollbar styles for elements
// const initializeScrollbarStyles = () => {
// // Get all matching elements within the container, including the container itself
// const scrollElements = [
// ...(containerRef.current?.matches(overflowSelector) ? [containerRef.current] : []),
// ...Array.from(containerRef.current?.querySelectorAll(overflowSelector) || [])
// ];
// Only initialize fade effects for elements that haven't been initialized yet
scrollElements.forEach(element => {
if (!(element as any).__scrollbarCleanup) {
let fadeTimeout: NodeJS.Timeout | null = null;
let fadeInterval: NodeJS.Timeout | null = null;
// // Apply basic styling to all elements
// scrollElements.forEach(element => {
// element.classList.add('void-scrollable-element');
// });
const fadeIn = () => {
if (fadeInterval) clearInterval(fadeInterval);
// // Only initialize fade effects for elements that haven't been initialized yet
// scrollElements.forEach(element => {
// if (!(element as any).__scrollbarCleanup) {
// let fadeTimeout: NodeJS.Timeout | null = null;
// let fadeInterval: NodeJS.Timeout | null = null;
let step = 0;
fadeInterval = setInterval(() => {
if (step <= 10) {
element.classList.remove(`show-scrollbar-${step - 1}`);
element.classList.add(`show-scrollbar-${step}`);
step++;
} else {
clearInterval(fadeInterval!);
}
}, 10);
};
// const fadeIn = () => {
// if (fadeInterval) clearInterval(fadeInterval);
const fadeOut = () => {
if (fadeInterval) clearInterval(fadeInterval);
// let step = 0;
// fadeInterval = setInterval(() => {
// if (step <= 10) {
// element.classList.remove(`show-scrollbar-${step - 1}`);
// element.classList.add(`show-scrollbar-${step}`);
// step++;
// } else {
// clearInterval(fadeInterval!);
// }
// }, 10);
// };
let step = 10;
fadeInterval = setInterval(() => {
if (step >= 0) {
element.classList.remove(`show-scrollbar-${step + 1}`);
element.classList.add(`show-scrollbar-${step}`);
step--;
} else {
clearInterval(fadeInterval!);
}
}, 60);
};
// const fadeOut = () => {
// if (fadeInterval) clearInterval(fadeInterval);
const onMouseEnter = () => {
if (fadeTimeout) clearTimeout(fadeTimeout);
if (fadeInterval) clearInterval(fadeInterval);
fadeIn();
};
// let step = 10;
// fadeInterval = setInterval(() => {
// if (step >= 0) {
// element.classList.remove(`show-scrollbar-${step + 1}`);
// element.classList.add(`show-scrollbar-${step}`);
// step--;
// } else {
// clearInterval(fadeInterval!);
// }
// }, 60);
// };
const onMouseLeave = () => {
if (fadeTimeout) clearTimeout(fadeTimeout);
fadeTimeout = setTimeout(() => {
fadeOut();
}, 10);
};
// const onMouseEnter = () => {
// if (fadeTimeout) clearTimeout(fadeTimeout);
// if (fadeInterval) clearInterval(fadeInterval);
// fadeIn();
// };
element.addEventListener('mouseenter', onMouseEnter);
element.addEventListener('mouseleave', onMouseLeave);
// const onMouseLeave = () => {
// if (fadeTimeout) clearTimeout(fadeTimeout);
// fadeTimeout = setTimeout(() => {
// fadeOut();
// }, 10);
// };
// Store cleanup function
const cleanup = () => {
element.removeEventListener('mouseenter', onMouseEnter);
element.removeEventListener('mouseleave', onMouseLeave);
if (fadeTimeout) clearTimeout(fadeTimeout);
if (fadeInterval) clearInterval(fadeInterval);
element.classList.remove('void-scrollable-element');
// Remove any remaining show-scrollbar classes
for (let i = 0; i <= 10; i++) {
element.classList.remove(`show-scrollbar-${i}`);
}
};
// element.addEventListener('mouseenter', onMouseEnter);
// element.addEventListener('mouseleave', onMouseLeave);
// Store the cleanup function on the element for later use
(element as any).__scrollbarCleanup = cleanup;
}
});
};
// // Store cleanup function
// const cleanup = () => {
// element.removeEventListener('mouseenter', onMouseEnter);
// element.removeEventListener('mouseleave', onMouseLeave);
// if (fadeTimeout) clearTimeout(fadeTimeout);
// if (fadeInterval) clearInterval(fadeInterval);
// element.classList.remove('void-scrollable-element');
// // Remove any remaining show-scrollbar classes
// for (let i = 0; i <= 10; i++) {
// element.classList.remove(`show-scrollbar-${i}`);
// }
// };
// Initialize for the first time
initializeScrollbarStyles();
// // Store the cleanup function on the element for later use
// (element as any).__scrollbarCleanup = cleanup;
// }
// });
// };
// Set up mutation observer to do the same
const observer = new MutationObserver(() => {
initializeScrollbarStyles();
});
// // Initialize for the first time
// initializeScrollbarStyles();
// Start observing the container for child changes
observer.observe(containerRef.current, {
childList: true,
subtree: true
});
// // Set up mutation observer to do the same
// const observer = new MutationObserver(() => {
// initializeScrollbarStyles();
// });
return () => {
observer.disconnect();
// Your existing cleanup code...
if (containerRef.current) {
const scrollElements = [
...(containerRef.current.matches(overflowSelector) ? [containerRef.current] : []),
...Array.from(containerRef.current.querySelectorAll(overflowSelector))
];
scrollElements.forEach(element => {
if ((element as any).__scrollbarCleanup) {
(element as any).__scrollbarCleanup();
}
});
}
};
}, [containerRef]);
};
// // Start observing the container for child changes
// observer.observe(containerRef.current, {
// childList: true,
// subtree: true
// });
// return () => {
// observer.disconnect();
// // Your existing cleanup code...
// if (containerRef.current) {
// const scrollElements = [
// ...(containerRef.current.matches(overflowSelector) ? [containerRef.current] : []),
// ...Array.from(containerRef.current.querySelectorAll(overflowSelector))
// ];
// scrollElements.forEach(element => {
// if ((element as any).__scrollbarCleanup) {
// (element as any).__scrollbarCleanup();
// }
// });
// }
// };
// }, [containerRef]);
// };
@@ -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 { 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, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js';
import { VoidCommandBarProps } from '../../../voidCommandBarService.js';
import { AcceptAllButtonWrapper, RejectAllButtonWrapper } from '../sidebar-tsx/SidebarChat.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 { stateOfURI: 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: rejectBg,
// border: rejectBorder,
// color: 'white',
// fontSize: buttonFontSize,
// padding: '2px 4px',
// borderRadius: '6px',
// cursor: 'pointer'
// }}
// >
// Reject File
// </button>
const acceptAllButton = <AcceptAllButtonWrapper
text={'Accept File'}
onClick={onAcceptAll}
/>
const rejectAllButton = <RejectAllButtonWrapper
text={'Reject File'}
onClick={onRejectAll}
/>
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 leading-[1]">
{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 leading-[1]">
{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 pointer-events-auto`}>
{showAcceptRejectAll && acceptRejectAllButtons}
{leftRightUpDownButtons}
</div>
}
@@ -0,0 +1,170 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { useAccessor, useActiveURI, useIsDark, useSettingsState } from '../util/services.js';
import '../styles.css'
import { VOID_CTRL_K_ACTION_ID, VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js';
import { Circle, MoreVertical } from 'lucide-react';
import { useEffect, useState } from 'react';
import { VoidSelectionHelperProps } from '../../../../../../contrib/void/browser/voidSelectionHelperWidget.js';
import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js';
export const VoidSelectionHelperMain = (props: VoidSelectionHelperProps) => {
const isDark = useIsDark()
return <div
className={`@@void-scope ${isDark ? 'dark' : ''}`}
>
<VoidSelectionHelper {...props} />
</div>
}
const VoidSelectionHelper = ({ rerenderKey }: VoidSelectionHelperProps) => {
const accessor = useAccessor()
const keybindingService = accessor.get('IKeybindingService')
const commandService = accessor.get('ICommandService')
const ctrlLKeybind = keybindingService.lookupKeybinding(VOID_CTRL_L_ACTION_ID)
const ctrlKKeybind = keybindingService.lookupKeybinding(VOID_CTRL_K_ACTION_ID)
const dividerHTML = <div className='w-[0.5px] bg-void-border-3'></div>
const [reactRerenderCount, setReactRerenderKey] = useState(rerenderKey)
const [clickState, setClickState] = useState<'init' | 'clickedOption' | 'clickedMore'>('init')
useEffect(() => {
const disposable = commandService.onWillExecuteCommand(e => {
if (e.commandId === VOID_CTRL_L_ACTION_ID || e.commandId === VOID_CTRL_K_ACTION_ID) {
setClickState('clickedOption')
}
});
return () => {
disposable.dispose();
};
}, [commandService, setClickState]);
// rerender when the key changes
if (reactRerenderCount !== rerenderKey) {
setReactRerenderKey(rerenderKey)
setClickState('init')
}
// useEffect(() => {
// }, [rerenderKey, reactRerenderCount, setReactRerenderKey, setClickState])
// if the user selected an option, close
if (clickState === 'clickedOption') {
return null
}
const defaultHTML = <>
{ctrlLKeybind &&
<div
className='
flex items-center px-2 py-1.5
cursor-pointer
'
onClick={() => {
commandService.executeCommand(VOID_CTRL_L_ACTION_ID)
setClickState('clickedOption');
}}
>
<span>Add to Chat</span>
<span className='ml-1 px-1 rounded bg-[var(--vscode-keybindingLabel-background)] text-[var(--vscode-keybindingLabel-foreground)] border border-[var(--vscode-keybindingLabel-border)]'>
{ctrlLKeybind.getLabel()}
</span>
</div>
}
{ctrlLKeybind && ctrlKKeybind &&
dividerHTML
}
{ctrlKKeybind &&
<div
className='
flex items-center px-2 py-1.5
cursor-pointer
'
onClick={() => {
commandService.executeCommand(VOID_CTRL_K_ACTION_ID)
setClickState('clickedOption');
}}
>
<span className='ml-1'>Edit Inline</span>
<span className='ml-1 px-1 rounded bg-[var(--vscode-keybindingLabel-background)] text-[var(--vscode-keybindingLabel-foreground)] border border-[var(--vscode-keybindingLabel-border)]'>
{ctrlKKeybind.getLabel()}
</span>
</div>
}
{dividerHTML}
<div
className='
flex items-center px-0.5
cursor-pointer
'
onClick={() => {
setClickState('clickedMore');
}}
>
<MoreVertical className="w-4" />
</div>
</>
const moreOptionsHTML = <>
<div
className='
flex items-center px-2 py-1.5
cursor-pointer
'
onClick={() => {
commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID);
setClickState('clickedOption');
}}
>
Disable Suggestions?
</div>
{dividerHTML}
<div
className='
flex items-center px-0.5
cursor-pointer
'
onClick={() => {
setClickState('init');
}}
>
<MoreVertical className="w-4" />
</div>
</>
return <div className='
pointer-events-auto select-none
z-[1000]
rounded-sm shadow-md flex flex-nowrap text-nowrap
border border-void-border-3 bg-void-bg-2
transition-all duration-200
'>
{clickState === 'init' ? defaultHTML
: clickState === 'clickedMore' ? moreOptionsHTML
: <></>
}
</div>
}
@@ -0,0 +1,13 @@
/*--------------------------------------------------------------------------------------
* 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'
import { VoidSelectionHelperMain } from './VoidSelectionHelper.js'
export const mountVoidCommandBar = mountFnGenerator(VoidCommandBarMain)
export const mountVoidSelectionHelper = mountFnGenerator(VoidSelectionHelperMain)
@@ -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')
@@ -40,7 +40,7 @@ const ModelSelectBox = ({ options, featureName }: { options: ModelOption[], feat
getOptionDropdownName={(option) => option.selection.modelName}
getOptionDropdownDetail={(option) => option.selection.providerName}
getOptionsEqual={(a, b) => optionsEqual([a], [b])}
className='text-xs text-void-fg-3'
className={className}
matchInputWidth={false}
/>
}
@@ -77,7 +77,7 @@ 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)
@@ -86,7 +86,7 @@ const MemoizedModelDropdown = ({ featureName }: { featureName: FeatureName }) =>
useEffect(() => {
const oldOptions = oldOptionsRef.current
const newOptions = settingsState._modelOptions.filter((o) => filter(o.selection))
const newOptions = settingsState._modelOptions.filter((o) => filter(o.selection, { chatMode: settingsState.globalSettings.chatMode }))
if (!optionsEqual(oldOptions, newOptions)) {
setMemoizedOptions(newOptions)
@@ -95,14 +95,14 @@ const MemoizedModelDropdown = ({ featureName }: { featureName: FeatureName }) =>
}, [settingsState._modelOptions, filter])
if (memoizedOptions.length === 0) { // Pretty sure this will never be reached unless filter is enabled
return <WarningBox text={emptyMessage || 'No models available'} />
return <WarningBox text={emptyMessage?.message || 'No models available'} />
}
return <ModelSelectBox featureName={featureName} options={memoizedOptions} />
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()
@@ -116,12 +116,12 @@ export const ModelDropdown = ({ featureName }: { featureName: FeatureName }) =>
const isDisabled = isFeatureNameDisabled(featureName, settingsState)
if (isDisabled)
return <WarningBox onClick={openSettings} text={
emptyMessage ? emptyMessage :
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} />
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,97 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { Tooltip } from 'react-tooltip';
import 'react-tooltip/dist/react-tooltip.css';
import { useIsDark } from '../util/services.js';
/**
* Creates a configured global tooltip component with consistent styling
* To use:
* 1. Mount a Tooltip with some id eg id='void-tooltip'
* 2. Add data-tooltip-id="void-tooltip" and data-tooltip-content="Your tooltip text" to any element
*/
export const VoidTooltip = () => {
const isDark = useIsDark()
return (
// use native colors so we don't have to worry about @@void-scope styles
// --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);
<>
<style>
{`
#void-tooltip, #void-tooltip-orange, #void-tooltip-green {
font-size: 12px;
padding: 0px 8px;
border-radius: 6px;
z-index: 999;
}
#void-tooltip {
background-color: var(--vscode-editor-background);
color: var(--vscode-input-foreground);
}
#void-tooltip-orange {
background-color: #F6762A;
color: white;
}
#void-tooltip-green {
background-color: #228B22;
color: white;
}
.react-tooltip-arrow {
z-index: -1 !important; /* Keep arrow behind content (somehow this isnt done automatically) */
}
`}
</style>
<Tooltip
id="void-tooltip"
// border='1px solid var(--vscode-editorGroup-border)'
border='1px solid rgba(100,100,100,.2)'
opacity={1}
delayShow={50}
/>
<Tooltip
id="void-tooltip-orange"
border='1px solid rgba(200,200,200,.3)'
opacity={1}
delayShow={50}
/>
<Tooltip
id="void-tooltip-green"
border='1px solid rgba(200,200,200,.3)'
opacity={1}
delayShow={50}
/>
</>
);
};
@@ -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 { VoidTooltip } from './VoidTooltip.js'
export const mountVoidTooltip = mountFnGenerator(VoidTooltip)

Some files were not shown because too many files have changed in this diff Show More