Compare commits

...

632 Commits

Author SHA1 Message Date
Mathew Pareles 41fe5c50e2 fix scrollbars 2025-02-16 19:46:41 -08:00
Mathew Pareles 36b1b56690 bug 2025-02-16 19:46:41 -08:00
Mathew Pareles 249eee341d better context and file reading 2025-02-16 19:46:40 -08:00
Mathew Pareles 198a948f6c star button commit 2025-02-14 21:30:20 -08:00
Mathew Pareles 7dcb08b09a comment out unwanted code 2025-02-12 20:50:04 -08:00
Mathew Pareles efeb85b48b Merge remote-tracking branch 'origin/model-selection' into edit-chats 2025-02-11 18:44:20 -08:00
Mathew Pareles d698e6f3c1 prepare fast apply 2025-02-11 18:42:40 -08:00
Andrew Pareles 2161d740df minor change 2025-02-11 16:44:40 -08:00
Andrew Pareles 96b5b72d30 triple tick 2025-02-10 20:22:31 -08:00
Andrew Pareles e2e8279fce add back imports 2025-02-10 20:18:45 -08:00
Andrew Pareles 5ed1f465a2 MOVE VOID FILES TO WORKBENCH 2025-02-10 18:59:26 -08:00
Andrew Pareles 995357710e misc 2025-02-10 18:33:51 -08:00
Andrew Pareles a98106072c Merge remote-tracking branch 'origin/edit-chats' into model-selection 2025-02-10 15:35:37 -08:00
Andrew Pareles c1be466467 tool tests 2025-02-10 15:34:07 -08:00
Andrew Pareles 9cbe83b57c comment 2025-02-09 00:46:39 -08:00
Andrew Pareles e46b295449 names 2025-02-09 00:45:06 -08:00
Andrew Pareles 3fd3634257 void message 2025-02-09 00:36:03 -08:00
Andrew Pareles 231990088a uri 2025-02-09 00:28:44 -08:00
Andrew Pareles 20da82762e rm comment 2025-02-09 00:27:36 -08:00
Andrew Pareles 0d9be68aff rm comment 2025-02-09 00:27:21 -08:00
Andrew Pareles 165b5a4fd2 + 2025-02-09 00:26:58 -08:00
Andrew Pareles 812014d6d3 all tools seem to work! 2025-02-09 00:26:42 -08:00
Andrew Pareles d3e73b92e0 + 2025-02-08 22:56:55 -08:00
Andrew Pareles 00da05f24f context tools 2025-02-08 22:53:13 -08:00
Mathew Pareles e4694256eb Merge remote-tracking branch 'origin/model-selection' into edit-chats 2025-02-08 21:47:27 -08:00
Andrew Pareles 18b278577d tools 2025-02-08 21:17:38 -08:00
Mathew Pareles a252bb7a30 prepare merge 2025-02-08 17:26:28 -08:00
Mathew Pareles c240360710 edit update locally (need to test) 2025-02-08 17:14:16 -08:00
Mathew Pareles 893308c38e edits work 2025-02-07 02:06:04 -08:00
Mathew Pareles 48defe849d edit backend 2025-02-06 22:34:39 -08:00
Andrew Pareles 3c03f58488 clarify API 2025-02-06 22:16:42 -08:00
Mathew Pareles 2470e0de7f refactor 2025-02-06 19:01:15 -08:00
Mathew Pareles 387fd64db0 edit ability 2025-02-06 03:18:48 -08:00
Andrew Pareles 4ffd269db5 + 2025-02-06 03:11:26 -08:00
Andrew Pareles 5ce25e20c3 css for <html> md tags 2025-02-06 03:10:07 -08:00
Andrew Pareles 63efa219f5 div 2025-02-06 03:04:48 -08:00
Andrew Pareles 826d148305 html 2025-02-06 03:03:14 -08:00
Andrew Pareles 323d80ef55 add divs for html 2025-02-06 03:02:46 -08:00
Andrew Pareles 902588638d Merge remote-tracking branch 'origin/main' into model-selection 2025-02-06 02:04:48 -08:00
Andrew Pareles a1ab094652 + 2025-02-06 02:01:24 -08:00
Andrew Pareles 902b460b9c + 2025-02-06 02:00:33 -08:00
Andrew Pareles 073205ace3 warningbox and misc 2025-02-06 01:59:26 -08:00
Andrew Pareles 98c1d81875 focus() fix 2025-02-06 01:55:36 -08:00
Andrew Pareles 2f49650e15 model and provider error handling 2025-02-06 01:32:48 -08:00
Mathew Pareles 1be9300dc4 typeo 2025-02-05 18:19:04 -08:00
Andrew Pareles d9b77b67b2 prefix 2025-02-05 01:50:29 -08:00
Andrew Pareles 2f674f5323 update openai models 2025-02-05 01:10:40 -08:00
Andrew Pareles c8fa39e552 openai fim progress 2025-02-05 00:57:03 -08:00
Andrew Pareles 3b73e3ab26 metrics for sendFIM 2025-02-05 00:40:09 -08:00
Andrew Pareles c606ac4361 chat name 2025-02-05 00:38:49 -08:00
Andrew Pareles da4c9c8c40 fim better abstraction 2025-02-05 00:37:28 -08:00
Mathew Pareles 31ed11393f fix non-ollama autocomplete 2025-02-04 21:24:49 -08:00
Andrew Pareles 1c5157d97e remove hi333 2025-02-04 20:56:58 -08:00
Andrew Pareles cf241867d8 add recognizedModels code 2025-02-04 20:34:02 -08:00
Andrew Pareles 5a9f9f70d9 separate fastapply model 2025-02-04 20:34:02 -08:00
Andrew Pareles 19ff19fb80 Merge branch 'copy-icons' of https://github.com/voideditor/void into model-selection 2025-02-04 20:34:02 -08:00
Mathew Pareles 87076ed586 remove comments 2025-02-04 20:07:57 -08:00
Mathew Pareles 473250d8bc autocomplete works without context 2025-02-04 20:02:14 -08:00
Mathew Pareles cd983f0dd9 refactor input box with no noticable errors 2025-02-04 01:20:33 -08:00
Mathew Pareles 5c4753555e chatbubble add edit button (need to refactor InputBox to finish) 2025-02-03 21:06:29 -08:00
Mathew Pareles 8f79222b8b fix spacing 2025-02-03 18:45:25 -08:00
Mathew Pareles 145fd9614b changes 2025-02-03 18:27:57 -08:00
Mathew Pareles 41057c20fc autocomplete type context 2025-02-03 18:17:05 -08:00
Mathew Pareles 528b8a6b9b better model onboard 2025-02-03 18:17:05 -08:00
Andrew Pareles e3941d5565 + 2025-02-01 18:41:56 -08:00
Andrew Pareles 4d11a4fd29 Merge pull request #252 from bawsdeep/main
script to generate appimages
2025-01-31 20:52:44 -08:00
Andrew Pareles 3baaf8d6c4 comment 2025-01-31 20:24:09 -08:00
Andrew Pareles 0ec0c324b1 void settings opening behavior 2025-01-31 20:17:51 -08:00
Andrew Pareles aa580351d9 Merge pull request #259 from DoctorGlitchy/main
Update voidSettingsTypes.ts
2025-01-31 01:22:16 -08:00
Adrian Chan 570e13127d Update voidSettingsTypes.ts
Removed "distil-whisper-large-v3-en" because Whisper is not a model you chat with.
Added "llama3-70b-8192" + "mixtral-8x7b-32768"
2025-01-31 13:11:33 +11:00
Andrew Pareles fb7b50b36b Merge pull request #258 from voideditor/model-selection
Latest UI updates
2025-01-30 16:39:13 -08:00
Andrew Pareles 9cd8295dab fix comment 2025-01-30 16:38:51 -08:00
Andrew Pareles 9ec13d2ad8 rm comments 2025-01-30 16:37:44 -08:00
Andrew Pareles d9cf21448c add model selection + misc UI improvements (notably, change how model dropdown works) 2025-01-30 15:07:02 -08:00
Andrew Pareles 27d009314b finish merge 2025-01-29 15:55:18 -08:00
Andrew Pareles 5fdd21f12b Merge remote-tracking branch 'origin/main' into model-selection 2025-01-29 15:54:07 -08:00
Andrew Pareles dc5c7e9a46 Merge remote-tracking branch 'origin/main' into model-selection 2025-01-28 21:02:17 -08:00
Baws Deep d1c5cf518e Add files via upload 2025-01-29 05:41:12 +01:00
Andrew Pareles c1123f2cfd Merge pull request #233 from bjoaquinc/react-build-bug-fix
Fix npm run watchreact initial build bug
2025-01-28 20:04:51 -08:00
Andrew Pareles fb0bb0ca31 Merge remote-tracking branch 'origin/main' into pr/bjoaquinc/233 2025-01-28 20:04:11 -08:00
Andrew Pareles 4863802713 contrib 2025-01-28 20:02:57 -08:00
Andrew Pareles 5589b2e302 updates 2025-01-28 20:02:15 -08:00
Andrew Pareles 327dbb937b Merge pull request #183 from bjoaquinc/react-build-improvements
Improve React build by incorporating the watcher in base build commands
2025-01-28 19:56:05 -08:00
Mathew Pareles a3e62ee6f4 autocompletion multiline works 2025-01-28 01:43:47 -08:00
Andrew Pareles d7a6611d89 read 2025-01-28 01:23:40 -08:00
Mathew Pareles e4bb15ef64 autocomplete draft 2025-01-27 22:20:19 -08:00
Andrew Pareles a98c77572e add gap-4 and remove mt-4 for chat bubbles 2025-01-27 22:18:12 -08:00
Mathew Pareles e4d747d0a6 andrew ollamaFIM progress 2025-01-26 23:49:16 -08:00
Mathew Pareles 01b1358a01 autocomplete refactor 2025-01-26 19:01:03 -08:00
Mathew Pareles c7a6bddfbe groq 2025-01-26 19:01:03 -08:00
Andrew Pareles 55ea8e61c6 Merge pull request #190 from jcommaret/feat-mistral-new
[LLM]Mistral implementation
2025-01-25 19:40:44 -08:00
Andrew Pareles 4f7fd66c2f explanation 2025-01-25 19:39:38 -08:00
Andrew Pareles 4547363dde mistral fix 2025-01-25 19:38:55 -08:00
Andrew Pareles a1dfeb0f00 .tool-versions 2025-01-25 19:36:49 -08:00
Andrew Pareles b036a8c59c Merge remote-tracking branch 'origin/main' into pr/jcommaret/190 2025-01-25 19:35:05 -08:00
Andrew Pareles 72cd23f0ac Merge branch 'main' into pr/jcommaret/190 2025-01-25 19:31:25 -08:00
Andrew Pareles 49c2bcb848 Merge pull request #216 from Piyu-Pika/Gemini-experimental-model
Add new experimental Gemini models to default settings
2025-01-25 19:30:09 -08:00
Andrew Pareles 97ea0f77f9 remove duplicate model name and add storage fix 2025-01-25 19:29:22 -08:00
Andrew Pareles 0cd6e0fb67 ... 2025-01-25 19:01:47 -08:00
Andrew Pareles 7c227e237f key 2025-01-25 19:01:29 -08:00
Andrew Pareles 3089bb31b5 mistral 2025-01-25 18:39:37 -08:00
Jérôme Commaret 018fd016c8 trying to fix error 2025-01-26 02:05:58 +01:00
Mathew Pareles f69f90b4c8 Reset Window: Zoom Level default to 0 (#239)
Co-authored-by: Chandler Thompson <3262190+chandlertee@users.noreplyrgithub.com>
2025-01-25 16:37:00 -08:00
Jérôme Commaret 000747eb88 Merge branch 'main' into feat-mistral-new 2025-01-25 23:58:50 +01:00
Piyush Bhardwaj 74022e0306 Merge branch 'main' into Gemini-experimental-model 2025-01-25 19:42:54 +05:30
Andrew Pareles 21708fd7e0 contrib 2025-01-25 03:14:31 -08:00
Andrew Pareles 5593ff5d79 add key 2025-01-25 03:11:47 -08:00
Andrew Pareles 197067af5a contrib 2025-01-24 15:19:13 -08:00
Andrew Pareles eec4f6c95e email 2025-01-24 15:18:04 -08:00
Andrew Pareles 3e045117d6 contrib 2025-01-24 15:11:57 -08:00
Andrew Pareles 67c557b7b9 contributing 2025-01-24 15:02:16 -08:00
Joaquin Coromina 5ffd569fd8 Fixed npm run watch bug for first time builders by checking if src2 folder already exists, and if not then create it first before setting up the watchers 2025-01-25 00:02:26 +07:00
Jérôme Commaret ff83bb5edc Merge branch 'main' into feat-mistral-new and fix conflict introduced by Deepseek 2025-01-24 14:22:06 +01:00
Andrew Pareles a3f145328b Merge pull request #231 from voideditor/model-selection
DeepSeek as a provider
2025-01-24 03:24:46 -08:00
Mathew Pareles 02894c12de deepseek 2025-01-24 03:18:42 -08:00
Mathew Pareles 52b6c81507 update style of quickedit 2025-01-24 03:18:42 -08:00
Andrew Pareles 5df5f37a6a contributing 2025-01-24 01:12:12 -08:00
Andrew Pareles f247b76a6e Update README.md 2025-01-24 01:06:07 -08:00
Andrew Pareles d1c659be9d + 2025-01-24 01:05:31 -08:00
Andrew Pareles a6b4955f8d + 2025-01-23 21:58:48 -08:00
Andrew Pareles 03b0b98eed + 2025-01-23 21:58:26 -08:00
Andrew Pareles 7f7d30e40b + 2025-01-23 21:55:32 -08:00
Andrew Pareles f68bc52023 update readme 2025-01-23 21:42:55 -08:00
Andrew Pareles 520c971e3e remove jitter on load 2025-01-23 21:41:03 -08:00
Andrew Pareles 657e752987 fix autoupdate 2025-01-23 21:14:10 -08:00
Andrew Pareles 458a5160b0 metrics for updates 2025-01-23 20:58:07 -08:00
Andrew Pareles a90bbffeb9 Merge pull request #229 from voideditor/model-selection
Minor usability updates
2025-01-23 20:37:38 -08:00
Andrew Pareles 99ff885ce2 Merge remote-tracking branch 'origin/main' into model-selection 2025-01-23 20:34:35 -08:00
Andrew Pareles a97047ed43 higher code icon res 2025-01-23 20:34:10 -08:00
Andrew Pareles 00f10c283e update tracking 2025-01-23 20:11:15 -08:00
Andrew Pareles 57628ec244 rename version to vscodeVersion 2025-01-23 20:08:20 -08:00
Andrew Pareles c54dcd0b0c fix delta issue when streaming 2025-01-23 19:47:01 -08:00
Andrew Pareles e957932f39 avoid duplicate model listeners 2025-01-23 15:51:37 -08:00
Piyush Bhardwaj fea9d380cc Merge branch 'main' into Gemini-experimental-model 2025-01-23 22:35:17 +05:30
bjoaquinc f2318cc8a2 Merge branch 'voideditor:main' into react-build-improvements 2025-01-23 22:06:50 +07:00
Mathew Pareles 8264cccd04 change default theme to dark+. also set default zoom to -1 (window.zoomLevel.default) 2025-01-23 03:30:26 -08:00
Andrew Pareles a1ca0f9291 format 2025-01-23 03:25:00 -08:00
Andrew Pareles 58f06d3ebb minor changes 2025-01-23 02:55:29 -08:00
Mathew Pareles e06eda1cc4 wording 2025-01-23 02:28:24 -08:00
Mathew Pareles f4d72e3ee8 remove prompt spaces 2025-01-23 02:26:03 -08:00
Mathew Pareles b3837a3099 changes to prompt 2025-01-23 02:25:01 -08:00
Andrew Pareles c6ae6d5152 await whenReady 2025-01-23 01:36:45 -08:00
Andrew Pareles 2d43dbded1 write version 2025-01-23 00:18:13 -08:00
Andrew Pareles a34d652a1d improve prompts (+ change chat history types) 2025-01-23 00:02:50 -08:00
Andrew Pareles a0569b1ead better FIM codeblock matching 2025-01-23 00:02:50 -08:00
Andrew Pareles b3ebe85c04 rm system message 2025-01-23 00:02:50 -08:00
Mathew Pareles 0919775e77 refactors for autocomplete 2025-01-22 22:19:54 -08:00
Andrew Pareles da6636a948 update deviceId code 2025-01-22 18:13:48 -08:00
Mathew Pareles 6327c584cc remove role=system for compatibility 2025-01-22 17:23:02 -08:00
Andrew Pareles f7cf44136b loading 2025-01-22 16:57:02 -08:00
Andrew Pareles c8635a1bdb memo previous chat messages as we stream (important) 2025-01-22 16:02:34 -08:00
Andrew Pareles d7501112f5 fix error handling 2025-01-22 15:47:33 -08:00
Jérôme Commaret d7ca99e23d Merge branch 'main' into feat-mistral-new 2025-01-23 00:17:23 +01:00
Andrew Pareles 387be4db76 fix gemini 2025-01-22 03:25:27 -08:00
Andrew Pareles 2aec37fc57 numbering 2025-01-22 03:09:18 -08:00
Andrew Pareles 7e5cc27349 tes 2025-01-22 03:08:50 -08:00
Andrew Pareles 151cdb13e6 add roadmap 2025-01-22 02:56:56 -08:00
Andrew Pareles d54ccff4a2 see 2025-01-22 02:30:06 -08:00
Andrew Pareles c2f358bd5e #announcements 2025-01-22 01:18:50 -08:00
Andrew Pareles 198f04d87a minor 2025-01-22 01:04:08 -08:00
Andrew Pareles 326e75eb45 minor 2025-01-22 00:57:57 -08:00
Andrew Pareles 7891a908ff links 2025-01-22 00:55:22 -08:00
Andrew Pareles 6970ec9fd2 contrib 2025-01-22 00:54:01 -08:00
Mathew Pareles fd4f3c3ab6 Merge remote-tracking branch 'origin/main' into model-selection 2025-01-22 00:45:17 -08:00
Andrew Pareles aa2394e207 Update README.md 2025-01-22 00:38:26 -08:00
Andrew Pareles 3aa6d3d28a rm changelog 2025-01-21 23:15:44 -08:00
Andrew Pareles 98c1a7e528 minor change 2025-01-21 23:15:32 -08:00
Andrew Pareles c94dda65f7 Update CONTRIBUTING.md 2025-01-21 19:13:59 -08:00
Mathew Pareles 24e276ba47 ux 2025-01-21 18:55:15 -08:00
Andrew Pareles 028061c879 Update CONTRIBUTING.md 2025-01-21 18:48:27 -08:00
Mathew Pareles 4e0dd8f8db fix spacing 2025-01-21 17:59:16 -08:00
Mathew Pareles c239682ad8 better ux 2025-01-21 14:18:44 -08:00
Piyu-Pika cd9bb6a07d Add new experimental Gemini models to default settings 2025-01-21 21:46:02 +05:30
Jérôme Commaret fd989b2c13 Merge branch 'main' into feat-mistral-new 2025-01-20 16:12:45 +01:00
Andrew 3bfb50e6ee update bmp 2025-01-20 04:26:14 -08:00
Andrew Pareles 362d481ec4 Merge pull request #215 from voideditor/model-selection
Beta!
2025-01-20 03:02:41 -08:00
Andrew Pareles 1b4943f39e devmode 2025-01-20 01:03:53 -08:00
Andrew Pareles 8c443ab780 metrics 2025-01-20 01:01:33 -08:00
Andrew Pareles 7519f05238 update state 2025-01-20 00:51:27 -08:00
Andrew Pareles c2fc2577e0 move 2025-01-20 00:31:14 -08:00
Andrew Pareles 6e1f8a08b3 void manual updates 2025-01-20 00:21:38 -08:00
Andrew Pareles d9be67d17d updates 2025-01-19 21:36:48 -08:00
Andrew Pareles 60d22674e2 updates 2025-01-19 19:24:44 -08:00
Andrew Pareles e8c0ee631c fix 2025-01-19 17:36:54 -08:00
Andrew Pareles 6ba9d10a4a update 2025-01-19 17:23:23 -08:00
Andrew Pareles 4eff8f8e76 building files 2025-01-19 17:23:00 -08:00
Andrew Pareles 66e349c72e fix updates part 1 2025-01-19 06:21:09 -08:00
Andrew 146d63c41e Merge remote-tracking branch 'origin/main' into model-selection 2025-01-19 03:04:15 -08:00
Andrew 82439406a0 icon updates 2025-01-19 03:03:45 -08:00
Andrew Pareles efeb0d28b7 tab switch fix 2025-01-19 02:59:22 -08:00
Andrew Pareles aba7d7bf90 Merge pull request #214 from voideditor/model-selection
Latest UX/UI updates
2025-01-17 21:13:16 -08:00
Andrew Pareles f517dc79a9 rm console 2025-01-17 19:10:13 -08:00
Andrew Pareles dc33d4d0c0 fix offset collision with deletion 2025-01-17 19:09:00 -08:00
Andrew Pareles d3647bf93c red lines above green 2025-01-17 19:02:46 -08:00
Mathew Pareles 29c4603909 fix spacing 2025-01-17 18:04:48 -08:00
Andrew Pareles 85426e28bd + 2025-01-17 17:59:11 -08:00
Andrew Pareles c6ff563e1b style 2025-01-17 17:54:09 -08:00
Andrew Pareles 7ec574eeee rm log 2025-01-17 17:45:44 -08:00
Andrew Pareles 66c880945c ctrlk settings handling 2025-01-17 17:43:21 -08:00
Andrew Pareles 6f1905a962 fix streaming issue, cleanup chat error styles 2025-01-17 17:32:49 -08:00
Mathew Pareles 268d2ae99a ux 2025-01-17 17:21:04 -08:00
Mathew Pareles 1521038e03 fixes 2025-01-17 16:56:48 -08:00
Andrew Pareles dce0378eb2 fix cancel streaming 2025-01-17 16:39:26 -08:00
Mathew Pareles ce7ee62abe remove old files 2025-01-17 15:49:36 -08:00
Andrew Pareles 015943329f misc 2025-01-16 23:58:57 -08:00
Mathew Pareles 4b15885147 summary styles 2025-01-16 21:38:06 -08:00
Mathew Pareles 2e3f608805 better history handling 2025-01-16 20:17:49 -08:00
Mathew Pareles 789016709a multiple prospective 2025-01-16 20:02:22 -08:00
Mathew Pareles 835a2bdf92 spacing 2025-01-16 17:10:32 -08:00
Mathew Pareles c02f1de758 fix spacing 2025-01-16 17:09:38 -08:00
Mathew Pareles ddd9cf857f ux 2025-01-16 03:31:17 -08:00
Mathew Pareles e4fd6d05a4 prospective file adding 2025-01-16 02:20:33 -08:00
Andrew Pareles 9756ab6d16 filter out empty thread 2025-01-15 23:52:32 -08:00
Andrew Pareles 433884b36c do not ignore version mismatch 2025-01-15 23:42:25 -08:00
Andrew Pareles 0d48db1a0c fix metrics 2025-01-15 23:17:05 -08:00
Andrew Pareles 10adc13b60 add manual device id 2025-01-15 22:57:17 -08:00
Andrew Pareles 2103a95930 VOID ICON - remove code-icon.svg 2025-01-15 22:42:46 -08:00
Andrew Pareles f8c454544c add logging of debug metrics 2025-01-15 20:53:41 -08:00
Andrew Pareles e3da020c0a bigger weight 2025-01-15 19:27:32 -08:00
Andrew Pareles 7dec57ae04 add high priority to void keybinds 2025-01-15 19:24:08 -08:00
Andrew Pareles f8db386c3b Update CONTRIBUTING.md 2025-01-15 18:45:24 -08:00
Andrew Pareles 2d583b87dc Merge pull request #212 from voideditor/model-selection
Latest updates
2025-01-15 18:38:51 -08:00
Andrew Pareles b9dba7d08b loading icon fix 2025-01-15 18:26:47 -08:00
Andrew Pareles 4e18dd4ac3 rename 2025-01-15 18:23:35 -08:00
Andrew Pareles 471333ec16 can have many streams at once! 2025-01-15 18:23:35 -08:00
Andrew Pareles d43453e5ed ui 2025-01-15 18:23:35 -08:00
Mathew Pareles 3abafa76ec fix cmd+k tabbing 2025-01-15 18:14:12 -08:00
Andrew Pareles e48e798afc bigger bar 2025-01-15 06:38:47 -08:00
Mathew Pareles 4619e4146a + 2025-01-15 06:07:54 -08:00
Mathew Pareles f522281c6d comment 2025-01-15 06:03:41 -08:00
Mathew Pareles 1e3668058b change aux bar min width 2025-01-15 06:03:20 -08:00
Mathew Pareles 88c921b059 minor 2025-01-15 06:00:00 -08:00
Mathew Pareles 8cfe494632 pair programming fix model refresh state 2025-01-15 05:59:38 -08:00
Andrew Pareles d4fc59034a add ai instructions, fix prompts 2025-01-15 05:25:46 -08:00
Mathew Pareles 9ba70cdc4a minor 2025-01-15 03:07:53 -08:00
Mathew Pareles 12d08eb709 maxheight increase 2025-01-15 02:47:00 -08:00
Mathew Pareles f3270a2219 add editor maxheight back 2025-01-15 02:45:27 -08:00
Mathew Pareles bd30f14980 add scrollbar styles to quickedit 2025-01-15 02:22:12 -08:00
Andrew Pareles 725de600cf schema !== 'file' for peek, and minor accept all state fix 2025-01-15 02:18:20 -08:00
Andrew Pareles ccfd54fcfe fix 2025-01-15 01:51:49 -08:00
Andrew Pareles 071c4893c2 add state for accept|reject all 2025-01-15 01:42:17 -08:00
Mathew Pareles 8f15c67960 acceptall rejectall ui 2025-01-15 01:27:27 -08:00
Mathew Pareles fd546ff7b7 starting point for accept|reject all 2025-01-15 00:04:24 -08:00
Andrew Pareles 895628bb91 comment 2025-01-14 23:15:54 -08:00
Andrew Pareles 4898875532 fix 2025-01-14 23:13:28 -08:00
Mathew Pareles 9ddc22b63c fix package.json 2025-01-14 19:58:36 -08:00
Andrew Pareles 44438fe1fe re-remove streaming state for nwo 2025-01-14 19:34:09 -08:00
Andrew Pareles 64e43fa53e fix ctrlK state 2025-01-14 19:14:32 -08:00
Mathew Pareles 07f4a59ce6 diff on redf 2025-01-14 18:36:23 -08:00
Andrew Pareles c7d5d5d183 fix streaming alignment 2025-01-14 16:57:08 -08:00
Andrew Pareles 9f96ebcfc2 fix lag by changing rendering process in red zones 2025-01-14 15:44:39 -08:00
Mathew Pareles 01839654d4 acceptall rejectall widget draft 2025-01-13 22:10:47 -08:00
Andrew Pareles 37200dacbd add metrics + streaming works pretty well! 2025-01-13 21:48:14 -08:00
Mathew Pareles 86612fd5ed accept/reject styles 2025-01-13 19:08:21 -08:00
Mathew Pareles 937816519c more spacing 2025-01-13 17:47:17 -08:00
Andrew Pareles 35e366a5ba rm testDiffs 2025-01-13 17:30:18 -08:00
Andrew Pareles d2fdbd7ffd date 2025-01-13 17:16:58 -08:00
Andrew Pareles 33a003fc2d get ready to release 2025-01-13 17:16:27 -08:00
Andrew Pareles 9589134830 modify changelog 2025-01-13 17:15:44 -08:00
Mathew Pareles 164684a39b input color 2025-01-13 17:00:49 -08:00
Andrew Pareles accff821f8 style fixes 2025-01-13 16:54:26 -08:00
Mathew Pareles 00fc9964e5 ts by default 2025-01-13 16:45:15 -08:00
Jérôme Commaret 4b8afc1771 Merge branch 'voideditor:main' into feat-mistral-new 2025-01-14 01:38:11 +01:00
Mathew Pareles 3e5ead2547 dropdown 2025-01-13 16:31:03 -08:00
Jérôme Commaret d067966b58 Merge branch 'model-selection' into feat-mistral-new 2025-01-13 23:31:19 +01:00
Jérôme Commaret 278c52bd35 Merging everything here 2025-01-13 22:48:43 +01:00
Andrew Pareles 52d89e5815 heursitc check 2025-01-13 04:23:34 -08:00
Andrew Pareles 4cf13b67b0 comment 2025-01-13 04:16:54 -08:00
Andrew Pareles fc327e3fcb colors 2025-01-13 04:16:16 -08:00
Andrew Pareles 59afe2d6ff fix with requestanimationframe 2025-01-13 04:14:43 -08:00
Andrew Pareles 1d590510ae mostly works!! tracethroughs would be insanely helpful... 2025-01-13 03:55:20 -08:00
Andrew Pareles c5a6f476ef cleanup 2025-01-13 03:55:20 -08:00
Andrew Pareles ae4ece1826 bug fixes and migrate to custom VoidInputBox2 2025-01-13 03:55:20 -08:00
Mathew Pareles ec2f04be91 merge temporary fix 2025-01-13 03:33:06 -08:00
Mathew Pareles 8a56ea9755 merge bug 2025-01-13 03:32:21 -08:00
Mathew Pareles 261736bde5 prepare edit button 2025-01-13 03:23:38 -08:00
Mathew Pareles 7dd5171bd2 empty threads 2025-01-12 19:31:37 -08:00
Mathew Pareles becd109776 empty tyhreaeds 2025-01-12 19:29:22 -08:00
Mathew Pareles 0d9f441967 fix spacing settings 2025-01-12 19:22:29 -08:00
Andrew Pareles 6386fc95ee misc 2025-01-12 19:10:33 -08:00
Andrew Pareles 06688228ae notif details 2025-01-12 19:03:10 -08:00
Mathew Pareles b3d2453e7f history style 2025-01-12 19:02:28 -08:00
Andrew Pareles 29e1f312c3 scroll to top on thread switch 2025-01-12 18:06:52 -08:00
Andrew Pareles b9b645522a improve changelog 2025-01-12 17:53:00 -08:00
Mathew Pareles 83bf660b76 md styles 2025-01-12 17:52:00 -08:00
Andrew Pareles 268f08c51d changelog 2025-01-12 17:30:35 -08:00
Andrew Pareles 046942be75 remove telemetry 2025-01-12 17:29:12 -08:00
Andrew Pareles 6fed97eb83 + 2025-01-12 15:42:21 -08:00
Mathew Pareles e888f261fe accept/reject on right 2025-01-12 03:58:44 -08:00
Andrew Pareles 6e8498414c add ability to test diffs 2025-01-12 03:49:35 -08:00
Andrew Pareles bcda1dbe2b don't let ctrlK overlap 2025-01-12 03:23:56 -08:00
Mathew Pareles be3128acb4 ctrlk styles 2025-01-12 03:14:59 -08:00
Andrew Pareles 29cbb15911 fix async race condition 2025-01-12 03:01:14 -08:00
Andrew Pareles 8da71dc1b7 jitter changes 2025-01-12 02:07:32 -08:00
Andrew Pareles a69b99b562 file tab switches only 2025-01-12 01:58:50 -08:00
Mathew Pareles 6868a8b1d8 fix menu ctrl+k 2025-01-12 01:23:49 -08:00
Mathew Pareles 10f8fcc17d language detection better 2025-01-11 22:37:31 -08:00
Mathew Pareles ee9f84a475 fix delete button 2025-01-11 19:12:38 -08:00
Mathew Pareles c056214684 prompt adjust 2025-01-11 18:12:21 -08:00
Mathew Pareles fcf03f2710 Merge branch 'model-selection' of https://github.com/voideditor/void into model-selection 2025-01-11 17:22:53 -08:00
Mathew Pareles ed50ed5319 get rid of delete 2025-01-11 17:18:33 -08:00
Andrew Pareles f15f5ce8ed clear button - styles 2025-01-11 17:15:42 -08:00
Mathew Pareles 11578d6288 clear all button 2025-01-11 16:48:59 -08:00
Mathew Pareles d904dbb93e clearall style 2025-01-11 16:25:44 -08:00
Andrew Pareles 6df0c0ac69 add tab switch listeners that account for visibility 2025-01-11 16:22:53 -08:00
Andrew Pareles 0f98af7fc1 ignore not compatible 2025-01-11 15:50:55 -08:00
Mathew Pareles e1b8e49b13 update metrics 2025-01-11 00:51:55 -08:00
Andrew Pareles e6f6c7500d update api comment 2025-01-10 23:57:44 -08:00
Mathew Pareles 951da8e31a merge 2025-01-10 19:02:53 -08:00
Jérôme Commaret 2701226ef3 Translate abort comments 2025-01-10 23:57:40 +01:00
Jérôme Commaret 9abc3ad3dc _aborter set 2025-01-10 23:46:58 +01:00
Andrew Pareles 966470cf68 fix icon for clear 2025-01-09 23:24:50 -08:00
Andrew Pareles b2cf709685 add clear button and increate prefix/suffix FIM length 2025-01-09 23:16:00 -08:00
Andrew Pareles ab0bf4bef0 ring inset 2025-01-09 22:45:42 -08:00
Andrew Pareles db20584a19 separate open sidebar from select for sidebar 2025-01-09 22:29:30 -08:00
Andrew Pareles 7d1d638dae add updater link 2025-01-09 22:22:34 -08:00
Andrew Pareles 0d1c4944c1 only open 1 editor 2025-01-09 21:22:44 -08:00
Andrew Pareles f7c4d28d55 toggle vs open settings 2025-01-09 21:20:09 -08:00
Mathew Pareles a2ae7a9674 Update README.md 2025-01-09 21:08:31 -08:00
Mathew Pareles 262e15764b Update README.md 2025-01-09 21:06:25 -08:00
Mathew Pareles 26f97f5939 Update README.md 2025-01-09 20:48:13 -08:00
Mathew Pareles a9dc35e5db Update README.md 2025-01-09 19:37:02 -08:00
Andrew Pareles 326dcae24f Merge remote-tracking branch 'origin/main' into model-selection 2025-01-09 18:59:45 -08:00
Andrew Pareles f880ddd6dc Merge pull request #211 from voideditor/lic
License
2025-01-09 18:53:42 -08:00
Andrew Pareles 7925e438c6 header update 2025-01-09 18:51:40 -08:00
Andrew Pareles 2f5e954ebf update 2025-01-09 18:49:05 -08:00
Andrew Pareles 6a8ac1a68e apache 2.0 2025-01-09 18:48:52 -08:00
Andrew Pareles c5b6fd9b75 Create temp 2025-01-09 18:48:15 -08:00
Andrew Pareles 7e68a485f2 misc 2025-01-09 18:15:51 -08:00
Jérôme Commaret af85949561 Merge branch 'main' into feat-mistral-new 2025-01-09 15:48:57 +01:00
Mathew Pareles a1da8bd12f settings improve 2025-01-09 03:59:41 -08:00
Andrew Pareles ebd78a902d misc 2025-01-09 03:58:18 -08:00
Andrew Pareles f96c03c846 fix scroll ctrlK 2025-01-09 03:55:16 -08:00
Andrew Pareles fed865e753 copy 2025-01-09 03:40:44 -08:00
Andrew Pareles 8d2917d331 more 2025-01-09 03:38:07 -08:00
Andrew Pareles f4c1fc8917 settings style updates 2025-01-09 03:38:07 -08:00
Mathew Pareles 0a3f31acae scrolbar 2025-01-09 03:27:39 -08:00
Mathew Pareles 924c953697 setttings minwidth 2025-01-09 03:24:30 -08:00
Andrew Pareles 446dede5e4 refreshes ollama only 2025-01-09 02:52:18 -08:00
Andrew Pareles 7bb0993249 transfer button feedback 2025-01-09 02:48:48 -08:00
Andrew Pareles 4968452003 proper warning 2025-01-09 02:22:54 -08:00
Andrew Pareles 037f9f38e5 one click switch 2025-01-09 02:17:29 -08:00
Mathew Pareles e132c87792 generalize for @andrew 2025-01-09 02:15:52 -08:00
Mathew Pareles 07d87c8bf0 styles 2025-01-09 01:56:36 -08:00
Mathew Pareles c7ad7d3761 fix dropdown 2025-01-09 01:53:05 -08:00
Mathew Pareles ff9d21eee0 fix dropdown 2025-01-09 01:50:11 -08:00
Mathew Pareles e4953057ef color improvements 2025-01-09 01:50:11 -08:00
Mathew Pareles 865c34f282 new model dropdown draft 2025-01-09 01:50:11 -08:00
Andrew Pareles ec65903692 misc 2025-01-08 21:07:54 -08:00
Andrew Pareles a08e6078f2 reject diffarea and cleanup 2025-01-08 19:32:44 -08:00
Andrew Pareles 20e1b287f8 scrollbar settings 2025-01-08 19:32:44 -08:00
Mathew Pareles f2f9b6ccca context fits in inputbox 2025-01-08 17:38:54 -08:00
Andrew Pareles c4e4701dc3 placeholder 2025-01-08 17:12:39 -08:00
Andrew Pareles 37af31f958 minor comment 2025-01-08 16:30:38 -08:00
Mathew Pareles b5dff4cf0f selection bug fix 2025-01-08 03:04:19 -08:00
Mathew Pareles 3621bb63f4 wrap 2025-01-08 02:58:27 -08:00
Mathew Pareles da247c6ed4 color 2025-01-08 01:46:54 -08:00
Mathew Pareles ce634eadaa better styles 2025-01-08 00:14:57 -08:00
Mathew Pareles 910ed7faa9 fix scrollbar bug 2025-01-07 22:48:32 -08:00
Andrew Pareles 9e852355d9 add important tags to build files 2025-01-07 22:35:34 -08:00
Mathew Pareles 8ae4700bb2 fix to annoying out of order import problem 2025-01-07 22:28:14 -08:00
Mathew Pareles f13c30ae0d refactor blockcode 2025-01-07 21:02:59 -08:00
Mathew Pareles af244938b8 refactor blockcode 2025-01-07 20:39:24 -08:00
Andrew Pareles a53f92432b keybinding for ctrl+L and ctrl+K shows accurately even if user changes it 2025-01-07 19:45:41 -08:00
Mathew Pareles ca2b18d9da scrollbar styles recursive 2025-01-07 00:33:27 -08:00
Mathew Pareles 5293095eb8 custom scrollbar 2025-01-06 23:11:52 -08:00
Andrew Pareles 4e392bee8e start adding icons for copying files and folders in the explorer 2025-01-06 22:25:41 -08:00
Mathew Pareles 86346f1bf0 scrollbars 2025-01-06 19:15:41 -08:00
Mathew Pareles a0672f845d selection ui 2025-01-06 18:38:53 -08:00
Andrew Pareles f96d320ce0 fix tricky reject bug 2025-01-06 02:02:48 -08:00
Andrew Pareles bb7e69b20b small fix for realigning 2025-01-06 01:43:54 -08:00
Andrew Pareles f19e579ce9 extract code works! ignore </MID> etc 2025-01-06 01:19:27 -08:00
Andrew Pareles dd133d2cd5 ctrlK roundRange 2025-01-05 21:27:16 -08:00
Andrew Pareles 2ba04a78a8 add X to ctrlK 2025-01-05 21:02:31 -08:00
Andrew Pareles fb948b331f ctrlK 2025-01-05 18:03:36 -08:00
Raz Luvaton 5083b8e971 fix react build.js to properly find the base folder (#203) 2025-01-05 18:00:40 -08:00
Mathew Pareles 35091eb8f2 change wording 2025-01-05 17:11:10 -08:00
Mathew Pareles b9e61bf4e2 styles 2025-01-05 17:10:20 -08:00
Andrew Pareles 55ec3365a2 empty seln 2025-01-05 17:04:07 -08:00
Mathew Pareles d6811e872a fix color 2025-01-05 16:57:39 -08:00
Mathew Pareles c176b8020f styles 2025-01-05 16:35:43 -08:00
Andrew Pareles 85c2c80db0 Update README.md 2025-01-04 23:55:52 -08:00
Andrew Pareles b677d3dfc1 Update CONTRIBUTING.md 2025-01-03 17:18:18 -08:00
Andrew Pareles 6dcf684429 updateService changes 2025-01-03 17:11:18 -08:00
Mathew Pareles 26ddd6418c extract <MID> result 2025-01-03 16:54:04 -08:00
Andrew Pareles cc177b9cd4 update package 2025-01-03 14:14:37 -08:00
Andrew Pareles dd204978e5 update lock 2025-01-03 13:31:54 -08:00
Andrew Pareles fb48c4e67e Update VOID_USEFUL_LINKS.md 2025-01-03 13:15:55 -08:00
Andrew Pareles 74fa5cb505 Merge pull request #202 from PraveenAsh/patch-1
Update CONTRIBUTING.md
2025-01-03 12:44:33 -08:00
Andrew Pareles fae03c3060 Update CONTRIBUTING.md 2025-01-03 12:44:12 -08:00
Jérôme Commaret 5453acdb19 Mistral : update 2025-01-03 20:21:46 +01:00
Jérôme Commaret 7047459e57 Merge branch 'main' into feat-mistral-new 2025-01-03 19:57:34 +01:00
Praveen Naik 68722ee5c3 Update CONTRIBUTING.md
Added details about Bundling output. Most of us open only the repo without workspace to save memory. It's easy to miss out on where the bundle is located. Added clarity
2025-01-03 21:04:54 +05:30
Mathew Pareles cffe558cf5 code editor bg 2025-01-03 01:35:36 -08:00
Mathew Pareles 9bc551f0ac code editor bg 2025-01-03 01:35:36 -08:00
Mathew Pareles 01d7878671 code editor bg 2025-01-03 01:31:47 -08:00
Mathew Pareles 2519f094cc input and dropdown styles 2025-01-03 00:00:03 -08:00
Andrew Pareles 4a993a716b Update README.md 2025-01-02 23:34:37 -08:00
Andrew Pareles 23d1d4169a update CONTRIBUTING.md 2025-01-02 23:20:36 -08:00
Andrew Pareles f688e1ffae Update LICENSE-VS-Code.txt 2025-01-02 23:06:06 -08:00
Andrew Pareles a443315c14 Merge branch 'main' of https://github.com/voideditor/void 2025-01-02 23:02:05 -08:00
Andrew Pareles eeacb2d3c9 separate licenses + header 2025-01-02 23:00:59 -08:00
Andrew Pareles cdbdf3c5c7 License update (#199) 2025-01-02 22:58:05 -08:00
Andrew Pareles 825c40bfff license 2025-01-02 22:40:43 -08:00
Andrew Pareles a72305819a copy 2025-01-02 19:15:08 -08:00
Andrew Pareles a3d3309b9d Merge pull request #197 from voideditor/model-selection
Fix line indexing for CtrlK
2025-01-02 18:59:08 -08:00
Andrew Pareles 32bfe9a111 fix line indexing when streaming 2025-01-02 18:58:44 -08:00
Andrew Pareles a9adcad560 misc 2025-01-02 17:57:38 -08:00
Mathew Pareles 80f7511eee default settings instructions 2025-01-02 17:34:31 -08:00
Andrew Pareles 7fe8f5c47d ctrlK line numbers are right 2025-01-02 17:26:28 -08:00
Mathew Pareles b31882565b fix watchreact 2025-01-02 17:11:32 -08:00
Andrew Pareles 6aaf425419 Settings update + ctrlK stream runs (but is broken) 2025-01-02 17:10:38 -08:00
Andrew Pareles b0d21dfdb1 Merge pull request #196 from voideditor/model-selection
misc necessary updates
2025-01-02 16:41:33 -08:00
Andrew Pareles 2087948789 misc necessary updates 2025-01-02 16:40:55 -08:00
Andrew Pareles 6feb713682 Merge pull request #192 from jcommaret/main
Fix - bundle - ajv missing deps
2025-01-02 16:31:45 -08:00
Andrew Pareles 667dc8bd7e merge 2025-01-02 16:31:29 -08:00
Andrew Pareles bdb0c65346 Merge pull request #195 from voideditor/model-selection
Merge model selection branch (ctrlK, model selection, UI)
2025-01-02 16:29:10 -08:00
Andrew Pareles df14b80d8b not sure where this came from 2025-01-02 16:28:33 -08:00
Andrew Pareles 86decf8ed9 add (c) headers 2025-01-02 16:27:06 -08:00
Andrew Pareles bd9b9a9058 minor updates 2025-01-02 16:20:19 -08:00
Mathew Pareles 83c1f3430a fix assistant chat 2025-01-02 15:51:00 -08:00
Andrew Pareles 02de8937ac update copy 2025-01-02 01:28:19 -08:00
Mathew Pareles 78f3819c33 move extractCodeFromResult 2025-01-01 23:19:16 -08:00
Mathew Pareles b9cea4906e fix chat bubble direction 2025-01-01 23:08:32 -08:00
Mathew Pareles 4a4677b5e2 center button; fix single line button 2025-01-01 22:35:48 -08:00
Andrew Pareles 5dab1a6a77 update product.json 2025-01-01 22:05:57 -08:00
Mathew Pareles 9b01d5ba7c minor changes 2025-01-01 21:28:44 -08:00
Mathew Pareles dcb708758d fix input resize 2025-01-01 21:28:44 -08:00
Andrew Pareles dd4548466a minor chagnes 2025-01-01 20:01:10 -08:00
Andrew Pareles cda489e3e0 small fixes 2025-01-01 05:04:51 -08:00
Andrew Pareles 13594ef06c rework copy 2025-01-01 04:57:10 -08:00
Andrew Pareles c2b8df06eb desc 2025-01-01 04:47:41 -08:00
Andrew Pareles 33809ec48b finish adding css 2025-01-01 04:44:43 -08:00
Andrew Pareles b4820abc9e css 2025-01-01 04:31:44 -08:00
Andrew Pareles 0e79dec0d7 VOID ICONS 2024-12-31 22:03:38 -08:00
Andrew Pareles 659f01413f misc + track down icons to change 2024-12-31 20:27:31 -08:00
Andrew Pareles 110534df30 memo model dropdown + add ability to remove ctrlK zone 2024-12-31 16:18:18 -08:00
Andrew Pareles d3b61867f0 ctrlK now switches tabs + UI + remove async 2024-12-31 15:41:24 -08:00
Andrew Pareles 954e611fda Merge branch 'new-services' into model-selection 2024-12-31 13:56:07 -08:00
Andrew Pareles 64760aae13 comment 2024-12-31 13:54:08 -08:00
Andrew Pareles d0d1124543 delete old files 2024-12-31 13:47:41 -08:00
Andrew Pareles 90a3fd0a21 merge 2024-12-31 13:45:48 -08:00
Andrew Pareles 11ed2545c7 Merge remote-tracking branch 'origin/main' into new-services 2024-12-31 13:40:20 -08:00
Andrew Pareles 3d1df92f6a re-add deletion logic 2024-12-31 13:39:31 -08:00
Andrew Pareles 4ca16caa4e fix misc history issues with ctrlk - looks good!!! 2024-12-31 02:24:34 -08:00
Andrew Pareles 06b5cca59a history snapshot is only for this URI 2024-12-30 23:29:05 -08:00
Andrew Pareles e3c292eee7 progress towards ctrlK 2024-12-30 22:14:55 -08:00
Andrew Pareles 99c3786d06 diffarea resize logic 2024-12-30 19:37:52 -08:00
Andrew Pareles 6e40a92315 add FIM streaming + instructions 2024-12-30 15:20:55 -08:00
Andrew Pareles a9b2ff631d ctrlK progress... 2024-12-30 03:04:32 -08:00
Mathew Pareles 198adaaf18 selection ui 2024-12-30 04:57:43 -05:00
Mathew Pareles 2df7fa9ebc better model selection 2024-12-30 04:36:31 -05:00
Mathew Pareles 312942b004 ollama instructions 2024-12-30 03:39:17 -05:00
Mathew Pareles 0a444f6ff8 mark if model is detected locally 2024-12-30 01:45:02 -05:00
Mathew Pareles 0c10cb73f6 fix polling enable bug 2024-12-29 23:45:46 -05:00
Andrew Pareles ef8bba00b9 update ctrlk 2024-12-29 19:16:16 -08:00
Mathew Pareles 3a3bc24fef better polling state 2024-12-29 05:07:52 -05:00
Andrew Pareles ff7830e704 resize observer mostly works 2024-12-28 22:30:46 -05:00
Andrew Pareles 01a345a391 ctrlk double redundancy fix 2024-12-28 21:28:20 -05:00
Andrew Pareles 33909b3143 add ctrl+K 2024-12-28 20:03:47 -05:00
Jérôme Commaret 9543bf8eb3 Fix : Ajv missing deps at bundle 2024-12-28 22:19:16 +01:00
Andrew Pareles 9a9d0da91e remove errors 2024-12-27 23:56:24 -05:00
Jérôme Commaret 4065c5b504 Mistral implementation : Working 2024-12-27 16:37:27 +01:00
Jérôme Commaret 4729e4dac3 Mistral v2 2024-12-27 14:46:02 +01:00
Jérôme Commaret d85a316536 Merge branch 'voideditor:main' into main 2024-12-27 13:59:49 +01:00
Andrew Pareles 1df044fb02 Update product.json 2024-12-25 22:23:01 -05:00
Mathew Pareles 076e0add44 prepare large merge 2024-12-25 19:21:16 -08:00
Andrew Pareles dfbd729fa4 nightly progress 2024-12-24 23:45:58 -05:00
Mathew Pareles 8a17c9246b language logic 2024-12-24 17:34:18 -08:00
mp 1afb39b155 minor changes 2024-12-24 02:22:45 -08:00
mp f08fd04af5 autodetect language 2024-12-23 16:21:37 -08:00
Andrew Pareles ef9d0daef4 minor chat styles 2024-12-23 15:37:06 -08:00
Andrew Pareles ffe5a41b01 fix rounding 2024-12-23 04:33:49 -08:00
Andrew Pareles 372beb7a2d rm temp 2024-12-23 03:30:05 -08:00
Andrew Pareles 406f29bb5a + 2024-12-23 03:29:49 -08:00
Andrew Pareles ee8b98ff74 minor changes 2024-12-23 03:00:19 -08:00
Andrew Pareles ffb0549c7c update zoneStyleService 2024-12-23 03:00:19 -08:00
Andrew Pareles 8f9c2f0630 misc 2024-12-23 03:00:19 -08:00
Andrew Pareles 84bfc26dce debugging 2024-12-23 03:00:10 -08:00
Andrew Pareles 80a87ff3c6 improvements but might still be buggy 2024-12-23 02:43:19 -08:00
mp 806ec939e5 ux for providers 2024-12-23 02:34:23 -08:00
Andrew Pareles 98a22d2eb6 minor changes 2024-12-23 00:16:37 -08:00
Andrew Pareles ffa31bc289 update zoneStyleService 2024-12-23 00:14:54 -08:00
Andrew Pareles a1ab8a5796 misc 2024-12-22 23:52:21 -08:00
mp 70c7d0261c correct bg 2024-12-22 21:41:37 -08:00
mp e7af8d9448 selection style 2024-12-22 21:28:09 -08:00
mp e1feaba74a warning icon 2024-12-22 02:12:24 -08:00
mp 8456f979ba warning icon 2024-12-22 02:07:29 -08:00
Andrew Pareles 6630d596f6 code box UI 2024-12-21 22:43:34 -08:00
Andrew Pareles a435a59a2e inlineDiffEdits add support for ctrl+K 2024-12-21 22:40:08 -08:00
mp 9cf4980004 selection 2024-12-21 22:37:08 -08:00
Mathew Pareles 6298be394d indentation 2024-12-21 21:13:21 -08:00
Andrew Pareles e647ce0054 fix + mount sidebar on init 2024-12-21 18:46:45 -08:00
Andrew Pareles 5fb30fed87 sidebar action 2024-12-21 18:29:15 -08:00
mp 7cc57bf086 input styles 2024-12-21 18:19:19 -08:00
Andrew Pareles 3fb6a4890d watermark 2024-12-21 17:51:15 -08:00
mp 97fd332c4e add code editor block 2024-12-21 17:29:02 -08:00
Andrew Pareles 81282e16b3 minor contrib update 2024-12-21 00:15:14 -08:00
Andrew Pareles 382275fa9f delete built-in extensions so the build works 2024-12-21 00:00:57 -08:00
Andrew Pareles 91f8321278 delete autoenable 2024-12-20 02:08:30 -08:00
Andrew Pareles 9b1f21d7c1 Merge pull request #185 from voideditor/model-selection
Daily UI improvements
2024-12-20 02:07:49 -08:00
Andrew Pareles 739ac2562b protected fix 2024-12-19 22:44:29 -08:00
Andrew Pareles 298423928e update styles and descs 2024-12-19 18:44:26 -08:00
mp 702d11ed3e Merge branch 'polishing-changes' into model-selection 2024-12-19 17:09:01 -08:00
Andrew Pareles 4513b732d6 all changes 2024-12-19 17:07:58 -08:00
Andrew Pareles 78e72f0c80 300 2024-12-19 17:00:16 -08:00
Andrew Pareles f457f006fb settings page styles 2024-12-19 04:05:31 -08:00
mp d6074e7ab5 ctrlk draft 2024-12-19 02:14:26 -08:00
Andrew Pareles 3669401819 upgrade to switches 2024-12-19 01:28:43 -08:00
Joaquin Coromina 1e975ecc3b Merge branch 'react-build-watcher' into react-build-improvements
Adding react build improvements to the proper branch#
2024-12-19 15:22:07 +08:00
Joaquin Coromina 8476a99903 Added watchreactd to tasks.json so that when using build hotkey (cmd/ctrl + shift + B) it includes watchreactd automatically 2024-12-19 15:10:42 +08:00
Joaquin Coromina 85314f8d47 Updated watch script to include watchreact, and added watchreactd 2024-12-19 15:09:16 +08:00
Andrew Pareles ee7d0ed836 Merge pull request #181 from bjoaquinc/react-build-watcher
Fix React build crashing watch-client and added watch mode
2024-12-18 19:45:24 -08:00
Andrew Pareles ba87b4f0de update contrib 2024-12-18 19:40:27 -08:00
Andrew Pareles e4bc21ffb1 Merge pull request #182 from voideditor/model-selection
UI Improvements and Model Selection
2024-12-18 19:33:57 -08:00
Andrew Pareles 81b78b7765 auto fetch model works 2024-12-18 19:31:50 -08:00
Andrew Pareles c3c53b5e81 slightly fix refresh state 2024-12-18 16:48:04 -08:00
Andrew Pareles f49a6f8e41 minor style changes and refresh model works in general 2024-12-18 16:06:53 -08:00
mp 5a26aa8746 Merge branch 'model-selection' into polishing-changes 2024-12-18 15:45:18 -08:00
Andrew Pareles 8661fe1417 fix selection styles and add void watermark (buttons look bad) 2024-12-18 13:35:34 -08:00
Joaquin Coromina 2a519157da Merge remote-tracking branch 'origin/react-build-watcher' into recovered-code
Added a watcher that syncs react with watch-client without crashing#
2024-12-18 18:47:37 +08:00
mp ac8365def4 ctrl+L 2024-12-18 02:29:11 -08:00
Joaquin Coromina 610b52587b Added watchreact npm command, resolved watch-client crashes during updates, and included nodemon as a dependency 2024-12-18 18:24:17 +08:00
mp 01efab6a1d cmd+L prompt 2024-12-17 19:30:14 -08:00
Andrew Pareles 7f8e1ff00d Merge pull request #180 from voideditor/model-selection
Misc UI Improvements
2024-12-17 18:16:07 -08:00
Andrew Pareles 5b55eed08a threads styles + misc 2024-12-17 18:15:09 -08:00
Andrew Pareles d618223289 misc improvements. need to add max h on thread histroy 2024-12-17 01:55:32 -08:00
Andrew Pareles 7752278720 settings styles 2024-12-17 01:33:33 -08:00
Andrew Pareles 597c04f63b settings UI 2024-12-16 23:08:01 -08:00
mp a7c4a1b179 input box style 2024-12-16 23:02:33 -08:00
Andrew Pareles a7aab9cf86 some UI for models! 2024-12-16 21:49:03 -08:00
Andrew Pareles c44497510b add responsive dark mode 2024-12-16 18:57:28 -08:00
Andrew Pareles c485d2c478 react updates 2024-12-16 17:43:07 -08:00
Andrew Pareles dfdf29fb36 rename 2024-12-16 15:05:49 -08:00
Andrew Pareles 420fb1a186 icon is incredibly annoying 2024-12-16 01:21:55 -08:00
Andrew Pareles 78a48bae13 add model selection 2024-12-16 00:17:50 -08:00
mp fc187e0028 sidebar scrolllock 2024-12-15 23:06:25 -08:00
mp f973f8d4e7 fix selection range bug 2024-12-15 22:24:16 -08:00
Andrew Pareles 8403eed8ca fix hook 2024-12-15 21:42:31 -08:00
Andrew Pareles 718b8cc61b add void settings in lower left 2024-12-15 21:10:17 -08:00
mp f6924df09b Fix rerenders 2024-12-15 20:52:43 -08:00
Andrew Pareles 8b25f80e9b model selection is accurate now, and lots of files moved to be more accurate 2024-12-15 20:43:11 -08:00
mp a42ff2d59a sidebar + ux 2024-12-15 01:33:02 -08:00
Andrew Pareles 0589a2a5ec remove maxtokens 2024-12-15 01:11:07 -08:00
Andrew Pareles 7dda4bdc08 add editor pane 2024-12-15 00:30:21 -08:00
Andrew Pareles f21ef61677 .js fix 2024-12-14 21:19:49 -08:00
Andrew Pareles 56b6d406d6 Merge remote-tracking branch 'origin/main' into model-selection 2024-12-14 21:16:43 -08:00
Andrew Pareles 2ae29a50e5 fix 2024-12-14 21:16:16 -08:00
mp 9673e974ea rename + merge 2024-12-14 20:47:27 -08:00
mp 842d388669 multiple selections 2024-12-14 20:39:50 -08:00
Andrew Pareles f146385e1d Update CONTRIBUTING.md 2024-12-14 20:17:30 -08:00
Andrew Pareles bb87726b34 Update CONTRIBUTING.md 2024-12-14 20:15:15 -08:00
Andrew Pareles deb30b49bc Update CONTRIBUTING.md 2024-12-14 19:51:28 -08:00
Andrew Pareles 56923eec52 Update CONTRIBUTING.md 2024-12-14 19:50:16 -08:00
Andrew Pareles d2692d2594 Update CONTRIBUTING.md 2024-12-14 19:47:44 -08:00
Andrew Pareles 5d44fdc17d Update CONTRIBUTING.md 2024-12-14 19:47:21 -08:00
Andrew Pareles aada60c5be Update CONTRIBUTING.md 2024-12-14 19:45:45 -08:00
Andrew Pareles b53e5d4886 Update CONTRIBUTING.md 2024-12-14 19:45:37 -08:00
Andrew Pareles f299998994 Update CONTRIBUTING.md 2024-12-14 19:41:18 -08:00
Andrew Pareles 967292e615 Update CONTRIBUTING.md 2024-12-14 19:31:37 -08:00
Andrew Pareles 19e3ebefda misc 2024-12-14 18:07:56 -08:00
Andrew Pareles 7cf6c9d798 Merge pull request #175 from voideditor/model-selection
Minor Contrib.md changes
2024-12-14 18:04:37 -08:00
Andrew Pareles 14e314e5b8 update contrib 2024-12-14 18:03:50 -08:00
Andrew Pareles 4e6e0b3bde Merge pull request #172 from voideditor/model-selection
Model selection
2024-12-14 17:58:38 -08:00
Andrew Pareles 63b086b786 dummy commit 2024-12-14 17:55:44 -08:00
Andrew Pareles bda6ad06c9 Merge branch 'main' into model-selection 2024-12-14 17:53:38 -08:00
Andrew Pareles 829d82850d rename Secondary Side Bar to Void Side Bar 2024-12-14 17:51:21 -08:00
Andrew Pareles 3991372f5b misc improvements 2024-12-14 17:51:07 -08:00
Andrew Pareles 9b427c78bf fix react state sync bug 2024-12-13 01:28:55 -08:00
Andrew Pareles 5dad4c8f06 relevant ollama update vals 2024-12-13 01:05:33 -08:00
Andrew Pareles e2d39fe4b1 ollama refreshes! 2024-12-13 00:41:34 -08:00
Andrew Pareles 1365400f21 llmMessageService now multiplexes between sendLLMMessage, ollama, etc 2024-12-12 19:30:45 -08:00
Andrew Pareles 94f4d2caaf add ollamaList to llmMessageService 2024-12-12 19:30:45 -08:00
Andrew Pareles 696abc39fd add ollama model fetcher, but proxy ignores its functions 2024-12-12 19:30:45 -08:00
mp abc7151cd6 Merge branch 'model-seln-2' into model-selection 2024-12-12 16:50:37 -08:00
Andrew Pareles c86f47743e update links 2024-12-12 16:42:48 -08:00
Andrew Pareles 9d6eb62c3e revert accidental change 2024-12-12 16:31:21 -08:00
Andrew Pareles 2f26b34a96 remove connect-src * that was introduced in testing 2024-12-12 16:25:23 -08:00
Andrew Pareles 5807c94a61 tailwind 2024-12-12 14:49:36 -08:00
Andrew Pareles df23314bd2 add css to useful links 2024-12-12 14:37:54 -08:00
Andrew Pareles 6703aa1545 get ready to sync ollama models 2024-12-12 01:42:06 -08:00
mp aaab289ece native tailwind colors 2024-12-12 01:10:52 -08:00
Andrew Pareles d809839530 add dummy models 2024-12-11 21:30:13 -08:00
Andrew Pareles 9e583960ae visual improvements 2024-12-11 21:17:24 -08:00
Andrew Pareles 19737d4cdc minor display change 2024-12-11 20:06:16 -08:00
mp 901579a445 Merge remote-tracking branch 'origin/model-selection' into model-seln-2 2024-12-11 20:04:37 -08:00
mp 051f8f38e7 Sidebar restructure 2024-12-11 20:02:07 -08:00
Andrew Pareles 07fcb3a72c better model seln UI + error handling works much better! 2024-12-11 19:53:36 -08:00
Jérôme Commaret 2d80a29275 Merge branch 'voideditor:main' into main 2024-12-12 01:03:51 +01:00
Andrew Pareles 62cdaba44b npm run react 2024-12-11 02:57:17 -08:00
Andrew Pareles 3147a92463 Update VOID_USEFUL_LINKS.md 2024-12-11 02:53:59 -08:00
Andrew Pareles d175ac20ef try dropdownmenu; didn't work 2024-12-11 02:40:19 -08:00
Andrew Pareles 42a2dec781 minor welcome changes 2024-12-11 02:19:22 -08:00
Andrew Pareles dbef72c01c update scope-tailwind to support -pr-5 2024-12-11 02:04:33 -08:00
Andrew Pareles 6ddf977c5d select box style 2024-12-11 01:47:57 -08:00
Andrew Pareles 522f136872 license comment 2024-12-11 01:28:55 -08:00
Andrew Pareles a28194c601 add sync state for dropdown 2024-12-11 01:24:04 -08:00
Andrew Pareles cb2de5505b fix 2024-12-11 00:49:52 -08:00
Andrew Pareles f84c964d10 finished model selection 2024-12-11 00:48:48 -08:00
Andrew Pareles bb4f99f687 error state, sendLLMMessage fix, continue adding state for provider/model pair selection 2024-12-10 23:29:39 -08:00
Andrew Pareles 49b43820b2 state - pick a provider/model 2024-12-10 23:24:36 -08:00
Andrew Pareles b22bbf9639 add not recommended line 2024-12-10 19:03:42 -08:00
Andrew Pareles a1ff458007 add deps and remove devDeps 2024-12-10 19:02:17 -08:00
Andrew Pareles fb263f9d81 update contrib for bundling 2024-12-10 17:54:58 -08:00
Andrew Pareles 72deba43dd consistent error messages 2024-12-10 17:41:13 -08:00
mp 24fdb1ca0d minor changes 2024-12-10 17:29:55 -08:00
Andrew Pareles f20f41c804 minor 'true' update 2024-12-10 02:42:32 -08:00
Andrew Pareles 66037e2049 update init state properly 2024-12-10 02:39:30 -08:00
Andrew Pareles adb504f2e0 reactify Widget 2024-12-10 02:09:06 -08:00
Andrew Pareles 8b90d7827d un-delete void channels 2024-12-10 00:25:06 -08:00
mp 62e1657274 change config key + merge 2024-12-09 19:45:49 -08:00
Andrew Pareles 7f776eebdb minor key 2024-12-09 19:45:37 -08:00
Andrew Pareles 0db0513752 finish merge errors 2024-12-09 19:25:54 -08:00
Andrew Pareles d8b6b3c306 Merge branch 're-add-autocomplete' into model-seln-2 2024-12-09 19:12:57 -08:00
Andrew Pareles f7c9d732f5 update type info 2024-12-09 19:01:08 -08:00
Andrew Pareles 8a95b13711 minor changes 2024-12-09 17:03:29 -08:00
Andrew Pareles 55884ed1c9 checkbox 2024-12-09 16:45:03 -08:00
Andrew Pareles 582b25dc44 build works 2024-12-09 16:26:31 -08:00
mp 252af4c863 build script 2024-12-09 16:17:53 -08:00
Andrew Pareles e5ac36facd add new config type for each feature, UI dump 2024-12-09 02:41:27 -08:00
Andrew Pareles 77fc2d2d4a temp 2024-12-08 18:44:04 -08:00
Jérôme Commaret d8e9b50890 Advanced builder section added 2024-12-07 14:49:28 +01:00
mp 02112fbc4f allow selection by feature + provider (incomplete) 2024-12-07 00:28:13 -08:00
mp 05241649ff comment grep + _uiConfig change 2024-12-06 18:16:18 -08:00
Andrew Pareles 296cd1b477 Update CONTRIBUTING.md 2024-12-06 02:51:30 -08:00
mp 695d6a1e30 Disable autocomplete 2024-12-05 19:52:05 -08:00
mp 48f9ac2ba1 Minor changes 2024-12-04 18:50:09 -08:00
mp f9198d4d97 Remove accepted autocomplete characters 2024-12-04 17:50:24 -08:00
mp a6cf723dbe Fix space issue 2024-11-26 19:01:38 -08:00
mp ef9ac7a9e3 Merge branch 'main' into re-add-autocomplete 2024-11-26 16:30:38 -08:00
mp 48426403f3 Re-added autocomplete 2024-11-26 15:15:05 -08:00
mp 4ea1bfa9c3 add structure for re-adding autocomplete 2024-11-21 19:05:54 -08:00
176 changed files with 19668 additions and 9191 deletions
+1
View File
@@ -10,6 +10,7 @@
"jsdoc",
"header",
"local"
// "react" // Void
],
"rules": {
"constructor-super": "warn",
+3
View File
@@ -21,3 +21,6 @@ vscode.db
product.overrides.json
*.snap.actual
.vscode-test
.tmp/
.tmp2/
.tool-versions
+30 -1
View File
@@ -29,6 +29,34 @@
}
}
},
{
"type": "npm",
"script": "watchreactd",
"label": "React - Build",
"isBackground": true,
"presentation": {
"reveal": "never",
"group": "buildWatchers",
"close": false
},
"problemMatcher": {
"owner": "typescript",
"applyTo": "closedDocuments",
"fileLocation": [
"absolute"
],
"pattern": {
"regexp": "Error: ([^(]+)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\): (.*)$",
"file": 1,
"location": 2,
"message": 3
},
"background": {
"beginsPattern": "Starting compilation",
"endsPattern": "Finished compilation"
}
}
},
{
"type": "npm",
"script": "watch-extensionsd",
@@ -61,7 +89,8 @@
"label": "VS Code - Build",
"dependsOn": [
"Core - Build",
"Ext - Build"
"Ext - Build",
"React - Build"
],
"group": {
"kind": "build",
+91 -69
View File
@@ -1,23 +1,33 @@
# Contributing to Void
Welcome! 👋 This is the official guide on how to contribute to Void. We want to make it as easy as possible to contribute, so if you have any questions or comments, reach out via email or discord!
### Welcome! 👋
This is the official guide on how to contribute to Void. We want to make it as easy as possible to contribute, so if you have any questions or comments, reach out via email or discord!
There are a few ways to contribute:
- Suggest New Features ([Discord](https://discord.gg/RSNjgaugJs))
- Build New Features ([Project](https://github.com/orgs/voideditor/projects/2/views/3))
- Submit Issues/Docs/Bugs ([Issues](https://github.com/voideditor/void/issues))
- 💫 Complete items on the [Roadmap](https://github.com/orgs/voideditor/projects/2).
- 💡 Make suggestions in our [Discord](https://discord.gg/RSNjgaugJs).
- 🪴 Start new Issues - see [Issues](https://github.com/voideditor/void/issues).
## Building the full IDE
Please follow the steps below to build the IDE. If you have any questions, feel free to [submit an issue](https://github.com/voideditor/void/issues/new) with any build errors, or refer to VSCode's full [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) page.
### Codebase Guide
Most of Void's code lives in `src/vs/workbench/contrib/void/browser/` and `src/vs/platform/void/`.
We highly recommend reading [this](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) article on VSCode's sourcecode organization.
<!-- ADD BLOG HERE
We wrote a [guide to working in VSCode].
-->
Most of Void's code lives in the two folders called `void/`.
## Building Void
### a. Build Prerequisites - Mac
If you're using a Mac, make sure you have Python and XCode installed (you probably do by default).
If you're using a Mac, you need Python and XCode. You probably have these by default.
### b. Build Prerequisites - Windows
@@ -28,38 +38,40 @@ Go to the "Workloads" tab and select:
- `Node.js build tools`
Go to the "Individual Components" tab and select:
- `MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs (Latest)`,
- `C++ ATL for latest build tools with Spectre Mitigations`,
- `C++ MFC for latest build tools with Spectre Mitigations`.
- `MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs (Latest)`
- `C++ ATL for latest build tools with Spectre Mitigations`
- `C++ MFC for latest build tools with Spectre Mitigations`
Finally, click Install.
### c. Build Prerequisites - Linux
First, make sure you've installed NodeJS and run `npm install -g node-gyp`. Then:
- Debian (Ubuntu, etc) - `sudo apt-get install build-essential g++ libx11-dev libxkbfile-dev libsecret-1-dev libkrb5-dev python-is-python3`.
- Red Hat (Fedora, etc) - `sudo dnf install @development-tools gcc gcc-c++ make libsecret-devel krb5-devel libX11-devel libxkbfile-devel`.
- Others - see [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute).
First, run `npm install -g node-gyp`. Then:
### Build instructions
- Debian (Ubuntu, etc): `sudo apt-get install build-essential g++ libx11-dev libxkbfile-dev libsecret-1-dev libkrb5-dev python-is-python3`.
- Red Hat (Fedora, etc): `sudo dnf install @development-tools gcc gcc-c++ make libsecret-devel krb5-devel libX11-devel libxkbfile-devel`.
- Others: see [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute).
To build Void, first follow the prerequisite steps above for your operating system and open `void/` inside VSCode. Then:
### d. Building Void
1. Install all dependencies.
To build Void, open `void/` inside VSCode. Then open your terminal and run:
```
npm install
```
1. `npm install` to install all dependencies.
2. `npm run watchreact` to build Void's browser dependencies like React. (If this doesn't work, try `npm run buildreact`).
3. Build Void.
- Press <kbd>Cmd+Shift+B</kbd> (Mac).
- Press <kbd>Ctrl+Shift+B</kbd> (Windows/Linux).
- This step can take ~5 min. The build is done when you see two check marks.
4. Run Void.
- Run `./scripts/code.sh` (Mac/Linux).
- Run `./scripts/code.bat` (Windows).
6. Nice-to-knows.
- You can always press <kbd>Ctrl+R</kbd> (<kbd>Cmd+R</kbd>) inside the new window to reload and see your new changes. It's faster than <kbd>Ctrl+Shift+P</kbd> and `Reload Window`.
- You might want to add the flags `--user-data-dir ./.tmp/user-data --extensions-dir ./.tmp/extensions` to the above run command, which lets you delete the `.tmp` folder to reset any IDE changes you made when testing.
2. Run `cd ./src/vs/workbench/contrib/void/browser/react/` and then `node ./build.js` to build Void's external dependencies (our React components, etc).
#### Building Void from Terminal
3. Press <kbd>Ctrl+Shift+B</kbd>, or if you prefer using the terminal run `npm run watch`.
This can take ~5 min.
If you ran <kbd>Ctrl+Shift+B</kbd>, the build is done when you see two check marks.
If you ran `npm run watch`, the build is done when you see something like this:
Alternatively, if you want to build Void from the terminal, instead of pressing <kbd>Cmd+Shift+B</kbd> you can run `npm run watch`. The build is done when you see something like this:
```
[watch-extensions] [00:37:39] Finished compilation extensions with 0 errors after 19303 ms
@@ -68,54 +80,67 @@ If you ran `npm run watch`, the build is done when you see something like this:
[watch-client ] [00:38:07] Finished compilation with 0 errors after 5 ms
```
<!-- 3. Press <kbd>Ctrl+Shift+B</kbd> to start the build process. -->
4. In a new terminal, run `./scripts/code.sh` (Mac/Linux) or `./scripts/code.bat` (Windows). This should open up the built IDE!
You can always press <kbd>Ctrl+Shift+P</kbd> and run "Reload Window" inside the new window to see changes without re-building.
Now that you're set up, feel free to check out our [Issues](https://github.com/voideditor/void/issues) page.
### Common Fixes
- Make sure you have the same NodeJS version as `.nvmrc`.
- If you see `[ERROR] Cannot start service: Host version "0.23.1" does not match binary version "0.23.0"`, run `npm i -D esbuild@0.23.0` or do a clean install of your npm dependencies.
## Bundling
#### Common Fixes
To bundle the IDE into an executable, run `npm run gulp vscode-darwin-arm64`.
Here are the full options: `vscode-{win32-ia32 | win32-x64 | darwin-x64 | darwin-arm64 | linux-ia32 | linux-x64 | linux-arm}(-min)`
## Roadmap
Please refer to our [Issues](https://github.com/voideditor/void/issues) page for the latest issues.
## ⭐⭐⭐ Creative.
Examples: creating better code search, or supporting AI agents that can edit across files and make multiple LLM calls.
Eventually, we want to build a convenient API for creating AI tools. The API will provide methods for creating the UI (showing an autocomplete suggestion, or creating a new diff), detecting event changes (like `onKeystroke` or `onFileOpen`), and modifying the user's file-system (storing indexes associated with each file), making it much easier to make your own AI plugin. We plan on building these features further along in timeline, but we wanted to list them for completeness.
# Guidelines
We're always glad to talk about new ideas, help you get set up, and make sure your changes align with our vision for the project. Feel free to shoot us a message in the #general channel of the [Discord](https://discord.gg/RSNjgaugJs) for any reason. Please check in especially if you want to make a lot of changes or build a large new feature.
- Make sure you followed the prerequisite steps.
- Make sure you have Node version `20.16.0` (the version in `.nvmrc`)!
- If you get `"TypeError: Failed to fetch dynamically imported module"`, make sure all imports end with `.js`.
- If you see missing styles, wait a few seconds and then reload.
- If you have any questions, feel free to [submit an issue](https://github.com/voideditor/void/issues/new). You can also refer to VSCode's complete [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) page.
## Submitting a Pull Request
## Packaging
Please submit a pull request once you've made a change. You don't need to submit an issue.
We don't usually recommend packaging. Instead, you should probably just build. If you're sure you want to package Void into an executable app, make sure you've built first, then run one of the following commands. This will create a folder named `VSCode-darwin-arm64` or similar outside of the void/ repo (see below). Be patient - packaging can take ~25 minutes.
Please don't use AI to write your PR 🙂.
### Mac
- `npm run gulp vscode-darwin-arm64` - most common (Apple Silicon)
- `npm run gulp vscode-darwin-x64` (Intel)
### Windows
- `npm run gulp vscode-win32-x64` - most common
- `npm run gulp vscode-win32-ia32`
### Linux
- `npm run gulp vscode-linux-x64` - most common
- `npm run gulp vscode-linux-arm`
- `npm run gulp vscode-linux-ia32`
### Output
This will generate a folder outside of `void/`:
```bash
workspace/
├── void/ # Your Void fork
└── VSCode-darwin-arm64/ # Generated output
```
### Distributing
Void's maintainers distribute Void on our website and in releases. If you'd like to see the scripts to convert `Mac .app -> .dmg`, `Windows folder -> .exe`, and `Linux folder -> appimage` for distribution, feel free to reach out.
## Pull Request Guidelines
- Please submit a pull request once you've made a change.
- No need to submit an Issue unless you're creating a new feature that might involve multiple PRs.
- Please don't use AI to write your PR 🙂
<!--
# Relevant files
We keep track of all the files we've changed with Void so it's easy to rebase:
Edit: far too many changes to track... this is old
- README.md
- CONTRIBUTING.md
- VOID_USEFUL_LINKS.md
@@ -136,7 +161,4 @@ We keep track of all the files we've changed with Void so it's easy to rebase:
- vscode.proposed.editorInsets.d.ts - not modified, but code copied
## References
For some useful links we've compiled on VSCode, see [`VOID_USEFUL_LINKS.md`](https://github.com/voideditor/void/blob/main/VOID_USEFUL_LINKS.md).
-->
+26
View File
@@ -0,0 +1,26 @@
Void is a fork of VS Code, which is licensed under the MIT License (below).
Void's additions and modifications are licensed under the Apache 2.0 License (see LICENSE.txt).
--------------------
MIT License
Copyright (c) 2015 - present Microsoft Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+197 -17
View File
@@ -1,21 +1,201 @@
MIT License
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Copyright (c) 2015 - present Microsoft Corporation
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
1. Definitions.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2025 Glass Devtools, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+28 -5
View File
@@ -1,16 +1,39 @@
# Welcome to Void.
Void is the open-source Cursor alternative. This repo contains the full sourcecode for Void. We have a [waitlist](https://voideditor.com/email) for downloading the official release, but you can build and develop Void right now.
<div align="center">
<img
src="./src/vs/workbench/browser/parts/editor/media/slice_of_void.png"
alt="Void Welcome"
width="300"
height="300"
/>
</div>
Void is the open-source Cursor alternative.
This repo contains the full sourcecode for Void. We are currently in [open beta](https://voideditor.com/email) for Discord members (see the `announcements` channel), with a waitlist for our official release. If you're new, welcome!
- 👋 [Discord](https://discord.gg/RSNjgaugJs)
- 🔨 [Contribute](https://github.com/voideditor/void/blob/main/CONTRIBUTING.md)
- 🚙 [Roadmap](https://github.com/orgs/voideditor/projects/2)
- 📝 [Changelog](https://voideditor.com/changelog)
If you're new, welcome!
## Contributing
To build and run Void, follow the steps in [`CONTRIBUTING.md`](https://github.com/voideditor/void/blob/main/CONTRIBUTING.md).
1. Feel free to attend a weekly meeting in our Discord channel if you'd like to contribute!
2. To get started working on Void, see [Contributing](https://github.com/voideditor/void/blob/main/CONTRIBUTING.md).
3. We're open to collaborations and suggestions of all types - just reach out.
## Reference
Void is a fork of the of [vscode](https://github.com/microsoft/vscode) repository. For some useful links on VSCode, see [`VOID_USEFUL_LINKS.md`](https://github.com/voideditor/void/blob/main/VOID_USEFUL_LINKS.md).
Void is a fork of the [vscode](https://github.com/microsoft/vscode) repository. For some useful links on VSCode, see [`VOID_USEFUL_LINKS.md`](https://github.com/voideditor/void/blob/main/VOID_USEFUL_LINKS.md).
## Support
Feel free to reach out in our [Discord](https://discord.gg/RSNjgaugJs) or contact us via email.
Feel free to reach out in our Discord or contact us via email: hello@voideditor.com.
-41
View File
@@ -1,41 +0,0 @@
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.9 BLOCK -->
## Security
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin).
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below.
## Reporting Security Issues
**Please do not report security vulnerabilities through public GitHub issues.**
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs.
## Preferred Languages
We prefer all communications to be in English.
## Policy
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd).
<!-- END MICROSOFT SECURITY.MD BLOCK -->
+9 -5
View File
@@ -2,18 +2,22 @@
The Void team put together this list of links to get up and running with VSCode's sourcecode. We hope it's helpful!
## Contributing
- [How VSCode's sourcecode is organized](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) - this explains where the entry point files are, what `browser/` and `common/` mean, etc. This is the most important read on this whole list! We recommend reading the whole thing.
- [Built-in VSCode styles](https://code.visualstudio.com/api/references/theme-color) - CSS variables that are built into VSCode. Use `var(--vscode-{theme but replacing . with -})`. You can also see their [Webview theming guide](https://code.visualstudio.com/api/extension-guides/webview#theming-webview-content).
## Beginners / Getting started
- [VSCode UI guide](https://code.visualstudio.com/docs/getstarted/userinterface) - covers auxbar, panels, etc.
- [UX guide](https://code.visualstudio.com/api/ux-guidelines/overview) - covers Containers, Views, Items, etc.
## Contributing
- [How VS Code's sourcecode is organized](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) - this explains where the entry point files are, what `browser/` and `common/` mean, etc. This is the most important read on this whole list! We recommend reading the whole thing.
## Misc
- [Every command](https://code.visualstudio.com/api/references/commands) built-in to VSCode - sometimes useful to reference.
- [Every command](https://code.visualstudio.com/api/references/commands) built-in to VSCode - not used often, but here for reference.
## VSCode's Extension API
@@ -28,6 +32,6 @@ Void is no longer an extension, so these links are no longer required, but they
- [The Full VSCode Extension API](https://code.visualstudio.com/api/references/vscode-api) - look on the right side for organization. The [bottom](https://code.visualstudio.com/api/references/vscode-api#api-patterns) of the page is easy to miss but is useful - cancellation tokens, events, disposables.
- [Activation events](https://code.visualstudio.com/api/references/activation-events) you can define in `package.json` (not the most useful)
- [Activation events](https://code.visualstudio.com/api/references/activation-events) you can define in `package.json` (not the most useful).
@@ -1,3 +1,5 @@
# Void - this looks like the relevant file for us (product-build-darwin.yml is independent and maybe just used for testing)
steps:
- task: NodeTool@0
inputs:
@@ -59,6 +61,8 @@ steps:
- script: node build/azure-pipelines/distro/mixin-quality
displayName: Mixin distro quality
## Void - IMPORTANT
- script: |
set -e
unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_x64_archive/VSCode-darwin-x64.zip -d $(agent.builddirectory)/VSCode-darwin-x64
@@ -66,6 +70,7 @@ steps:
DEBUG=* node build/darwin/create-universal-app.js $(agent.builddirectory)
displayName: Create Universal App
## Void - IMPORTANT
- script: |
set -e
security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain
+1
View File
@@ -4,6 +4,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
// Void explanation - product-build-darwin-universal.yml runs this (create-universal-app.ts), then sign.ts
const path = require("path");
const fs = require("fs");
const minimatch = require("minimatch");
+2
View File
@@ -3,6 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Void explanation - product-build-darwin-universal.yml runs this (create-universal-app.ts), then sign.ts
import * as path from 'path';
import * as fs from 'fs';
import * as minimatch from 'minimatch';
+4 -4
View File
@@ -78,24 +78,24 @@ async function main(buildDir) {
// universal will get its copy from the x64 build.
if (arch !== 'universal') {
await (0, cross_spawn_promise_1.spawn)('plutil', [
'-insert',
'-replace', // Void changed this to replace
'NSAppleEventsUsageDescription',
'-string',
'An application in Visual Studio Code wants to use AppleScript.',
'An application in Void wants to use AppleScript.',
`${infoPlistPath}`
]);
await (0, cross_spawn_promise_1.spawn)('plutil', [
'-replace',
'NSMicrophoneUsageDescription',
'-string',
'An application in Visual Studio Code wants to use the Microphone.',
'An application in Void wants to use the Microphone.',
`${infoPlistPath}`
]);
await (0, cross_spawn_promise_1.spawn)('plutil', [
'-replace',
'NSCameraUsageDescription',
'-string',
'An application in Visual Studio Code wants to use the Camera.',
'An application in Void wants to use the Camera.',
`${infoPlistPath}`
]);
}
+4 -4
View File
@@ -89,24 +89,24 @@ async function main(buildDir?: string): Promise<void> {
// universal will get its copy from the x64 build.
if (arch !== 'universal') {
await spawn('plutil', [
'-insert',
'-replace', // Void changed this to replace
'NSAppleEventsUsageDescription',
'-string',
'An application in Visual Studio Code wants to use AppleScript.',
'An application in Void wants to use AppleScript.',
`${infoPlistPath}`
]);
await spawn('plutil', [
'-replace',
'NSMicrophoneUsageDescription',
'-string',
'An application in Visual Studio Code wants to use the Microphone.',
'An application in Void wants to use the Microphone.',
`${infoPlistPath}`
]);
await spawn('plutil', [
'-replace',
'NSCameraUsageDescription',
'-string',
'An application in Visual Studio Code wants to use the Camera.',
'An application in Void wants to use the Camera.',
`${infoPlistPath}`
]);
}
+6 -4
View File
@@ -341,6 +341,8 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
this.emit('data', file);
}));
// 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 }))
@@ -425,15 +427,15 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
'resources/win32/vue.ico',
'resources/win32/xml.ico',
'resources/win32/yaml.ico',
'resources/win32/code_70x70.png',
'resources/win32/code_150x150.png'
'resources/win32/code_70x70.png', // <-- Void icon
'resources/win32/code_150x150.png' // <-- Void icon
], { base: '.' }));
} else if (platform === 'linux') {
all = es.merge(all, gulp.src('resources/linux/code.png', { base: '.' }));
all = es.merge(all, gulp.src('resources/linux/code.png', { base: '.' })); // <-- Void icon
} 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/code')); // <-- Void icon
all = es.merge(all, shortcut);
}
+2 -2
View File
@@ -54,7 +54,7 @@ function darwinBundleDocumentType(extensions, icon, nameOrSuffix, utis) {
role: 'Editor',
ostypes: ['TEXT', 'utxt', 'TUTX', '****'],
extensions,
iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns',
iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns', // <-- Void icon code.icns
utis
};
}
@@ -179,7 +179,7 @@ exports.config = {
darwinForceDarkModeSupport: true,
darwinCredits: darwinCreditsTemplate ? Buffer.from(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : undefined,
linuxExecutableName: product.applicationName,
winIcon: 'resources/win32/code.ico',
winIcon: 'resources/win32/code.ico', // <-- Void icon
token: process.env['GITHUB_TOKEN'],
repo: product.electronRepository || undefined,
validateChecksum: true,
+2 -2
View File
@@ -68,7 +68,7 @@ function darwinBundleDocumentType(extensions: string[], icon: string, nameOrSuff
role: 'Editor',
ostypes: ['TEXT', 'utxt', 'TUTX', '****'],
extensions,
iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns',
iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns', // <-- Void icon code.icns
utis
};
}
@@ -196,7 +196,7 @@ export const config = {
darwinForceDarkModeSupport: true,
darwinCredits: darwinCreditsTemplate ? Buffer.from(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : undefined,
linuxExecutableName: product.applicationName,
winIcon: 'resources/win32/code.ico',
winIcon: 'resources/win32/code.ico', // <-- Void icon
token: process.env['GITHUB_TOKEN'],
repo: product.electronRepository || undefined,
validateChecksum: true,
+5 -2
View File
@@ -4,6 +4,7 @@
? ('; LicenseFile: "' + RepoDir + '\licenses\LICENSE-' + Language + '.rtf"') \
: '; LicenseFile: "' + RepoDir + '\' + RootLicenseFileName + '"'
[Setup]
AppId={#AppId}
AppName={#NameLong}
@@ -20,8 +21,10 @@ Compression=lzma
SolidCompression=yes
AppMutex={code:GetAppMutex}
SetupMutex={#AppMutex}setup
WizardImageFile="{#RepoDir}\resources\win32\inno-big-100.bmp,{#RepoDir}\resources\win32\inno-big-125.bmp,{#RepoDir}\resources\win32\inno-big-150.bmp,{#RepoDir}\resources\win32\inno-big-175.bmp,{#RepoDir}\resources\win32\inno-big-200.bmp,{#RepoDir}\resources\win32\inno-big-225.bmp,{#RepoDir}\resources\win32\inno-big-250.bmp"
WizardSmallImageFile="{#RepoDir}\resources\win32\inno-small-100.bmp,{#RepoDir}\resources\win32\inno-small-125.bmp,{#RepoDir}\resources\win32\inno-small-150.bmp,{#RepoDir}\resources\win32\inno-small-175.bmp,{#RepoDir}\resources\win32\inno-small-200.bmp,{#RepoDir}\resources\win32\inno-small-225.bmp,{#RepoDir}\resources\win32\inno-small-250.bmp"
; this is a Void icon comment. Old: WizardImageFile="{#RepoDir}\resources\win32\inno-big-100.bmp,{#RepoDir}\resources\win32\inno-big-125.bmp,{#RepoDir}\resources\win32\inno-big-150.bmp,{#RepoDir}\resources\win32\inno-big-175.bmp,{#RepoDir}\resources\win32\inno-big-200.bmp,{#RepoDir}\resources\win32\inno-big-225.bmp,{#RepoDir}\resources\win32\inno-big-250.bmp"
; this is a Void icon comment. Old: WizardSmallImageFile="{#RepoDir}\resources\win32\inno-small-100.bmp,{#RepoDir}\resources\win32\inno-small-125.bmp,{#RepoDir}\resources\win32\inno-small-150.bmp,{#RepoDir}\resources\win32\inno-small-175.bmp,{#RepoDir}\resources\win32\inno-small-200.bmp,{#RepoDir}\resources\win32\inno-small-225.bmp,{#RepoDir}\resources\win32\inno-small-250.bmp"
; WizardImageFile="{#RepoDir}\resources\win32\inno-void.bmp"
WizardSmallImageFile="{#RepoDir}\resources\win32\inno-void.bmp"
SetupIconFile={#RepoDir}\resources\win32\code.ico
UninstallDisplayIcon={app}\{#ExeBasename}.exe
ChangesEnvironment=true
+112
View File
@@ -0,0 +1,112 @@
#!/bin/bash
set -e # Exit on error
set -x # Print commands as they are executed
# Configuration
APP_NAME="void"
APP_VERSION="1.0.0"
ARCH="x86_64"
export ARCH
# Check if void binary exists in current directory
if [ ! -f "./void" ]; then
echo "Error: void binary not found in current directory"
exit 1
fi
# Check if icon exists
if [ ! -f "./void.png" ]; then
echo "Error: void.png icon not found in current directory"
exit 1
fi
# Create temporary directory
TEMP_DIR="$(mktemp -d)"
echo "Created temporary directory: $TEMP_DIR"
APP_DIR="$TEMP_DIR/$APP_NAME.AppDir"
# Create basic AppDir structure
mkdir -pv "$APP_DIR/usr/bin"
mkdir -pv "$APP_DIR/usr/lib"
mkdir -pv "$APP_DIR/usr/share/applications"
mkdir -pv "$APP_DIR/usr/share/icons/hicolor/256x256/apps"
# Exclude create-appimage.sh and appimagetool-x86_64.AppImage from being copied
echo "Copying files excluding create-appimage.sh and appimagetool-x86_64.AppImage..."
for file in ./*; do
if [[ "$file" != "./create-appimage.sh" && "$file" != "./appimagetool-x86_64.AppImage" ]]; then
cp -rv "$file" "$APP_DIR/usr/bin/"
fi
done
# Copy the icon to required locations
cp -v ./void.png "$APP_DIR/void.png"
cp -v ./void.png "$APP_DIR/usr/share/icons/hicolor/256x256/apps/void.png"
# Copy dependencies with error checking
echo "Copying dependencies..."
for lib in $(ldd ./void | grep "=> /" | awk '{print $3}'); do
if [ -f "$lib" ]; then
cp -v "$lib" "$APP_DIR/usr/lib/" || echo "Failed to copy $lib"
else
echo "Warning: Library $lib not found"
fi
done
# Create desktop file with error checking
echo "Creating desktop file..."
if ! cat > "$APP_DIR/$APP_NAME.desktop" <<EOF
[Desktop Entry]
Name=$APP_NAME
Exec=void
Icon=void
Type=Application
Categories=Utility;
Comment=Void Linux Application
EOF
then
echo "Error creating desktop file"
exit 1
fi
# Make desktop file executable
chmod +x "$APP_DIR/$APP_NAME.desktop"
# Copy the desktop file to the applications directory
cp -v "$APP_DIR/$APP_NAME.desktop" "$APP_DIR/usr/share/applications/"
# Create AppRun with error checking
echo "Creating AppRun..."
if ! cat > "$APP_DIR/AppRun" <<EOF
#!/bin/bash
cd "\$(dirname "\$0")/usr/bin"
export LD_LIBRARY_PATH="\$APPDIR/usr/lib:\$LD_LIBRARY_PATH"
exec ./void "\$@"
EOF
then
echo "Error creating AppRun"
exit 1
fi
# Make AppRun executable
chmod +x "$APP_DIR/AppRun"
# Download appimagetool if not present in the current directory
if [ ! -f "./appimagetool-x86_64.AppImage" ]; then
echo "Downloading appimagetool-x86_64.AppImage..."
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
chmod +x appimagetool-x86_64.AppImage
else
echo "appimagetool-x86_64.AppImage is already present."
fi
# Create the AppImage
echo "Creating AppImage..."
ARCH=x86_64 ./appimagetool-x86_64.AppImage "$APP_DIR" "${APP_NAME}-${APP_VERSION}-${ARCH}.AppImage"
# Cleanup
echo "Cleaning up..."
rm -rf "$TEMP_DIR"
echo "AppImage creation complete!"
@@ -1,333 +0,0 @@
import * as vscode from 'vscode';
import Parser from 'tree-sitter';
import JavaScript from 'tree-sitter-javascript';
interface Definition {
file: string;
node: Parser.SyntaxNode;
}
interface DefnUse {
parent: Parser.SyntaxNode;
file: string;
}
interface ImportInfo {
source: string;
imported: string;
}
class ProjectAnalyzer {
private parser: Parser;
private graph: Map<string, Set<string>>;
private visited: Set<string>;
private parsedFiles: Map<string, Parser.Tree>;
private imports: Map<string, Map<string, ImportInfo>>;
private definitions: Map<string, Definition>;
private fileStack: Set<string>;
constructor() {
this.parser = new Parser();
this.parser.setLanguage(JavaScript);
this.graph = new Map();
this.visited = new Set();
this.parsedFiles = new Map();
this.imports = new Map();
this.definitions = new Map();
this.fileStack = new Set();
}
async parseFile(filePath: string): Promise<Parser.Tree | null> {
if (this.parsedFiles.has(filePath)) {
return this.parsedFiles.get(filePath)!;
}
if (this.fileStack.has(filePath)) {
return null; // Circular import
}
this.fileStack.add(filePath);
try {
const uri = vscode.Uri.file(filePath);
const document = await vscode.workspace.openTextDocument(uri);
const code = document.getText();
const tree = this.parser.parse(code);
this.parsedFiles.set(filePath, tree);
this.collectImports(filePath, tree);
this.collectDefinitions(filePath, tree);
return tree;
} catch (error) {
console.error(`Error parsing ${filePath}:`, error);
return null;
} finally {
this.fileStack.delete(filePath);
}
}
private collectImports(filePath: string, tree: Parser.Tree): void {
const fileImports = new Map<string, ImportInfo>();
const visit = (node: Parser.SyntaxNode): void => {
if (node.type === 'import_declaration') {
const source = node.childForFieldName('source')?.text.slice(1, -1) ?? '';
const specifiers = node.childForFieldName('specifiers');
specifiers?.children.forEach(spec => {
if (spec.type === 'import_specifier') {
const local = spec.childForFieldName('local')?.text ?? '';
const imported = spec.childForFieldName('imported')?.text ?? '';
fileImports.set(local, { source, imported });
}
});
}
node.children.forEach(visit);
};
visit(tree.rootNode);
this.imports.set(filePath, fileImports);
}
private collectDefinitions(filePath: string, tree: Parser.Tree): void {
const visit = (node: Parser.SyntaxNode): void => {
if (node.type === 'function_declaration') {
const name = node.childForFieldName('name')?.text ?? '';
this.definitions.set(name, { file: filePath, node });
}
else if (node.type === 'variable_declarator') {
const name = node.childForFieldName('name')?.text;
const value = node.childForFieldName('value');
if (name && (value?.type === 'arrow_function' || value?.type === 'function')) {
this.definitions.set(name, { file: filePath, node: value });
}
}
node.children.forEach(visit);
};
visit(tree.rootNode);
}
private async getTypeFromPosition(uri: vscode.Uri, position: vscode.Position): Promise<string | null> {
const hover = await vscode.commands.executeCommand<vscode.Hover[]>(
'vscode.executeHoverProvider',
uri,
position
);
if (hover?.[0]?.contents.length) {
for (const content of hover[0].contents) {
let hoverText = typeof content === 'string' ?
content :
('value' in content ? content.value : '');
// Remove typescript backticks if present
hoverText = hoverText.replace(/```typescript\s*/, '').replace(/```\s*$/, '');
console.log('Processing hover text:', hoverText);
// Extract the type information - look for the type after the colon
const typeMatches = [
/:\s*([\w<>]+)(?:\[\])?/, // matches "foo: Type" or "foo: Type[]"
/var\s+\w+:\s*([\w<>]+)/, // matches "var foo: Type"
/\(type\)\s+[\w<>]+:\s*([\w<>]+)/, // matches "(type) foo: Type"
/\(method\)\s*([\w<>]+)\./ // matches "(method) Type.method"
];
for (const pattern of typeMatches) {
const match = pattern.exec(hoverText);
if (match) {
let type = match[1];
// Handle array types
if (hoverText.includes('[]')) {
return 'Array';
}
// Extract base type from generics
if (type.includes('<')) {
type = type.split('<')[0];
}
return type;
}
}
}
}
return null;
}
private async getCallsInDefn(defnNode: Parser.SyntaxNode, currentFile: string): Promise<Set<string>> {
const calls = new Set<string>();
const fileImports = this.imports.get(currentFile) ?? new Map();
const uri = vscode.Uri.file(currentFile);
const visit = async (node: Parser.SyntaxNode): Promise<void> => {
if (node.type === 'call_expression') {
const callee = node.childForFieldName('function');
if (callee?.type === 'identifier') {
const name = callee.text;
const importInfo = fileImports.get(name);
if (importInfo) {
calls.add(`${importInfo.source}:${importInfo.imported}`);
} else {
calls.add(name);
}
}
else if (callee?.type === 'member_expression') {
const method = callee.childForFieldName('property')?.text;
const object = callee.childForFieldName('object');
if (method && object) {
const position = new vscode.Position(
object.startPosition.row,
object.startPosition.column
);
const type = await this.getTypeFromPosition(uri, position);
if (type) {
calls.add(`${type}.${method}`);
} else {
calls.add(`method:${method}`);
}
}
}
}
for (const child of node.children) {
await visit(child);
}
};
await visit(defnNode);
return calls;
}
private gotoDefn(name: string): Definition | null {
if (name.includes(':')) {
const [file, funcName] = name.split(':');
const def = this.definitions.get(funcName);
return def ?? null;
}
return this.definitions.get(name) ?? null;
}
private getUses(defnNode: Parser.SyntaxNode, currentFile: string): DefnUse[] {
const uses: DefnUse[] = [];
let fnName: string | undefined;
if (defnNode.type === 'function_declaration') {
fnName = defnNode.childForFieldName('name')?.text;
} else if (defnNode.type === 'arrow_function' || defnNode.type === 'function') {
const parent = defnNode.parent;
if (parent?.type === 'variable_declarator') {
fnName = parent.childForFieldName('name')?.text;
}
}
if (!fnName) return uses;
for (const [file, tree] of this.parsedFiles) {
const visit = (node: Parser.SyntaxNode): void => {
if (node.type === 'call_expression') {
const callee = node.childForFieldName('function');
if (callee?.type === 'identifier' && callee.text === fnName) {
let current: Parser.SyntaxNode | null = node;
while (current) {
if (current.type === 'function_declaration' ||
current.type === 'arrow_function' ||
current.type === 'function') {
uses.push({ parent: current, file });
break;
}
current = current.parent;
}
}
}
node.children.forEach(visit);
};
visit(tree.rootNode);
}
return uses;
}
private async visitAllNodesInGraphFromDefinition(defn: Parser.SyntaxNode, currentFile: string): Promise<void> {
let defnName: string | undefined;
if (defn.type === 'function_declaration') {
defnName = defn.childForFieldName('name')?.text;
} else if (defn.type === 'arrow_function' || defn.type === 'function') {
const parent = defn.parent;
if (parent?.type === 'variable_declarator') {
defnName = parent.childForFieldName('name')?.text;
}
}
if (!defnName) return;
const fullName = `${currentFile}:${defnName}`;
if (this.visited.has(fullName)) return;
const calls = await this.getCallsInDefn(defn, currentFile);
this.graph.set(fullName, calls);
this.visited.add(fullName);
const callDefns = Array.from(calls).map(call => this.gotoDefn(call));
for (const callDefn of callDefns) {
if (callDefn) {
await this.visitAllNodesInGraphFromDefinition(callDefn.node, callDefn.file);
}
}
const defnUses = this.getUses(defn, currentFile);
for (const defnUse of defnUses) {
await this.visitAllNodesInGraphFromDefinition(defnUse.parent, defnUse.file);
}
}
async analyze(entryFile: string): Promise<Map<string, Set<string>>> {
const tree = await this.parseFile(entryFile);
if (!tree) return new Map();
const visit = async (node: Parser.SyntaxNode): Promise<void> => {
if (node.type === 'function_declaration') {
await this.visitAllNodesInGraphFromDefinition(node, entryFile);
}
else if (node.type === 'variable_declarator') {
const value = node.childForFieldName('value');
if (value?.type === 'arrow_function' || value?.type === 'function') {
await this.visitAllNodesInGraphFromDefinition(value, entryFile);
}
}
for (const child of node.children) {
await visit(child);
}
};
await visit(tree.rootNode);
return this.graph;
}
}
export async function runTreeSitter(filePath?: string): Promise<Map<string, Set<string>> | null> {
const editor = vscode.window.activeTextEditor;
if (!editor && !filePath) {
vscode.window.showWarningMessage('No active editor found');
return null;
}
try {
const targetPath = filePath ?? editor!.document.uri.fsPath;
const analyzer = new ProjectAnalyzer();
const graph = await analyzer.analyze(targetPath);
for (const [defn, calls] of graph) {
console.log(`${defn} calls: ${[...calls].join(', ')}`);
}
return graph;
} catch (error) {
console.error('Error analyzing file:', error);
vscode.window.showErrorMessage('Error analyzing file');
return null;
}
}
@@ -1,62 +0,0 @@
import * as vscode from 'vscode';
const legend = new vscode.SemanticTokensLegend([], []);
export async function findFunctions() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const document = editor.document;
const tokens = await vscode.commands.executeCommand<vscode.SemanticTokens>(
'vscode.provideDocumentSemanticTokens',
document.uri
);
if (!tokens) {
console.error('No tokens found');
return [];
}
const allTokens = decodeTokens(tokens, document);
return allTokens;
}
function decodeTokens(tokens: vscode.SemanticTokens, document: vscode.TextDocument) {
const data = tokens.data;
const decodedTokens = [];
let line = 0;
let character = 0;
for (let i = 0; i < data.length; i += 5) {
const deltaLine = data[i];
const deltaStartChar = data[i + 1];
const length = data[i + 2];
const tokenTypeIdx = data[i + 3];
const tokenModifierIdx = data[i + 4];
line += deltaLine;
character = deltaLine === 0 ? character + deltaStartChar : deltaStartChar;
const type = legend.tokenTypes[tokenTypeIdx] || `(${tokenTypeIdx})`;
const modifier = legend.tokenModifiers[tokenModifierIdx] || `(${tokenModifierIdx})`;
const tokenRange = new vscode.Range(line, character, line, character + length);
const tokenText = document.getText(tokenRange);
decodedTokens.push({
line,
startCharacter: character,
length,
type,
modifier,
text: tokenText,
});
console.log(`Token: '${tokenText}' | Type: ${type} | Modifier: ${modifier} | Line: ${line}, Character: ${character}`);
}
return decodedTokens;
}
+5045 -3901
View File
File diff suppressed because it is too large Load Diff
+27 -14
View File
@@ -1,14 +1,20 @@
{
"name": "code-oss-dev",
"name": "void-dev",
"productName": "Void",
"version": "1.94.0",
"distro": "ffcc24343ac46468a625666e5b9e673971dd1a1f",
"distro": "this is a commit number if we want to publish on npm",
"homepage": "https://voideditor.com",
"author": {
"name": "Microsoft Corporation"
"name": "Glass Devtools, Inc.",
"email": "andrew@voideditor.com"
},
"license": "MIT",
"main": "./out/main",
"private": true,
"scripts": {
"buildreact": "cd ./src/vs/workbench/contrib/void/browser/react/ && node build.js && cd ../../../../../../../",
"watchreact": "cd ./src/vs/workbench/contrib/void/browser/react/ && node build.js --watch && cd ../../../../../../../",
"watchreactd": "deemon npm run watchreact",
"test": "echo Please run any of the test scripts from the scripts folder.",
"test-browser": "npx playwright install && node test/unit/browser/index.js",
"test-browser-amd": "npx playwright install && node test/unit/browser/index.amd.js",
@@ -20,7 +26,7 @@
"preinstall": "node build/npm/preinstall.js",
"postinstall": "node build/npm/postinstall.js",
"compile": "node ./node_modules/gulp/bin/gulp.js compile",
"watch": "npm-run-all -lp watch-client watch-extensions",
"watch": "npm-run-all -lp watchreact watch-client watch-extensions",
"watch-amd": "npm-run-all -lp watch-client-amd watch-extensions",
"watchd": "deemon npm run watch",
"watch-webd": "deemon npm run watch-web",
@@ -72,8 +78,12 @@
"update-build-ts-version": "npm install typescript@next && tsc -p ./build/tsconfig.build.json"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.32.1",
"@floating-ui/react": "^0.27.3",
"@google/generative-ai": "^0.21.0",
"@microsoft/1ds-core-js": "^3.2.13",
"@microsoft/1ds-post-js": "^3.2.13",
"@mistralai/mistralai": "^1.4.0",
"@parcel/watcher": "2.1.0",
"@rrweb/record": "^2.0.0-alpha.17",
"@rrweb/types": "^2.0.0-alpha.17",
@@ -99,6 +109,8 @@
"@xterm/addon-webgl": "^0.19.0-beta.64",
"@xterm/headless": "^5.6.0-beta.64",
"@xterm/xterm": "^5.6.0-beta.64",
"ajv": "^8.17.1",
"diff": "^7.0.0",
"groq-sdk": "^0.9.0",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.2",
@@ -110,24 +122,29 @@
"native-keymap": "^3.3.5",
"native-watchdog": "^1.4.1",
"node-pty": "1.1.0-beta21",
"ollama": "^0.5.11",
"open": "^8.4.2",
"openai": "^4.76.1",
"posthog-node": "^4.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-syntax-highlighter": "^15.6.1",
"tas-client-umd": "0.2.0",
"v8-inspect-profiler": "^0.1.1",
"vscode-oniguruma": "1.7.0",
"vscode-regexpp": "^3.1.0",
"vscode-textmate": "9.1.0",
"yauzl": "^3.0.0",
"yazl": "^2.4.3"
"yazl": "^2.4.3",
"zod": "^3.24.1"
},
"devDependencies": {
"@anthropic-ai/sdk": "^0.32.1",
"@google/generative-ai": "^0.21.0",
"@playwright/test": "^1.46.1",
"@swc/core": "1.3.62",
"@types/cookie": "^0.3.3",
"@types/debug": "^4.1.5",
"@types/diff": "^6.0.0",
"@types/eslint": "^9.6.1",
"@types/gulp-svgmin": "^1.2.1",
"@types/http-proxy-agent": "^2.0.1",
"@types/kerberos": "^1.1.2",
@@ -168,7 +185,6 @@
"cssnano": "^6.0.3",
"debounce": "^1.0.0",
"deemon": "^1.8.0",
"diff": "^7.0.0",
"electron": "30.5.1",
"eslint": "8.36.0",
"eslint-plugin-header": "3.1.1",
@@ -211,9 +227,9 @@
"mocha": "^10.2.0",
"mocha-junit-reporter": "^2.2.1",
"mocha-multi-reporters": "^1.5.1",
"next": "^15.1.4",
"nodemon": "^3.1.9",
"npm-run-all": "^4.1.5",
"ollama": "^0.5.9",
"openai": "^4.71.1",
"opn": "^6.0.0",
"original-fs": "^1.2.0",
"os-browserify": "^0.3.0",
@@ -223,11 +239,8 @@
"postcss-nesting": "^12.0.2",
"pump": "^1.0.1",
"rcedit": "^1.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-syntax-highlighter": "^15.6.1",
"rimraf": "^2.7.1",
"scope-tailwind": "^1.0.1",
"scope-tailwind": "^1.0.5",
"sinon": "^12.0.1",
"sinon-test": "^3.1.3",
"source-map": "0.6.1",
+28 -74
View File
@@ -1,84 +1,38 @@
{
"nameShort": "Void",
"nameLong": "Void",
"applicationName": "code-oss",
"dataFolderName": ".vscode-oss",
"win32MutexName": "vscodeoss",
"applicationName": "void",
"dataFolderName": ".void-editor",
"win32MutexName": "voideditor",
"licenseName": "MIT",
"licenseUrl": "https://github.com/microsoft/vscode/blob/main/LICENSE.txt",
"serverLicenseUrl": "https://github.com/microsoft/vscode/blob/main/LICENSE.txt",
"licenseUrl": "https://github.com/voideditor/void/blob/main/LICENSE.txt",
"serverLicenseUrl": "https://github.com/voideditor/void/blob/main/LICENSE.txt",
"serverGreeting": [],
"serverLicense": [],
"serverLicensePrompt": "",
"serverApplicationName": "code-server-oss",
"serverDataFolderName": ".vscode-server-oss",
"tunnelApplicationName": "code-tunnel-oss",
"win32DirName": "Microsoft Code OSS",
"win32NameVersion": "Microsoft Code OSS",
"win32RegValueName": "CodeOSS",
"win32x64AppId": "{{D77B7E06-80BA-4137-BCF4-654B95CCEBC5}",
"win32arm64AppId": "{{D1ACE434-89C5-48D1-88D3-E2991DF85475}",
"win32x64UserAppId": "{{CC6B787D-37A0-49E8-AE24-8559A032BE0C}",
"win32arm64UserAppId": "{{3AEBF0C8-F733-4AD4-BADE-FDB816D53D7B}",
"win32AppUserModelId": "Microsoft.CodeOSS",
"win32ShellNameShort": "C&ode - OSS",
"win32TunnelServiceMutex": "vscodeoss-tunnelservice",
"win32TunnelMutex": "vscodeoss-tunnel",
"darwinBundleIdentifier": "com.visualstudio.code.oss",
"linuxIconName": "code-oss",
"serverApplicationName": "void-server",
"serverDataFolderName": ".void-server",
"tunnelApplicationName": "void-tunnel",
"win32DirName": "Void",
"win32NameVersion": "Void",
"win32RegValueName": "VoidEditor",
"win32x64AppId": "{{9D394D01-1728-45A7-B997-A6C82C5452C3}",
"win32arm64AppId": "{{0668DD58-2BDE-4101-8CDA-40252DF8875D}",
"win32x64UserAppId": "{{8BED5DC1-6C55-46E6-9FE6-18F7E6F7C7F1}",
"win32arm64UserAppId": "{{F6C87466-BC82-4A8F-B0FF-18CA366BA4D8}",
"win32AppUserModelId": "Void.Editor",
"win32ShellNameShort": "V&oid",
"win32TunnelServiceMutex": "void-tunnelservice",
"win32TunnelMutex": "void-tunnel",
"darwinBundleIdentifier": "com.voideditor.code",
"linuxIconName": "void-editor",
"licenseFileName": "LICENSE.txt",
"reportIssueUrl": "https://github.com/microsoft/vscode/issues/new",
"reportIssueUrl": "https://github.com/voideditor/void/issues/new",
"nodejsRepository": "https://nodejs.org",
"urlProtocol": "code-oss",
"webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/ef65ac1ba57f57f2a3961bfe94aa20481caca4c6/out/vs/workbench/contrib/webview/browser/pre/",
"builtInExtensions": [
{
"name": "ms-vscode.js-debug-companion",
"version": "1.1.3",
"sha256": "7380a890787452f14b2db7835dfa94de538caf358ebc263f9d46dd68ac52de93",
"repo": "https://github.com/microsoft/vscode-js-debug-companion",
"metadata": {
"id": "99cb0b7f-7354-4278-b8da-6cc79972169d",
"publisherId": {
"publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee",
"publisherName": "ms-vscode",
"displayName": "Microsoft",
"flags": "verified"
},
"publisherDisplayName": "Microsoft"
}
},
{
"name": "ms-vscode.js-debug",
"version": "1.93.0",
"sha256": "9339cb8e6b77f554df54d79e71f533279cb76b0f9b04c207f633bfd507442b6a",
"repo": "https://github.com/microsoft/vscode-js-debug",
"metadata": {
"id": "25629058-ddac-4e17-abba-74678e126c5d",
"publisherId": {
"publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee",
"publisherName": "ms-vscode",
"displayName": "Microsoft",
"flags": "verified"
},
"publisherDisplayName": "Microsoft"
}
},
{
"name": "ms-vscode.vscode-js-profile-table",
"version": "1.0.9",
"sha256": "3b62ee4276a2bbea3fe230f94b1d5edd915b05966090ea56f882e1e0ab53e1a6",
"repo": "https://github.com/microsoft/vscode-js-profile-visualizer",
"metadata": {
"id": "7e52b41b-71ad-457b-ab7e-0620f1fc4feb",
"publisherId": {
"publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee",
"publisherName": "ms-vscode",
"displayName": "Microsoft",
"flags": "verified"
},
"publisherDisplayName": "Microsoft"
}
}
]
"urlProtocol": "void-editor",
"extensionsGallery": {
"serviceUrl": "https://open-vsx.org/vscode/gallery",
"itemUrl": "https://open-vsx.org/vscode/item"
},
"builtInExtensions": []
}
+7
View File
@@ -29,6 +29,7 @@
"@xterm/headless": "^5.6.0-beta.64",
"@xterm/xterm": "^5.6.0-beta.64",
"cookie": "^0.4.0",
"debounced": "1.0.2",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.2",
"jschardet": "3.1.3",
@@ -396,6 +397,12 @@
"node": ">= 0.6"
}
},
"node_modules/debounced": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/debounced/-/debounced-1.0.2.tgz",
"integrity": "sha512-6GPv+l/OOtdb1DKNY70k5ubuJhVjtBjUnujC5vQAHHrMuvBpDXsTc91xEMTdeA3/v4swYHamtdB9XIN7DcKxpw==",
"license": "MIT"
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+1
View File
@@ -26,6 +26,7 @@
"cookie": "^0.4.0",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.2",
"debounced": "1.0.2",
"jschardet": "3.1.3",
"kerberos": "2.1.1",
"minimist": "^1.2.6",
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 813 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 29 KiB

+3 -3
View File
@@ -1,9 +1,9 @@
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<VisualElements
BackgroundColor="#2D2D30"
BackgroundColor="#FFFFFF"
ShowNameOnSquare150x150Logo="on"
Square150x150Logo="resources\app\resources\win32\code_150x150.png"
Square70x70Logo="resources\app\resources\win32\code_70x70.png"
ForegroundText="light"
ShortDisplayName="Code - OSS" />
ForegroundText="light"
ShortDisplayName="Void" />
</Application>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 395 B

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 B

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 KiB

+1 -1
View File
@@ -123,7 +123,7 @@ protocol.registerSchemesAsPrivileged([
},
{
scheme: 'vscode-file',
privileges: { secure: true, standard: true, supportFetchAPI: true, corsEnabled: true, codeCache: true }
privileges: { secure: true, standard: true, supportFetchAPI: true, corsEnabled: true, codeCache: true, }
}
]);
+1 -1
View File
@@ -159,7 +159,7 @@ export class InputBox extends Widget {
this.scrollableElement = new ScrollableElement(this.element, { vertical: ScrollbarVisibility.Auto });
if (this.options.flexibleWidth) {
this.input.setAttribute('wrap', 'off');
this.input.setAttribute('wrap', 'on');
this.mirror.style.whiteSpace = 'pre';
this.mirror.style.wordWrap = 'initial';
}
+12 -7
View File
@@ -121,10 +121,11 @@ import { normalizeNFC } from '../../base/common/normalization.js';
import { ICSSDevelopmentService, CSSDevelopmentService } from '../../platform/cssDev/node/cssDevService.js';
import { ExtensionSignatureVerificationService, IExtensionSignatureVerificationService } from '../../platform/extensionManagement/node/extensionSignatureVerificationService.js';
import { LLMMessageChannel } from '../../platform/void/electron-main/llmMessageChannel.js';
import { IMetricsService } from '../../platform/void/common/metricsService.js';
import { MetricsMainService } from '../../platform/void/electron-main/metricsMainService.js';
import { LLMMessageChannel } from '../../workbench/contrib/void/electron-main/llmMessageChannel.js';
import { IMetricsService } from '../../workbench/contrib/void/common/metricsService.js';
import { MetricsMainService } from '../../workbench/contrib/void/electron-main/metricsMainService.js';
import { VoidMainUpdateService } from '../../workbench/contrib/void/electron-main/voidUpdateMainService.js';
import { IVoidUpdateService } from '../../workbench/contrib/void/common/voidUpdateService.js';
/**
* The main VS Code application. There will only ever be one instance,
* even if the user starts many instances (e.g. from the command line).
@@ -1107,6 +1108,7 @@ export class CodeApplication extends Disposable {
// Void main process services (required for services with a channel for comm between browser and electron-main (node))
services.set(IMetricsService, new SyncDescriptor(MetricsMainService, undefined, false));
services.set(IVoidUpdateService, new SyncDescriptor(VoidMainUpdateService, undefined, false));
// Default Extensions Profile Init
services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService, undefined, true));
@@ -1242,12 +1244,15 @@ export class CodeApplication extends Disposable {
mainProcessElectronServer.registerChannel('logger', loggerChannel);
sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));
// Void
// Void - use loggerChannel as reference
const metricsChannel = ProxyChannel.fromService(accessor.get(IMetricsService), disposables);
mainProcessElectronServer.registerChannel('void-channel-metrics', metricsChannel);
const sendLLMMessageChannel = new LLMMessageChannel(accessor.get(IMetricsService));
mainProcessElectronServer.registerChannel('void-channel-sendLLMMessage', sendLLMMessageChannel);
const voidUpdatesChannel = ProxyChannel.fromService(accessor.get(IVoidUpdateService), disposables);
mainProcessElectronServer.registerChannel('void-channel-update', voidUpdatesChannel);
const llmMessageChannel = new LLMMessageChannel(accessor.get(IMetricsService));
mainProcessElectronServer.registerChannel('void-channel-llmMessageService', llmMessageChannel);
// Extension Host Debug Broadcasting
const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService));
@@ -6,6 +6,11 @@ import { ICodeEditor, IViewZone } from '../../editorBrowser.js';
import { IRange } from '../../../common/core/range.js';
import { EditorOption } from '../../../common/config/editorOptions.js';
// THIS FILE IS OLD + UNUSED!!!
// SEE inlineDiffsService.ts INSTEAD.
export interface IInlineDiffService {
readonly _serviceBrand: undefined;
addDiff(editor: ICodeEditor, originalText: string, modifiedRange: IRange): void;
@@ -223,6 +223,11 @@ export class BaseEditorSimpleWorker implements IDisposable, IWorkerTextModelSync
private static readonly _diffLimit = 100000;
public async $computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[], pretty: boolean): Promise<TextEdit[]> {
return this.$Void_computeMoreMinimalEdits(modelUrl, edits, pretty)
}
// Void added this as non async
public $Void_computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[], pretty: boolean): TextEdit[] {
const model = this._getModel(modelUrl);
if (!model) {
return edits;
@@ -22,7 +22,7 @@ export class ExpandLineSelectionAction extends EditorAction {
kbOpts: {
weight: KeybindingWeight.EditorCore,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KeyL
primary: KeyMod.CtrlCmd | KeyCode.KeyM // Void changed this to Cmd+M
},
});
}
@@ -403,6 +403,7 @@ function isVersionValid(currentVersion: string, date: ProductDate, requestedVers
}
if (!isValidVersion(currentVersion, date, desiredVersion)) {
// Void - ignore not compatible
notices.push(nls.localize('versionMismatch', "Extension is not compatible with Code {0}. Extension requires: {1}.", currentVersion, requestedVersion));
return false;
}
@@ -64,7 +64,8 @@ export const enum KeybindingWeight {
EditorContrib = 100,
WorkbenchContrib = 200,
BuiltinExtension = 300,
ExternalExtension = 400
ExternalExtension = 400,
VoidExtension = 605, // Void - must trump any external extension
}
export interface ICommandAndKeybindingRule extends IKeybindingRule {
@@ -31,25 +31,29 @@ export class ServerTelemetryService extends TelemetryService implements IServerT
}
override publicLog(eventName: string, data?: ITelemetryData) {
if (this._injectedTelemetryLevel < TelemetryLevel.USAGE) {
return;
}
return super.publicLog(eventName, data);
// Void commented this out
// if (this._injectedTelemetryLevel < TelemetryLevel.USAGE) {
// return;
// }
// return super.publicLog(eventName, data);
}
override publicLog2<E extends ClassifiedEvent<OmitMetadata<T>> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
return this.publicLog(eventName, data as ITelemetryData | undefined);
// Void commented this out
// return this.publicLog(eventName, data as ITelemetryData | undefined);
}
override publicLogError(errorEventName: string, data?: ITelemetryData) {
if (this._injectedTelemetryLevel < TelemetryLevel.ERROR) {
return Promise.resolve(undefined);
}
return super.publicLogError(errorEventName, data);
// Void commented this out
// if (this._injectedTelemetryLevel < TelemetryLevel.ERROR) {
// return Promise.resolve(undefined);
// }
// return super.publicLogError(errorEventName, data);
}
override publicLogError2<E extends ClassifiedEvent<OmitMetadata<T>> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
return this.publicLogError(eventName, data as ITelemetryData | undefined);
// Void commented this out
// return this.publicLogError(eventName, data as ITelemetryData | undefined);
}
async updateInjectedTelemetryLevel(telemetryLevel: TelemetryLevel): Promise<void> {
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { DisposableStore } from '../../../base/common/lifecycle.js';
import { mixin } from '../../../base/common/objects.js';
// import { mixin } from '../../../base/common/objects.js';
import { isWeb } from '../../../base/common/platform.js';
import { escapeRegExpCharacters } from '../../../base/common/strings.js';
import { localize } from '../../../nls.js';
@@ -15,7 +15,8 @@ import { IProductService } from '../../product/common/productService.js';
import { Registry } from '../../registry/common/platform.js';
import { ClassifiedEvent, IGDPRProperty, OmitMetadata, StrictPropertyCheck } from './gdprTypings.js';
import { ITelemetryData, ITelemetryService, TelemetryConfiguration, TelemetryLevel, TELEMETRY_CRASH_REPORTER_SETTING_ID, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SECTION_ID, TELEMETRY_SETTING_ID, ICommonProperties } from './telemetry.js';
import { cleanData, getTelemetryLevel, ITelemetryAppender } from './telemetryUtils.js';
import { getTelemetryLevel, ITelemetryAppender } from './telemetryUtils.js';
// import { cleanData } from './telemetryUtils.js';
export interface ITelemetryServiceConfig {
appenders: ITelemetryAppender[];
@@ -38,7 +39,7 @@ export class TelemetryService implements ITelemetryService {
readonly firstSessionDate: string;
readonly msftInternal: boolean | undefined;
private _appenders: ITelemetryAppender[];
// private _appenders: ITelemetryAppender[];
private _commonProperties: ICommonProperties;
private _experimentProperties: { [name: string]: string } = {};
private _piiPaths: string[];
@@ -53,7 +54,7 @@ export class TelemetryService implements ITelemetryService {
@IConfigurationService private _configurationService: IConfigurationService,
@IProductService private _productService: IProductService
) {
this._appenders = config.appenders;
// this._appenders = config.appenders;
this._commonProperties = config.commonProperties ?? Object.create(null);
this.sessionId = this._commonProperties['sessionID'] as string;
@@ -121,44 +122,47 @@ export class TelemetryService implements ITelemetryService {
this._disposables.dispose();
}
private _log(eventName: string, eventLevel: TelemetryLevel, data?: ITelemetryData) {
// don't send events when the user is optout
if (this._telemetryLevel < eventLevel) {
return;
}
// Void commented this out
// private _log(eventName: string, eventLevel: TelemetryLevel, data?: ITelemetryData) {
// // don't send events when the user is optout
// if (this._telemetryLevel < eventLevel) {
// return;
// }
// add experiment properties
data = mixin(data, this._experimentProperties);
// // add experiment properties
// data = mixin(data, this._experimentProperties);
// remove all PII from data
data = cleanData(data as Record<string, any>, this._cleanupPatterns);
// // remove all PII from data
// data = cleanData(data as Record<string, any>, this._cleanupPatterns);
// add common properties
data = mixin(data, this._commonProperties);
// // add common properties
// data = mixin(data, this._commonProperties);
// Log to the appenders of sufficient level
this._appenders.forEach(a => a.log(eventName, data));
}
// // Log to the appenders of sufficient level
// this._appenders.forEach(a => a.log(eventName, data));
// }
publicLog(eventName: string, data?: ITelemetryData) {
this._log(eventName, TelemetryLevel.USAGE, data);
// this._log(eventName, TelemetryLevel.USAGE, data);
}
publicLog2<E extends ClassifiedEvent<OmitMetadata<T>> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
this.publicLog(eventName, data as ITelemetryData);
// this.publicLog(eventName, data as ITelemetryData);
}
publicLogError(errorEventName: string, data?: ITelemetryData) {
if (!this._sendErrorTelemetry) {
return;
}
// Void commented this out
// if (!this._sendErrorTelemetry) {
// return;
// }
// Send error event and anonymize paths
this._log(errorEventName, TelemetryLevel.ERROR, data);
// // Send error event and anonymize paths
// this._log(errorEventName, TelemetryLevel.ERROR, data);
}
publicLogError2<E extends ClassifiedEvent<OmitMetadata<T>> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
this.publicLogError(eventName, data as ITelemetryData);
// Void commented this out
// this.publicLogError(eventName, data as ITelemetryData);
}
}
@@ -3,11 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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';
import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js';
// import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js';
import { ILifecycleMainService, LifecycleMainPhase } from '../../lifecycle/electron-main/lifecycleMainService.js';
import { ILogService } from '../../log/common/log.js';
import { IProductService } from '../../product/common/productService.js';
@@ -15,7 +16,10 @@ import { IRequestService } from '../../request/common/request.js';
import { AvailableForDownload, DisablementReason, IUpdateService, State, StateType, UpdateType } from '../common/update.js';
export function createUpdateURL(platform: string, quality: string, productService: IProductService): string {
return `${productService.updateUrl}/api/update/${platform}/${quality}/${productService.commit}`;
// 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}`;
}
export type UpdateNotAvailableClassification = {
@@ -57,7 +61,7 @@ export abstract class AbstractUpdateService implements IUpdateService {
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
@IRequestService protected requestService: IRequestService,
@ILogService protected logService: ILogService,
@IProductService protected readonly productService: IProductService
@IProductService protected readonly productService: IProductService,
) {
lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen)
.finally(() => this.initialize());
@@ -70,75 +74,35 @@ export abstract class AbstractUpdateService implements IUpdateService {
*/
protected async initialize(): Promise<void> {
if (!this.environmentMainService.isBuilt) {
console.log('is NOT built, canceling update service')
this.setState(State.Disabled(DisablementReason.NotBuilt));
return; // updates are never enabled when running out of sources
}
console.log('is built, continuing with update service')
if (this.environmentMainService.disableUpdates) {
this.setState(State.Disabled(DisablementReason.DisabledByEnvironment));
this.logService.info('update#ctor - updates are disabled by the environment');
return;
}
if (!this.productService.updateUrl || !this.productService.commit) {
this.setState(State.Disabled(DisablementReason.MissingConfiguration));
this.logService.info('update#ctor - updates are disabled as there is no update URL');
return;
}
const updateMode = this.configurationService.getValue<'none' | 'manual' | 'start' | 'default'>('update.mode');
const quality = this.getProductQuality(updateMode);
if (!quality) {
this.setState(State.Disabled(DisablementReason.ManuallyDisabled));
this.logService.info('update#ctor - updates are disabled by user preference');
return;
}
this.url = this.buildUpdateFeedUrl(quality);
this.url = this.doBuildUpdateFeedUrl('stable');
if (!this.url) {
this.setState(State.Disabled(DisablementReason.InvalidConfiguration));
this.logService.info('update#ctor - updates are disabled as the update URL is badly formed');
return;
}
// hidden setting
if (this.configurationService.getValue<boolean>('_update.prss')) {
const url = new URL(this.url);
url.searchParams.set('prss', 'true');
this.url = url.toString();
}
this.setState(State.Disabled(DisablementReason.ManuallyDisabled));
this.setState(State.Idle(this.getUpdateType()));
if (updateMode === 'manual') {
this.logService.info('update#ctor - manual checks only; automatic updates are disabled by user preference');
return;
}
// Void - temporarily disabled while we figure out how to do this the right way
if (updateMode === 'start') {
this.logService.info('update#ctor - startup checks only; automatic updates are disabled by user preference');
// this.setState(State.Idle(this.getUpdateType()));
// Check for updates only once after 30 seconds
setTimeout(() => this.checkForUpdates(false), 30 * 1000);
} else {
// Start checking for updates after 30 seconds
this.scheduleCheckForUpdates(30 * 1000).then(undefined, err => this.logService.error(err));
}
// start checking for updates after 10 seconds
// this.scheduleCheckForUpdates(10 * 1000).then(undefined, err => this.logService.error(err));
}
private getProductQuality(updateMode: string): string | undefined {
return updateMode === 'none' ? undefined : this.productService.quality;
}
private scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise<void> {
return timeout(delay)
.then(() => this.checkForUpdates(false))
.then(() => {
// Check again after 1 hour
return 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);
@@ -160,6 +124,7 @@ export abstract class AbstractUpdateService implements IUpdateService {
await this.doDownloadUpdate(this.state);
}
// override implemented by windows and linux
protected async doDownloadUpdate(state: AvailableForDownload): Promise<void> {
// noop
}
@@ -174,6 +139,7 @@ export abstract class AbstractUpdateService implements IUpdateService {
await this.doApplyUpdate();
}
// windows overrides this
protected async doApplyUpdate(): Promise<void> {
// noop
}
@@ -236,6 +202,6 @@ export abstract class AbstractUpdateService implements IUpdateService {
// noop
}
protected abstract buildUpdateFeedUrl(quality: string): string | undefined;
protected abstract doBuildUpdateFeedUrl(quality: string): string | undefined;
protected abstract doCheckForUpdates(context: any): void;
}
@@ -34,7 +34,7 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau
@IEnvironmentMainService environmentMainService: IEnvironmentMainService,
@IRequestService requestService: IRequestService,
@ILogService logService: ILogService,
@IProductService productService: IProductService
@IProductService productService: IProductService,
) {
super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService);
@@ -73,7 +73,7 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau
this.setState(State.Idle(UpdateType.Archive, message));
}
protected buildUpdateFeedUrl(quality: string): string | undefined {
protected doBuildUpdateFeedUrl(quality: string): string | undefined {
let assetID: string;
if (!this.productService.darwinUniversalAssetId) {
assetID = process.arch === 'x64' ? 'darwin' : 'darwin-arm64';
@@ -30,7 +30,7 @@ export class LinuxUpdateService extends AbstractUpdateService {
super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService);
}
protected buildUpdateFeedUrl(quality: string): string {
protected doBuildUpdateFeedUrl(quality: string): string {
return createUpdateURL(`linux-${process.arch}`, quality, this.productService);
}
@@ -67,7 +67,7 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
@ILogService logService: ILogService,
@IFileService private readonly fileService: IFileService,
@INativeHostMainService private readonly nativeHostMainService: INativeHostMainService,
@IProductService productService: IProductService
@IProductService productService: IProductService,
) {
super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService);
@@ -99,7 +99,7 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
await super.initialize();
}
protected buildUpdateFeedUrl(quality: string): string | undefined {
protected doBuildUpdateFeedUrl(quality: string): string | undefined {
let platform = `win32-${process.arch}`;
if (getUpdateType() === UpdateType.Archive) {
@@ -1,105 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
import { ProxyOnTextPayload, ProxyOnErrorPayload, ProxyOnFinalMessagePayload, LLMMessageServiceParams, ProxyLLMMessageParams, ProxyLLMMessageAbortParams } from '../common/llmMessageTypes.js';
import { IChannel } from '../../../base/parts/ipc/common/ipc.js';
import { IMainProcessService } from '../../ipc/common/mainProcessService.js';
import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
import { generateUuid } from '../../../base/common/uuid.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { Event } from '../../../base/common/event.js';
import { IDisposable } from '../../../base/common/lifecycle.js';
// BROWSER IMPLEMENTATION OF SENDLLMMESSAGE
export const ISendLLMMessageService = createDecorator<ISendLLMMessageService>('sendLLMMessageService');
// defines an interface that node/ creates and browser/ uses
export interface ISendLLMMessageService {
readonly _serviceBrand: undefined;
sendLLMMessage: (params: LLMMessageServiceParams) => string;
abort: (requestId: string) => void;
}
export class SendLLMMessageService implements ISendLLMMessageService {
readonly _serviceBrand: undefined;
private readonly channel: IChannel;
private readonly _disposablesOfRequestId: Record<string, IDisposable[]> = {}
constructor(
@IMainProcessService mainProcessService: IMainProcessService // used as a renderer (only usable on client side)
) {
this.channel = mainProcessService.getChannel('void-channel-sendLLMMessage')
// const service = ProxyChannel.toService<LLMMessageChannel>(mainProcessService.getChannel('void-channel-sendLLMMessage')); // lets you call it like a service, not needed here
}
_addDisposable(requestId: string, disposable: IDisposable) {
if (!this._disposablesOfRequestId[requestId]) {
this._disposablesOfRequestId[requestId] = []
}
this._disposablesOfRequestId[requestId].push(disposable)
}
sendLLMMessage(params: LLMMessageServiceParams) {
const requestId_ = generateUuid();
const { onText, onFinalMessage, onError, ...proxyParams } = params;
// listen for listenerName='onText' | 'onFinalMessage' | 'onError', and call the original function on it
const onTextEvent: Event<ProxyOnTextPayload> = this.channel.listen('onText')
this._addDisposable(requestId_,
onTextEvent(e => {
if (requestId_ !== e.requestId) return;
onText(e)
})
)
const onFinalMessageEvent: Event<ProxyOnFinalMessagePayload> = this.channel.listen('onFinalMessage')
this._addDisposable(requestId_,
onFinalMessageEvent(e => {
if (requestId_ !== e.requestId) return;
onFinalMessage(e)
this._dispose(requestId_)
})
)
const onErrorEvent: Event<ProxyOnErrorPayload> = this.channel.listen('onError')
this._addDisposable(requestId_,
onErrorEvent(e => {
if (requestId_ !== e.requestId) return;
console.log('event onError', JSON.stringify(e))
onError(e)
this._dispose(requestId_)
})
)
// params will be stripped of all its functions
this.channel.call('sendLLMMessage', { ...proxyParams, requestId: requestId_ } satisfies ProxyLLMMessageParams);
return requestId_
}
private _dispose(requestId: string) {
if (!(requestId in this._disposablesOfRequestId)) return
for (const disposable of this._disposablesOfRequestId[requestId]) {
disposable.dispose()
}
delete this._disposablesOfRequestId[requestId]
}
abort(requestId: string) {
this.channel.call('abort', { requestId } satisfies ProxyLLMMessageAbortParams);
this._dispose(requestId)
}
}
registerSingleton(ISendLLMMessageService, SendLLMMessageService, InstantiationType.Delayed);
@@ -1,32 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
import { IChannel } from '../../../base/parts/ipc/common/ipc.js';
import { IMainProcessService } from '../../ipc/common/mainProcessService.js';
import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
import { IMetricsService } from '../common/metricsService.js';
// BROWSER IMPLEMENTATION, calls channel
export class MetricsService implements IMetricsService {
readonly _serviceBrand: undefined;
private readonly channel: IChannel;
constructor(
@IMainProcessService mainProcessService: IMainProcessService // (only usable on client side)
) {
this.channel = mainProcessService.getChannel('void-channel-metrics')
}
// call capture on the channel
capture(...params: Parameters<IMetricsService['capture']>) {
this.channel.call('capture', params);
}
}
registerSingleton(IMetricsService, MetricsService, InstantiationType.Eager);
-210
View File
@@ -1,210 +0,0 @@
const configEnum = <EnumArr extends readonly string[]>(description: string, defaultVal: EnumArr[number], enumArr: EnumArr) => {
return {
description,
defaultVal,
enumArr,
}
}
const configString = (description: string, defaultVal: string) => {
return {
description,
defaultVal,
enumArr: undefined,
}
}
// fields you can customize (don't forget 'default' - it isn't included here!)
export const nonDefaultConfigFields = [
'anthropic',
'openAI',
'gemini',
'greptile',
'groq',
'ollama',
'openRouter',
'openAICompatible',
'azure',
] as const
export const voidConfigInfo: Record<
typeof nonDefaultConfigFields[number] | 'default', {
[prop: string]: {
description: string;
enumArr?: readonly string[] | undefined;
defaultVal: string;
};
}
> = {
default: {
whichApi: configEnum(
'API Provider.',
'anthropic',
nonDefaultConfigFields,
),
maxTokens: configEnum(
'Max number of tokens to output.',
'1024',
[
'default', // this will be parseInt'd into NaN and ignored by the API. Anything that's not a number has this behavior.
'1024',
'2048',
'4096',
'8192'
] as const,
),
},
anthropic: {
apikey: configString('Anthropic API key.', ''),
model: configEnum(
'Anthropic model to use.',
'claude-3-5-sonnet-20240620',
[
'claude-3-5-sonnet-20240620',
'claude-3-opus-20240229',
'claude-3-sonnet-20240229',
'claude-3-haiku-20240307'
] as const,
),
},
openAI: {
apikey: configString('OpenAI API key.', ''),
model: configEnum(
'OpenAI model to use.',
'gpt-4o',
[
'o1-preview',
'o1-mini',
'gpt-4o',
'gpt-4o-2024-05-13',
'gpt-4o-2024-08-06',
'gpt-4o-mini',
'gpt-4o-mini-2024-07-18',
'gpt-4-turbo',
'gpt-4-turbo-2024-04-09',
'gpt-4-turbo-preview',
'gpt-4-0125-preview',
'gpt-4-1106-preview',
'gpt-4',
'gpt-4-0613',
'gpt-3.5-turbo-0125',
'gpt-3.5-turbo',
'gpt-3.5-turbo-1106'
] as const
),
},
greptile: {
apikey: configString('Greptile API key.', ''),
githubPAT: configString('Github PAT that Greptile uses to access your repository', ''),
remote: configEnum(
'Repo location',
'github',
[
'github',
'gitlab'
] as const
),
repository: configString('Repository identifier in "owner / repository" format.', ''),
branch: configString('Name of the branch to use.', 'main'),
},
groq: {
apikey: configString('Groq API key.', ''),
model: configEnum(
'Groq model to use.',
'mixtral-8x7b-32768',
[
"mixtral-8x7b-32768",
"llama2-70b-4096",
"gemma-7b-it"
] as const
),
},
ollama: {
endpoint: configString(
'The endpoint of your Ollama instance. Start Ollama by running `OLLAMA_ORIGINS="vscode - webview://*" ollama serve`.',
'http://127.0.0.1:11434'
),
model: configEnum(
'Ollama model to use.',
'codestral',
['codestral', 'qwen2.5-coder', 'qwen2.5-coder:0.5b', 'qwen2.5-coder:1.5b', 'qwen2.5-coder:3b', 'qwen2.5-coder:7b', 'qwen2.5-coder:14b', 'qwen2.5-coder:32b', 'codegemma', 'codegemma:2b', 'codegemma:7b', 'codellama', 'codellama:7b', 'codellama:13b', 'codellama:34b', 'codellama:70b', 'codellama:code', 'codellama:python', 'command-r', 'command-r:35b', 'command-r-plus', 'command-r-plus:104b', 'deepseek-coder-v2', 'deepseek-coder-v2:16b', 'deepseek-coder-v2:236b', 'falcon2', 'falcon2:11b', 'firefunction-v2', 'firefunction-v2:70b', 'gemma', 'gemma:2b', 'gemma:7b', 'gemma2', 'gemma2:2b', 'gemma2:9b', 'gemma2:27b', 'llama2', 'llama2:7b', 'llama2:13b', 'llama2:70b', 'llama3', 'llama3:8b', 'llama3:70b', 'llama3-chatqa', 'llama3-chatqa:8b', 'llama3-chatqa:70b', 'llama3-gradient', 'llama3-gradient:8b', 'llama3-gradient:70b', 'llama3.1', 'llama3.1:8b', 'llama3.1:70b', 'llama3.1:405b', 'llava', 'llava:7b', 'llava:13b', 'llava:34b', 'llava-llama3', 'llava-llama3:8b', 'llava-phi3', 'llava-phi3:3.8b', 'mistral', 'mistral:7b', 'mistral-large', 'mistral-large:123b', 'mistral-nemo', 'mistral-nemo:12b', 'mixtral', 'mixtral:8x7b', 'mixtral:8x22b', 'moondream', 'moondream:1.8b', 'openhermes', 'openhermes:v2.5', 'phi3', 'phi3:3.8b', 'phi3:14b', 'phi3.5', 'phi3.5:3.8b', 'qwen', 'qwen:7b', 'qwen:14b', 'qwen:32b', 'qwen:72b', 'qwen:110b', 'qwen2', 'qwen2:0.5b', 'qwen2:1.5b', 'qwen2:7b', 'qwen2:72b', 'smollm', 'smollm:135m', 'smollm:360m', 'smollm:1.7b'] as const
),
},
openRouter: {
model: configString(
'OpenRouter model to use.',
'openai/gpt-4o'
),
apikey: configString('OpenRouter API key.', ''),
},
openAICompatible: {
endpoint: configString('The baseUrl (exluding /chat/completions).', 'http://127.0.0.1:11434/v1'),
model: configString('The name of the model to use.', 'gpt-4o'),
apikey: configString('Your API key.', ''),
},
azure: {
// 'void.azure.apiKey': {
// 'type': 'string',
// 'description': 'Azure API key.'
// },
// 'void.azure.deploymentId': {
// 'type': 'string',
// 'description': 'Azure API deployment ID.'
// },
// 'void.azure.resourceName': {
// 'type': 'string',
// 'description': 'Name of the Azure OpenAI resource. Either this or `baseURL` can be used. \nThe resource name is used in the assembled URL: `https://{resourceName}.openai.azure.com/openai/deployments/{modelId}{path}`'
// },
// 'void.azure.providerSettings': {
// 'type': 'object',
// 'properties': {
// 'baseURL': {
// 'type': 'string',
// 'default': 'https://${resourceName}.openai.azure.com/openai/deployments',
// 'description': 'Azure API base URL.'
// },
// 'headers': {
// 'type': 'object',
// 'description': 'Custom headers to include in the requests.'
// }
// }
// },
},
gemini: {
apikey: configString('Google API key.', ''),
model: configEnum(
'Gemini model to use.',
'gemini-1.5-flash',
[
'gemini-1.5-flash',
'gemini-1.5-pro',
'gemini-1.5-flash-8b',
'gemini-1.0-pro'
] as const
),
},
}
// this is the type that comes with metadata like desc, default val, etc
export type VoidConfigInfo = typeof voidConfigInfo
export type VoidConfigField = keyof typeof voidConfigInfo // typeof configFields[number]
// this is the type that specifies the user's actual config
export type PartialVoidConfig = {
[K in keyof typeof voidConfigInfo]?: {
[P in keyof typeof voidConfigInfo[K]]?: typeof voidConfigInfo[K][P]['defaultVal']
}
}
export type VoidConfig = {
[K in keyof typeof voidConfigInfo]: {
[P in keyof typeof voidConfigInfo[K]]: typeof voidConfigInfo[K][P]['defaultVal']
}
}
@@ -1,59 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
import { VoidConfig } from './configTypes.js'
// ---------- type definitions ----------
export type OnText = (p: { newText: string, fullText: string }) => void
export type OnFinalMessage = (p: { fullText: string }) => void
export type OnError = (p: { error: Error | string }) => void
export type AbortRef = { current: (() => void) | null }
export type LLMMessage = {
role: 'system' | 'user' | 'assistant';
content: string;
}
export type LLMMessageServiceParams = {
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: OnError;
messages: LLMMessage[];
voidConfig: VoidConfig | null;
logging: {
loggingName: string,
};
}
export type SendLLMMMessageParams = {
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: OnError;
messages: LLMMessage[];
voidConfig: VoidConfig | null;
logging: {
loggingName: string,
};
abortRef: AbortRef;
}
// can't send functions across a proxy, use listeners instead
export const listenerNames = ['onText', 'onFinalMessage', 'onError'] as const
export type ProxyLLMMessageParams = Omit<LLMMessageServiceParams, typeof listenerNames[number]> & { requestId: string }
export type ProxyOnTextPayload = Parameters<OnText>[0] & { requestId: string }
export type ProxyOnFinalMessagePayload = Parameters<OnFinalMessage>[0] & { requestId: string }
export type ProxyOnErrorPayload = Parameters<OnError>[0] & { requestId: string }
export type ProxyLLMMessageAbortParams = { requestId: string }
@@ -1,10 +0,0 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
export interface IMetricsService {
readonly _serviceBrand: undefined;
capture(event: string, params: Record<string, any>): void;
}
export const IMetricsService = createDecorator<IMetricsService>('metricsService');
@@ -1,57 +0,0 @@
import Anthropic from '@anthropic-ai/sdk';
import { parseMaxTokensStr, SendLLMMessageFnTypeInternal } from './util.js';
// Anthropic
type LLMMessageAnthropic = {
role: 'user' | 'assistant';
content: string;
}
export const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => {
const thisConfig = voidConfig.anthropic
const anthropic = new Anthropic({ apiKey: thisConfig.apikey, dangerouslyAllowBrowser: true }); // defaults to process.env["ANTHROPIC_API_KEY"]
// find system messages and concatenate them
const systemMessage = messages
.filter(msg => msg.role === 'system')
.map(msg => msg.content)
.join('\n');
// remove system messages for Anthropic
const anthropicMessages = messages.filter(msg => msg.role !== 'system') as LLMMessageAnthropic[]
const stream = anthropic.messages.stream({
system: systemMessage,
messages: anthropicMessages,
model: thisConfig.model,
max_tokens: parseMaxTokensStr(voidConfig.default.maxTokens)!, // this might be undefined, but it will just throw an error for the user
});
// when receive text
stream.on('text', (newText, fullText) => {
onText({ newText, fullText })
})
// when we get the final message on this stream (or when error/fail)
stream.on('finalMessage', (claude_response) => {
// stringify the response's content
const content = claude_response.content.map(c => c.type === 'text' ? c.text : c.type).join('\n');
onFinalMessage({ fullText: content })
})
stream.on('error', (error) => {
// the most common error will be invalid API key (401), so we handle this with a nice message
if (error instanceof Anthropic.APIError && error.status === 401) {
onError({ error: 'Invalid API key.' })
}
else {
onError({ error })
}
})
// TODO need to test this to make sure it works, it might throw an error
_setAborter(() => stream.controller.abort())
};
@@ -1,48 +0,0 @@
import { Content, GoogleGenerativeAI, GoogleGenerativeAIFetchError } from '@google/generative-ai';
import { SendLLMMessageFnTypeInternal } from './util';
// Gemini
export const sendGeminiMsg: SendLLMMessageFnTypeInternal = async ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => {
let fullText = ''
const thisConfig = voidConfig.gemini
const genAI = new GoogleGenerativeAI(thisConfig.apikey);
const model = genAI.getGenerativeModel({ model: thisConfig.model });
// remove system messages that get sent to Gemini
// str of all system messages
const systemMessage = messages
.filter(msg => msg.role === 'system')
.map(msg => msg.content)
.join('\n');
// Convert messages to Gemini format
const geminiMessages: Content[] = messages
.filter(msg => msg.role !== 'system')
.map((msg, i) => ({
parts: [{ text: msg.content }],
role: msg.role === 'assistant' ? 'model' : 'user'
}))
model.generateContentStream({ contents: geminiMessages, systemInstruction: systemMessage, })
.then(async response => {
_setAborter(() => response.stream.return(fullText))
for await (const chunk of response.stream) {
const newText = chunk.text();
fullText += newText;
onText({ newText, fullText });
}
onFinalMessage({ fullText });
})
.catch((error) => {
if (error instanceof GoogleGenerativeAIFetchError && error.status === 400) {
onError({ error: 'Invalid API key.' });
}
else {
onError({ error });
}
})
}
@@ -1,64 +0,0 @@
// Greptile
// https://docs.greptile.com/api-reference/query
// https://docs.greptile.com/quickstart#sample-response-streamed
import { SendLLMMessageFnTypeInternal } from './util';
export const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => {
let fullText = ''
const thisConfig = voidConfig.greptile
fetch('https://api.greptile.com/v2/query', {
method: 'POST',
headers: {
'Authorization': `Bearer ${thisConfig.apikey}`,
'X-Github-Token': `${thisConfig.githubPAT}`,
'Content-Type': `application/json`,
},
body: JSON.stringify({
messages,
stream: true,
repositories: [thisConfig.repoinfo],
}),
})
// this is {message}\n{message}\n{message}...\n
.then(async response => {
const text = await response.text()
console.log('got greptile', text)
return JSON.parse(`[${text.trim().split('\n').join(',')}]`)
})
// TODO make this actually stream, right now it just sends one message at the end
// TODO add _setAborter() when add streaming
.then(async responseArr => {
for (const response of responseArr) {
const type: string = response['type']
const message = response['message']
// when receive text
if (type === 'message') {
fullText += message
onText({ newText: message, fullText })
}
else if (type === 'sources') {
const { filepath, linestart: _, lineend: _2 } = message as { filepath: string; linestart: number | null; lineend: number | null }
fullText += filepath
onText({ newText: filepath, fullText })
}
// type: 'status' with an empty 'message' means last message
else if (type === 'status') {
if (!message) {
onFinalMessage({ fullText })
}
}
}
})
.catch(error => {
onError({ error })
});
}
@@ -1,40 +0,0 @@
import Groq from 'groq-sdk';
import { SendLLMMessageFnTypeInternal } from './util';
import { parseMaxTokensStr } from './util.js';
// Groq
export const sendGroqMsg: SendLLMMessageFnTypeInternal = async ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => {
let fullText = '';
const groq = new Groq({
apiKey: voidConfig.groq.apikey,
dangerouslyAllowBrowser: true
});
await groq.chat.completions
.create({
messages: messages,
model: voidConfig.groq.model,
stream: true,
temperature: 0.7,
max_tokens: parseMaxTokensStr(voidConfig.default.maxTokens),
})
.then(async response => {
_setAborter(() => response.controller.abort())
// when receive text
for await (const chunk of response) {
const newText = chunk.choices[0]?.delta?.content || '';
if (newText) {
fullText += newText;
onText({ newText, fullText });
}
}
onFinalMessage({ fullText });
})
.catch(error => {
onError({ error });
})
};
@@ -1,47 +0,0 @@
import { Ollama, ErrorResponse } from 'ollama';
import { SendLLMMessageFnTypeInternal } from './util';
import { parseMaxTokensStr } from './util.js';
// Ollama
export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => {
const thisConfig = voidConfig.ollama
let fullText = ''
const ollama = new Ollama({ host: thisConfig.endpoint })
ollama.chat({
model: thisConfig.model,
messages: messages,
stream: true,
options: { num_predict: parseMaxTokensStr(voidConfig.default.maxTokens) } // this is max_tokens
})
.then(async stream => {
_setAborter(() => stream.abort())
// iterate through the stream
for await (const chunk of stream) {
const newText = chunk.message.content;
fullText += newText;
onText({ newText, fullText });
}
onFinalMessage({ fullText });
})
// when error/fail
.catch((error) => {
if (typeof error === 'object') {
const e = error.error as ErrorResponse['error']
if (e) {
const name = error.name ?? 'Error'
onError({ error: `${name}: ${e}` })
return;
}
}
onError({ error })
})
};
@@ -1,64 +0,0 @@
import OpenAI from 'openai';
import { SendLLMMessageFnTypeInternal } from './util';
import { parseMaxTokensStr } from './util.js';
// OpenAI, OpenRouter, OpenAICompatible
export const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => {
let fullText = ''
let openai: OpenAI
let options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
const maxTokens = parseMaxTokensStr(voidConfig.default.maxTokens)
if (voidConfig.default.whichApi === 'openAI') {
const thisConfig = voidConfig.openAI
openai = new OpenAI({ apiKey: thisConfig.apikey, dangerouslyAllowBrowser: true });
options = { model: thisConfig.model, messages: messages, stream: true, max_completion_tokens: maxTokens }
}
else if (voidConfig.default.whichApi === 'openRouter') {
const thisConfig = voidConfig.openRouter
openai = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1', apiKey: thisConfig.apikey, dangerouslyAllowBrowser: true,
defaultHeaders: {
'HTTP-Referer': 'https://voideditor.com', // Optional, for including your app on openrouter.ai rankings.
'X-Title': 'Void Editor', // Optional. Shows in rankings on openrouter.ai.
},
});
options = { model: thisConfig.model, messages: messages, stream: true, max_completion_tokens: maxTokens }
}
else if (voidConfig.default.whichApi === 'openAICompatible') {
const thisConfig = voidConfig.openAICompatible
openai = new OpenAI({ baseURL: thisConfig.endpoint, apiKey: thisConfig.apikey, dangerouslyAllowBrowser: true })
options = { model: thisConfig.model, messages: messages, stream: true, max_completion_tokens: maxTokens }
}
else {
console.error(`sendOpenAIMsg: invalid whichApi: ${voidConfig.default.whichApi}`)
throw new Error(`voidConfig.whichAPI was invalid: ${voidConfig.default.whichApi}`)
}
openai.chat.completions
.create(options)
.then(async response => {
_setAborter(() => response.controller.abort())
// when receive text
for await (const chunk of response) {
const newText = chunk.choices[0]?.delta?.content || '';
fullText += newText;
onText({ newText, fullText });
}
onFinalMessage({ fullText });
})
// when error/fail - this catches errors of both .create() and .then(for await)
.catch(error => {
if (error instanceof OpenAI.APIError && error.status === 401) {
onError({ error: 'Invalid API key.' });
}
else {
onError({ error });
}
})
};
@@ -1,109 +0,0 @@
import { SendLLMMMessageParams, OnText, OnFinalMessage, OnError } from '../../common/llmMessageTypes.js';
import { IMetricsService } from '../../common/metricsService.js';
import { sendAnthropicMsg } from './anthropic.js';
import { sendGeminiMsg } from './gemini.js';
import { sendGreptileMsg } from './greptile.js';
import { sendGroqMsg } from './groq.js';
import { sendOllamaMsg } from './ollama.js';
import { sendOpenAIMsg } from './openai.js';
export const sendLLMMessage = (
{
messages, onText: onText_, onFinalMessage: onFinalMessage_, onError: onError_,
abortRef: abortRef_, voidConfig, logging: { loggingName },
}: SendLLMMMessageParams,
metricsService: IMetricsService
) => {
if (!voidConfig) return;
// trim message content (Anthropic and other providers give an error if there is trailing whitespace)
messages = messages.map(m => ({ ...m, content: m.content.trim() }))
// only captures number of messages and message "shape", no actual code, instructions, prompts, etc
const captureChatEvent = (eventId: string, extras?: object) => {
metricsService.capture(eventId, {
whichApi: voidConfig.default['whichApi'],
numMessages: messages?.length,
messagesShape: messages?.map(msg => ({ role: msg.role, length: msg.content.length })),
version: '2024-11-14',
...extras,
})
}
const submit_time = new Date()
let _fullTextSoFar = ''
let _aborter: (() => void) | null = null
let _setAborter = (fn: () => void) => { _aborter = fn }
let _didAbort = false
const onText: OnText = ({ newText, fullText }) => {
if (_didAbort) return
onText_({ newText, fullText })
_fullTextSoFar = fullText
}
const onFinalMessage: OnFinalMessage = ({ fullText }) => {
if (_didAbort) return
captureChatEvent(`${loggingName} - Received Full Message`, { messageLength: fullText.length, duration: new Date().getMilliseconds() - submit_time.getMilliseconds() })
onFinalMessage_({ fullText })
}
const onError: OnError = ({ error }) => {
if (_didAbort) return
console.error('sendLLMMessage onError:', error)
captureChatEvent(`${loggingName} - Error`, { error })
onError_({ error })
}
const onAbort = () => {
captureChatEvent(`${loggingName} - Abort`, { messageLengthSoFar: _fullTextSoFar.length })
try { _aborter?.() } // aborter sometimes automatically throws an error
catch (e) { }
_didAbort = true
}
abortRef_.current = onAbort
captureChatEvent(`${loggingName} - Sending Message`, { messageLength: messages[messages.length - 1]?.content.length })
try {
switch (voidConfig.default.whichApi) {
case 'anthropic':
sendAnthropicMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, });
break;
case 'openAI':
case 'openRouter':
case 'openAICompatible':
sendOpenAIMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, });
break;
case 'gemini':
sendGeminiMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, });
break;
case 'ollama':
sendOllamaMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, });
break;
case 'greptile':
sendGreptileMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, });
break;
case 'groq':
sendGroqMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, });
break;
default:
onError({ error: `Error: whichApi was "${voidConfig.default.whichApi}", which is not recognized!` })
break;
}
}
catch (error) {
if (error instanceof Error) { onError({ error }) }
else { onError({ error: `Unexpected Error in sendLLMMessage: ${error}` }); }
// ; (_aborter as any)?.()
// _didAbort = true
}
}
@@ -1,21 +0,0 @@
import { VoidConfig } from '../../common/configTypes'
import { LLMMessage, OnText, OnFinalMessage, OnError } from '../../common/llmMessageTypes'
export const parseMaxTokensStr = (maxTokensStr: string) => {
// parse the string but only if the full string is a valid number, eg parseInt('100abc') should return NaN
const int = isNaN(Number(maxTokensStr)) ? undefined : parseInt(maxTokensStr)
if (Number.isNaN(int))
return undefined
return int
}
export type SendLLMMessageFnTypeInternal = (params: {
messages: LLMMessage[];
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: OnError;
voidConfig: VoidConfig;
_setAborter: (aborter: () => void) => void;
}) => void
@@ -1,95 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
// registered in app.ts
// code convention is to make a service responsible for this stuff, and not a channel, but having fewer files is simpler...
import { IServerChannel } from '../../../base/parts/ipc/common/ipc.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { listenerNames, ProxyOnTextPayload, ProxyOnErrorPayload, ProxyOnFinalMessagePayload, ProxyLLMMessageParams, AbortRef, SendLLMMMessageParams, ProxyLLMMessageAbortParams } from '../common/llmMessageTypes.js';
import { sendLLMMessage } from './llmMessage/sendLLMMessage.js'
import { IMetricsService } from '../common/metricsService.js';
// NODE IMPLEMENTATION - calls actual sendLLMMessage() and returns listeners to it
export class LLMMessageChannel implements IServerChannel {
private readonly _onText = new Emitter<ProxyOnTextPayload>();
readonly onText = this._onText.event;
private readonly _onFinalMessage = new Emitter<ProxyOnFinalMessagePayload>();
readonly onFinalMessage = this._onFinalMessage.event;
private readonly _onError = new Emitter<ProxyOnErrorPayload>();
readonly onError = this._onError.event;
private readonly _abortRefOfRequestId: Record<string, AbortRef> = {}
constructor(
private readonly metricsService: IMetricsService
) { }
// browser uses this to listen for changes
listen(_: unknown, event: typeof listenerNames[number]): Event<any> {
if (event === 'onText') {
return this.onText;
}
else if (event === 'onFinalMessage') {
return this.onFinalMessage;
}
else if (event === 'onError') {
return this.onError;
}
else {
throw new Error(`Event not found: ${event}`);
}
}
// browser uses this to call
async call(_: unknown, command: string, params: any): Promise<any> {
try {
if (command === 'sendLLMMessage') {
this._callSendLLMMessage(params)
}
else if (command === 'abort') {
this._callAbort(params)
}
else {
throw new Error(`Void sendLLM: command "${command}" not recognized.`)
}
}
catch (e) {
console.log('llmMessageChannel: Call Error:', e)
}
}
// the only place sendLLMMessage is actually called
private _callSendLLMMessage(params: ProxyLLMMessageParams) {
const { requestId } = params;
if (!(requestId in this._abortRefOfRequestId))
this._abortRefOfRequestId[requestId] = { current: null }
const mainThreadParams: SendLLMMMessageParams = {
...params,
onText: ({ newText, fullText }) => { this._onText.fire({ requestId, newText, fullText }); },
onFinalMessage: ({ fullText }) => { this._onFinalMessage.fire({ requestId, fullText }); },
onError: ({ error }) => { this._onError.fire({ requestId, error }); },
abortRef: this._abortRefOfRequestId[requestId],
}
sendLLMMessage(mainThreadParams, this.metricsService);
}
private _callAbort(params: ProxyLLMMessageAbortParams) {
const { requestId } = params;
if (!(requestId in this._abortRefOfRequestId)) return
this._abortRefOfRequestId[requestId].current?.()
delete this._abortRefOfRequestId[requestId]
}
}
@@ -1,52 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from '../../../base/common/lifecycle.js';
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
import { IMetricsService } from '../common/metricsService.js';
import { PostHog } from 'posthog-node'
// posthog.init('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2', { api_host: 'https://us.i.posthog.com', })
// const buildEnv = 'development';
// const buildNumber = '1.0.0';
// const isMac = process.platform === 'darwin';
// // TODO use commandKey
// const commandKey = isMac ? '⌘' : 'Ctrl';
// const systemInfo = {
// buildEnv,
// buildNumber,
// isMac,
// }
export class MetricsMainService extends Disposable implements IMetricsService {
_serviceBrand: undefined;
readonly _distinctId: string
readonly client: PostHog
constructor(
@ITelemetryService private readonly _telemetryService: ITelemetryService
) {
super()
this.client = new PostHog('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2', { host: 'https://us.i.posthog.com', })
const { devDeviceId, firstSessionDate, machineId } = this._telemetryService
this._distinctId = devDeviceId
this.client.identify({ distinctId: devDeviceId, properties: { firstSessionDate, machineId } })
console.log('Void posthog metrics info:', JSON.stringify({ devDeviceId, firstSessionDate, machineId }))
}
capture: IMetricsService['capture'] = (event, params) => {
console.log('Capturing', { event, params })
console.log('full capture:', { distinctId: this._distinctId, event, properties: params })
this.client.capture({ distinctId: this._distinctId, event, properties: params })
}
}
@@ -742,18 +742,6 @@ export class CodeWindow extends BaseWindow implements ICodeWindow {
cb({ cancel: false, requestHeaders: Object.assign(details.requestHeaders, headers) });
});
// // Void: send from https://
// this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, async (details, cb) => {
// // const voidConfig = this.voidConfigStateService.state.voidConfig
// // const whichApi = voidConfig.default['whichApi']
// const endpoint = 'http://127.' //string | undefined = voidConfig[whichApi as VoidConfigField].endpoint
// if (endpoint && details.url.startsWith(endpoint)) {
// details.requestHeaders['Origin'] = 'https://app.voideditor.com'
// }
// cb({ cancel: false, requestHeaders: details.requestHeaders });
// });
}
@@ -14,6 +14,8 @@ import { WorkspaceEdit } from '../../../editor/common/languages.js';
// import { IHistoryService } from '../../services/history/common/history.js';
// VOID: THIS FILE IS OUTDATED!!!!!! No longer used anywhere.
@extHostNamedCustomer(MainContext.MainThreadInlineDiff)
export class MainThreadInlineDiff extends Disposable implements MainThreadInlineDiffShape {
@@ -239,7 +239,7 @@ MenuRegistry.appendMenuItems([{
group: '3_workbench_layout_move',
command: {
id: ToggleSidebarPositionAction.ID,
title: localize('move second sidebar left', "Move Secondary Side Bar Left")
title: localize('move second sidebar left', "Move Void Side Bar Left")
},
when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))),
order: 1
@@ -250,7 +250,7 @@ MenuRegistry.appendMenuItems([{
group: '3_workbench_layout_move',
command: {
id: ToggleSidebarPositionAction.ID,
title: localize('move second sidebar right', "Move Secondary Side Bar Right")
title: localize('move second sidebar right', "Move Void Side Bar Right")
},
when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))),
order: 1
@@ -949,7 +949,7 @@ registerAction2(class extends Action2 {
if (!hasAddedView) {
results.push({
type: 'separator',
label: localize('secondarySideBarContainer', "Secondary Side Bar / {0}", containerModel.title)
label: localize('secondarySideBarContainer', "Void Side Bar / {0}", containerModel.title)
});
hasAddedView = true;
}
@@ -1056,7 +1056,7 @@ class MoveFocusedViewAction extends Action2 {
if (!(isViewSolo && currentLocation === ViewContainerLocation.AuxiliaryBar)) {
items.push({
id: '_.auxiliarybar.newcontainer',
label: localize('moveFocusedView.newContainerInSidePanel', "New Secondary Side Bar Entry")
label: localize('moveFocusedView.newContainerInSidePanel', "New Void Side Bar Entry")
});
}
@@ -1104,7 +1104,7 @@ class MoveFocusedViewAction extends Action2 {
items.push({
type: 'separator',
label: localize('secondarySideBar', "Secondary Side Bar")
label: localize('secondarySideBar', "Void Side Bar")
});
const pinnedAuxPanels = paneCompositePartService.getPinnedPaneCompositeIds(ViewContainerLocation.AuxiliaryBar);
@@ -1386,7 +1386,7 @@ if (!isMacintosh || !isNative) {
ToggleVisibilityActions.push(...[
CreateToggleLayoutItem(ToggleActivityBarVisibilityActionId, ContextKeyExpr.notEquals('config.workbench.activityBar.location', 'hidden'), localize('activityBar', "Activity Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: activityBarLeftIcon, iconB: activityBarRightIcon }),
CreateToggleLayoutItem(ToggleSidebarVisibilityAction.ID, SideBarVisibleContext, localize('sideBar', "Primary Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelLeftIcon, iconB: panelRightIcon }),
CreateToggleLayoutItem(ToggleAuxiliaryBarAction.ID, AuxiliaryBarVisibleContext, localize('secondarySideBar', "Secondary Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelRightIcon, iconB: panelLeftIcon }),
CreateToggleLayoutItem(ToggleAuxiliaryBarAction.ID, AuxiliaryBarVisibleContext, localize('secondarySideBar', "Void Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelRightIcon, iconB: panelLeftIcon }),
CreateToggleLayoutItem(TogglePanelAction.ID, PanelVisibleContext, localize('panel', "Panel"), panelIcon),
CreateToggleLayoutItem(ToggleStatusbarVisibilityAction.ID, ContextKeyExpr.equals('config.workbench.statusBar.visible', true), localize('statusBar', "Status Bar"), statusBarIcon),
]);
+2 -2
View File
@@ -2434,7 +2434,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
comment: 'Information about the layout of the workbench during statup';
activityBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the activity bar is visible' };
sideBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the primary side bar is visible' };
auxiliaryBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the secondary side bar is visible' };
auxiliaryBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the Void side bar is visible' };
panelVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the panel is visible' };
statusbarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the status bar is visible' };
sideBarPosition: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the primary side bar is on the left or right' };
@@ -2534,7 +2534,7 @@ const LayoutStateKeys = {
// Part Sizing
GRID_SIZE: new InitializationStateKey('grid.size', StorageScope.PROFILE, StorageTarget.MACHINE, { width: 800, height: 600 }),
SIDEBAR_SIZE: new InitializationStateKey<number>('sideBar.size', StorageScope.PROFILE, StorageTarget.MACHINE, 200),
AUXILIARYBAR_SIZE: new InitializationStateKey<number>('auxiliaryBar.size', StorageScope.PROFILE, StorageTarget.MACHINE, 200),
AUXILIARYBAR_SIZE: new InitializationStateKey<number>('auxiliaryBar.size', StorageScope.PROFILE, StorageTarget.MACHINE, 800), // Void changed this from 200 to 800
PANEL_SIZE: new InitializationStateKey<number>('panel.size', StorageScope.PROFILE, StorageTarget.MACHINE, 300),
PANEL_LAST_NON_MAXIMIZED_HEIGHT: new RuntimeStateKey<number>('panel.lastNonMaximizedHeight', StorageScope.PROFILE, StorageTarget.MACHINE, 300),
@@ -1 +0,0 @@
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><style>.st0{fill:#f6f6f6;fill-opacity:0}.st1{fill:#fff}.st2{fill:#167abf}</style><path class="st0" d="M1024 1024H0V0h1024v1024z"/><path class="st1" d="M1024 85.333v853.333H0V85.333h1024z"/><path class="st2" d="M0 85.333h298.667v853.333H0V85.333zm1024 0v853.333H384V85.333h640zm-554.667 160h341.333v-64H469.333v64zm341.334 533.334H469.333v64h341.333l.001-64zm128-149.334H597.333v64h341.333l.001-64zm0-149.333H597.333v64h341.333l.001-64zm0-149.333H597.333v64h341.333l.001-64z"/></svg>

Before

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 KiB

@@ -26,7 +26,7 @@ const auxiliaryBarLeftOffIcon = registerIcon('auxiliarybar-left-off-layout-icon'
export class ToggleAuxiliaryBarAction extends Action2 {
static readonly ID = 'workbench.action.toggleAuxiliaryBar';
static readonly LABEL = localize2('toggleAuxiliaryBar', "Toggle Secondary Side Bar Visibility");
static readonly LABEL = localize2('toggleAuxiliaryBar', "Toggle Void Side Bar Visibility");
constructor() {
super({
@@ -34,7 +34,7 @@ export class ToggleAuxiliaryBarAction extends Action2 {
title: ToggleAuxiliaryBarAction.LABEL,
toggled: {
condition: AuxiliaryBarVisibleContext,
title: localize('secondary sidebar', "Secondary Side Bar"),
title: localize('secondary sidebar', "Void Side Bar"),
mnemonicTitle: localize({ key: 'secondary sidebar mnemonic', comment: ['&& denotes a mnemonic'] }, "Secondary Si&&de Bar"),
},
@@ -70,7 +70,7 @@ registerAction2(ToggleAuxiliaryBarAction);
registerAction2(class FocusAuxiliaryBarAction extends Action2 {
static readonly ID = 'workbench.action.focusAuxiliaryBar';
static readonly LABEL = localize2('focusAuxiliaryBar', "Focus into Secondary Side Bar");
static readonly LABEL = localize2('focusAuxiliaryBar', "Focus into Void Side Bar");
constructor() {
super({
@@ -103,7 +103,7 @@ MenuRegistry.appendMenuItems([
group: '0_workbench_toggles',
command: {
id: ToggleAuxiliaryBarAction.ID,
title: localize('toggleSecondarySideBar', "Toggle Secondary Side Bar"),
title: localize('toggleSecondarySideBar', "Toggle Void Side Bar"),
toggled: { condition: AuxiliaryBarVisibleContext, icon: auxiliaryBarLeftIcon },
icon: auxiliaryBarLeftOffIcon,
},
@@ -116,7 +116,7 @@ MenuRegistry.appendMenuItems([
group: '0_workbench_toggles',
command: {
id: ToggleAuxiliaryBarAction.ID,
title: localize('toggleSecondarySideBar', "Toggle Secondary Side Bar"),
title: localize('toggleSecondarySideBar', "Toggle Void Side Bar"),
toggled: { condition: AuxiliaryBarVisibleContext, icon: auxiliaryBarRightIcon },
icon: auxiliaryBarRightOffIcon,
},
@@ -129,7 +129,7 @@ MenuRegistry.appendMenuItems([
group: '3_workbench_layout_move',
command: {
id: ToggleAuxiliaryBarAction.ID,
title: localize2('hideAuxiliaryBar', 'Hide Secondary Side Bar'),
title: localize2('hideAuxiliaryBar', 'Hide Void Side Bar'),
},
when: ContextKeyExpr.and(AuxiliaryBarVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))),
order: 2
@@ -45,7 +45,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart {
static readonly viewContainersWorkspaceStateKey = 'workbench.auxiliarybar.viewContainersWorkspaceState';
// Use the side bar dimensions
override readonly minimumWidth: number = 170;
override readonly minimumWidth: number = 230; // Void changed this (was 170)
override readonly maximumWidth: number = Number.POSITIVE_INFINITY;
override readonly minimumHeight: number = 0;
override readonly maximumHeight: number = Number.POSITIVE_INFINITY;
@@ -195,11 +195,13 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart {
const positionActions: IAction[] = [];
createAndFillInContextMenuActions(activityBarPositionMenu, { primary: [], secondary: positionActions });
// appears when right click
actions.push(...[
new Separator(),
new SubmenuAction('workbench.action.panel.position', localize('activity bar position', "Activity Bar Position"), positionActions),
toAction({ id: ToggleSidebarPositionAction.ID, label: currentPositionRight ? localize('move second side bar left', "Move Secondary Side Bar Left") : localize('move second side bar right', "Move Secondary Side Bar Right"), run: () => this.commandService.executeCommand(ToggleSidebarPositionAction.ID) }),
toAction({ id: ToggleAuxiliaryBarAction.ID, label: localize('hide second side bar', "Hide Secondary Side Bar"), run: () => this.commandService.executeCommand(ToggleAuxiliaryBarAction.ID) })
toAction({ id: ToggleSidebarPositionAction.ID, label: currentPositionRight ? localize('move second side bar left', "Move Void Side Bar Left") : localize('move second side bar right', "Move Void Side Bar Right"), run: () => this.commandService.executeCommand(ToggleSidebarPositionAction.ID) }),
toAction({ id: ToggleAuxiliaryBarAction.ID, label: localize('hide second side bar', "Hide Void Side Bar"), run: () => this.commandService.executeCommand(ToggleAuxiliaryBarAction.ID) })
]);
}
@@ -30,7 +30,7 @@
background-repeat: no-repeat;
background-position: center center;
background-size: 16px;
background-image: url('../../../../browser/media/code-icon.svg');
background-image: url('../../../../browser/media/void-icon-sm.png');
width: 16px;
padding: 0;
margin: 0 6px 0 10px;
@@ -432,7 +432,7 @@ export class UnpinEditorAction extends Action {
label: string,
@ICommandService private readonly commandService: ICommandService
) {
super(id, label, ThemeIcon.asClassName(Codicon.pinned));
super(id, label, ThemeIcon.asClassName(Codicon.starFull));
}
override run(context?: IEditorCommandsContext): Promise<void> {
@@ -440,6 +440,24 @@ export class UnpinEditorAction extends Action {
}
}
export class PinEditorAction extends Action {
static readonly ID = 'workbench.action.pinEditor';
static readonly LABEL = localize('pinEditor', "Pin Editor");
constructor(
id: string,
label: string,
@ICommandService private readonly commandService: ICommandService
) {
super(id, label, ThemeIcon.asClassName(Codicon.star));
}
override async run(context?: IEditorCommandsContext): Promise<void> {
return this.commandService.executeCommand('workbench.action.pinEditor', undefined, context);
}
}
export class CloseEditorTabAction extends Action {
static readonly ID = 'workbench.action.closeActiveEditor';
@@ -4,82 +4,112 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from '../../../../nls.js';
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
import { isMacintosh, isWeb, OS } from '../../../../base/common/platform.js';
import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { append, clearNode, $, h } from '../../../../base/browser/dom.js';
import { KeybindingLabel } from '../../../../base/browser/ui/keybindingLabel/keybindingLabel.js';
import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';
import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { defaultKeybindingLabelStyles } from '../../../../platform/theme/browser/defaultStyles.js';
import { editorForeground, registerColor, transparent } from '../../../../platform/theme/common/colorRegistry.js';
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
import { ColorScheme } from '../../../../platform/theme/common/theme.js';
import { isRecentFolder, IWorkspacesService } from '../../../../platform/workspaces/common/workspaces.js';
// import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
import { OpenFileFolderAction, OpenFolderAction } from '../../actions/workspaceActions.js';
import { isMacintosh, isNative, OS } from '../../../../base/common/platform.js';
import { defaultKeybindingLabelStyles } from '../../../../platform/theme/browser/defaultStyles.js';
import { IWindowOpenable } from '../../../../platform/window/common/window.js';
import { ILabelService, Verbosity } from '../../../../platform/label/common/label.js';
import { splitRecentLabel } from '../../../../base/common/labels.js';
import { IHostService } from '../../../services/host/browser/host.js';
import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../contrib/void/browser/voidSettingsPane.js';
import { VOID_CTRL_K_ACTION_ID, VOID_CTRL_L_ACTION_ID } from '../../../contrib/void/browser/actionIDs.js';
// import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
registerColor('editorWatermark.foreground', { dark: transparent(editorForeground, 0.6), light: transparent(editorForeground, 0.68), hcDark: editorForeground, hcLight: editorForeground }, localize('editorLineHighlight', 'Foreground color for the labels in the editor watermark.'));
interface WatermarkEntry {
readonly text: string;
readonly id: string;
readonly mac?: boolean;
readonly when?: ContextKeyExpression;
}
// interface WatermarkEntry {
// readonly text: string;
// readonly id: string;
// readonly mac?: boolean;
// readonly when?: ContextKeyExpression;
// }
const showCommands: WatermarkEntry = { text: localize('watermark.showCommands', "Show All Commands"), id: 'workbench.action.showCommands' };
const quickAccess: WatermarkEntry = { text: localize('watermark.quickAccess', "Go to File"), id: 'workbench.action.quickOpen' };
const openFileNonMacOnly: WatermarkEntry = { text: localize('watermark.openFile', "Open File"), id: 'workbench.action.files.openFile', mac: false };
const openFolderNonMacOnly: WatermarkEntry = { text: localize('watermark.openFolder', "Open Folder"), id: 'workbench.action.files.openFolder', mac: false };
const openFileOrFolderMacOnly: WatermarkEntry = { text: localize('watermark.openFileFolder', "Open File or Folder"), id: 'workbench.action.files.openFileFolder', mac: true };
const openRecent: WatermarkEntry = { text: localize('watermark.openRecent', "Open Recent"), id: 'workbench.action.openRecent' };
const newUntitledFileMacOnly: WatermarkEntry = { text: localize('watermark.newUntitledFile', "New Untitled Text File"), id: 'workbench.action.files.newUntitledFile', mac: true };
const findInFiles: WatermarkEntry = { text: localize('watermark.findInFiles', "Find in Files"), id: 'workbench.action.findInFiles' };
const toggleTerminal: WatermarkEntry = { text: localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), id: 'workbench.action.terminal.toggleTerminal', when: ContextKeyExpr.equals('terminalProcessSupported', true) };
const startDebugging: WatermarkEntry = { text: localize('watermark.startDebugging', "Start Debugging"), id: 'workbench.action.debug.start', when: ContextKeyExpr.equals('terminalProcessSupported', true) };
const toggleFullscreen: WatermarkEntry = { text: localize({ key: 'watermark.toggleFullscreen', comment: ['toggle is a verb here'] }, "Toggle Full Screen"), id: 'workbench.action.toggleFullScreen' };
const showSettings: WatermarkEntry = { text: localize('watermark.showSettings', "Show Settings"), id: 'workbench.action.openSettings' };
// const showCommands: WatermarkEntry = { text: localize('watermark.showCommands', "Show All Commands"), id: 'workbench.action.showCommands' };
// const quickAccess: WatermarkEntry = { text: localize('watermark.quickAccess', "Go to File"), id: 'workbench.action.quickOpen' };
// const openFileNonMacOnly: WatermarkEntry = { text: localize('watermark.openFile', "Open File"), id: 'workbench.action.files.openFile', mac: false };
// const openFolderNonMacOnly: WatermarkEntry = { text: localize('watermark.openFolder', "Open Folder"), id: 'workbench.action.files.openFolder', mac: false };
// const openFileOrFolderMacOnly: WatermarkEntry = { text: localize('watermark.openFileFolder', "Open File or Folder"), id: 'workbench.action.files.openFileFolder', mac: true };
// const openRecent: WatermarkEntry = { text: localize('watermark.openRecent', "Open Recent"), id: 'workbench.action.openRecent' };
// const newUntitledFileMacOnly: WatermarkEntry = { text: localize('watermark.newUntitledFile', "New Untitled Text File"), id: 'workbench.action.files.newUntitledFile', mac: true };
// const findInFiles: WatermarkEntry = { text: localize('watermark.findInFiles', "Find in Files"), id: 'workbench.action.findInFiles' };
// const toggleTerminal: WatermarkEntry = { text: localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), id: 'workbench.action.terminal.toggleTerminal', when: ContextKeyExpr.equals('terminalProcessSupported', true) };
// const startDebugging: WatermarkEntry = { text: localize('watermark.startDebugging', "Start Debugging"), id: 'workbench.action.debug.start', when: ContextKeyExpr.equals('terminalProcessSupported', true) };
// const toggleFullscreen: WatermarkEntry = { text: localize({ key: 'watermark.toggleFullscreen', comment: ['toggle is a verb here'] }, "Toggle Full Screen"), id: 'workbench.action.toggleFullScreen' };
// const showSettings: WatermarkEntry = { text: localize('watermark.showSettings', "Show Settings"), id: 'workbench.action.openSettings' };
const noFolderEntries = [
showCommands,
openFileNonMacOnly,
openFolderNonMacOnly,
openFileOrFolderMacOnly,
openRecent,
newUntitledFileMacOnly
];
// // shown when Void is emtpty
// const noFolderEntries = [
// // showCommands,
// openFileNonMacOnly,
// openFolderNonMacOnly,
// openFileOrFolderMacOnly,
// openRecent,
// // newUntitledFileMacOnly
// ];
const folderEntries = [
showCommands,
quickAccess,
findInFiles,
startDebugging,
toggleTerminal,
toggleFullscreen,
showSettings
];
// const folderEntries = [
// showCommands,
// // quickAccess,
// // findInFiles,
// // startDebugging,
// // toggleTerminal,
// // toggleFullscreen,
// // showSettings
// ];
export class EditorGroupWatermark extends Disposable {
private readonly shortcuts: HTMLElement;
private readonly transientDisposables = this._register(new DisposableStore());
private enabled: boolean = false;
// private enabled: boolean = false;
private workbenchState: WorkbenchState;
private keybindingLabels = new Set<KeybindingLabel>();
private currentDisposables = new Set<IDisposable>();
constructor(
container: HTMLElement,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IConfigurationService private readonly configurationService: IConfigurationService
// @IContextKeyService private readonly contextKeyService: IContextKeyService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IThemeService private readonly themeService: IThemeService,
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
@ICommandService private readonly commandService: ICommandService,
@IHostService private readonly hostService: IHostService,
@ILabelService private readonly labelService: ILabelService,
) {
super();
const elements = h('.editor-group-watermark', [
h('.letterpress'),
h('.letterpress@icon'),
h('.shortcuts@shortcuts'),
]);
append(container, elements.root);
this.shortcuts = elements.shortcuts;
this.shortcuts = elements.shortcuts; // shortcuts div is modified on render()
// void icon style
const updateTheme = () => {
const theme = this.themeService.getColorTheme().type
const isDark = theme === ColorScheme.DARK || theme === ColorScheme.HIGH_CONTRAST_DARK
elements.icon.style.maxWidth = '220px'
elements.icon.style.opacity = '50%'
elements.icon.style.filter = isDark ? '' : 'invert(1)' //brightness(.5)
}
updateTheme()
this._register(
this.themeService.onDidColorThemeChange(updateTheme)
)
this.registerListeners();
@@ -103,56 +133,159 @@ export class EditorGroupWatermark extends Disposable {
this.render();
}));
const allEntriesWhenClauses = [...noFolderEntries, ...folderEntries].filter(entry => entry.when !== undefined).map(entry => entry.when!);
const allKeys = new Set<string>();
allEntriesWhenClauses.forEach(when => when.keys().forEach(key => allKeys.add(key)));
this._register(this.contextKeyService.onDidChangeContext(e => {
if (e.affectsSome(allKeys)) {
this.render();
}
}));
// const allEntriesWhenClauses = [...noFolderEntries, ...folderEntries].filter(entry => entry.when !== undefined).map(entry => entry.when!);
// const allKeys = new Set<string>();
// allEntriesWhenClauses.forEach(when => when.keys().forEach(key => allKeys.add(key)));
// this._register(this.contextKeyService.onDidChangeContext(e => {
// if (e.affectsSome(allKeys)) {
// this.render();
// }
// }));
}
private render(): void {
const enabled = this.configurationService.getValue<boolean>('workbench.tips.enabled');
if (enabled === this.enabled) {
return;
}
this.enabled = enabled;
this.clear();
const voidIconBox = append(this.shortcuts, $('.watermark-box'));
const recentsBox = append(this.shortcuts, $('div'));
recentsBox.style.display = 'flex'
recentsBox.style.flex = 'row'
recentsBox.style.justifyContent = 'center'
if (!enabled) {
return;
}
const box = append(this.shortcuts, $('.watermark-box'));
const folder = this.workbenchState !== WorkbenchState.EMPTY;
const selected = (folder ? folderEntries : noFolderEntries)
.filter(entry => !('when' in entry) || this.contextKeyService.contextMatchesRules(entry.when))
.filter(entry => !('mac' in entry) || entry.mac === (isMacintosh && !isWeb))
.filter(entry => !!CommandsRegistry.getCommand(entry.id))
.filter(entry => !!this.keybindingService.lookupKeybinding(entry.id));
const update = async () => {
const update = () => {
clearNode(box);
this.keybindingLabels.forEach(label => label.dispose());
this.keybindingLabels.clear();
// put async at top so don't need to wait (this prevents a jitter on load)
const recentlyOpened = await this.workspacesService.getRecentlyOpened()
.catch(() => ({ files: [], workspaces: [] })).then(w => w.workspaces);
for (const entry of selected) {
const keys = this.keybindingService.lookupKeybinding(entry.id);
if (!keys) {
continue;
clearNode(voidIconBox);
clearNode(recentsBox);
this.currentDisposables.forEach(label => label.dispose());
this.currentDisposables.clear();
// Void - if the workbench is empty, show open
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
// Open a folder
const openFolderButton = h('button')
openFolderButton.root.classList.add('void-watermark-button')
openFolderButton.root.style.display = 'block'
openFolderButton.root.style.marginLeft = 'auto'
openFolderButton.root.style.marginRight = 'auto'
openFolderButton.root.style.marginBottom = '16px'
openFolderButton.root.textContent = 'Open a folder'
openFolderButton.root.onclick = () => {
this.commandService.executeCommand(isMacintosh && isNative ? OpenFileFolderAction.ID : OpenFolderAction.ID)
// if (this.contextKeyService.contextMatchesRules(ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace')))) {
// this.commandService.executeCommand(OpenFolderViaWorkspaceAction.ID);
// } else {
// this.commandService.executeCommand(isMacintosh ? 'workbench.action.files.openFileFolder' : 'workbench.action.files.openFolder');
// }
}
const dl = append(box, $('dl'));
voidIconBox.appendChild(openFolderButton.root);
// Recents
if (recentlyOpened.length !== 0) {
voidIconBox.append(
...recentlyOpened.map((w, i) => {
let fullPath: string;
let windowOpenable: IWindowOpenable;
if (isRecentFolder(w)) {
windowOpenable = { folderUri: w.folderUri };
fullPath = w.label || this.labelService.getWorkspaceLabel(w.folderUri, { verbose: Verbosity.LONG });
}
else {
return null
// fullPath = w.label || this.labelService.getWorkspaceLabel(w.workspace, { verbose: Verbosity.LONG });
// windowOpenable = { workspaceUri: w.workspace.configPath };
}
const { name, parentPath } = splitRecentLabel(fullPath);
const linkSpan = $('span');
linkSpan.classList.add('void-link')
linkSpan.style.display = 'flex'
linkSpan.style.gap = '4px'
linkSpan.style.padding = '8px'
linkSpan.addEventListener('click', e => {
this.hostService.openWindow([windowOpenable], {
forceNewWindow: e.ctrlKey || e.metaKey,
remoteAuthority: w.remoteAuthority || null // local window if remoteAuthority is not set or can not be deducted from the openable
});
e.preventDefault();
e.stopPropagation();
});
const nameSpan = $('span');
nameSpan.innerText = name;
nameSpan.title = fullPath;
linkSpan.appendChild(nameSpan);
const dirSpan = $('span');
dirSpan.style.paddingLeft = '4px';
dirSpan.innerText = parentPath;
dirSpan.title = fullPath;
linkSpan.appendChild(dirSpan);
return linkSpan
}).filter(v => !!v)
)
}
}
else {
// show them Void keybindings
const keys = this.keybindingService.lookupKeybinding(VOID_CTRL_L_ACTION_ID);
const dl = append(voidIconBox, $('dl'));
const dt = append(dl, $('dt'));
dt.textContent = entry.text;
dt.textContent = 'Chat'
const dd = append(dl, $('dd'));
const label = new KeybindingLabel(dd, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles });
label.set(keys);
this.keybindingLabels.add(label);
if (keys)
label.set(keys);
this.currentDisposables.add(label);
const keys2 = this.keybindingService.lookupKeybinding(VOID_CTRL_K_ACTION_ID);
const dl2 = append(voidIconBox, $('dl'));
const dt2 = append(dl2, $('dt'));
dt2.textContent = 'Quick Edit'
const dd2 = append(dl2, $('dd'));
const label2 = new KeybindingLabel(dd2, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles });
if (keys2)
label2.set(keys2);
this.currentDisposables.add(label2);
const keys3 = this.keybindingService.lookupKeybinding('workbench.action.openGlobalKeybindings');
const button3 = append(recentsBox, $('button'));
button3.textContent = 'Void Settings'
button3.style.display = 'block'
button3.style.marginLeft = 'auto'
button3.style.marginRight = 'auto'
button3.classList.add('void-settings-watermark-button')
const label3 = new KeybindingLabel(button3, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles });
if (keys3)
label3.set(keys3);
button3.onclick = () => {
this.commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID)
}
this.currentDisposables.add(label3);
}
};
update();
@@ -167,6 +300,6 @@ export class EditorGroupWatermark extends Disposable {
override dispose(): void {
super.dispose();
this.clear();
this.keybindingLabels.forEach(label => label.dispose());
this.currentDisposables.forEach(label => label.dispose());
}
}
@@ -9,13 +9,15 @@
height: 100%;
}
.monaco-workbench .part.editor > .content .editor-group-container.empty {
opacity: 0.5; /* dimmed to indicate inactive state */
.monaco-workbench .part.editor > .content .editor-group-container.empty {
opacity: 0.5;
/* dimmed to indicate inactive state */
}
.monaco-workbench .part.editor > .content .editor-group-container.empty.active,
.monaco-workbench .part.editor > .content .editor-group-container.empty.dragged-over {
opacity: 1; /* indicate active/dragged-over group through undimmed state */
opacity: 1;
/* indicate active/dragged-over group through undimmed state */
}
.monaco-workbench .part.editor > .content:not(.empty) .editor-group-container.empty.active:focus {
@@ -24,12 +26,13 @@
}
.monaco-workbench .part.editor > .content.empty .editor-group-container.empty.active:focus {
outline: none; /* never show outline for empty group if it is the last */
outline: none;
/* never show outline for empty group if it is the last */
}
/* Watermark & shortcuts */
.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-watermark {
.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-watermark {
display: flex;
height: 100%;
max-width: 290px;
@@ -49,26 +52,27 @@
height: calc(100% - 70px);
}
/* light */
.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-watermark > .letterpress {
width: 100%;
max-height: 100%;
aspect-ratio: 1/1;
background-image: url('./letterpress-light.svg');
background-image: url('./void_cube_noshadow.png');
background-size: contain;
background-position-x: center;
background-repeat: no-repeat;
}
.monaco-workbench.vs-dark .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress {
background-image: url('./letterpress-dark.svg');
background-image: url('./void_cube_noshadow.png');
}
.monaco-workbench.hc-light .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress {
background-image: url('./letterpress-hcLight.svg');
background-image: url('./void_cube_noshadow.png');
}
.monaco-workbench.hc-black .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress {
background-image: url('./letterpress-hcDark.svg');
background-image: url('./void_cube_noshadow.png');
}
.monaco-workbench .part.editor > .content:not(.empty) .editor-group-container > .editor-group-watermark > .shortcuts,
@@ -109,12 +113,13 @@
.monaco-workbench .part.editor > .content .editor-group-container > .title {
position: relative;
box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title:not(.tabs) {
display: flex; /* when tabs are not shown, use flex layout */
display: flex;
/* when tabs are not shown, use flex layout */
flex-wrap: nowrap;
}
@@ -144,7 +149,8 @@
.monaco-workbench .part.editor > .content .editor-group-container.empty.locked > .editor-group-container-toolbar,
.monaco-workbench .part.editor > .content:not(.empty) .editor-group-container.empty > .editor-group-container-toolbar,
.monaco-workbench .part.editor > .content.auxiliary .editor-group-container.empty > .editor-group-container-toolbar {
display: block; /* show toolbar when more than one editor group or always when auxiliary or locked */
display: block;
/* show toolbar when more than one editor group or always when auxiliary or locked */
}
.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar .actions-container {
@@ -157,7 +163,7 @@
/* Editor */
.monaco-workbench .part.editor > .content .editor-group-container.empty > .editor-container {
.monaco-workbench .part.editor > .content .editor-group-container.empty > .editor-container {
display: none;
}
@@ -385,7 +385,7 @@
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink > .tab-actions,
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-fixed > .tab-actions {
flex: 0;
overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink/fixed to make more room */
overflow: visible; /* ensure tab actions are always visible */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.tab-actions-right.sizing-shrink > .tab-actions,
@@ -399,18 +399,8 @@
overflow: visible; /* ...but still show the tab actions on hover, focus and when dirty or sticky */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-action-off:not(.dirty) > .tab-actions,
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sticky-compact > .tab-actions {
display: none; /* hide the tab actions when we are configured to hide it (unless dirty, but always when sticky-compact) */
}
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active > .tab-actions .action-label, /* always show tab actions for active tab */
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-actions .action-label:focus, /* always show tab actions on focus */
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab:hover > .tab-actions .action-label, /* always show tab actions on hover */
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:hover > .tab-actions .action-label, /* always show tab actions on hover */
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.sticky:not(.pinned-action-off) > .tab-actions .action-label, /* always show tab actions for sticky tabs */
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-actions .action-label { /* always show tab actions for dirty tabs */
opacity: 1;
display: none; /* only hide tab actions when sticky-compact */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions .actions-container {
@@ -444,11 +434,11 @@
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-actions .action-label,
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sticky:not(.pinned-action-off) > .tab-actions .action-label,
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-actions .action-label {
opacity: 0.5; /* show tab actions dimmed for inactive group */
opacity: 1;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions .action-label {
opacity: 0;
opacity: 1;
}
/* Tab Actions: Off */
Binary file not shown.

After

Width:  |  Height:  |  Size: 850 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 KiB

@@ -35,7 +35,7 @@ import { MergeGroupMode, IMergeGroupOptions } from '../../../services/editor/com
import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode, DragAndDropObserver, isMouseEvent, getWindow } from '../../../../base/browser/dom.js';
import { localize } from '../../../../nls.js';
import { IEditorGroupsView, EditorServiceImpl, IEditorGroupView, IInternalEditorOpenOptions, IEditorPartsView } from './editor.js';
import { CloseEditorTabAction, UnpinEditorAction } from './editorActions.js';
import { CloseEditorTabAction, PinEditorAction, UnpinEditorAction } from './editorActions.js';
import { assertAllDefined, assertIsDefined } from '../../../../base/common/types.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { basenameOrAuthority } from '../../../../base/common/resources.js';
@@ -113,6 +113,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
private readonly closeEditorAction = this._register(this.instantiationService.createInstance(CloseEditorTabAction, CloseEditorTabAction.ID, CloseEditorTabAction.LABEL));
private readonly unpinEditorAction = this._register(this.instantiationService.createInstance(UnpinEditorAction, UnpinEditorAction.ID, UnpinEditorAction.LABEL));
private readonly pinEditorAction = this._register(this.instantiationService.createInstance(PinEditorAction, PinEditorAction.ID, PinEditorAction.LABEL)); // Add this line
private readonly tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER));
private tabLabels: IEditorInputLabel[] = [];
@@ -1518,28 +1519,28 @@ export class MultiEditorTabsControl extends EditorTabsControl {
this.redrawTabLabel(editor, tabIndex, tabContainer, tabLabelWidget, tabLabel);
// Action
const hasUnpinAction = isTabSticky && options.tabActionUnpinVisibility;
const hasCloseAction = !hasUnpinAction && options.tabActionCloseVisibility;
const hasAction = hasUnpinAction || hasCloseAction;
const hasCloseAction = options.tabActionCloseVisibility;
const hasAction = true; // Always show actions
// Determine which action to show
let tabAction;
if (hasAction) {
tabAction = hasUnpinAction ? this.unpinEditorAction : this.closeEditorAction;
if (isTabSticky) {
tabAction = this.unpinEditorAction;
} else {
// Even if the action is not visible, add it as it contains the dirty indicator
tabAction = isTabSticky ? this.unpinEditorAction : this.closeEditorAction;
tabAction = this.pinEditorAction; // Use pin action instead of close action
}
// Update action bar
if (!tabActionBar.hasAction(tabAction)) {
if (!tabActionBar.isEmpty()) {
tabActionBar.clear();
}
tabActionBar.push(tabAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(tabAction) });
}
tabContainer.classList.toggle(`pinned-action-off`, isTabSticky && !hasUnpinAction);
tabContainer.classList.toggle(`close-action-off`, !hasUnpinAction && !hasCloseAction);
tabContainer.classList.toggle('sticky', isTabSticky);
tabContainer.classList.toggle(`pinned-action-off`, false);
tabContainer.classList.toggle(`close-action-off`, !hasCloseAction);
for (const option of ['left', 'right']) {
tabContainer.classList.toggle(`tab-actions-${option}`, hasAction && options.tabActionLocation === option);
@@ -342,7 +342,7 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.action.closeAuxiliaryBar',
title: localize2('closeSecondarySideBar', 'Hide Secondary Side Bar'),
title: localize2('closeSecondarySideBar', 'Hide Void Side Bar'),
category: Categories.View,
icon: closeIcon,
menu: [{
@@ -415,14 +415,16 @@ class MoveViewsBetweenPanelsAction extends Action2 {
}
}
// --- Move Panel Views To Secondary Side Bar
// --- Move Panel Views To Void Side Bar
// these are just for the command pallette
class MovePanelToSidePanelAction extends MoveViewsBetweenPanelsAction {
static readonly ID = 'workbench.action.movePanelToSidePanel';
constructor() {
super(ViewContainerLocation.Panel, ViewContainerLocation.AuxiliaryBar, {
id: MovePanelToSidePanelAction.ID,
title: localize2('movePanelToSecondarySideBar', "Move Panel Views To Secondary Side Bar"),
title: localize2('movePanelToSecondarySideBar', "Move Panel Views To Void Side Bar"),
category: Categories.View,
f1: false
});
@@ -434,7 +436,7 @@ export class MovePanelToSecondarySideBarAction extends MoveViewsBetweenPanelsAct
constructor() {
super(ViewContainerLocation.Panel, ViewContainerLocation.AuxiliaryBar, {
id: MovePanelToSecondarySideBarAction.ID,
title: localize2('movePanelToSecondarySideBar', "Move Panel Views To Secondary Side Bar"),
title: localize2('movePanelToSecondarySideBar', "Move Panel Views To Void Side Bar"),
category: Categories.View,
f1: true
});
@@ -452,7 +454,7 @@ class MoveSidePanelToPanelAction extends MoveViewsBetweenPanelsAction {
constructor() {
super(ViewContainerLocation.AuxiliaryBar, ViewContainerLocation.Panel, {
id: MoveSidePanelToPanelAction.ID,
title: localize2('moveSidePanelToPanel', "Move Secondary Side Bar Views To Panel"),
title: localize2('moveSidePanelToPanel', "Move Side Bar Views To Panel"), // Void - this seemed to have a typo before
category: Categories.View,
f1: false
});
@@ -465,7 +467,7 @@ export class MoveSecondarySideBarToPanelAction extends MoveViewsBetweenPanelsAct
constructor() {
super(ViewContainerLocation.AuxiliaryBar, ViewContainerLocation.Panel, {
id: MoveSecondarySideBarToPanelAction.ID,
title: localize2('moveSidePanelToPanel', "Move Secondary Side Bar Views To Panel"),
title: localize2('moveSidePanelToPanel', "Move Void Side Bar Views To Panel"),
category: Categories.View,
f1: true
});
@@ -253,7 +253,7 @@
}
.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-left > .window-appicon:not(.codicon) {
background-image: url('../../../media/code-icon.svg');
background-image: url('../../../media/void-icon-sm.png');
background-repeat: no-repeat;
background-position: center center;
background-size: 16px;
@@ -275,7 +275,7 @@
height: 8px;
z-index: 1;
/* on top of home indicator */
background-image: url('../../../media/code-icon.svg');
background-image: url('../../../media/void-icon-sm.png');
background-repeat: no-repeat;
background-position: center center;
background-size: 8px;
@@ -512,7 +512,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
'type': 'string',
'enum': ['left', 'right'],
'default': 'left',
'description': localize('sideBarLocation', "Controls the location of the primary side bar and activity bar. They can either show on the left or right of the workbench. The secondary side bar will show on the opposite side of the workbench.")
'description': localize('sideBarLocation', "Controls the location of the primary side bar and activity bar. They can either show on the left or right of the workbench. The Void side bar will show on the opposite side of the workbench.")
},
'workbench.panel.showLabel': {
'type': 'boolean',
@@ -545,12 +545,12 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
'type': 'string',
'enum': ['default', 'top', 'bottom', 'hidden'],
'default': 'default',
'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the Activity Bar relative to the Primary and Secondary Side Bars."),
'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the Activity Bar relative to the Primary and Void Side Bars."),
'enumDescriptions': [
localize('workbench.activityBar.location.default', "Show the Activity Bar on the side of the Primary Side Bar and on top of the Secondary Side Bar."),
localize('workbench.activityBar.location.top', "Show the Activity Bar on top of the Primary and Secondary Side Bars."),
localize('workbench.activityBar.location.bottom', "Show the Activity Bar at the bottom of the Primary and Secondary Side Bars."),
localize('workbench.activityBar.location.hide', "Hide the Activity Bar in the Primary and Secondary Side Bars.")
localize('workbench.activityBar.location.default', "Show the Activity Bar on the side of the Primary Side Bar and on top of the Void Side Bar."),
localize('workbench.activityBar.location.top', "Show the Activity Bar on top of the Primary and Void Side Bars."),
localize('workbench.activityBar.location.bottom', "Show the Activity Bar at the bottom of the Primary and Void Side Bars."),
localize('workbench.activityBar.location.hide', "Hide the Activity Bar in the Primary and Void Side Bars.")
],
},
'workbench.activityBar.iconClickBehavior': {
@@ -598,7 +598,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': isMacintosh ? 300 : 300, // <-- Void edited this to 300 : 300 (was 1500 : 500)
'minimum': 0
},
'workbench.reduceMotion': {
@@ -289,6 +289,7 @@ export interface IFileTemplateData {
readonly templateDisposables: DisposableStore;
readonly elementDisposables: DisposableStore;
readonly label: IResourceLabel;
// readonly voidLabels: IResourceLabel;
readonly container: HTMLElement;
readonly contribs: IExplorerFileContribution[];
currentContext?: ExplorerItem;
@@ -347,15 +348,25 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
renderTemplate(container: HTMLElement): IFileTemplateData {
const templateDisposables = new DisposableStore();
// Void added this
// // Create void buttons container
// const voidButtonsContainer = DOM.append(container, DOM.$('div'));
// voidButtonsContainer.style.position = 'absolute'
// voidButtonsContainer.style.top = '0'
// voidButtonsContainer.style.right = '0'
// // const voidButtons = DOM.append(voidButtonsContainer, DOM.$('span'));
// // voidButtons.textContent = 'voidbuttons'
// // voidButtons.addEventListener('click', () => {
// // console.log('ON CLICK', templateData.currentContext?.children)
// // })
// const voidLabels = this.labels.create(voidButtonsContainer, { supportHighlights: false, supportIcons: false, });
// voidLabels.element.textContent = 'hi333'
const label = templateDisposables.add(this.labels.create(container, { supportHighlights: true }));
templateDisposables.add(label.onDidRender(() => {
try {
if (templateData.currentContext) {
this.updateWidth(templateData.currentContext);
}
} catch (e) {
// noop since the element might no longer be in the tree, no update of width necessary
}
try { if (templateData.currentContext) this.updateWidth(templateData.currentContext); }
catch (e) { /* noop since the element might no longer be in the tree, no update of width necessary*/ }
}));
const contribs = explorerFileContribRegistry.create(this.instantiationService, container, templateDisposables);
@@ -365,10 +376,15 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
contr.setResource(templateData.currentContext?.resource);
}));
// const templateData: IFileTemplateData = { templateDisposables, elementDisposables: templateDisposables.add(new DisposableStore()), label, voidLabels, container, contribs };
const templateData: IFileTemplateData = { templateDisposables, elementDisposables: templateDisposables.add(new DisposableStore()), label, container, contribs };
return templateData;
}
// Void cares about this function, this is where elements in the tree are rendered
renderElement(node: ITreeNode<ExplorerItem, FuzzyScore>, index: number, templateData: IFileTemplateData): void {
const stat = node.element;
templateData.currentContext = stat;
@@ -382,8 +398,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
templateData.label.element.style.display = 'flex';
this.renderStat(stat, stat.name, undefined, node.filterData, templateData);
}
// Input Box
// Input Box (Void - shown only if currently editing - this is the box that appears when user edits the name of the file)
else {
templateData.label.element.style.display = 'none';
templateData.contribs.forEach(c => c.setResource(undefined));
@@ -477,6 +492,13 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
separator: this.labelService.getSeparator(stat.resource.scheme, stat.resource.authority),
domId
});
// templateData.voidLabels.setResource({ resource: undefined, name: 'hi', }, {
// hideIcon: true,
// extraClasses: realignNestedChildren ? [...extraClasses, 'align-nest-icon-with-parent-icon'] : extraClasses,
// forceLabel: true,
// });
}
private renderInputBox(container: HTMLElement, stat: ExplorerItem, editableData: IEditableData): IDisposable {
@@ -95,7 +95,12 @@ suite('Files - ExplorerView', () => {
label: <any>{
container: label,
onDidRender: emitter.event
}
},
// voidLabels: <any>{
// container: label,
// onDidRender: emitter.event
// },
}, 1, false);
ds.add(navigationController);
@@ -151,7 +151,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider<IViewQuic
// Viewlets / Panels
addPaneComposites(ViewContainerLocation.Sidebar, localize('views', "Side Bar"));
addPaneComposites(ViewContainerLocation.Panel, localize('panels', "Panel"));
addPaneComposites(ViewContainerLocation.AuxiliaryBar, localize('secondary side bar', "Secondary Side Bar"));
addPaneComposites(ViewContainerLocation.AuxiliaryBar, localize('secondary side bar', "Void Side Bar"));
const addPaneCompositeViews = (location: ViewContainerLocation) => {
const paneComposites = this.paneCompositeService.getPaneComposites(location);
@@ -5,5 +5,5 @@
.file-icons-enabled .show-file-icons .webview-vs_code_release_notes-name-file-icon.file-icon::before {
content: ' ';
background-image: url('../../../../browser/media/code-icon.svg');
background-image: url('../../../../browser/media/void-icon-sm.png');
}
@@ -1 +0,0 @@
void-imports/
@@ -0,0 +1,6 @@
// Normally you'd want to put these exports in the files that register them, but if you do that you'll get an import order error if you import them in certain cases.
// (importing them runs the whole file to get the ID, causing an import error). I guess it's best practice to separate out IDs, pretty annoying...
export const VOID_CTRL_L_ACTION_ID = 'void.ctrlLAction'
export const VOID_CTRL_K_ACTION_ID = 'void.ctrlKAction'
@@ -0,0 +1,934 @@
/*--------------------------------------------------------------------------------------
* 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 { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
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 { Range } from '../../../../editor/common/core/range.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js';
import { EditorResourceAccessor } from '../../../common/editor.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { extractCodeFromRegular } from './helpers/extractCodeFromResult.js';
import { isWindows } from '../../../../base/common/platform.js';
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
import { ILLMMessageService } from '../common/llmMessageService.js';
// import { IContextGatheringService } from './contextGatheringService.js';
// The extension this was called from is here - https://github.com/voideditor/void/blob/autocomplete/extensions/void/src/extension/extension.ts
/*
A summary of autotab:
Postprocessing
-one common problem for all models is outputting unbalanced parentheses
we solve this by trimming all extra closing parentheses from the generated string
in future, should make sure parentheses are always balanced
-another problem is completing the middle of a string, eg. "const [x, CURSOR] = useState()"
we complete up to first matchup character
but should instead complete the whole line / block (difficult because of parenthesis accuracy)
-too much info is bad. usually we want to show the user 1 line, and have a preloaded response afterwards
this should happen automatically with caching system
should break preloaded responses into \n\n chunks
Preprocessing
- we don't generate if cursor is at end / beginning of a line (no spaces)
- we generate 1 line if there is text to the right of cursor
- we generate 1 line if variable declaration
- (in many cases want to show 1 line but generate multiple)
State
- cache based on prefix (and do some trimming first)
- when press tab on one line, should have an immediate followup response
to do this, show autocompletes before they're fully finished
- [todo] remove each autotab when accepted
!- [todo] provide type information
Details
-generated results are trimmed up to 1 leading/trailing space
-prefixes are cached up to 1 trailing newline
-
*/
class LRUCache<K, V> {
public items: Map<K, V>;
private keyOrder: K[];
private maxSize: number;
private disposeCallback?: (value: V, key?: K) => void;
constructor(maxSize: number, disposeCallback?: (value: V, key?: K) => void) {
if (maxSize <= 0) throw new Error('Cache size must be greater than 0');
this.items = new Map();
this.keyOrder = [];
this.maxSize = maxSize;
this.disposeCallback = disposeCallback;
}
set(key: K, value: V): void {
// If key exists, remove it from the order list
if (this.items.has(key)) {
this.keyOrder = this.keyOrder.filter(k => k !== key);
}
// If cache is full, remove least recently used item
else if (this.items.size >= this.maxSize) {
const key = this.keyOrder[0];
const value = this.items.get(key);
// Call dispose callback if it exists
if (this.disposeCallback && value !== undefined) {
this.disposeCallback(value, key);
}
this.items.delete(key);
this.keyOrder.shift();
}
// Add new item
this.items.set(key, value);
this.keyOrder.push(key);
}
delete(key: K): boolean {
const value = this.items.get(key);
if (value !== undefined) {
// Call dispose callback if it exists
if (this.disposeCallback) {
this.disposeCallback(value, key);
}
this.items.delete(key);
this.keyOrder = this.keyOrder.filter(k => k !== key);
return true;
}
return false;
}
clear(): void {
// Call dispose callback for all items if it exists
if (this.disposeCallback) {
for (const [key, value] of this.items.entries()) {
this.disposeCallback(value, key);
}
}
this.items.clear();
this.keyOrder = [];
}
get size(): number {
return this.items.size;
}
has(key: K): boolean {
return this.items.has(key);
}
}
type AutocompletionPredictionType =
| 'single-line-fill-middle'
| 'single-line-redo-suffix'
// | 'multi-line-start-here'
| 'multi-line-start-on-next-line'
| 'do-not-predict'
type Autocompletion = {
id: number,
prefix: string,
suffix: string,
llmPrefix: string,
llmSuffix: string,
startTime: number,
endTime: number | undefined,
status: 'pending' | 'finished' | 'error',
type: AutocompletionPredictionType,
llmPromise: Promise<string> | undefined,
insertText: string,
requestId: string | null,
_newlineCount: number,
}
const DEBOUNCE_TIME = 500
const TIMEOUT_TIME = 60000
const MAX_CACHE_SIZE = 20
const MAX_PENDING_REQUESTS = 2
// postprocesses the result
const processStartAndEndSpaces = (result: string) => {
// trim all whitespace except for a single leading/trailing space
// return result.trim()
[result,] = extractCodeFromRegular({ text: result, recentlyAddedTextLen: result.length })
const hasLeadingSpace = result.startsWith(' ');
const hasTrailingSpace = result.endsWith(' ');
return (hasLeadingSpace ? ' ' : '')
+ result.trim()
+ (hasTrailingSpace ? ' ' : '');
}
// trims the end of the prefix to improve cache hit rate
const removeLeftTabsAndTrimEnds = (s: string): string => {
const trimmedString = s.trimEnd();
const trailingEnd = s.slice(trimmedString.length);
// keep only a single trailing newline
if (trailingEnd.includes(_ln)) {
s = trimmedString + _ln;
}
s = s.replace(/^\s+/gm, ''); // remove left tabs
return s;
}
const removeAllWhitespace = (str: string): string => str.replace(/\s+/g, '');
function getIsSubsequence({ of, subsequence }: { of: string, subsequence: string }): [boolean, string] {
if (subsequence.length === 0) return [true, ''];
if (of.length === 0) return [false, ''];
let subsequenceIndex = 0;
let lastMatchChar = '';
for (let i = 0; i < of.length; i++) {
if (of[i] === subsequence[subsequenceIndex]) {
lastMatchChar = of[i];
subsequenceIndex++;
}
if (subsequenceIndex === subsequence.length) {
return [true, lastMatchChar];
}
}
return [false, lastMatchChar];
}
function getStringUpToUnbalancedClosingParenthesis(s: string, prefix: string): string {
const pairs: Record<string, string> = { ')': '(', '}': '{', ']': '[' };
// process all bracets in prefix
let stack: string[] = []
const firstOpenIdx = prefix.search(/[[({]/);
if (firstOpenIdx !== -1) {
const brackets = prefix.slice(firstOpenIdx).split('').filter(c => '()[]{}'.includes(c));
for (const bracket of brackets) {
if (bracket === '(' || bracket === '{' || bracket === '[') {
stack.push(bracket);
} else {
if (stack.length > 0 && stack[stack.length - 1] === pairs[bracket]) {
stack.pop();
} else {
stack.push(bracket);
}
}
}
}
// iterate through each character
for (let i = 0; i < s.length; i++) {
const char = s[i];
if (char === '(' || char === '{' || char === '[') { stack.push(char); }
else if (char === ')' || char === '}' || char === ']') {
if (stack.length === 0 || stack.pop() !== pairs[char]) { return s.substring(0, i); }
}
}
return s;
}
// further trim the autocompletion
const postprocessAutocompletion = ({ autocompletionMatchup, autocompletion, prefixAndSuffix }: { autocompletionMatchup: AutocompletionMatchupBounds, autocompletion: Autocompletion, prefixAndSuffix: PrefixAndSuffixInfo }) => {
const { prefix, prefixToTheLeftOfCursor, suffixToTheRightOfCursor } = prefixAndSuffix
const generatedMiddle = autocompletion.insertText
let startIdx = autocompletionMatchup.startIdx
let endIdx = generatedMiddle.length // exclusive bounds
// const naiveReturnValue = generatedMiddle.slice(startIdx)
// console.log('naiveReturnValue: ', JSON.stringify(naiveReturnValue))
// return [{ insertText: naiveReturnValue, }]
// do postprocessing for better ux
// this is a bit hacky but may change a lot
// if there is space at the start of the completion and user has added it, remove it
const charToLeftOfCursor = prefixToTheLeftOfCursor.slice(-1)[0] || ''
const userHasAddedASpace = charToLeftOfCursor === ' ' || charToLeftOfCursor === '\t'
const rawFirstNonspaceIdx = generatedMiddle.slice(startIdx).search(/[^\t ]/)
if (rawFirstNonspaceIdx > -1 && userHasAddedASpace) {
const firstNonspaceIdx = rawFirstNonspaceIdx + startIdx;
// console.log('p0', startIdx, rawFirstNonspaceIdx)
startIdx = Math.max(startIdx, firstNonspaceIdx)
}
// if user is on a blank line and the generation starts with newline(s), remove them
const numStartingNewlines = generatedMiddle.slice(startIdx).match(new RegExp(`^${_ln}+`))?.[0].length || 0;
if (
!prefixToTheLeftOfCursor.trim()
&& !suffixToTheRightOfCursor.trim()
&& numStartingNewlines > 0
) {
// console.log('p1', numStartingNewlines)
startIdx += numStartingNewlines
}
// if the generated FIM text matches with the suffix on the current line, stop
if (autocompletion.type === 'single-line-fill-middle' && suffixToTheRightOfCursor.trim()) { // completing in the middle of a line
// complete until there is a match
const rawMatchIndex = generatedMiddle.slice(startIdx).lastIndexOf(suffixToTheRightOfCursor.trim()[0])
if (rawMatchIndex > -1) {
// console.log('p2', rawMatchIndex, startIdx, suffixToTheRightOfCursor.trim()[0], 'AAA', generatedMiddle.slice(startIdx))
const matchIdx = rawMatchIndex + startIdx;
const matchChar = generatedMiddle[matchIdx]
if (`{}()[]<>\`'"`.includes(matchChar)) {
endIdx = Math.min(endIdx, matchIdx)
}
}
}
const restOfLineToGenerate = generatedMiddle.slice(startIdx).split(_ln)[0] ?? ''
// condition to complete as a single line completion
if (
prefixToTheLeftOfCursor.trim()
&& !suffixToTheRightOfCursor.trim()
&& restOfLineToGenerate.trim()
) {
const rawNewlineIdx = generatedMiddle.slice(startIdx).indexOf(_ln)
if (rawNewlineIdx > -1) {
// console.log('p3', startIdx, rawNewlineIdx)
const newlineIdx = rawNewlineIdx + startIdx;
endIdx = Math.min(endIdx, newlineIdx)
}
}
// // if a generated line matches with a suffix line, stop
// if (suffixLines.length > 1) {
// console.log('4')
// const lines = []
// for (const generatedLine of generatedLines) {
// if (suffixLines.slice(0, 10).some(suffixLine =>
// generatedLine.trim() !== '' && suffixLine.trim() !== ''
// && generatedLine.trim().startsWith(suffixLine.trim())
// )) break;
// lines.push(generatedLine)
// }
// endIdx = lines.join('\n').length // this is hacky, remove or refactor in future
// }
// console.log('pFinal', startIdx, endIdx)
let completionStr = generatedMiddle.slice(startIdx, endIdx)
// filter out unbalanced parentheses
completionStr = getStringUpToUnbalancedClosingParenthesis(completionStr, prefix)
// console.log('originalCompletionStr: ', JSON.stringify(generatedMiddle.slice(startIdx)))
// console.log('finalCompletionStr: ', JSON.stringify(completionStr))
return completionStr
}
// returns the text in the autocompletion to display, assuming the prefix is already matched
const toInlineCompletions = ({ autocompletionMatchup, autocompletion, prefixAndSuffix, position, debug }: { autocompletionMatchup: AutocompletionMatchupBounds, autocompletion: Autocompletion, prefixAndSuffix: PrefixAndSuffixInfo, position: Position, debug?: boolean }): { insertText: string, range: Range }[] => {
let trimmedInsertText = postprocessAutocompletion({ autocompletionMatchup, autocompletion, prefixAndSuffix, })
let rangeToReplace: Range = new Range(position.lineNumber, position.column, position.lineNumber, position.column)
// handle special cases
// if we redid the suffix, replace the suffix
if (autocompletion.type === 'single-line-redo-suffix') {
const oldSuffix = prefixAndSuffix.suffixToTheRightOfCursor
const newSuffix = autocompletion.insertText
const [isSubsequence, lastMatchingChar] = getIsSubsequence({ // check that the old text contains the same brackets + symbols as the new text
subsequence: removeAllWhitespace(oldSuffix), // old suffix
of: removeAllWhitespace(newSuffix), // new suffix
})
if (isSubsequence) {
rangeToReplace = new Range(position.lineNumber, position.column, position.lineNumber, Number.MAX_SAFE_INTEGER)
}
else {
const lastMatchupIdx = trimmedInsertText.lastIndexOf(lastMatchingChar)
trimmedInsertText = trimmedInsertText.slice(0, lastMatchupIdx + 1)
const numCharsToReplace = oldSuffix.lastIndexOf(lastMatchingChar) + 1
rangeToReplace = new Range(position.lineNumber, position.column, position.lineNumber, position.column + numCharsToReplace)
// console.log('show____', trimmedInsertText, rangeToReplace)
}
}
return [{
insertText: trimmedInsertText,
range: rangeToReplace,
}]
}
// returns whether this autocompletion is in the cache
// const doesPrefixMatchAutocompletion = ({ prefix, autocompletion }: { prefix: string, autocompletion: Autocompletion }): boolean => {
// const originalPrefix = autocompletion.prefix
// const generatedMiddle = autocompletion.result
// const originalPrefixTrimmed = trimPrefix(originalPrefix)
// const currentPrefixTrimmed = trimPrefix(prefix)
// if (currentPrefixTrimmed.length < originalPrefixTrimmed.length) {
// return false
// }
// const isMatch = (originalPrefixTrimmed + generatedMiddle).startsWith(currentPrefixTrimmed)
// return isMatch
// }
const allLinebreakSymbols = ['\r\n', '\n']
const _ln = isWindows ? allLinebreakSymbols[0] : allLinebreakSymbols[1]
type PrefixAndSuffixInfo = { prefix: string, suffix: string, prefixLines: string[], suffixLines: string[], prefixToTheLeftOfCursor: string, suffixToTheRightOfCursor: string }
const getPrefixAndSuffixInfo = (model: ITextModel, position: Position): PrefixAndSuffixInfo => {
const fullText = model.getValue();
const cursorOffset = model.getOffsetAt(position)
const prefix = fullText.substring(0, cursorOffset)
const suffix = fullText.substring(cursorOffset)
const prefixLines = prefix.split(_ln)
const suffixLines = suffix.split(_ln)
const prefixToTheLeftOfCursor = prefixLines.slice(-1)[0] ?? ''
const suffixToTheRightOfCursor = suffixLines[0] ?? ''
return { prefix, suffix, prefixLines, suffixLines, prefixToTheLeftOfCursor, suffixToTheRightOfCursor }
}
const getIndex = (str: string, line: number, char: number) => {
return str.split(_ln).slice(0, line).join(_ln).length + (line > 0 ? 1 : 0) + char;
}
const getLastLine = (s: string): string => {
const matches = s.match(new RegExp(`[^${_ln}]*$`))
return matches ? matches[0] : ''
}
type AutocompletionMatchupBounds = {
startLine: number,
startCharacter: number,
startIdx: number,
}
// returns the startIdx of the match if there is a match, or undefined if there is no match
// all results are wrt `autocompletion.result`
const getAutocompletionMatchup = ({ prefix, autocompletion }: { prefix: string, autocompletion: Autocompletion }): AutocompletionMatchupBounds | undefined => {
const trimmedCurrentPrefix = removeLeftTabsAndTrimEnds(prefix)
const trimmedCompletionPrefix = removeLeftTabsAndTrimEnds(autocompletion.prefix)
const trimmedCompletionMiddle = removeLeftTabsAndTrimEnds(autocompletion.insertText)
// console.log('@result: ', JSON.stringify(autocompletion.insertText))
// console.log('@trimmedCurrentPrefix: ', JSON.stringify(trimmedCurrentPrefix))
// console.log('@trimmedCompletionPrefix: ', JSON.stringify(trimmedCompletionPrefix))
// console.log('@trimmedCompletionMiddle: ', JSON.stringify(trimmedCompletionMiddle))
if (trimmedCurrentPrefix.length < trimmedCompletionPrefix.length) { // user must write text beyond the original prefix at generation time
// console.log('@undefined1')
return undefined
}
if ( // check that completion starts with the prefix
!(trimmedCompletionPrefix + trimmedCompletionMiddle)
.startsWith(trimmedCurrentPrefix)
) {
// console.log('@undefined2')
return undefined
}
// reverse map to find position wrt `autocompletion.result`
const lineStart =
trimmedCurrentPrefix.split(_ln).length -
trimmedCompletionPrefix.split(_ln).length;
if (lineStart < 0) {
// console.log('@undefined3')
console.error('Error: No line found.');
return undefined;
}
const currentPrefixLine = getLastLine(trimmedCurrentPrefix)
const completionPrefixLine = lineStart === 0 ? getLastLine(trimmedCompletionPrefix) : ''
const completionMiddleLine = autocompletion.insertText.split(_ln)[lineStart]
const fullCompletionLine = completionPrefixLine + completionMiddleLine
// console.log('currentPrefixLine', currentPrefixLine)
// console.log('completionPrefixLine', completionPrefixLine)
// console.log('completionMiddleLine', completionMiddleLine)
const charMatchIdx = fullCompletionLine.indexOf(currentPrefixLine)
if (charMatchIdx < 0) {
// console.log('@undefined4', charMatchIdx)
console.error('Warning: Found character with negative index. This should never happen.')
return undefined
}
const character = (charMatchIdx +
currentPrefixLine.length
- completionPrefixLine.length
)
const startIdx = getIndex(autocompletion.insertText, lineStart, character)
return {
startLine: lineStart,
startCharacter: character,
startIdx,
}
}
type CompletionOptions = {
predictionType: AutocompletionPredictionType,
shouldGenerate: boolean,
llmPrefix: string,
llmSuffix: string,
stopTokens: string[],
}
const getCompletionOptions = (prefixAndSuffix: PrefixAndSuffixInfo, relevantContext: string, justAcceptedAutocompletion: boolean): CompletionOptions => {
let { prefix, suffix, prefixToTheLeftOfCursor, suffixToTheRightOfCursor, suffixLines, prefixLines } = prefixAndSuffix
// trim prefix and suffix to not be very large
suffixLines = suffix.split(_ln).slice(0, 25)
prefixLines = prefix.split(_ln).slice(-25)
prefix = prefixLines.join(_ln)
suffix = suffixLines.join(_ln)
let completionOptions: CompletionOptions
// if line is empty, do multiline completion
const isLineEmpty = !prefixToTheLeftOfCursor.trim() && !suffixToTheRightOfCursor.trim()
const isLinePrefixEmpty = removeAllWhitespace(prefixToTheLeftOfCursor).length === 0
const isLineSuffixEmpty = removeAllWhitespace(suffixToTheRightOfCursor).length === 0
// TODO add context to prefix
// llmPrefix = '\n\n/* Relevant context:\n' + relevantContext + '\n*/\n' + llmPrefix
// if we just accepted an autocompletion, predict a multiline completion starting on the next line
if (justAcceptedAutocompletion && isLineSuffixEmpty) {
const prefixWithNewline = prefix + _ln
completionOptions = {
predictionType: 'multi-line-start-on-next-line',
shouldGenerate: true,
llmPrefix: prefixWithNewline,
llmSuffix: suffix,
stopTokens: [`${_ln}${_ln}`] // double newlines
}
}
// if the current line is empty, predict a single-line completion
else if (isLineEmpty) {
completionOptions = {
predictionType: 'single-line-fill-middle',
shouldGenerate: true,
llmPrefix: prefix,
llmSuffix: suffix,
stopTokens: allLinebreakSymbols
}
}
// if suffix is 3 or fewer characters, attempt to complete the line ignorning it
else if (removeAllWhitespace(suffixToTheRightOfCursor).length <= 3) {
const suffixLinesIgnoringThisLine = suffixLines.slice(1)
const suffixStringIgnoringThisLine = suffixLinesIgnoringThisLine.length === 0 ? '' : _ln + suffixLinesIgnoringThisLine.join(_ln)
completionOptions = {
predictionType: 'single-line-redo-suffix',
shouldGenerate: true,
llmPrefix: prefix,
llmSuffix: suffixStringIgnoringThisLine,
stopTokens: allLinebreakSymbols
}
}
// else attempt to complete the middle of the line if there is a prefix (the completion looks bad if there is no prefix)
else if (!isLinePrefixEmpty) {
completionOptions = {
predictionType: 'single-line-fill-middle',
shouldGenerate: true,
llmPrefix: prefix,
llmSuffix: suffix,
stopTokens: allLinebreakSymbols
}
} else {
completionOptions = {
predictionType: 'do-not-predict',
shouldGenerate: false,
llmPrefix: prefix,
llmSuffix: suffix,
stopTokens: []
}
}
return completionOptions
}
export interface IAutocompleteService {
readonly _serviceBrand: undefined;
}
export const IAutocompleteService = createDecorator<IAutocompleteService>('AutocompleteService');
export class AutocompleteService extends Disposable implements IAutocompleteService {
static readonly ID = 'void.autocompleteService'
_serviceBrand: undefined;
private _autocompletionId: number = 0;
private _autocompletionsOfDocument: { [docUriStr: string]: LRUCache<number, Autocompletion> } = {}
private _lastCompletionStart = 0
private _lastCompletionAccept = 0
// private _lastPrefix: string = ''
// used internally by vscode
// fires after every keystroke and returns the completion to show
async _provideInlineCompletionItems(
model: ITextModel,
position: Position,
context: InlineCompletionContext,
token: CancellationToken,
): Promise<InlineCompletion[]> {
const testMode = false
const docUriStr = model.uri.toString();
const prefixAndSuffix = getPrefixAndSuffixInfo(model, position)
const { prefix, suffix } = prefixAndSuffix
// initialize cache if it doesnt exist
// note that whenever an autocompletion is accepted, it is removed from cache
if (!this._autocompletionsOfDocument[docUriStr]) {
this._autocompletionsOfDocument[docUriStr] = new LRUCache<number, Autocompletion>(
MAX_CACHE_SIZE,
(autocompletion: Autocompletion) => {
if (autocompletion.requestId)
this._llmMessageService.abort(autocompletion.requestId)
}
)
}
// this._lastPrefix = prefix
// print all pending autocompletions
// let _numPending = 0
// this._autocompletionsOfDocument[docUriStr].items.forEach((a: Autocompletion) => { if (a.status === 'pending') _numPending += 1 })
// console.log('@numPending: ' + _numPending)
// get autocompletion from cache
let cachedAutocompletion: Autocompletion | undefined = undefined
let autocompletionMatchup: AutocompletionMatchupBounds | undefined = undefined
for (const autocompletion of this._autocompletionsOfDocument[docUriStr].items.values()) {
// if the user's change matches with the autocompletion
autocompletionMatchup = getAutocompletionMatchup({ prefix, autocompletion })
if (autocompletionMatchup !== undefined) {
cachedAutocompletion = autocompletion
break;
}
}
// if there is a cached autocompletion, return it
if (cachedAutocompletion && autocompletionMatchup) {
console.log('AA')
// console.log('id: ' + cachedAutocompletion.id)
if (cachedAutocompletion.status === 'finished') {
console.log('A1')
const inlineCompletions = toInlineCompletions({ autocompletionMatchup, autocompletion: cachedAutocompletion, prefixAndSuffix, position, debug: true })
return inlineCompletions
} else if (cachedAutocompletion.status === 'pending') {
console.log('A2')
try {
await cachedAutocompletion.llmPromise;
const inlineCompletions = toInlineCompletions({ autocompletionMatchup, autocompletion: cachedAutocompletion, prefixAndSuffix, position })
return inlineCompletions
} catch (e) {
this._autocompletionsOfDocument[docUriStr].delete(cachedAutocompletion.id)
console.error('Error creating autocompletion (1): ' + e)
}
} else if (cachedAutocompletion.status === 'error') {
console.log('A3')
} else {
console.log('A4')
}
return []
}
// else if no more typing happens, then go forwards with the request
// wait DEBOUNCE_TIME for the user to stop typing
const thisTime = Date.now()
const justAcceptedAutocompletion = thisTime - this._lastCompletionAccept < 500
this._lastCompletionStart = thisTime
const didTypingHappenDuringDebounce = await new Promise((resolve, reject) =>
setTimeout(() => {
if (this._lastCompletionStart === thisTime) {
resolve(false)
} else {
resolve(true)
}
}, DEBOUNCE_TIME)
)
// if more typing happened, then do not go forwards with the request
if (didTypingHappenDuringDebounce) {
return []
}
// if there are too many pending requests, cancel the oldest one
let numPending = 0
let oldestPending: Autocompletion | undefined = undefined
for (const autocompletion of this._autocompletionsOfDocument[docUriStr].items.values()) {
if (autocompletion.status === 'pending') {
numPending += 1
if (oldestPending === undefined) {
oldestPending = autocompletion
}
if (numPending >= MAX_PENDING_REQUESTS) {
// cancel the oldest pending request and remove it from cache
this._autocompletionsOfDocument[docUriStr].delete(oldestPending.id)
break
}
}
}
// gather relevant context from the code around the user's selection and definitions
// const relevantSnippetsList = await this._contextGatheringService.readCachedSnippets(model, position, 3);
// const relevantSnippetsList = this._contextGatheringService.getCachedSnippets();
// const relevantSnippets = relevantSnippetsList.map((text) => `${text}`).join('\n-------------------------------\n')
// console.log('@@---------------------\n' + relevantSnippets)
const relevantContext = ''
const { shouldGenerate, predictionType, llmPrefix, llmSuffix, stopTokens } = getCompletionOptions(prefixAndSuffix, relevantContext, justAcceptedAutocompletion)
if (!shouldGenerate) return []
if (testMode && this._autocompletionId !== 0) { // TODO remove this
return []
}
// console.log('B')
// create a new autocompletion and add it to cache
const newAutocompletion: Autocompletion = {
id: this._autocompletionId++,
prefix: prefix, // the actual prefix and suffix
suffix: suffix,
llmPrefix: llmPrefix, // the prefix and suffix the llm sees
llmSuffix: llmSuffix,
startTime: Date.now(),
endTime: undefined,
type: predictionType,
status: 'pending',
llmPromise: undefined,
insertText: '',
requestId: null,
_newlineCount: 0,
}
console.log('starting autocomplete...', predictionType)
// set parameters of `newAutocompletion` appropriately
newAutocompletion.llmPromise = new Promise((resolve, reject) => {
const requestId = this._llmMessageService.sendLLMMessage({
messagesType: 'FIMMessage',
messages: {
prefix: llmPrefix,
suffix: llmSuffix,
stopTokens: stopTokens,
},
useProviderFor: 'Autocomplete',
logging: { loggingName: 'Autocomplete' },
onText: async ({ fullText, newText }) => {
newAutocompletion.insertText = fullText
// count newlines in newText
const numNewlines = newText.match(/\n|\r\n/g)?.length || 0
newAutocompletion._newlineCount += numNewlines
// if too many newlines, resolve up to last newline
if (newAutocompletion._newlineCount > 10) {
const lastNewlinePos = fullText.lastIndexOf('\n')
newAutocompletion.insertText = fullText.substring(0, lastNewlinePos)
resolve(newAutocompletion.insertText)
return
}
// if (!getAutocompletionMatchup({ prefix: this._lastPrefix, autocompletion: newAutocompletion })) {
// reject('LLM response did not match user\'s text.')
// }
},
onFinalMessage: ({ fullText }) => {
// console.log('____res: ', JSON.stringify(newAutocompletion.insertText))
newAutocompletion.endTime = Date.now()
newAutocompletion.status = 'finished'
const [text, _] = extractCodeFromRegular({ text: fullText, recentlyAddedTextLen: 0 })
newAutocompletion.insertText = processStartAndEndSpaces(text)
// handle special case for predicting starting on the next line, add a newline character
if (newAutocompletion.type === 'multi-line-start-on-next-line') {
newAutocompletion.insertText = _ln + newAutocompletion.insertText
}
resolve(newAutocompletion.insertText)
},
onError: ({ message }) => {
newAutocompletion.endTime = Date.now()
newAutocompletion.status = 'error'
reject(message)
},
})
newAutocompletion.requestId = requestId
// if the request hasnt resolved in TIMEOUT_TIME seconds, reject it
setTimeout(() => {
if (newAutocompletion.status === 'pending') {
reject('Timeout receiving message to LLM.')
}
}, TIMEOUT_TIME)
})
// add autocompletion to cache
this._autocompletionsOfDocument[docUriStr].set(newAutocompletion.id, newAutocompletion)
// show autocompletion
try {
await newAutocompletion.llmPromise
// console.log('id: ' + newAutocompletion.id)
const autocompletionMatchup: AutocompletionMatchupBounds = { startIdx: 0, startLine: 0, startCharacter: 0 }
const inlineCompletions = toInlineCompletions({ autocompletionMatchup, autocompletion: newAutocompletion, prefixAndSuffix, position })
return inlineCompletions
} catch (e) {
this._autocompletionsOfDocument[docUriStr].delete(newAutocompletion.id)
console.error('Error creating autocompletion (2): ' + e)
return []
}
}
constructor(
@ILanguageFeaturesService private _langFeatureService: ILanguageFeaturesService,
@ILLMMessageService private readonly _llmMessageService: ILLMMessageService,
@IEditorService private readonly _editorService: IEditorService,
@IModelService private readonly _modelService: IModelService,
// @IContextGatheringService private readonly _contextGatheringService: IContextGatheringService,
) {
super()
this._langFeatureService.inlineCompletionsProvider.register('*', {
provideInlineCompletions: async (model, position, context, token) => {
const items = await this._provideInlineCompletionItems(model, position, context, token)
// console.log('item: ', items?.[0]?.insertText)
return { items: items, }
},
freeInlineCompletions: (completions) => {
// get the `docUriStr` and the `position` of the cursor
const activePane = this._editorService.activeEditorPane;
if (!activePane) return;
const control = activePane.getControl();
if (!control || !isCodeEditor(control)) return;
const position = control.getPosition();
if (!position) return;
const resource = EditorResourceAccessor.getCanonicalUri(this._editorService.activeEditor);
if (!resource) return;
const model = this._modelService.getModel(resource)
if (!model) return;
const docUriStr = resource.toString();
if (!this._autocompletionsOfDocument[docUriStr]) return;
const { prefix, } = getPrefixAndSuffixInfo(model, position)
// go through cached items and remove matching ones
// autocompletion.prefix + autocompletion.insertedText ~== insertedText
this._autocompletionsOfDocument[docUriStr].items.forEach((autocompletion: Autocompletion) => {
// we can do this more efficiently, I just didn't want to deal with all of the edge cases
const matchup = removeAllWhitespace(prefix) === removeAllWhitespace(autocompletion.prefix + autocompletion.insertText)
if (matchup) {
console.log('ACCEPT', autocompletion.id)
this._lastCompletionAccept = Date.now()
this._autocompletionsOfDocument[docUriStr].delete(autocompletion.id);
}
});
},
})
}
}
registerWorkbenchContribution2(AutocompleteService.ID, AutocompleteService, WorkbenchPhase.BlockRestore);
@@ -0,0 +1,552 @@
/*--------------------------------------------------------------------------------------
* 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 { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
import { URI } from '../../../../base/common/uri.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { IRange } from '../../../../editor/common/core/range.js';
import { ILLMMessageService } from '../common/llmMessageService.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { chat_userMessageContent, chat_systemMessage, chat_userMessageContentWithAllFilesToo as chat_userMessageContentWithAllFiles } from './prompt/prompts.js';
import { LLMChatMessage } from '../common/llmMessageTypes.js';
import { IFileService } from '../../../../platform/files/common/files.js';
// one of the square items that indicates a selection in a chat bubble (NOT a file, a Selection of text)
export type CodeSelection = {
type: 'Selection';
fileURI: URI;
selectionStr: string;
range: IRange;
}
export type FileSelection = {
type: 'File';
fileURI: URI;
selectionStr: null;
range: null;
}
export type StagingSelectionItem = CodeSelection | FileSelection
// WARNING: changing this format is a big deal!!!!!! need to migrate old format to new format on users' computers so people don't get errors.
export type ChatMessage =
| {
role: 'user';
content: string | null; // content displayed to the LLM on future calls - allowed to be '', will be replaced with (empty)
displayContent: string | null; // content displayed to user - allowed to be '', will be ignored
selections: StagingSelectionItem[] | null; // the user's selection
state: {
stagingSelections: StagingSelectionItem[];
isBeingEdited: boolean;
}
}
| {
role: 'assistant';
content: string | null; // content received from LLM - allowed to be '', will be replaced with (empty)
displayContent: string | null; // content displayed to user (this is the same as content for now) - allowed to be '', will be ignored
}
| {
role: 'system';
content: string;
displayContent?: undefined;
}
type UserMessageType = ChatMessage & { role: 'user' }
type UserMessageState = UserMessageType['state']
export const defaultMessageState: UserMessageState = { stagingSelections: [], isBeingEdited: false }
// a 'thread' means a chat message history
export type ChatThreads = {
[id: string]: {
id: string; // store the id here too
createdAt: string; // ISO string
lastModified: string; // ISO string
messages: ChatMessage[];
state: {
stagingSelections: StagingSelectionItem[];
focusedMessageIdx: number | undefined; // index of the message that is being edited (undefined if none)
isCheckedOfSelectionId: { [selectionId: string]: boolean };
}
};
}
type ThreadType = ChatThreads[string]
const defaultThreadState: ThreadType['state'] = { stagingSelections: [], focusedMessageIdx: undefined, isCheckedOfSelectionId: {} }
export type ThreadsState = {
allThreads: ChatThreads;
currentThreadId: string; // intended for internal use only
}
export type ThreadStreamState = {
[threadId: string]: undefined | {
error?: { message: string, fullError: Error | null, };
messageSoFar?: string;
streamingToken?: string;
}
}
const newThreadObject = () => {
const now = new Date().toISOString()
return {
id: new Date().getTime().toString(),
createdAt: now,
lastModified: now,
messages: [],
state: {
stagingSelections: [],
focusedMessageIdx: undefined,
isCheckedOfSelectionId: {}
},
} satisfies ChatThreads[string]
}
const THREAD_VERSION_KEY = 'void.chatThreadVersion'
const THREAD_VERSION = 'v2'
const THREAD_STORAGE_KEY = 'void.chatThreadStorage'
export interface IChatThreadService {
readonly _serviceBrand: undefined;
readonly state: ThreadsState;
readonly streamState: ThreadStreamState;
onDidChangeCurrentThread: Event<void>;
onDidChangeStreamState: Event<{ threadId: string }>
getCurrentThread(): ChatThreads[string];
openNewThread(): void;
switchToThread(threadId: string): void;
getFocusedMessageIdx(): number | undefined;
isFocusingMessage(): boolean;
setFocusedMessageIdx(messageIdx: number | undefined): void;
// _useFocusedStagingState(messageIdx?: number | undefined): readonly [StagingInfo, (stagingInfo: StagingInfo) => void];
_useCurrentThreadState(): readonly [ThreadType['state'], (newState: Partial<ThreadType['state']>) => void];
_useCurrentMessageState(messageIdx: number): readonly [UserMessageState, (newState: Partial<UserMessageState>) => void];
editUserMessageAndStreamResponse(userMessage: string, messageIdx: number): Promise<void>;
addUserMessageAndStreamResponse(userMessage: string): Promise<void>;
cancelStreaming(threadId: string): void;
dismissStreamError(threadId: string): void;
}
export const IChatThreadService = createDecorator<IChatThreadService>('voidChatThreadService');
class ChatThreadService extends Disposable implements IChatThreadService {
_serviceBrand: undefined;
// this fires when the current thread changes at all (a switch of currentThread, or a message added to it, etc)
private readonly _onDidChangeCurrentThread = new Emitter<void>();
readonly onDidChangeCurrentThread: Event<void> = this._onDidChangeCurrentThread.event;
readonly streamState: ThreadStreamState = {}
private readonly _onDidChangeStreamState = new Emitter<{ threadId: string }>();
readonly onDidChangeStreamState: Event<{ threadId: string }> = this._onDidChangeStreamState.event;
state: ThreadsState // allThreads is persisted, currentThread is not
constructor(
@IStorageService private readonly _storageService: IStorageService,
@IModelService private readonly _modelService: IModelService,
@IFileService private readonly _fileService: IFileService,
@ILLMMessageService private readonly _llmMessageService: ILLMMessageService,
) {
super()
this.state = {
allThreads: this._readAllThreads(),
currentThreadId: null as unknown as string, // gets set in startNewThread()
}
// always be in a thread
this.openNewThread()
// for now just write the version, anticipating bigger changes in the future where we'll want to access this
this._storageService.store(THREAD_VERSION_KEY, THREAD_VERSION, StorageScope.APPLICATION, StorageTarget.USER)
}
private _readAllThreads(): ChatThreads {
// PUT ANY VERSION CHANGE FORMAT CONVERSION CODE HERE
// CAN ADD "v0" TAG IN STORAGE AND CONVERT
const threadsStr = this._storageService.get(THREAD_STORAGE_KEY, StorageScope.APPLICATION)
const threads: ChatThreads = threadsStr ? JSON.parse(threadsStr) : {}
this._updateThreadsToVersion(threads, THREAD_VERSION)
return threads
}
private _updateThreadsToVersion(oldThreadsObject: any, toVersion: string) {
if (toVersion === 'v2') {
const threads: ChatThreads = oldThreadsObject
/** v1 -> v2
- threads.state.currentStagingSelections: CodeStagingSelection[] | null;
+ thread[threadIdx].state
+ message.state
*/
// check if we need to update
let shouldUpdate = false
for (const thread of Object.values(threads)) {
if (!thread.state) {
shouldUpdate = true
}
for (const chatMessage of Object.values(thread.messages)) {
if (chatMessage.role === 'user' && !chatMessage.state) {
shouldUpdate = true
}
}
}
if (!shouldUpdate) return;
// update the threads
for (const thread of Object.values(threads)) {
if (!thread.state) {
thread.state = defaultThreadState
}
for (const chatMessage of Object.values(thread.messages)) {
if (chatMessage.role === 'user' && !chatMessage.state) {
chatMessage.state = defaultMessageState
}
}
}
// push the update
this._storeAllThreads(threads)
}
}
private _storeAllThreads(threads: ChatThreads) {
this._storageService.store(THREAD_STORAGE_KEY, JSON.stringify(threads), StorageScope.APPLICATION, StorageTarget.USER)
}
// this should be the only place this.state = ... appears besides constructor
private _setState(state: Partial<ThreadsState>, affectsCurrent: boolean) {
this.state = {
...this.state,
...state
}
if (affectsCurrent)
this._onDidChangeCurrentThread.fire()
}
private _getAllSelections() {
const thread = this.getCurrentThread()
return thread.messages.flatMap(m => m.role === 'user' && m.selections || [])
}
private _getSelectionsUpToMessageIdx(messageIdx: number) {
const thread = this.getCurrentThread()
const prevMessages = thread.messages.slice(0, messageIdx)
return prevMessages.flatMap(m => m.role === 'user' && m.selections || [])
}
private _setStreamState(threadId: string, state: Partial<NonNullable<ThreadStreamState[string]>>) {
this.streamState[threadId] = {
...this.streamState[threadId],
...state
}
this._onDidChangeStreamState.fire({ threadId })
}
// ---------- streaming ----------
finishStreaming = (threadId: string, content: string, error?: { message: string, fullError: Error | null }) => {
// add assistant's message to chat history, and clear selection
const assistantHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content || null }
this._addMessageToThread(threadId, assistantHistoryElt)
this._setStreamState(threadId, { messageSoFar: undefined, streamingToken: undefined, error })
}
async editUserMessageAndStreamResponse(userMessage: string, messageIdx: number) {
const thread = this.getCurrentThread()
if (thread.messages?.[messageIdx]?.role !== 'user') {
throw new Error("Error: editing a message with role !=='user'")
}
// get prev and curr selections before clearing the message
const prevSelns = this._getSelectionsUpToMessageIdx(messageIdx)
const currSelns = thread.messages[messageIdx].selections || []
// clear messages up to the index
const slicedMessages = thread.messages.slice(0, messageIdx)
this._setState({
allThreads: {
...this.state.allThreads,
[thread.id]: {
...thread,
messages: slicedMessages
}
}
}, true)
// stream the edit
this.addUserMessageAndStreamResponse(userMessage, { prevSelns, currSelns })
}
async addUserMessageAndStreamResponse(userMessage: string, options?: { prevSelns?: StagingSelectionItem[], currSelns?: StagingSelectionItem[] }) {
const thread = this.getCurrentThread()
const threadId = thread.id
// add user's message to chat history
const instructions = userMessage
const prevSelns: StagingSelectionItem[] = options?.prevSelns ?? this._getAllSelections()
const currSelns: StagingSelectionItem[] = options?.currSelns ?? thread.state.stagingSelections
// read all curr+previous files on demand instead of adding them to the history
const messageContent = await chat_userMessageContent(instructions, prevSelns, currSelns)
const messageContentWithAllFiles = await chat_userMessageContentWithAllFiles(instructions, prevSelns, currSelns, this._modelService, this._fileService)
const prevLLMMessages = this.getCurrentThread().messages.map(m => ({ role: m.role, content: m.content || '(empty model output)' }))
const currLLMMessage: LLMChatMessage = { role: 'user', content: messageContentWithAllFiles }
const userHistoryElt: ChatMessage = { role: 'user', content: messageContent, displayContent: instructions, selections: currSelns, state: defaultMessageState }
this._addMessageToThread(threadId, userHistoryElt)
this._setStreamState(threadId, { error: undefined })
console.log(`messageContent`)
console.log([{ role: 'system', content: chat_systemMessage },
...prevLLMMessages,
currLLMMessage,])
const llmCancelToken = this._llmMessageService.sendLLMMessage({
messagesType: 'chatMessages',
logging: { loggingName: 'Chat' },
useProviderFor: 'Ctrl+L',
messages: [
{ role: 'system', content: chat_systemMessage },
...prevLLMMessages,
currLLMMessage,
],
onText: ({ newText, fullText }) => {
this._setStreamState(threadId, { messageSoFar: fullText })
},
onFinalMessage: ({ fullText: content }) => {
this.finishStreaming(threadId, content)
},
onError: (error) => {
this.finishStreaming(threadId, this.streamState[threadId]?.messageSoFar ?? '', error)
},
})
if (llmCancelToken === null) return
this._setStreamState(threadId, { streamingToken: llmCancelToken })
}
cancelStreaming(threadId: string) {
const llmCancelToken = this.streamState[threadId]?.streamingToken
if (llmCancelToken !== undefined) this._llmMessageService.abort(llmCancelToken)
this.finishStreaming(threadId, this.streamState[threadId]?.messageSoFar ?? '')
}
dismissStreamError(threadId: string): void {
this._setStreamState(threadId, { error: undefined })
}
// ---------- the rest ----------
getCurrentThread(): ChatThreads[string] {
const state = this.state
return state.allThreads[state.currentThreadId]
}
getFocusedMessageIdx() {
const thread = this.getCurrentThread()
// get the focusedMessageIdx
const focusedMessageIdx = thread.state.focusedMessageIdx
if (focusedMessageIdx === undefined) return;
// check that the message is actually being edited
const focusedMessage = thread.messages[focusedMessageIdx]
if (focusedMessage.role !== 'user') return;
if (!focusedMessage.state) return;
return focusedMessageIdx
}
isFocusingMessage() {
return this.getFocusedMessageIdx() !== undefined
}
switchToThread(threadId: string) {
this._setState({ currentThreadId: threadId }, true)
}
openNewThread() {
// if a thread with 0 messages already exists, switch to it
const { allThreads: currentThreads } = this.state
for (const threadId in currentThreads) {
if (currentThreads[threadId].messages.length === 0) {
this.switchToThread(threadId)
return
}
}
// otherwise, start a new thread
const newThread = newThreadObject()
// update state
const newThreads: ChatThreads = {
...currentThreads,
[newThread.id]: newThread
}
this._storeAllThreads(newThreads)
this._setState({ allThreads: newThreads, currentThreadId: newThread.id }, true)
}
_addMessageToThread(threadId: string, message: ChatMessage) {
const { allThreads } = this.state
const oldThread = allThreads[threadId]
// update state and store it
const newThreads = {
...allThreads,
[oldThread.id]: {
...oldThread,
lastModified: new Date().toISOString(),
messages: [...oldThread.messages, message],
}
}
this._storeAllThreads(newThreads)
this._setState({ allThreads: newThreads }, true) // the current thread just changed (it had a message added to it)
}
// sets the currently selected message (must be undefined if no message is selected)
setFocusedMessageIdx(messageIdx: number | undefined) {
const threadId = this.state.currentThreadId
const thread = this.state.allThreads[threadId]
if (!thread) return
this._setState({
allThreads: {
...this.state.allThreads,
[threadId]: {
...thread,
state: {
...thread.state,
focusedMessageIdx: messageIdx,
}
}
}
}, true)
}
// set message.state
private _setCurrentMessageState(state: Partial<UserMessageState>, messageIdx: number): void {
const threadId = this.state.currentThreadId
const thread = this.state.allThreads[threadId]
if (!thread) return
this._setState({
allThreads: {
...this.state.allThreads,
[threadId]: {
...thread,
messages: thread.messages.map((m, i) =>
i === messageIdx && m.role === 'user' ? {
...m,
state: {
...m.state,
...state
},
} : m
)
}
}
}, true)
}
// set thread.state
private _setCurrentThreadState(state: Partial<ThreadType['state']>): void {
const threadId = this.state.currentThreadId
const thread = this.state.allThreads[threadId]
if (!thread) return
this._setState({
allThreads: {
...this.state.allThreads,
[thread.id]: {
...thread,
state: {
...thread.state,
...state
}
}
}
}, true)
}
_useCurrentMessageState(messageIdx: number) {
const thread = this.getCurrentThread()
const messages = thread.messages
const currMessage = messages[messageIdx]
if (currMessage.role !== 'user') {
return [defaultMessageState, (s: any) => { }] as const
}
const state = currMessage.state
const setState = (newState: Partial<UserMessageState>) => this._setCurrentMessageState(newState, messageIdx)
return [state, setState] as const
}
_useCurrentThreadState() {
const thread = this.getCurrentThread()
const state = thread.state
const setState = this._setCurrentThreadState.bind(this)
return [state, setState] as const
}
}
registerSingleton(IChatThreadService, ChatThreadService, InstantiationType.Eager);
@@ -0,0 +1,354 @@
import { CancellationToken } from '../../../../base/common/cancellation.js';
import { Position } from '../../../../editor/common/core/position.js';
import { DocumentSymbol, SymbolKind } from '../../../../editor/common/languages.js';
import { ITextModel } from '../../../../editor/common/model.js';
import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { Range, IRange } from '../../../../editor/common/core/range.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { URI } from '../../../../base/common/uri.js';
// make sure snippet logic works
// change logic for `visited` to intervals
// atomically set new snippets at end
// throttle cache setting
interface IVisitedInterval {
uri: string;
startLine: number;
endLine: number;
}
export interface IContextGatheringService {
readonly _serviceBrand: undefined;
updateCache(model: ITextModel, pos: Position): Promise<void>;
getCachedSnippets(): string[];
}
export const IContextGatheringService = createDecorator<IContextGatheringService>('contextGatheringService');
class ContextGatheringService extends Disposable implements IContextGatheringService {
_serviceBrand: undefined;
private readonly _NUM_LINES = 3;
private readonly _MAX_SNIPPET_LINES = 7; // Reasonable size for context
// Cache holds the most recent list of snippets.
private _cache: string[] = [];
private _snippetIntervals: IVisitedInterval[] = [];
constructor(
@ILanguageFeaturesService private readonly _langFeaturesService: ILanguageFeaturesService,
@IModelService private readonly _modelService: IModelService,
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService
) {
super();
this._modelService.getModels().forEach(model => this._subscribeToModel(model));
this._register(this._modelService.onModelAdded(model => this._subscribeToModel(model)));
}
private _subscribeToModel(model: ITextModel): void {
console.log("Subscribing to model:", model.uri.toString());
this._register(model.onDidChangeContent(() => {
const editor = this._codeEditorService.getFocusedCodeEditor();
if (editor && editor.getModel() === model) {
const pos = editor.getPosition();
console.log("updateCache called at position:", pos);
if (pos) {
this.updateCache(model, pos);
}
}
}));
}
public async updateCache(model: ITextModel, pos: Position): Promise<void> {
const snippets = new Set<string>();
this._snippetIntervals = []; // Reset intervals for new cache update
await this._gatherNearbySnippets(model, pos, this._NUM_LINES, 3, snippets, this._snippetIntervals);
await this._gatherParentSnippets(model, pos, this._NUM_LINES, 3, snippets, this._snippetIntervals);
// Convert to array and filter overlapping snippets
this._cache = Array.from(snippets);
console.log("Cache updated:", this._cache);
}
public getCachedSnippets(): string[] {
return this._cache;
}
// Basic snippet extraction.
private _getSnippetForRange(model: ITextModel, range: IRange, numLines: number): string {
const startLine = Math.max(range.startLineNumber - numLines, 1);
const endLine = Math.min(range.endLineNumber + numLines, model.getLineCount());
// Enforce maximum snippet size
const totalLines = endLine - startLine + 1;
const adjustedStartLine = totalLines > this._MAX_SNIPPET_LINES
? endLine - this._MAX_SNIPPET_LINES + 1
: startLine;
const snippetRange = new Range(adjustedStartLine, 1, endLine, model.getLineMaxColumn(endLine));
return this._cleanSnippet(model.getValueInRange(snippetRange));
}
private _cleanSnippet(snippet: string): string {
return snippet
.split('\n')
// Remove empty lines and lines with only comments
.filter(line => {
const trimmed = line.trim();
return trimmed && !/^\/\/+$/.test(trimmed);
})
// Rejoin with newlines
.join('\n')
// Remove excess whitespace
.trim();
}
private _normalizeSnippet(snippet: string): string {
return snippet
// Remove multiple newlines
.replace(/\n{2,}/g, '\n')
// Remove trailing whitespace
.trim();
}
private _addSnippetIfNotOverlapping(
model: ITextModel,
range: IRange,
snippets: Set<string>,
visited: IVisitedInterval[]
): void {
const startLine = range.startLineNumber;
const endLine = range.endLineNumber;
const uri = model.uri.toString();
if (!this._isRangeVisited(uri, startLine, endLine, visited)) {
visited.push({ uri, startLine, endLine });
const snippet = this._normalizeSnippet(this._getSnippetForRange(model, range, this._NUM_LINES));
if (snippet.length > 0) {
snippets.add(snippet);
}
}
}
private async _gatherNearbySnippets(
model: ITextModel,
pos: Position,
numLines: number,
depth: number,
snippets: Set<string>,
visited: IVisitedInterval[]
): Promise<void> {
if (depth <= 0) return;
const startLine = Math.max(pos.lineNumber - numLines, 1);
const endLine = Math.min(pos.lineNumber + numLines, model.getLineCount());
const range = new Range(startLine, 1, endLine, model.getLineMaxColumn(endLine));
this._addSnippetIfNotOverlapping(model, range, snippets, visited);
const symbols = await this._getSymbolsNearPosition(model, pos, numLines);
for (const sym of symbols) {
const defs = await this._getDefinitionSymbols(model, sym);
for (const def of defs) {
const defModel = this._modelService.getModel(def.uri);
if (defModel) {
const defPos = new Position(def.range.startLineNumber, def.range.startColumn);
this._addSnippetIfNotOverlapping(defModel, def.range, snippets, visited);
await this._gatherNearbySnippets(defModel, defPos, numLines, depth - 1, snippets, visited);
}
}
}
}
private async _gatherParentSnippets(
model: ITextModel,
pos: Position,
numLines: number,
depth: number,
snippets: Set<string>,
visited: IVisitedInterval[]
): Promise<void> {
if (depth <= 0) return;
const container = await this._findContainerFunction(model, pos);
if (!container) return;
const containerRange = container.kind === SymbolKind.Method ? container.selectionRange : container.range;
this._addSnippetIfNotOverlapping(model, containerRange, snippets, visited);
const symbols = await this._getSymbolsNearRange(model, containerRange, numLines);
for (const sym of symbols) {
const defs = await this._getDefinitionSymbols(model, sym);
for (const def of defs) {
const defModel = this._modelService.getModel(def.uri);
if (defModel) {
const defPos = new Position(def.range.startLineNumber, def.range.startColumn);
this._addSnippetIfNotOverlapping(defModel, def.range, snippets, visited);
await this._gatherNearbySnippets(defModel, defPos, numLines, depth - 1, snippets, visited);
}
}
}
const containerPos = new Position(containerRange.startLineNumber, containerRange.startColumn);
await this._gatherParentSnippets(model, containerPos, numLines, depth - 1, snippets, visited);
}
private _isRangeVisited(uri: string, startLine: number, endLine: number, visited: IVisitedInterval[]): boolean {
return visited.some(interval =>
interval.uri === uri &&
!(endLine < interval.startLine || startLine > interval.endLine)
);
}
private async _getSymbolsNearPosition(model: ITextModel, pos: Position, numLines: number): Promise<DocumentSymbol[]> {
const startLine = Math.max(pos.lineNumber - numLines, 1);
const endLine = Math.min(pos.lineNumber + numLines, model.getLineCount());
const range = new Range(startLine, 1, endLine, model.getLineMaxColumn(endLine));
return this._getSymbolsInRange(model, range);
}
private async _getSymbolsNearRange(model: ITextModel, range: IRange, numLines: number): Promise<DocumentSymbol[]> {
const centerLine = Math.floor((range.startLineNumber + range.endLineNumber) / 2);
const startLine = Math.max(centerLine - numLines, 1);
const endLine = Math.min(centerLine + numLines, model.getLineCount());
const searchRange = new Range(startLine, 1, endLine, model.getLineMaxColumn(endLine));
return this._getSymbolsInRange(model, searchRange);
}
private async _getSymbolsInRange(model: ITextModel, range: IRange): Promise<DocumentSymbol[]> {
const symbols: DocumentSymbol[] = [];
const providers = this._langFeaturesService.documentSymbolProvider.ordered(model);
for (const provider of providers) {
try {
const result = await provider.provideDocumentSymbols(model, CancellationToken.None);
if (result) {
const flat = this._flattenSymbols(result);
const intersecting = flat.filter(sym => this._rangesIntersect(sym.range, range));
symbols.push(...intersecting);
}
} catch (e) {
console.warn("Symbol provider error:", e);
}
}
// Also check reference providers.
const refProviders = this._langFeaturesService.referenceProvider.ordered(model);
for (let line = range.startLineNumber; line <= range.endLineNumber; line++) {
const content = model.getLineContent(line);
const words = content.match(/[a-zA-Z_]\w*/g) || [];
for (const word of words) {
const startColumn = content.indexOf(word) + 1;
const pos = new Position(line, startColumn);
if (!this._positionInRange(pos, range)) continue;
for (const provider of refProviders) {
try {
const refs = await provider.provideReferences(model, pos, { includeDeclaration: true }, CancellationToken.None);
if (refs) {
const filtered = refs.filter(ref => this._rangesIntersect(ref.range, range));
for (const ref of filtered) {
symbols.push({
name: word,
detail: '',
kind: SymbolKind.Variable,
range: ref.range,
selectionRange: ref.range,
children: [],
tags: []
});
}
}
} catch (e) {
console.warn("Reference provider error:", e);
}
}
}
}
return symbols;
}
private _flattenSymbols(symbols: DocumentSymbol[]): DocumentSymbol[] {
const flat: DocumentSymbol[] = [];
for (const sym of symbols) {
flat.push(sym);
if (sym.children && sym.children.length > 0) {
flat.push(...this._flattenSymbols(sym.children));
}
}
return flat;
}
private _rangesIntersect(a: IRange, b: IRange): boolean {
return !(
a.endLineNumber < b.startLineNumber ||
a.startLineNumber > b.endLineNumber ||
(a.endLineNumber === b.startLineNumber && a.endColumn < b.startColumn) ||
(a.startLineNumber === b.endLineNumber && a.endColumn > b.endColumn)
);
}
private _positionInRange(pos: Position, range: IRange): boolean {
return pos.lineNumber >= range.startLineNumber &&
pos.lineNumber <= range.endLineNumber &&
(pos.lineNumber !== range.startLineNumber || pos.column >= range.startColumn) &&
(pos.lineNumber !== range.endLineNumber || pos.column <= range.endColumn);
}
// Get definition symbols for a given symbol.
private async _getDefinitionSymbols(model: ITextModel, symbol: DocumentSymbol): Promise<(DocumentSymbol & { uri: URI })[]> {
const pos = new Position(symbol.range.startLineNumber, symbol.range.startColumn);
const providers = this._langFeaturesService.definitionProvider.ordered(model);
const defs: (DocumentSymbol & { uri: URI })[] = [];
for (const provider of providers) {
try {
const res = await provider.provideDefinition(model, pos, CancellationToken.None);
if (res) {
const links = Array.isArray(res) ? res : [res];
defs.push(...links.map(link => ({
name: symbol.name,
detail: symbol.detail,
kind: symbol.kind,
range: link.range,
selectionRange: link.range,
children: [],
tags: symbol.tags || [],
uri: link.uri // Now keeping it as URI instead of converting to string
})));
}
} catch (e) {
console.warn("Definition provider error:", e);
}
}
return defs;
}
private async _findContainerFunction(model: ITextModel, pos: Position): Promise<DocumentSymbol | null> {
const searchRange = new Range(
Math.max(pos.lineNumber - 1, 1), 1,
Math.min(pos.lineNumber + 1, model.getLineCount()),
model.getLineMaxColumn(pos.lineNumber)
);
const symbols = await this._getSymbolsInRange(model, searchRange);
const funcs = symbols.filter(s =>
(s.kind === SymbolKind.Function || s.kind === SymbolKind.Method) &&
this._positionInRange(pos, s.range)
);
if (!funcs.length) return null;
return funcs.reduce((innermost, current) => {
if (!innermost) return current;
const moreInner =
(current.range.startLineNumber > innermost.range.startLineNumber ||
(current.range.startLineNumber === innermost.range.startLineNumber &&
current.range.startColumn > innermost.range.startColumn)) &&
(current.range.endLineNumber < innermost.range.endLineNumber ||
(current.range.endLineNumber === innermost.range.endLineNumber &&
current.range.endColumn < innermost.range.endColumn));
return moreInner ? current : innermost;
}, null as DocumentSymbol | null);
}
}
registerSingleton(IContextGatheringService, ContextGatheringService, InstantiationType.Eager);
@@ -1,18 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
import { OperatingSystem, OS } from '../../../../base/common/platform.js';
export function getCmdKey(): string {
if (OS === OperatingSystem.Macintosh) {
return '⌘';
} else {
return 'Ctrl';
}
}
@@ -0,0 +1,422 @@
/*--------------------------------------------------------------------------------------
* 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 { URI } from '../../../../../base/common/uri.js';
import { generateUuid } from '../../../../../base/common/uuid.js';
import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js';
import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js';
import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js';
import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js';
// lets you add a "consistent" item to a Model (aka URI), instead of just to a single editor
type AddItemInputs = { uri: URI; fn: (editor: ICodeEditor) => (() => void); }
export interface IConsistentItemService {
readonly _serviceBrand: undefined;
getEditorsOnURI(uri: URI): ICodeEditor[];
addConsistentItemToURI(inputs: AddItemInputs): string;
removeConsistentItemFromURI(consistentItemId: string): void;
}
export const IConsistentItemService = createDecorator<IConsistentItemService>('ConsistentItemService');
export class ConsistentItemService extends Disposable {
readonly _serviceBrand: undefined
// the items that are attached to each URI, completely independent from current state of editors
private readonly consistentItemIdsOfURI: Record<string, Set<string> | undefined> = {}
private readonly infoOfConsistentItemId: Record<string, AddItemInputs> = {}
// current state of items on each editor, and the fns to call to remove them
private readonly itemIdsOfEditorId: Record<string, Set<string> | undefined> = {}
private readonly consistentItemIdOfItemId: Record<string, string> = {}
private readonly disposeFnOfItemId: Record<string, () => void> = {}
constructor(
@ICodeEditorService private readonly _editorService: ICodeEditorService,
) {
super()
const removeItemsFromEditor = (editor: ICodeEditor) => {
const editorId = editor.getId()
for (const itemId of this.itemIdsOfEditorId[editorId] ?? [])
this._removeItemFromEditor(editor, itemId)
}
// put items on the editor, based on the consistent items for that URI
const putItemsOnEditor = (editor: ICodeEditor, uri: URI | null) => {
if (!uri) return
for (const consistentItemId of this.consistentItemIdsOfURI[uri.fsPath] ?? [])
this._putItemOnEditor(editor, consistentItemId)
}
// when editor switches tabs (models)
const addTabSwitchListeners = (editor: ICodeEditor) => {
this._register(
editor.onDidChangeModel(e => {
removeItemsFromEditor(editor)
putItemsOnEditor(editor, e.newModelUrl)
})
)
}
// when editor is disposed
const addDisposeListener = (editor: ICodeEditor) => {
this._register(editor.onDidDispose(() => {
// anything on the editor has been disposed already
for (const itemId of this.itemIdsOfEditorId[editor.getId()] ?? [])
delete this.disposeFnOfItemId[itemId]
}))
}
const initializeEditor = (editor: ICodeEditor) => {
// if (editor.getModel()?.uri.scheme !== 'file') return // THIS BREAKS THINGS
addTabSwitchListeners(editor)
addDisposeListener(editor)
putItemsOnEditor(editor, editor.getModel()?.uri ?? null)
}
// initialize current editors + any new editors
for (let editor of this._editorService.listCodeEditors()) initializeEditor(editor)
this._register(this._editorService.onCodeEditorAdd(editor => { initializeEditor(editor) }))
// when an editor is deleted, remove its items
this._register(this._editorService.onCodeEditorRemove(editor => { removeItemsFromEditor(editor) }))
}
_putItemOnEditor(editor: ICodeEditor, consistentItemId: string) {
const { fn } = this.infoOfConsistentItemId[consistentItemId]
// add item
const dispose = fn(editor)
const itemId = generateUuid()
const editorId = editor.getId()
if (!(editorId in this.itemIdsOfEditorId))
this.itemIdsOfEditorId[editorId] = new Set()
this.itemIdsOfEditorId[editorId]!.add(itemId)
this.consistentItemIdOfItemId[itemId] = consistentItemId
this.disposeFnOfItemId[itemId] = () => {
// console.log('calling remove for', itemId)
dispose?.()
}
}
_removeItemFromEditor(editor: ICodeEditor, itemId: string) {
const editorId = editor.getId()
this.itemIdsOfEditorId[editorId]?.delete(itemId)
this.disposeFnOfItemId[itemId]?.()
delete this.disposeFnOfItemId[itemId]
delete this.consistentItemIdOfItemId[itemId]
}
getEditorsOnURI(uri: URI) {
const editors = this._editorService.listCodeEditors().filter(editor => editor.getModel()?.uri.fsPath === uri.fsPath)
return editors
}
consistentItemIdPool = 0
addConsistentItemToURI({ uri, fn }: AddItemInputs) {
const consistentItemId = (this.consistentItemIdPool++) + ''
if (!(uri.fsPath in this.consistentItemIdsOfURI))
this.consistentItemIdsOfURI[uri.fsPath] = new Set()
this.consistentItemIdsOfURI[uri.fsPath]!.add(consistentItemId)
this.infoOfConsistentItemId[consistentItemId] = { fn, uri }
const editors = this.getEditorsOnURI(uri)
for (const editor of editors)
this._putItemOnEditor(editor, consistentItemId)
return consistentItemId
}
removeConsistentItemFromURI(consistentItemId: string) {
if (!(consistentItemId in this.infoOfConsistentItemId))
return
const { uri } = this.infoOfConsistentItemId[consistentItemId]
const editors = this.getEditorsOnURI(uri)
for (const editor of editors) {
for (const itemId of this.itemIdsOfEditorId[editor.getId()] ?? []) {
if (this.consistentItemIdOfItemId[itemId] === consistentItemId)
this._removeItemFromEditor(editor, itemId)
}
}
// clear
this.consistentItemIdsOfURI[uri.fsPath]?.delete(consistentItemId)
delete this.infoOfConsistentItemId[consistentItemId]
}
}
registerSingleton(IConsistentItemService, ConsistentItemService, InstantiationType.Eager);
// mostly generated by o1 (almost the same as above, but just for 1 editor)
export interface IConsistentEditorItemService {
readonly _serviceBrand: undefined;
addToEditor(editor: ICodeEditor, fn: () => () => void): string;
removeFromEditor(itemId: string): void;
}
export const IConsistentEditorItemService = createDecorator<IConsistentEditorItemService>('ConsistentEditorItemService');
export class ConsistentEditorItemService extends Disposable {
readonly _serviceBrand: undefined;
/**
* For each editorId, we track the set of itemIds that have been "added" to that editor.
* This does *not* necessarily mean they're currently mounted (the user may have switched models).
*/
private readonly itemIdsByEditorId: Record<string, Set<string>> = {};
/**
* For each itemId, we store relevant info (the fn to call on the editor, the editorId, the uri, and the current dispose function).
*/
private readonly itemInfoById: Record<
string,
{
editorId: string;
uriFsPath: string;
fn: (editor: ICodeEditor) => () => void;
disposeFn?: () => void;
}
> = {};
constructor(
@ICodeEditorService private readonly _editorService: ICodeEditorService,
) {
super();
//
// Wire up listeners to watch for new editors, removed editors, etc.
//
// Initialize any already-existing editors
for (const editor of this._editorService.listCodeEditors()) {
this._initializeEditor(editor);
}
// When an editor is added, track it
this._register(
this._editorService.onCodeEditorAdd((editor) => {
this._initializeEditor(editor);
})
);
// When an editor is removed, remove all items associated with that editor
this._register(
this._editorService.onCodeEditorRemove((editor) => {
this._removeAllItemsFromEditor(editor);
})
);
}
/**
* Sets up listeners on the provided editor so that:
* - If the editor changes models, we remove items and re-mount only if the new model matches.
* - If the editor is disposed, we do the needed cleanup.
*/
private _initializeEditor(editor: ICodeEditor) {
const editorId = editor.getId();
//
// Listen for model changes
//
this._register(
editor.onDidChangeModel((e) => {
this._removeAllItemsFromEditor(editor);
if (!e.newModelUrl) {
return;
}
// Re-mount any items that belong to this editor and match the new URI
const itemsForEditor = this.itemIdsByEditorId[editorId];
if (itemsForEditor) {
for (const itemId of itemsForEditor) {
const itemInfo = this.itemInfoById[itemId];
if (itemInfo && itemInfo.uriFsPath === e.newModelUrl.fsPath) {
this._mountItemOnEditor(editor, itemId);
}
}
}
})
);
//
// When the editor is disposed, remove all items from it
//
this._register(
editor.onDidDispose(() => {
this._removeAllItemsFromEditor(editor);
})
);
//
// If the editor already has a model (e.g. on initial load), try mounting items
//
const uri = editor.getModel()?.uri;
if (!uri) {
return;
}
const itemsForEditor = this.itemIdsByEditorId[editorId];
if (itemsForEditor) {
for (const itemId of itemsForEditor) {
const itemInfo = this.itemInfoById[itemId];
if (itemInfo && itemInfo.uriFsPath === uri.fsPath) {
this._mountItemOnEditor(editor, itemId);
}
}
}
}
/**
* Actually calls the item-creation function `fn(editor)` and saves the resulting disposeFn
* so we can later clean it up.
*/
private _mountItemOnEditor(editor: ICodeEditor, itemId: string) {
const info = this.itemInfoById[itemId];
if (!info) {
return;
}
const { fn } = info;
const disposeFn = fn(editor);
info.disposeFn = disposeFn;
}
/**
* Removes a single item from an editor (calling its `disposeFn` if present).
*/
private _removeItemFromEditor(editor: ICodeEditor, itemId: string) {
const info = this.itemInfoById[itemId];
if (info?.disposeFn) {
info.disposeFn();
info.disposeFn = undefined;
}
}
/**
* Removes *all* items from the given editor. Typically called when the editor changes model or is disposed.
*/
private _removeAllItemsFromEditor(editor: ICodeEditor) {
const editorId = editor.getId();
const itemsForEditor = this.itemIdsByEditorId[editorId];
if (!itemsForEditor) {
return;
}
for (const itemId of itemsForEditor) {
this._removeItemFromEditor(editor, itemId);
}
}
/**
* Public API: Adds an item to an *individual* editor (determined by editor ID),
* but only when that editor is showing the same model (uri.fsPath).
*/
addToEditor(editor: ICodeEditor, fn: () => () => void): string {
const uri = editor.getModel()?.uri
if (!uri) {
throw new Error('No URI on the provided editor or in AddItemInputs.');
}
const editorId = editor.getId();
// Create an ID for this item
const itemId = generateUuid();
// Record the info
this.itemInfoById[itemId] = {
editorId,
uriFsPath: uri.fsPath,
fn,
};
// Add to the editor's known items
if (!this.itemIdsByEditorId[editorId]) {
this.itemIdsByEditorId[editorId] = new Set();
}
this.itemIdsByEditorId[editorId].add(itemId);
// If the editor's current URI matches, mount it now
if (editor.getModel()?.uri.fsPath === uri.fsPath) {
this._mountItemOnEditor(editor, itemId);
}
return itemId;
}
/**
* Public API: Removes an item from the *specific* editor. We look up which editor
* had this item and remove it from that editor.
*/
removeFromEditor(itemId: string): void {
const info = this.itemInfoById[itemId];
if (!info) {
// Nothing to remove
return;
}
const { editorId } = info;
// Find the editor in question
const editor = this._editorService.listCodeEditors().find(
(ed) => ed.getId() === editorId
);
if (editor) {
// Dispose on that editor
this._removeItemFromEditor(editor, itemId);
}
// Clean up references
this.itemIdsByEditorId[editorId]?.delete(itemId);
delete this.itemInfoById[itemId];
}
}
registerSingleton(IConsistentEditorItemService, ConsistentEditorItemService, InstantiationType.Eager);
@@ -0,0 +1,170 @@
// eg "bash" -> "shell"
export const nameToVscodeLanguage: { [key: string]: string } = {
// Web Technologies
'html': 'html',
'css': 'css',
'scss': 'scss',
'sass': 'scss',
'less': 'less',
'javascript': 'typescript',
'js': 'typescript', // use more general renderer
'jsx': 'typescript',
'typescript': 'typescript',
'ts': 'typescript',
'tsx': 'typescript',
'json': 'json',
'jsonc': 'json',
// Programming Languages
'python': 'python',
'py': 'python',
'java': 'java',
'cpp': 'cpp',
'c++': 'cpp',
'c': 'c',
'csharp': 'csharp',
'cs': 'csharp',
'c#': 'csharp',
'go': 'go',
'golang': 'go',
'rust': 'rust',
'rs': 'rust',
'ruby': 'ruby',
'rb': 'ruby',
'php': 'php',
'shell': 'shell',
'bash': 'shell',
'sh': 'shell',
'zsh': 'shell',
// Markup and Config
'markdown': 'markdown',
'md': 'markdown',
'xml': 'xml',
'svg': 'xml',
'yaml': 'yaml',
'yml': 'yaml',
'ini': 'ini',
'toml': 'ini',
// Database and Query Languages
'sql': 'sql',
'mysql': 'sql',
'postgresql': 'sql',
'graphql': 'graphql',
'gql': 'graphql',
// Others
'dockerfile': 'dockerfile',
'docker': 'dockerfile',
'makefile': 'makefile',
'plaintext': 'plaintext',
'text': 'plaintext'
};
// eg ".ts" -> "typescript"
const fileExtensionToVscodeLanguage: { [key: string]: string } = {
// Web
'html': 'html',
'htm': 'html',
'css': 'css',
'scss': 'scss',
'less': 'less',
'js': 'javascript',
'jsx': 'javascript',
'ts': 'typescript',
'tsx': 'typescript',
'json': 'json',
'jsonc': 'json',
// Programming Languages
'py': 'python',
'java': 'java',
'cpp': 'cpp',
'cc': 'cpp',
'c': 'c',
'h': 'cpp',
'hpp': 'cpp',
'cs': 'csharp',
'go': 'go',
'rs': 'rust',
'rb': 'ruby',
'php': 'php',
'sh': 'shell',
'bash': 'shell',
'zsh': 'shell',
// Markup/Config
'md': 'markdown',
'markdown': 'markdown',
'xml': 'xml',
'svg': 'xml',
'yaml': 'yaml',
'yml': 'yaml',
'ini': 'ini',
'toml': 'ini',
// Other
'sql': 'sql',
'graphql': 'graphql',
'gql': 'graphql',
'dockerfile': 'dockerfile',
'docker': 'dockerfile',
'mk': 'makefile',
// Config Files and Dot Files
'npmrc': 'ini',
'env': 'ini',
'gitignore': 'ignore',
'dockerignore': 'ignore',
'eslintrc': 'json',
'babelrc': 'json',
'prettierrc': 'json',
'stylelintrc': 'json',
'editorconfig': 'ini',
'htaccess': 'apacheconf',
'conf': 'ini',
'config': 'ini',
// Package Files
'package': 'json',
'package-lock': 'json',
'gemfile': 'ruby',
'podfile': 'ruby',
'rakefile': 'ruby',
// Build Systems
'cmake': 'cmake',
'makefile': 'makefile',
'gradle': 'groovy',
// Shell Scripts
'bashrc': 'shell',
'zshrc': 'shell',
'fish': 'shell',
// Version Control
'gitconfig': 'ini',
'hgrc': 'ini',
'svnconfig': 'ini',
// Web Server
'nginx': 'nginx',
// Misc Config
'properties': 'properties',
'cfg': 'ini',
'reg': 'ini'
};
export function filenameToVscodeLanguage(filename: string): string | undefined {
const ext = filename.toLowerCase().split('.').pop();
if (!ext) return undefined;
return fileExtensionToVscodeLanguage[ext];
}
@@ -0,0 +1,177 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
class SurroundingsRemover {
readonly originalS: string
i: number
j: number
// string is s[i...j]
constructor(s: string) {
this.originalS = s
this.i = 0
this.j = s.length - 1
}
value() {
return this.originalS.substring(this.i, this.j + 1)
}
// returns whether it removed the whole prefix
removePrefix = (prefix: string): boolean => {
let offset = 0
// console.log('prefix', prefix, Math.min(this.j, prefix.length - 1))
while (this.i <= this.j && offset <= prefix.length - 1) {
if (this.originalS.charAt(this.i) !== prefix.charAt(offset))
break
offset += 1
this.i += 1
}
return offset === prefix.length
}
// // removes suffix from right to left
removeSuffix = (suffix: string): boolean => {
// e.g. suffix = <PRE/>, the string is <PRE>hi<P
const s = this.value()
// for every possible prefix of `suffix`, check if string ends with it
for (let len = Math.min(s.length, suffix.length); len >= 1; len -= 1) {
if (s.endsWith(suffix.substring(0, len))) { // the end of the string equals a prefix
this.j -= len
return len === suffix.length
}
}
return false
}
// removeSuffix = (suffix: string): boolean => {
// let offset = 0
// while (this.j >= Math.max(this.i, 0)) {
// if (this.originalS.charAt(this.j) !== suffix.charAt(suffix.length - 1 - offset))
// break
// offset += 1
// this.j -= 1
// }
// return offset === suffix.length
// }
removeFromStartUntil = (until: string, alsoRemoveUntilStr: boolean) => {
const index = this.originalS.indexOf(until, this.i)
if (index === -1) {
this.i = this.j + 1
return false
}
// console.log('index', index, until.length)
if (alsoRemoveUntilStr)
this.i = index + until.length
else
this.i = index
return true
}
removeCodeBlock = () => {
// Match either:
// 1. ```language\n<code>\n```\n?
// 2. ```<code>\n```\n?
const pm = this
const foundCodeBlock = pm.removePrefix('```')
if (!foundCodeBlock) return false
pm.removeFromStartUntil('\n', true) // language
const j = pm.j
let foundCodeBlockEnd = pm.removeSuffix('```')
if (pm.j === j) foundCodeBlockEnd = pm.removeSuffix('```\n') // if no change, try again with \n after ```
if (!foundCodeBlockEnd) return false
pm.removeSuffix('\n') // remove the newline before ```
return true
}
deltaInfo = (recentlyAddedTextLen: number) => {
// aaaaaatextaaaaaa{recentlyAdded}
// ^ i j len
// |
// recentyAddedIdx
const recentlyAddedIdx = this.originalS.length - recentlyAddedTextLen
const actualDelta = this.originalS.substring(Math.max(this.i, recentlyAddedIdx), this.j + 1)
const ignoredSuffix = this.originalS.substring(Math.max(this.j + 1, recentlyAddedIdx), Infinity)
return [actualDelta, ignoredSuffix] as const
}
}
export const extractCodeFromRegular = ({ text, recentlyAddedTextLen }: { text: string, recentlyAddedTextLen: number }): [string, string, string] => {
const pm = new SurroundingsRemover(text)
pm.removeCodeBlock()
const s = pm.value()
const [delta, ignoredSuffix] = pm.deltaInfo(recentlyAddedTextLen)
return [s, delta, ignoredSuffix]
}
// Ollama has its own FIM, we should not use this if we use that
export const extractCodeFromFIM = ({ text, recentlyAddedTextLen, midTag, }: { text: string, recentlyAddedTextLen: number, midTag: string }): [string, string, string] => {
/* ------------- summary of the regex -------------
[optional ` | `` | ```]
(match optional_language_name)
[optional strings here]
[required <MID> tag]
(match the stuff between mid tags)
[optional <MID/> tag]
[optional ` | `` | ```]
*/
const pm = new SurroundingsRemover(text)
pm.removeCodeBlock()
const foundMid = pm.removePrefix(`<${midTag}>`)
if (foundMid) {
pm.removeSuffix(`</${midTag}>`)
}
const s = pm.value()
const [delta, ignoredSuffix] = pm.deltaInfo(recentlyAddedTextLen)
return [s, delta, ignoredSuffix]
// // const regex = /[\s\S]*?(?:`{1,3}\s*([a-zA-Z_]+[\w]*)?[\s\S]*?)?<MID>([\s\S]*?)(?:<\/MID>|`{1,3}|$)/;
// const regex = new RegExp(
// `[\\s\\S]*?(?:\`{1,3}\\s*([a-zA-Z_]+[\\w]*)?[\\s\\S]*?)?<${midTag}>([\\s\\S]*?)(?:</${midTag}>|\`{1,3}|$)`,
// ''
// );
// const match = text.match(regex);
// if (match) {
// const [_, languageName, codeBetweenMidTags] = match;
// return [languageName, codeBetweenMidTags] as const
// } else {
// return [undefined, extractCodeFromRegular(text)] as const
// }
}
@@ -1,9 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { diffLines } from './react/out/util/diffLines.js'
import { diffLines } from '../react/out/diff/index.js'
export type ComputedDiff = {
type: 'edit';
@@ -0,0 +1,44 @@
import { URI } from '../../../../../base/common/uri'
import { EndOfLinePreference } from '../../../../../editor/common/model'
import { IModelService } from '../../../../../editor/common/services/model.js'
import { IFileService } from '../../../../../platform/files/common/files'
// attempts to read URI of currently opened model, then of raw file
export const VSReadFile = async (modelService: IModelService, fileService: IFileService, uri: URI) => {
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.)
export const _VSReadModel = async (modelService: IModelService, uri: URI): Promise<string | null> => {
// attempt to read saved model (sometimes doesn't work if page is reloaded)
const model = modelService.getModel(uri)
if (model) {
return model.getValue(EndOfLinePreference.LF)
}
// 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.toString() === uri.fsPath.toString()) {
return model.getValue(EndOfLinePreference.LF);
}
}
return null
}
export const _VSReadFileRaw = async (fileService: IFileService, uri: URI) => {
const res = await fileService.readFile(uri)
const str = res.value.toString()
return str
}

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