Compare commits

..

207 Commits

Author SHA1 Message Date
Max Brunsfeld
4a3d56749e Remove debugging code, commented code 2025-07-30 16:04:59 -07:00
Max Brunsfeld
3519e8fd7c Restore text example 2025-07-30 15:59:47 -07:00
Max Brunsfeld
b3372e7eac Read direct_composition env var once, pass it everywhere 2025-07-30 15:51:26 -07:00
Max Brunsfeld
4f7bb14acf Merge branch 'windows/dx11' into windows/remove-d2d 2025-07-30 15:51:03 -07:00
Kate
99c5b72b3d finally the font looks nice 2025-07-31 00:00:35 +02:00
Junkui Zhang
c995dd2016 more context 2025-07-31 01:18:29 +08:00
Junkui Zhang
80be0e29b9 add more context for errors 2025-07-31 01:15:39 +08:00
Junkui Zhang
b75f6e2210 use if-else 2025-07-31 00:59:46 +08:00
Junkui Zhang
b8f85be372 use log::warn instead 2025-07-31 00:58:23 +08:00
Junkui Zhang
34e433ad90 check debug layer before creating 2025-07-31 00:05:56 +08:00
Junkui Zhang
6a91ac26d7 remove unneeded change 2025-07-30 23:29:32 +08:00
Junkui Zhang
345fd526fc log the feature level we are using 2025-07-30 23:17:15 +08:00
Junkui Zhang
7c8074ce5c update feature level 2025-07-30 22:56:28 +08:00
Junkui Zhang
0a0803e2a7 remove enable-renderdoc 2025-07-30 18:22:30 +08:00
Junkui Zhang
8c8b91470a remove unused 2025-07-30 18:10:04 +08:00
Junkui Zhang
98692cc928 fix linux 2025-07-30 17:42:47 +08:00
Junkui Zhang
d194bf4f52 QOL improvement when device lost happens 2025-07-30 17:36:14 +08:00
Junkui Zhang
cc763729a0 add GPUI_DISABLE_DIRECT_COMPOSITION env 2025-07-30 14:53:36 +08:00
Junkui Zhang
e370c3d601 misc 2025-07-30 09:29:09 +08:00
Kate
1d60984cb6 get the blending state a bit closer to vulkan
still looks bad :(
2025-07-29 21:41:11 +02:00
Junkui Zhang
eb3bb95c91 fix error msg 2025-07-29 22:45:58 +08:00
Junkui Zhang
554b36fd3c fix where fxc.exe 2025-07-29 22:44:05 +08:00
Junkui Zhang
89a863d012 update workspace-hack 2025-07-29 20:01:39 +08:00
Junkui Zhang
441731de2e fix build.rs 2025-07-29 19:59:04 +08:00
Junkui Zhang
ead7a1e1f0 remove blade 2025-07-29 19:54:34 +08:00
Junkui Zhang
73eaee8f6f use none instead of stretch 2025-07-29 17:44:54 +08:00
Junkui Zhang
98f31172ab fix atlas sometime fails 2025-07-29 16:56:01 +08:00
Junkui Zhang
181f324473 fix device lost 2025-07-29 16:50:10 +08:00
Junkui Zhang
a1f03ee42c log unknown vendor id 2025-07-29 16:03:59 +08:00
Junkui Zhang
741b38f906 remove unused repr(c) 2025-07-29 14:45:28 +08:00
Junkui Zhang
599b82fc9d remove unused 2025-07-29 14:41:52 +08:00
Junkui Zhang
64b3b050e3 fix 2025-07-29 14:24:43 +08:00
Junkui Zhang
62d1b7e36f fix PathRasterization pipeline 2025-07-29 14:05:38 +08:00
Junkui Zhang
d7b14d8dc5 rename pipeline 2025-07-29 13:24:56 +08:00
Junkui Zhang
ce67ce1482 Revert "Use pre-multiplied alpha for path rasterization"
This reverts commit 8eea9aad40.
2025-07-29 13:07:00 +08:00
Max Brunsfeld
8eea9aad40 Use pre-multiplied alpha for path rasterization 2025-07-28 17:52:55 -07:00
Max Brunsfeld
e9697e4639 Start work on doing path MSAA using intermediate texture 2025-07-28 17:45:35 -07:00
Max Brunsfeld
92b0a7e760 Merge branch 'main' into windows/dx11 2025-07-28 15:41:04 -07:00
Kate
d96bafb1e5 Merge branch 'windows/dx11' into windows/remove-d2d 2025-07-28 14:55:51 +02:00
Junkui Zhang
5f3a1bdbd1 trigger ci 2025-07-28 19:34:15 +08:00
Junkui Zhang
1ee81a507b bundle ags.dll 2025-07-28 18:50:25 +08:00
Junkui Zhang
89d34e1513 clippy 2025-07-28 17:44:40 +08:00
Junkui Zhang
a9058346bf remove static linking of ags 2025-07-28 17:28:58 +08:00
Junkui Zhang
ac1ea0f96d revert idle 2025-07-28 17:10:17 +08:00
Junkui Zhang
0065e5fd76 handle WM_DEVICECHANGE 2025-07-28 17:04:28 +08:00
Junkui Zhang
ca6aa25d1e remove walkaround for close animation 2025-07-28 14:59:30 +08:00
Junkui Zhang
6964cecc14 ensure app is idle 2025-07-27 17:51:17 +08:00
Junkui Zhang
63daf44693 remove debug print 2025-07-27 17:51:17 +08:00
Junkui Zhang
4de2ebf954 acctually enable vsync 2025-07-27 17:51:17 +08:00
Junkui Zhang
3277640f55 fix 2025-07-27 17:51:17 +08:00
Junkui Zhang
d192ac6b7f ags is not typo 2025-07-27 17:51:17 +08:00
Junkui Zhang
c67ddd7572 AGS is not typo 2025-07-27 17:51:17 +08:00
Junkui Zhang
b54eaecbbc clippy 2025-07-27 17:51:17 +08:00
Junkui Zhang
2744e6cb65 Revert "initial removal attempt"
This reverts commit 6928488aad.
2025-07-27 17:51:17 +08:00
Junkui Zhang
18937f5756 Revert "make it not crash"
This reverts commit a7e34ab0bc.
2025-07-27 17:51:17 +08:00
Junkui Zhang
347b863ac6 Revert "more fixes and debugging"
This reverts commit 2fb31a9157.
2025-07-27 17:51:17 +08:00
Junkui Zhang
9d8ef8156d Revert "Translate rasterized glyphs from texture to bitmap"
This reverts commit 6fc8d7746f.
2025-07-27 17:51:17 +08:00
Junkui Zhang
9dbbee0334 Revert "Add emojis to text example"
This reverts commit 34d5926ebd.
2025-07-27 17:51:17 +08:00
Junkui Zhang
32f2505fbf use ? 2025-07-27 17:51:17 +08:00
Junkui Zhang
2711d8823c use vsync 2025-07-27 17:51:17 +08:00
Junkui Zhang
787fee8a1a fix 2025-07-27 17:51:17 +08:00
Junkui Zhang
be7d56e11b fix 2025-07-27 17:51:17 +08:00
Junkui Zhang
fcb77979f3 fix build 2025-07-27 17:51:17 +08:00
Junkui Zhang
787c6382f9 remove unused 2025-07-27 17:51:17 +08:00
Junkui Zhang
74d953d024 checkpoint 2025-07-27 17:51:17 +08:00
Junkui Zhang
b5377c56f2 remove noise when device lost 2025-07-27 17:51:16 +08:00
Junkui Zhang
275d84d566 init handle_device_lost 2025-07-27 17:51:16 +08:00
Junkui Zhang
3978bba5a7 fix 2025-07-27 17:51:16 +08:00
Junkui Zhang
52c0fa5ce9 remove debug print 2025-07-27 17:51:16 +08:00
Junkui Zhang
d208f75f46 enable O3 optimization for fxc 2025-07-27 17:51:16 +08:00
Junkui Zhang
1b0a0aa58e add x86 support for nvidia 2025-07-27 17:51:16 +08:00
Junkui Zhang
5ff9114b18 add runtime shader 2025-07-27 17:51:16 +08:00
Junkui Zhang
d9c6d09545 checkpoint 2025-07-27 17:51:16 +08:00
Junkui Zhang
61981aabb5 allow to compile shader at building 2025-07-27 17:51:16 +08:00
Junkui Zhang
0b57c86e07 add amd gpu version support 2025-07-27 17:51:16 +08:00
Junkui Zhang
c7342a9df5 remove unused 2025-07-27 17:51:16 +08:00
Junkui Zhang
0e45ef7e43 better output for nvidia 2025-07-27 17:51:16 +08:00
Junkui Zhang
0c40bb9b5f impl intel driver version 2025-07-27 17:51:16 +08:00
Junkui Zhang
5058752f2d cleanup 2025-07-27 17:51:16 +08:00
Junkui Zhang
432d11f57b implement gpu driver version for nvidia 2025-07-27 17:51:16 +08:00
Junkui Zhang
32488e1e2d fix 2025-07-27 17:51:16 +08:00
Junkui Zhang
9acee42c38 show err if failed to create new window 2025-07-27 17:51:16 +08:00
Junkui Zhang
72c55b4653 add new feature enable-renderdoc 2025-07-27 17:51:16 +08:00
Junkui Zhang
fa1320d9aa remove unused 2025-07-27 17:51:16 +08:00
Junkui Zhang
eb310bcf7d wip 2025-07-27 17:51:16 +08:00
Junkui Zhang
8c1d9f75d1 refactor 2025-07-27 17:51:16 +08:00
Junkui Zhang
499b3b6b50 rename to DirectXResources 2025-07-27 17:51:16 +08:00
Junkui Zhang
c6e020f60f finetune transpanrency 2025-07-27 17:51:15 +08:00
Junkui Zhang
7ab2d0d800 add transparency 2025-07-27 17:51:15 +08:00
Junkui Zhang
c007121b41 remove unused 2025-07-27 17:51:15 +08:00
Junkui Zhang
22c9d133bd wip 2025-07-27 17:51:15 +08:00
Junkui Zhang
32758022df wip 2025-07-27 17:51:15 +08:00
Junkui Zhang
0d8600bf1e checkpoint msaa 2025-07-27 17:51:15 +08:00
Junkui Zhang
22cba07072 add msaa 2025-07-27 17:51:15 +08:00
Junkui Zhang
642d769502 update default buffer size 2025-07-27 17:51:15 +08:00
Junkui Zhang
bfdcc65801 reenable transparency 2025-07-27 17:51:15 +08:00
Junkui Zhang
54e2420405 introduce set_pipeline_state 2025-07-27 17:51:15 +08:00
Junkui Zhang
b012246d2b refactor 2025-07-27 17:51:15 +08:00
Junkui Zhang
667c19907a refactor 2025-07-27 17:51:15 +08:00
Junkui Zhang
5261c02d18 refactor 2025-07-27 17:51:15 +08:00
Junkui Zhang
204071e6bf remove unused 2025-07-27 17:51:15 +08:00
Junkui Zhang
5472c71f1a fix paths rendering 2025-07-27 17:51:15 +08:00
Junkui Zhang
723712e3cf Revert "Fix path rendering - draw all paths w/ one regular draw call"
This reverts commit 83d942611f.
2025-07-27 17:51:15 +08:00
Junkui Zhang
0c274370c3 wip 2025-07-27 17:51:15 +08:00
Max Brunsfeld
31fab3a37a Fix dxgi_factory type error in release mode 2025-07-27 17:51:15 +08:00
Max Brunsfeld
4f416d3818 Fix path rendering - draw all paths w/ one regular draw call 2025-07-27 17:51:15 +08:00
Junkui Zhang
ffef9fd25a bringback our colorful avatar 2025-07-27 17:51:15 +08:00
Junkui Zhang
2a6b83f190 remove debug print 2025-07-27 17:51:15 +08:00
Junkui Zhang
1b12dd39cc fix all 2025-07-27 17:51:14 +08:00
Max Brunsfeld
9162583bac Add emojis to text example 2025-07-27 17:51:14 +08:00
Max Brunsfeld
8075998c09 Translate rasterized glyphs from texture to bitmap 2025-07-27 17:51:14 +08:00
Kate
3b6105b713 more fixes and debugging 2025-07-27 17:51:14 +08:00
Kate
2b53a2cb12 make it not crash 2025-07-27 17:51:14 +08:00
Kate
96d847b6d1 initial removal attempt 2025-07-27 17:51:14 +08:00
Junkui Zhang
7fde34f85e temporarily disable transparancy 2025-07-27 17:51:14 +08:00
Junkui Zhang
401e0e6f41 wip 2025-07-27 17:51:14 +08:00
Junkui Zhang
201c274c4b wip 2025-07-27 17:51:14 +08:00
Junkui Zhang
ecde968a0c wip 2025-07-27 17:51:14 +08:00
Junkui Zhang
4a78ce7cfd wip 2025-07-27 17:51:14 +08:00
Junkui Zhang
fda3d56d87 wip 2025-07-27 17:51:14 +08:00
Junkui Zhang
9c3cfca835 apply #23576 2025-07-27 17:51:14 +08:00
Junkui Zhang
1fb689bad3 apply #19772 2025-07-27 17:51:14 +08:00
Junkui Zhang
238ccec5ee fix 2025-07-27 17:51:14 +08:00
Junkui Zhang
c8ae5a3b11 fix all 2025-07-27 17:51:14 +08:00
Junkui Zhang
dbe2ce2464 wip 2025-07-27 17:51:14 +08:00
Junkui Zhang
5287183667 apply #20812 2025-07-27 17:51:14 +08:00
Junkui Zhang
a48ae50e1a apply #15782 2025-07-27 17:51:14 +08:00
Junkui Zhang
ca3d55ee4d wip 2025-07-27 17:51:13 +08:00
Junkui Zhang
c0bad42968 wip 2025-07-27 17:51:13 +08:00
Junkui Zhang
7186f1322e init 2025-07-27 17:51:13 +08:00
Kate
21e14b5f9a Merge branch 'windows/dx11' into windows/remove-d2d 2025-07-22 16:03:55 +02:00
Max Brunsfeld
7d84014ad2 Update cargo lock 2025-07-21 13:58:59 -07:00
Max Brunsfeld
68780da673 Pass scale factor transform to glyph analysis when computing bounds
rather than simply multiplying every rect field by the scale factor.
This fixes clipping of glyphs and removes the need for magic numbers
expanding the bounds vertically.

Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
2025-07-21 13:35:21 -07:00
Kate
1f55a0a358 cleanup code a bit 2025-07-21 17:17:42 +02:00
Kate
ba80e16339 color rasterization works now 2025-07-18 21:12:19 +02:00
Kate
11dc14ad4d gpu rasterization works now 2025-07-18 16:15:53 +02:00
Junkui Zhang
9f200ebf5a fix 2025-07-18 17:56:46 +08:00
Junkui Zhang
788865e892 remove debug print 2025-07-18 17:54:26 +08:00
Junkui Zhang
e87ee91d8e enable O3 optimization for fxc 2025-07-18 17:53:45 +08:00
Junkui Zhang
b0e48d01ce add x86 support for nvidia 2025-07-18 17:48:05 +08:00
Junkui Zhang
825ee6233b add runtime shader 2025-07-18 17:43:01 +08:00
Junkui Zhang
154705e729 checkpoint 2025-07-18 17:08:44 +08:00
Junkui Zhang
636a057373 allow to compile shader at building 2025-07-18 16:46:34 +08:00
Junkui Zhang
df1f62477c add amd gpu version support 2025-07-18 15:11:49 +08:00
Kate
6907064be6 prepare for gpu rasterization 2025-07-17 20:00:14 +02:00
Kate
c1eaf3317d Merge branch 'windows/dx11' into HEAD 2025-07-17 19:59:21 +02:00
Junkui Zhang
6477a9b056 remove unused 2025-07-17 21:35:42 +08:00
Junkui Zhang
84f75fe683 better output for nvidia 2025-07-17 21:31:00 +08:00
Junkui Zhang
7627097875 impl intel driver version 2025-07-17 21:10:33 +08:00
Junkui Zhang
78824390d0 cleanup 2025-07-17 19:48:42 +08:00
Junkui Zhang
4d936845f3 implement gpu driver version for nvidia 2025-07-17 19:30:49 +08:00
Junkui Zhang
76fb80eaeb fix 2025-07-17 17:12:27 +08:00
Junkui Zhang
29b5acf27b show err if failed to create new window 2025-07-17 17:04:36 +08:00
Junkui Zhang
e560c6813f add new feature enable-renderdoc 2025-07-17 16:57:23 +08:00
Junkui Zhang
a57cbe4636 remove unused 2025-07-17 16:34:34 +08:00
Junkui Zhang
7cf10d110c wip 2025-07-17 16:34:09 +08:00
Junkui Zhang
1888f21a14 refactor 2025-07-17 15:52:51 +08:00
Junkui Zhang
63727f99da rename to DirectXResources 2025-07-17 14:53:28 +08:00
Junkui Zhang
602bd189f6 finetune transpanrency 2025-07-17 14:36:41 +08:00
Junkui Zhang
b8314e74db add transparency 2025-07-17 11:29:44 +08:00
Kate
a486bb28f6 initial color emoji implementation, currently only monochrome, still
figuring out why it doesn't render even though it rasterizes to the
bitmap correctly
2025-07-16 23:22:08 +02:00
Junkui Zhang
b1b5a383e0 remove unused 2025-07-17 00:05:46 +08:00
Junkui Zhang
b0fe5fd56f wip 2025-07-16 23:55:32 +08:00
Junkui Zhang
398d492f85 wip 2025-07-16 22:58:51 +08:00
Junkui Zhang
55edee58fb checkpoint msaa 2025-07-16 22:20:38 +08:00
Junkui Zhang
da3736bd5f add msaa 2025-07-16 21:26:11 +08:00
Junkui Zhang
4b2ff5e251 update default buffer size 2025-07-16 19:56:56 +08:00
Junkui Zhang
46fc76fdf8 reenable transparency 2025-07-16 17:14:24 +08:00
Junkui Zhang
ffbb47452d introduce set_pipeline_state 2025-07-16 15:48:55 +08:00
Junkui Zhang
5ed8b13e4a refactor 2025-07-16 15:36:24 +08:00
Junkui Zhang
1baafae3f7 refactor 2025-07-16 14:14:31 +08:00
Junkui Zhang
2017ce3699 refactor 2025-07-16 14:04:49 +08:00
Junkui Zhang
f715acc92a remove unused 2025-07-16 11:37:51 +08:00
Junkui Zhang
291691ca0e fix paths rendering 2025-07-16 11:24:55 +08:00
Junkui Zhang
158732eb17 Revert "Fix path rendering - draw all paths w/ one regular draw call"
This reverts commit 83d942611f.
2025-07-16 11:08:30 +08:00
Junkui Zhang
cdbaff8375 wip 2025-07-16 09:43:08 +08:00
Max Brunsfeld
c014dbae8c Fix dxgi_factory type error in release mode 2025-07-15 16:48:19 -07:00
Max Brunsfeld
83d942611f Fix path rendering - draw all paths w/ one regular draw call 2025-07-15 16:15:50 -07:00
Junkui Zhang
f16f07b36f bringback our colorful avatar 2025-07-15 23:38:18 +08:00
Junkui Zhang
85cf9e405e remove debug print 2025-07-15 23:29:32 +08:00
Junkui Zhang
a1c00ed87f fix all 2025-07-15 23:28:25 +08:00
Max Brunsfeld
34d5926ebd Add emojis to text example 2025-07-15 13:56:30 +08:00
Max Brunsfeld
6fc8d7746f Translate rasterized glyphs from texture to bitmap 2025-07-15 13:56:30 +08:00
Kate
2fb31a9157 more fixes and debugging 2025-07-15 13:56:29 +08:00
Kate
a7e34ab0bc make it not crash 2025-07-15 13:56:29 +08:00
Kate
6928488aad initial removal attempt 2025-07-15 13:56:29 +08:00
Junkui Zhang
8514850ad4 temporarily disable transparancy 2025-07-15 12:35:33 +08:00
Max Brunsfeld
231c38aa41 Add emojis to text example 2025-07-14 17:57:52 -07:00
Max Brunsfeld
8d538fad0c Translate rasterized glyphs from texture to bitmap 2025-07-14 17:57:38 -07:00
Kate
f5aa88ca6a more fixes and debugging 2025-07-14 22:34:36 +02:00
Kate
b9eb18eb7f make it not crash 2025-07-14 21:44:20 +02:00
Kate
b130346ede initial removal attempt 2025-07-14 20:55:16 +02:00
Junkui Zhang
e8bd47f668 wip 2025-07-14 19:59:26 +08:00
Junkui Zhang
6a918b64bf wip 2025-07-14 18:35:52 +08:00
Junkui Zhang
c82edc38a9 wip 2025-07-14 17:55:50 +08:00
Junkui Zhang
622a42e3aa wip 2025-07-14 17:49:44 +08:00
Junkui Zhang
dcdd7404e4 wip 2025-07-14 16:47:45 +08:00
Junkui Zhang
52c181328c apply #23576 2025-07-14 14:50:21 +08:00
Junkui Zhang
2319cd8211 apply #19772 2025-07-14 14:24:47 +08:00
Junkui Zhang
d0a2257472 fix 2025-07-13 20:31:39 +08:00
Junkui Zhang
af2009710a fix all 2025-07-13 20:17:01 +08:00
Junkui Zhang
eec406bb36 wip 2025-07-13 17:13:53 +08:00
Junkui Zhang
83ea328be5 apply #20812 2025-07-13 16:34:16 +08:00
Junkui Zhang
f2c847a1b0 apply #15782 2025-07-13 13:28:52 +08:00
Junkui Zhang
5d03296dc2 wip 2025-07-13 13:15:24 +08:00
Junkui Zhang
b4771bc4f8 wip 2025-07-13 12:49:05 +08:00
Junkui Zhang
68192052fd init 2025-07-13 12:32:59 +08:00
160 changed files with 5906 additions and 4816 deletions

View File

@@ -771,8 +771,7 @@ jobs:
timeout-minutes: 120
name: Create a Windows installer
runs-on: [self-hosted, Windows, X64]
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
# if: (startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling'))
if: true && (startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling'))
needs: [windows_tests]
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
@@ -808,16 +807,16 @@ jobs:
name: ZedEditorUserSetup-x64-${{ github.event.pull_request.head.sha || github.sha }}.exe
path: ${{ env.SETUP_PATH }}
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
# Re-enable when we are ready to publish windows preview releases
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) && env.RELEASE_CHANNEL == 'preview' }} # upload only preview
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: ${{ env.SETUP_PATH }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# - name: Upload Artifacts to release
# uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
# # Re-enable when we are ready to publish windows preview releases
# if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) && env.RELEASE_CHANNEL == 'preview' }} # upload only preview
# with:
# draft: true
# prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
# files: ${{ env.SETUP_PATH }}
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
auto-release-preview:
name: Auto release preview

238
Cargo.lock generated
View File

@@ -90,7 +90,6 @@ dependencies = [
"assistant_tools",
"chrono",
"client",
"cloud_llm_client",
"collections",
"component",
"context_server",
@@ -133,6 +132,7 @@ dependencies = [
"uuid",
"workspace",
"workspace-hack",
"zed_llm_client",
"zstd",
]
@@ -189,7 +189,6 @@ name = "agent_settings"
version = "0.1.0"
dependencies = [
"anyhow",
"cloud_llm_client",
"collections",
"fs",
"gpui",
@@ -201,6 +200,7 @@ dependencies = [
"serde_json_lenient",
"settings",
"workspace-hack",
"zed_llm_client",
]
[[package]]
@@ -223,7 +223,6 @@ dependencies = [
"buffer_diff",
"chrono",
"client",
"cloud_llm_client",
"collections",
"command_palette_hooks",
"component",
@@ -295,6 +294,7 @@ dependencies = [
"workspace",
"workspace-hack",
"zed_actions",
"zed_llm_client",
]
[[package]]
@@ -687,7 +687,6 @@ dependencies = [
"chrono",
"client",
"clock",
"cloud_llm_client",
"collections",
"context_server",
"fs",
@@ -721,6 +720,7 @@ dependencies = [
"uuid",
"workspace",
"workspace-hack",
"zed_llm_client",
]
[[package]]
@@ -828,7 +828,6 @@ dependencies = [
"chrono",
"client",
"clock",
"cloud_llm_client",
"collections",
"component",
"derive_more 0.99.19",
@@ -882,6 +881,7 @@ dependencies = [
"which 6.0.3",
"workspace",
"workspace-hack",
"zed_llm_client",
"zlog",
]
@@ -2976,7 +2976,6 @@ dependencies = [
"base64 0.22.1",
"chrono",
"clock",
"cloud_llm_client",
"cocoa 0.26.0",
"collections",
"credentials_provider",
@@ -3019,6 +3018,7 @@ dependencies = [
"windows 0.61.1",
"workspace-hack",
"worktree",
"zed_llm_client",
]
[[package]]
@@ -3031,19 +3031,6 @@ dependencies = [
"workspace-hack",
]
[[package]]
name = "cloud_llm_client"
version = "0.1.0"
dependencies = [
"anyhow",
"pretty_assertions",
"serde",
"serde_json",
"strum 0.27.1",
"uuid",
"workspace-hack",
]
[[package]]
name = "clru"
version = "0.6.2"
@@ -3170,7 +3157,6 @@ dependencies = [
"chrono",
"client",
"clock",
"cloud_llm_client",
"collab_ui",
"collections",
"command_palette_hooks",
@@ -3257,6 +3243,7 @@ dependencies = [
"workspace",
"workspace-hack",
"worktree",
"zed_llm_client",
"zlog",
]
@@ -3697,6 +3684,17 @@ dependencies = [
"libm",
]
[[package]]
name = "coreaudio-rs"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace"
dependencies = [
"bitflags 1.3.2",
"core-foundation-sys",
"coreaudio-sys",
]
[[package]]
name = "coreaudio-rs"
version = "0.12.1"
@@ -3754,6 +3752,29 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "cpal"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779"
dependencies = [
"alsa",
"core-foundation-sys",
"coreaudio-rs 0.11.3",
"dasp_sample",
"jni",
"js-sys",
"libc",
"mach2",
"ndk 0.8.0",
"ndk-context",
"oboe",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows 0.54.0",
]
[[package]]
name = "cpal"
version = "0.16.0"
@@ -3767,7 +3788,7 @@ dependencies = [
"js-sys",
"libc",
"mach2",
"ndk",
"ndk 0.9.0",
"ndk-context",
"num-derive",
"num-traits",
@@ -4771,6 +4792,7 @@ name = "docs_preprocessor"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"command_palette",
"gpui",
"mdbook",
@@ -4781,7 +4803,6 @@ dependencies = [
"util",
"workspace-hack",
"zed",
"zlog",
]
[[package]]
@@ -4960,7 +4981,6 @@ dependencies = [
"theme",
"time",
"tree-sitter-bash",
"tree-sitter-c",
"tree-sitter-html",
"tree-sitter-python",
"tree-sitter-rust",
@@ -5243,7 +5263,6 @@ dependencies = [
"chrono",
"clap",
"client",
"cloud_llm_client",
"collections",
"debug_adapter_extension",
"dirs 4.0.0",
@@ -5283,6 +5302,7 @@ dependencies = [
"uuid",
"watch",
"workspace-hack",
"zed_llm_client",
]
[[package]]
@@ -5347,12 +5367,6 @@ dependencies = [
"zune-inflate",
]
[[package]]
name = "extended"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365"
[[package]]
name = "extension"
version = "0.1.0"
@@ -6364,7 +6378,6 @@ dependencies = [
"call",
"chrono",
"client",
"cloud_llm_client",
"collections",
"command_palette_hooks",
"component",
@@ -6407,6 +6420,7 @@ dependencies = [
"workspace",
"workspace-hack",
"zed_actions",
"zed_llm_client",
"zlog",
]
@@ -7728,6 +7742,12 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "hound"
version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
[[package]]
name = "html5ever"
version = "0.27.0"
@@ -8366,7 +8386,6 @@ version = "0.1.0"
dependencies = [
"anyhow",
"client",
"cloud_llm_client",
"copilot",
"editor",
"feature_flags",
@@ -8389,6 +8408,7 @@ dependencies = [
"workspace",
"workspace-hack",
"zed_actions",
"zed_llm_client",
"zeta",
]
@@ -9070,7 +9090,6 @@ dependencies = [
"anyhow",
"base64 0.22.1",
"client",
"cloud_llm_client",
"collections",
"futures 0.3.31",
"gpui",
@@ -9088,6 +9107,7 @@ dependencies = [
"thiserror 2.0.12",
"util",
"workspace-hack",
"zed_llm_client",
]
[[package]]
@@ -9103,7 +9123,6 @@ dependencies = [
"bedrock",
"chrono",
"client",
"cloud_llm_client",
"collections",
"component",
"convert_case 0.8.0",
@@ -9145,6 +9164,7 @@ dependencies = [
"vercel",
"workspace-hack",
"x_ai",
"zed_llm_client",
]
[[package]]
@@ -9206,7 +9226,6 @@ dependencies = [
"chrono",
"collections",
"dap",
"feature_flags",
"futures 0.3.31",
"gpui",
"http_client",
@@ -9575,7 +9594,7 @@ dependencies = [
"core-foundation 0.10.0",
"core-video",
"coreaudio-rs 0.12.1",
"cpal",
"cpal 0.16.0",
"futures 0.3.31",
"gpui",
"gpui_tokio",
@@ -10346,6 +10365,20 @@ dependencies = [
"workspace-hack",
]
[[package]]
name = "ndk"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7"
dependencies = [
"bitflags 2.9.0",
"jni-sys",
"log",
"ndk-sys 0.5.0+25.2.9519653",
"num_enum",
"thiserror 1.0.69",
]
[[package]]
name = "ndk"
version = "0.9.0"
@@ -10355,7 +10388,7 @@ dependencies = [
"bitflags 2.9.0",
"jni-sys",
"log",
"ndk-sys",
"ndk-sys 0.6.0+11769913",
"num_enum",
"thiserror 1.0.69",
]
@@ -10366,6 +10399,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
[[package]]
name = "ndk-sys"
version = "0.5.0+25.2.9519653"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
dependencies = [
"jni-sys",
]
[[package]]
name = "ndk-sys"
version = "0.6.0+11769913"
@@ -10935,6 +10977,29 @@ dependencies = [
"memchr",
]
[[package]]
name = "oboe"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb"
dependencies = [
"jni",
"ndk 0.8.0",
"ndk-context",
"num-derive",
"num-traits",
"oboe-sys",
]
[[package]]
name = "oboe-sys"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d"
dependencies = [
"cc",
]
[[package]]
name = "ollama"
version = "0.1.0"
@@ -10955,12 +11020,9 @@ dependencies = [
"anyhow",
"command_palette_hooks",
"db",
"editor",
"feature_flags",
"fs",
"gpui",
"language",
"project",
"settings",
"theme",
"ui",
@@ -13717,15 +13779,12 @@ dependencies = [
[[package]]
name = "rodio"
version = "0.21.1"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e40ecf59e742e03336be6a3d53755e789fd05a059fa22dfa0ed624722319e183"
checksum = "e7ceb6607dd738c99bc8cb28eff249b7cd5c8ec88b9db96c0608c1480d140fb1"
dependencies = [
"cpal",
"dasp_sample",
"num-rational",
"symphonia",
"tracing",
"cpal 0.15.3",
"hound",
]
[[package]]
@@ -15746,66 +15805,6 @@ dependencies = [
"zeno",
]
[[package]]
name = "symphonia"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9"
dependencies = [
"lazy_static",
"symphonia-codec-pcm",
"symphonia-core",
"symphonia-format-riff",
"symphonia-metadata",
]
[[package]]
name = "symphonia-codec-pcm"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f395a67057c2ebc5e84d7bb1be71cce1a7ba99f64e0f0f0e303a03f79116f89b"
dependencies = [
"log",
"symphonia-core",
]
[[package]]
name = "symphonia-core"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3"
dependencies = [
"arrayvec",
"bitflags 1.3.2",
"bytemuck",
"lazy_static",
"log",
]
[[package]]
name = "symphonia-format-riff"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f7be232f962f937f4b7115cbe62c330929345434c834359425e043bfd15f50"
dependencies = [
"extended",
"log",
"symphonia-core",
"symphonia-metadata",
]
[[package]]
name = "symphonia-metadata"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c"
dependencies = [
"encoding_rs",
"lazy_static",
"log",
"symphonia-core",
]
[[package]]
name = "syn"
version = "1.0.109"
@@ -18506,11 +18505,11 @@ name = "web_search"
version = "0.1.0"
dependencies = [
"anyhow",
"cloud_llm_client",
"collections",
"gpui",
"serde",
"workspace-hack",
"zed_llm_client",
]
[[package]]
@@ -18519,7 +18518,6 @@ version = "0.1.0"
dependencies = [
"anyhow",
"client",
"cloud_llm_client",
"futures 0.3.31",
"gpui",
"http_client",
@@ -18528,6 +18526,7 @@ dependencies = [
"serde_json",
"web_search",
"workspace-hack",
"zed_llm_client",
]
[[package]]
@@ -19693,12 +19692,14 @@ dependencies = [
"cc",
"chrono",
"cipher",
"clang-sys",
"clap",
"clap_builder",
"codespan-reporting 0.12.0",
"concurrent-queue",
"core-foundation 0.9.4",
"core-foundation-sys",
"coreaudio-sys",
"cranelift-codegen",
"crc32fast",
"crossbeam-epoch",
@@ -20194,7 +20195,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.198.1"
version = "0.198.0"
dependencies = [
"activity_indicator",
"agent",
@@ -20355,7 +20356,7 @@ dependencies = [
[[package]]
name = "zed_emmet"
version = "0.0.4"
version = "0.0.3"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -20394,6 +20395,19 @@ dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_llm_client"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6607f74dee2a18a9ce0f091844944a0e59881359ab62e0768fb0618f55d4c1dc"
dependencies = [
"anyhow",
"serde",
"serde_json",
"strum 0.27.1",
"uuid",
]
[[package]]
name = "zed_proto"
version = "0.2.2"
@@ -20573,7 +20587,6 @@ dependencies = [
"call",
"client",
"clock",
"cloud_llm_client",
"collections",
"command_palette_hooks",
"copilot",
@@ -20615,6 +20628,7 @@ dependencies = [
"workspace-hack",
"worktree",
"zed_actions",
"zed_llm_client",
"zlog",
]

View File

@@ -29,7 +29,6 @@ members = [
"crates/cli",
"crates/client",
"crates/clock",
"crates/cloud_llm_client",
"crates/collab",
"crates/collab_ui",
"crates/collections",
@@ -71,6 +70,7 @@ members = [
"crates/gpui",
"crates/gpui_macros",
"crates/gpui_tokio",
"crates/html_to_markdown",
"crates/http_client",
"crates/http_client_tls",
@@ -251,7 +251,6 @@ channel = { path = "crates/channel" }
cli = { path = "crates/cli" }
client = { path = "crates/client" }
clock = { path = "crates/clock" }
cloud_llm_client = { path = "crates/cloud_llm_client" }
collab = { path = "crates/collab" }
collab_ui = { path = "crates/collab_ui" }
collections = { path = "crates/collections" }
@@ -646,6 +645,7 @@ which = "6.0.0"
windows-core = "0.61"
wit-component = "0.221"
workspace-hack = "0.1.0"
zed_llm_client = "= 0.8.6"
zstd = "0.11"
[workspace.dependencies.async-stripe]
@@ -674,8 +674,13 @@ features = [
"Win32_Globalization",
"Win32_Graphics_Direct2D",
"Win32_Graphics_Direct2D_Common",
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D11",
"Win32_Graphics_Direct3D_Fxc",
"Win32_Graphics_DirectComposition",
"Win32_Graphics_DirectWrite",
"Win32_Graphics_Dwm",
"Win32_Graphics_Dxgi",
"Win32_Graphics_Dxgi_Common",
"Win32_Graphics_Gdi",
"Win32_Graphics_Imaging",

View File

@@ -1,6 +1,5 @@
# Zed
[![Zed](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/zed-industries/zed/main/assets/badge/v0.json)](https://zed.dev)
[![CI](https://github.com/zed-industries/zed/actions/workflows/ci.yml/badge.svg)](https://github.com/zed-industries/zed/actions/workflows/ci.yml)
Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom](https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter).

View File

@@ -1,8 +0,0 @@
{
"label": "",
"message": "Zed",
"logoSvg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 96 96\"><rect width=\"96\" height=\"96\" fill=\"#000\"/><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M9 6C7.34315 6 6 7.34315 6 9V75H0V9C0 4.02944 4.02944 0 9 0H89.3787C93.3878 0 95.3955 4.84715 92.5607 7.68198L43.0551 57.1875H57V51H63V58.6875C63 61.1728 60.9853 63.1875 58.5 63.1875H37.0551L26.7426 73.5H73.5V36H79.5V73.5C79.5 76.8137 76.8137 79.5 73.5 79.5H20.7426L10.2426 90H87C88.6569 90 90 88.6569 90 87V21H96V87C96 91.9706 91.9706 96 87 96H6.62132C2.61224 96 0.604504 91.1529 3.43934 88.318L52.7574 39H39V45H33V37.5C33 35.0147 35.0147 33 37.5 33H58.7574L69.2574 22.5H22.5V60H16.5V22.5C16.5 19.1863 19.1863 16.5 22.5 16.5H75.2574L85.7574 6H9Z\" fill=\"#fff\"/></svg>",
"logoWidth": 16,
"labelColor": "black",
"color": "white"
}

View File

@@ -232,7 +232,7 @@
"ctrl-n": "agent::NewThread",
"ctrl-alt-n": "agent::NewTextThread",
"ctrl-shift-h": "agent::OpenHistory",
"ctrl-alt-c": "agent::OpenSettings",
"ctrl-alt-c": "agent::OpenConfiguration",
"ctrl-alt-p": "agent::OpenRulesLibrary",
"ctrl-i": "agent::ToggleProfileSelector",
"ctrl-alt-/": "agent::ToggleModelSelector",
@@ -495,7 +495,7 @@
"shift-f12": "editor::GoToImplementation",
"alt-ctrl-f12": "editor::GoToTypeDefinitionSplit",
"alt-shift-f12": "editor::FindAllReferences",
"ctrl-m": "editor::MoveToEnclosingBracket", // from jetbrains
"ctrl-m": "editor::MoveToEnclosingBracket",
"ctrl-|": "editor::MoveToEnclosingBracket",
"ctrl-{": "editor::Fold",
"ctrl-}": "editor::UnfoldLines",

View File

@@ -272,7 +272,7 @@
"cmd-n": "agent::NewThread",
"cmd-alt-n": "agent::NewTextThread",
"cmd-shift-h": "agent::OpenHistory",
"cmd-alt-c": "agent::OpenSettings",
"cmd-alt-c": "agent::OpenConfiguration",
"cmd-alt-p": "agent::OpenRulesLibrary",
"cmd-i": "agent::ToggleProfileSelector",
"cmd-alt-/": "agent::ToggleModelSelector",
@@ -549,7 +549,7 @@
"alt-cmd-f12": "editor::GoToTypeDefinitionSplit",
"alt-shift-f12": "editor::FindAllReferences",
"cmd-|": "editor::MoveToEnclosingBracket",
"ctrl-m": "editor::MoveToEnclosingBracket", // From Jetbrains
"ctrl-m": "editor::MoveToEnclosingBracket",
"alt-cmd-[": "editor::Fold",
"alt-cmd-]": "editor::UnfoldLines",
"cmd-k cmd-l": "editor::ToggleFold",

View File

@@ -8,7 +8,7 @@
"ctrl-shift-i": "agent::ToggleFocus",
"ctrl-l": "agent::ToggleFocus",
"ctrl-shift-l": "agent::ToggleFocus",
"ctrl-shift-j": "agent::OpenSettings"
"ctrl-shift-j": "agent::OpenConfiguration"
}
},
{

View File

@@ -95,7 +95,7 @@
"ctrl-shift-r": ["pane::DeploySearch", { "replace_enabled": true }],
"alt-shift-f10": "task::Spawn",
"ctrl-e": "file_finder::Toggle",
// "ctrl-k": "git_panel::ToggleFocus", // bug: This should also focus commit editor
"ctrl-k": "git_panel::ToggleFocus", // bug: This should also focus commit editor
"ctrl-shift-n": "file_finder::Toggle",
"ctrl-shift-a": "command_palette::Toggle",
"shift shift": "command_palette::Toggle",

View File

@@ -8,7 +8,7 @@
"cmd-shift-i": "agent::ToggleFocus",
"cmd-l": "agent::ToggleFocus",
"cmd-shift-l": "agent::ToggleFocus",
"cmd-shift-j": "agent::OpenSettings"
"cmd-shift-j": "agent::OpenConfiguration"
}
},
{

View File

@@ -97,7 +97,7 @@
"cmd-shift-r": ["pane::DeploySearch", { "replace_enabled": true }],
"ctrl-alt-r": "task::Spawn",
"cmd-e": "file_finder::Toggle",
// "cmd-k": "git_panel::ToggleFocus", // bug: This should also focus commit editor
"cmd-k": "git_panel::ToggleFocus", // bug: This should also focus commit editor
"cmd-shift-o": "file_finder::Toggle",
"cmd-shift-a": "command_palette::Toggle",
"shift shift": "command_palette::Toggle",

View File

@@ -1597,7 +1597,6 @@ mod tests {
name: "test",
connection,
child_status: io_task,
current_thread: thread_rc,
};
AcpThread::new(

View File

@@ -7,7 +7,6 @@ use gpui::{AppContext as _, AsyncApp, Entity, Task, WeakEntity};
use project::Project;
use std::{cell::RefCell, error::Error, fmt, path::Path, rc::Rc};
use ui::App;
use util::ResultExt as _;
use crate::{AcpThread, AgentConnection};
@@ -47,7 +46,7 @@ impl acp_old::Client for OldAcpClientDelegate {
thread.push_assistant_content_block(thought.into(), true, cx)
}
})
.log_err();
.ok();
})?;
Ok(())
@@ -365,7 +364,6 @@ pub struct OldAcpAgentConnection {
pub name: &'static str,
pub connection: acp_old::AgentConnection,
pub child_status: Task<Result<()>>,
pub current_thread: Rc<RefCell<WeakEntity<AcpThread>>>,
}
impl AgentConnection for OldAcpAgentConnection {
@@ -385,7 +383,6 @@ impl AgentConnection for OldAcpAgentConnection {
}
.into_any(),
);
let current_thread = self.current_thread.clone();
cx.spawn(async move |cx| {
let result = task.await?;
let result = acp_old::InitializeParams::response_from_any(result)?;
@@ -399,7 +396,6 @@ impl AgentConnection for OldAcpAgentConnection {
let session_id = acp::SessionId("acp-old-no-id".into());
AcpThread::new(self.clone(), project, session_id, cx)
});
current_thread.replace(thread.downgrade());
thread
})
})

View File

@@ -25,7 +25,6 @@ assistant_context.workspace = true
assistant_tool.workspace = true
chrono.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
component.workspace = true
context_server.workspace = true
@@ -36,9 +35,9 @@ futures.workspace = true
git.workspace = true
gpui.workspace = true
heed.workspace = true
http_client.workspace = true
icons.workspace = true
indoc.workspace = true
http_client.workspace = true
itertools.workspace = true
language.workspace = true
language_model.workspace = true
@@ -64,6 +63,7 @@ time.workspace = true
util.workspace = true
uuid.workspace = true
workspace-hack.workspace = true
zed_llm_client.workspace = true
zstd.workspace = true
[dev-dependencies]

View File

@@ -13,7 +13,6 @@ use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolWorkingSet};
use chrono::{DateTime, Utc};
use client::{ModelRequestUsage, RequestUsage};
use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
use collections::HashMap;
use feature_flags::{self, FeatureFlagAppExt};
use futures::{FutureExt, StreamExt as _, future::Shared};
@@ -50,6 +49,7 @@ use std::{
use thiserror::Error;
use util::{ResultExt as _, post_inc};
use uuid::Uuid;
use zed_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
const MAX_RETRY_ATTEMPTS: u8 = 4;
const BASE_RETRY_DELAY: Duration = Duration::from_secs(5);
@@ -1681,7 +1681,7 @@ impl Thread {
let completion_mode = request
.mode
.unwrap_or(cloud_llm_client::CompletionMode::Normal);
.unwrap_or(zed_llm_client::CompletionMode::Normal);
self.last_received_chunk_at = Some(Instant::now());

View File

@@ -47,7 +47,6 @@ impl AgentServer for Codex {
cx: &mut App,
) -> Task<Result<Rc<dyn AgentConnection>>> {
let project = project.clone();
let working_directory = project.read(cx).active_project_directory(cx);
cx.spawn(async move |cx| {
let settings = cx.read_global(|settings: &SettingsStore, _| {
settings.get::<AllAgentServersSettings>(None).codex.clone()
@@ -66,7 +65,6 @@ impl AgentServer for Codex {
args: command.args,
env: command.env,
},
working_directory,
)
.into();
ContextServer::start(client.clone(), cx).await?;
@@ -312,7 +310,7 @@ pub(crate) mod tests {
AgentServerCommand {
path: cli_path,
args: vec![],
args: vec!["mcp".into()],
env: None,
}
}

View File

@@ -12,6 +12,7 @@ use futures::{FutureExt, StreamExt, channel::mpsc, select};
use gpui::{Entity, TestAppContext};
use indoc::indoc;
use project::{FakeFs, Project};
use serde_json::json;
use settings::{Settings, SettingsStore};
use util::path;
@@ -26,11 +27,7 @@ pub async fn test_basic(server: impl AgentServer + 'static, cx: &mut TestAppCont
.unwrap();
thread.read_with(cx, |thread, _| {
assert!(
thread.entries().len() >= 2,
"Expected at least 2 entries. Got: {:?}",
thread.entries()
);
assert_eq!(thread.entries().len(), 2);
assert!(matches!(
thread.entries()[0],
AgentThreadEntry::UserMessage(_)
@@ -111,19 +108,19 @@ pub async fn test_path_mentions(server: impl AgentServer + 'static, cx: &mut Tes
}
pub async fn test_tool_call(server: impl AgentServer + 'static, cx: &mut TestAppContext) {
let _fs = init_test(cx).await;
let tempdir = tempfile::tempdir().unwrap();
let foo_path = tempdir.path().join("foo");
std::fs::write(&foo_path, "Lorem ipsum dolor").expect("failed to write file");
let project = Project::example([tempdir.path()], &mut cx.to_async()).await;
let fs = init_test(cx).await;
fs.insert_tree(
path!("/private/tmp"),
json!({"foo": "Lorem ipsum dolor", "bar": "bar", "baz": "baz"}),
)
.await;
let project = Project::test(fs, [path!("/private/tmp").as_ref()], cx).await;
let thread = new_test_thread(server, project.clone(), "/private/tmp", cx).await;
thread
.update(cx, |thread, cx| {
thread.send_raw(
&format!("Read {} and tell me what you see.", foo_path.display()),
"Read the '/private/tmp/foo' file and tell me what you see.",
cx,
)
})
@@ -146,8 +143,6 @@ pub async fn test_tool_call(server: impl AgentServer + 'static, cx: &mut TestApp
.any(|entry| { matches!(entry, AgentThreadEntry::AssistantMessage(_)) })
);
});
drop(tempdir);
}
pub async fn test_tool_call_with_confirmation(
@@ -160,7 +155,7 @@ pub async fn test_tool_call_with_confirmation(
let thread = new_test_thread(server, project.clone(), "/private/tmp", cx).await;
let full_turn = thread.update(cx, |thread, cx| {
thread.send_raw(
r#"Run exactly `touch hello.txt && echo "Hello, world!" | tee hello.txt` in the terminal."#,
r#"Run `touch hello.txt && echo "Hello, world!" | tee hello.txt`"#,
cx,
)
});
@@ -180,10 +175,10 @@ pub async fn test_tool_call_with_confirmation(
)
.await;
let tool_call_id = thread.read_with(cx, |thread, cx| {
let tool_call_id = thread.read_with(cx, |thread, _cx| {
let AgentThreadEntry::ToolCall(ToolCall {
id,
label,
content,
status: ToolCallStatus::WaitingForConfirmation { .. },
..
}) = &thread
@@ -195,8 +190,7 @@ pub async fn test_tool_call_with_confirmation(
panic!();
};
let label = label.read(cx).source();
assert!(label.contains("touch"), "Got: {}", label);
assert!(content.iter().any(|c| c.to_markdown(_cx).contains("touch")));
id.clone()
});
@@ -248,7 +242,7 @@ pub async fn test_cancel(server: impl AgentServer + 'static, cx: &mut TestAppCon
let thread = new_test_thread(server, project.clone(), "/private/tmp", cx).await;
let full_turn = thread.update(cx, |thread, cx| {
thread.send_raw(
r#"Run exactly `touch hello.txt && echo "Hello, world!" | tee hello.txt` in the terminal."#,
r#"Run `touch hello.txt && echo "Hello, world!" >> hello.txt`"#,
cx,
)
});
@@ -268,10 +262,10 @@ pub async fn test_cancel(server: impl AgentServer + 'static, cx: &mut TestAppCon
)
.await;
thread.read_with(cx, |thread, cx| {
thread.read_with(cx, |thread, _cx| {
let AgentThreadEntry::ToolCall(ToolCall {
id,
label,
content,
status: ToolCallStatus::WaitingForConfirmation { .. },
..
}) = &thread.entries()[first_tool_call_ix]
@@ -279,8 +273,7 @@ pub async fn test_cancel(server: impl AgentServer + 'static, cx: &mut TestAppCon
panic!("{:?}", thread.entries()[1]);
};
let label = label.read(cx).source();
assert!(label.contains("touch"), "Got: {}", label);
assert!(content.iter().any(|c| c.to_markdown(_cx).contains("touch")));
id.clone()
});

View File

@@ -107,7 +107,6 @@ impl AgentServer for Gemini {
name,
connection,
child_status,
current_thread: thread_rc,
});
Ok(connection)

View File

@@ -13,7 +13,6 @@ path = "src/agent_settings.rs"
[dependencies]
anyhow.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
gpui.workspace = true
language_model.workspace = true
@@ -21,6 +20,7 @@ schemars.workspace = true
serde.workspace = true
settings.workspace = true
workspace-hack.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
fs.workspace = true

View File

@@ -321,11 +321,11 @@ pub enum CompletionMode {
Burn,
}
impl From<CompletionMode> for cloud_llm_client::CompletionMode {
impl From<CompletionMode> for zed_llm_client::CompletionMode {
fn from(value: CompletionMode) -> Self {
match value {
CompletionMode::Normal => cloud_llm_client::CompletionMode::Normal,
CompletionMode::Burn => cloud_llm_client::CompletionMode::Max,
CompletionMode::Normal => zed_llm_client::CompletionMode::Normal,
CompletionMode::Burn => zed_llm_client::CompletionMode::Max,
}
}
}

View File

@@ -31,7 +31,6 @@ audio.workspace = true
buffer_diff.workspace = true
chrono.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
component.workspace = true
@@ -47,9 +46,9 @@ futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true
html_to_markdown.workspace = true
indoc.workspace = true
http_client.workspace = true
indexed_docs.workspace = true
indoc.workspace = true
inventory.workspace = true
itertools.workspace = true
jsonschema.workspace = true
@@ -98,6 +97,7 @@ watch.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_actions.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
assistant_tools.workspace = true

View File

@@ -14,7 +14,6 @@ use agent_settings::{AgentSettings, NotifyWhenAgentWaiting};
use anyhow::Context as _;
use assistant_tool::ToolUseStatus;
use audio::{Audio, Sound};
use cloud_llm_client::CompletionIntent;
use collections::{HashMap, HashSet};
use editor::actions::{MoveUp, Paste};
use editor::scroll::Autoscroll;
@@ -53,6 +52,7 @@ use util::ResultExt as _;
use util::markdown::MarkdownCodeBlock;
use workspace::{CollaboratorId, Workspace};
use zed_actions::assistant::OpenRulesLibrary;
use zed_llm_client::CompletionIntent;
const CODEBLOCK_CONTAINER_GROUP: &str = "codeblock_container";
const EDIT_PREVIOUS_MESSAGE_MIN_LINES: usize = 1;

View File

@@ -44,7 +44,6 @@ use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
use assistant_slash_command::SlashCommandWorkingSet;
use assistant_tool::ToolWorkingSet;
use client::{DisableAiSettings, UserStore, zed_urls};
use cloud_llm_client::{CompletionIntent, UsageLimit};
use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
use feature_flags::{self, FeatureFlagAppExt};
use fs::Fs;
@@ -78,9 +77,10 @@ use workspace::{
};
use zed_actions::{
DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize,
agent::{OpenOnboardingModal, OpenSettings, ResetOnboarding, ToggleModelSelector},
agent::{OpenConfiguration, OpenOnboardingModal, ResetOnboarding, ToggleModelSelector},
assistant::{OpenRulesLibrary, ToggleFocus},
};
use zed_llm_client::{CompletionIntent, UsageLimit};
const AGENT_PANEL_KEY: &str = "agent_panel";
@@ -105,7 +105,7 @@ pub fn init(cx: &mut App) {
panel.update(cx, |panel, cx| panel.open_history(window, cx));
}
})
.register_action(|workspace, _: &OpenSettings, window, cx| {
.register_action(|workspace, _: &OpenConfiguration, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
workspace.focus_panel::<AgentPanel>(window, cx);
panel.update(cx, |panel, cx| panel.open_configuration(window, cx));
@@ -2088,7 +2088,7 @@ impl AgentPanel {
menu = menu
.action("Rules…", Box::new(OpenRulesLibrary::default()))
.action("Settings", Box::new(OpenSettings))
.action("Settings", Box::new(OpenConfiguration))
.action(zoom_in_label, Box::new(ToggleZoom));
menu
}))
@@ -2482,14 +2482,14 @@ impl AgentPanel {
.icon_color(Color::Muted)
.full_width()
.key_binding(KeyBinding::for_action_in(
&OpenSettings,
&OpenConfiguration,
&focus_handle,
window,
cx,
))
.on_click(|_event, window, cx| {
window.dispatch_action(
OpenSettings.boxed_clone(),
OpenConfiguration.boxed_clone(),
cx,
)
}),
@@ -2713,11 +2713,16 @@ impl AgentPanel {
.style(ButtonStyle::Tinted(ui::TintColor::Warning))
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(&OpenSettings, &focus_handle, window, cx)
.map(|kb| kb.size(rems_from_px(12.))),
KeyBinding::for_action_in(
&OpenConfiguration,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(|_event, window, cx| {
window.dispatch_action(OpenSettings.boxed_clone(), cx)
window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
}),
),
ConfigurationError::ProviderPendingTermsAcceptance(provider) => {
@@ -3221,7 +3226,7 @@ impl Render for AgentPanel {
.on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
this.open_history(window, cx);
}))
.on_action(cx.listener(|this, _: &OpenSettings, window, cx| {
.on_action(cx.listener(|this, _: &OpenConfiguration, window, cx| {
this.open_configuration(window, cx);
}))
.on_action(cx.listener(Self::open_active_thread_as_markdown))

View File

@@ -265,8 +265,8 @@ fn update_command_palette_filter(cx: &mut App) {
filter.hide_namespace("agent");
filter.hide_namespace("assistant");
filter.hide_namespace("copilot");
filter.hide_namespace("supermaven");
filter.hide_namespace("zed_predict_onboarding");
filter.hide_namespace("edit_prediction");
use editor::actions::{

View File

@@ -6,7 +6,6 @@ use agent::{
use agent_settings::AgentSettings;
use anyhow::{Context as _, Result};
use client::telemetry::Telemetry;
use cloud_llm_client::CompletionIntent;
use collections::HashSet;
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
use futures::{
@@ -36,6 +35,7 @@ use std::{
};
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
use zed_llm_client::CompletionIntent;
pub struct BufferCodegen {
alternatives: Vec<Entity<CodegenAlternative>>,

View File

@@ -1,10 +1,10 @@
#![allow(unused, dead_code)]
use client::{ModelRequestUsage, RequestUsage};
use cloud_llm_client::{Plan, UsageLimit};
use gpui::Global;
use std::ops::{Deref, DerefMut};
use ui::prelude::*;
use zed_llm_client::{Plan, UsageLimit};
/// Debug only: Used for testing various account states
///

View File

@@ -48,7 +48,7 @@ use text::{OffsetRangeExt, ToPoint as _};
use ui::prelude::*;
use util::{RangeExt, ResultExt, maybe};
use workspace::{ItemHandle, Toast, Workspace, dock::Panel, notifications::NotificationId};
use zed_actions::agent::OpenSettings;
use zed_actions::agent::OpenConfiguration;
pub fn init(
fs: Arc<dyn Fs>,
@@ -345,7 +345,7 @@ impl InlineAssistant {
if let Some(answer) = answer {
if answer == 0 {
cx.update(|window, cx| {
window.dispatch_action(Box::new(OpenSettings), cx)
window.dispatch_action(Box::new(OpenConfiguration), cx)
})
.ok();
}

View File

@@ -576,7 +576,7 @@ impl PickerDelegate for LanguageModelPickerDelegate {
.icon_position(IconPosition::Start)
.on_click(|_, window, cx| {
window.dispatch_action(
zed_actions::agent::OpenSettings.boxed_clone(),
zed_actions::agent::OpenConfiguration.boxed_clone(),
cx,
);
}),

View File

@@ -18,7 +18,6 @@ use agent_settings::{AgentSettings, CompletionMode};
use ai_onboarding::ApiKeysWithProviders;
use buffer_diff::BufferDiff;
use client::UserStore;
use cloud_llm_client::CompletionIntent;
use collections::{HashMap, HashSet};
use editor::actions::{MoveUp, Paste};
use editor::display_map::CreaseId;
@@ -54,6 +53,7 @@ use util::ResultExt as _;
use workspace::{CollaboratorId, Workspace};
use zed_actions::agent::Chat;
use zed_actions::agent::ToggleModelSelector;
use zed_llm_client::CompletionIntent;
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
@@ -1300,11 +1300,11 @@ impl MessageEditor {
let plan = user_store
.current_plan()
.map(|plan| match plan {
Plan::Free => cloud_llm_client::Plan::ZedFree,
Plan::ZedPro => cloud_llm_client::Plan::ZedPro,
Plan::ZedProTrial => cloud_llm_client::Plan::ZedProTrial,
Plan::Free => zed_llm_client::Plan::ZedFree,
Plan::ZedPro => zed_llm_client::Plan::ZedPro,
Plan::ZedProTrial => zed_llm_client::Plan::ZedProTrial,
})
.unwrap_or(cloud_llm_client::Plan::ZedFree);
.unwrap_or(zed_llm_client::Plan::ZedFree);
let usage = user_store.model_request_usage()?;

View File

@@ -10,7 +10,6 @@ use agent::{
use agent_settings::AgentSettings;
use anyhow::{Context as _, Result};
use client::telemetry::Telemetry;
use cloud_llm_client::CompletionIntent;
use collections::{HashMap, VecDeque};
use editor::{MultiBuffer, actions::SelectAll};
use fs::Fs;
@@ -28,6 +27,7 @@ use terminal_view::TerminalView;
use ui::prelude::*;
use util::ResultExt;
use workspace::{Toast, Workspace, notifications::NotificationId};
use zed_llm_client::CompletionIntent;
pub fn init(
fs: Arc<dyn Fs>,

View File

@@ -1,8 +1,8 @@
use client::{ModelRequestUsage, RequestUsage, zed_urls};
use cloud_llm_client::{Plan, UsageLimit};
use component::{empty_example, example_group_with_title, single_example};
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
use ui::{Callout, prelude::*};
use zed_llm_client::{Plan, UsageLimit};
#[derive(IntoElement, RegisterComponent)]
pub struct UsageCallout {

View File

@@ -136,7 +136,10 @@ impl RenderOnce for ApiKeysWithoutProviders {
.full_width()
.style(ButtonStyle::Outlined)
.on_click(move |_, window, cx| {
window.dispatch_action(zed_actions::agent::OpenSettings.boxed_clone(), cx);
window.dispatch_action(
zed_actions::agent::OpenConfiguration.boxed_clone(),
cx,
);
}),
)
}

View File

@@ -19,7 +19,6 @@ assistant_slash_commands.workspace = true
chrono.workspace = true
client.workspace = true
clock.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
context_server.workspace = true
fs.workspace = true
@@ -49,6 +48,7 @@ util.workspace = true
uuid.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
indoc.workspace = true

View File

@@ -11,7 +11,6 @@ use assistant_slash_command::{
use assistant_slash_commands::FileCommandMetadata;
use client::{self, Client, proto, telemetry::Telemetry};
use clock::ReplicaId;
use cloud_llm_client::CompletionIntent;
use collections::{HashMap, HashSet};
use fs::{Fs, RenameOptions};
use futures::{FutureExt, StreamExt, future::Shared};
@@ -47,6 +46,7 @@ use text::{BufferSnapshot, ToPoint};
use ui::IconName;
use util::{ResultExt, TryFutureExt, post_inc};
use uuid::Uuid;
use zed_llm_client::CompletionIntent;
pub use crate::context_store::*;

View File

@@ -21,11 +21,9 @@ assistant_tool.workspace = true
buffer_diff.workspace = true
chrono.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
component.workspace = true
derive_more.workspace = true
diffy = "0.4.2"
editor.workspace = true
feature_flags.workspace = true
futures.workspace = true
@@ -65,6 +63,8 @@ web_search.workspace = true
which.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_llm_client.workspace = true
diffy = "0.4.2"
[dev-dependencies]
lsp = { workspace = true, features = ["test-support"] }

View File

@@ -7,7 +7,6 @@ mod streaming_fuzzy_matcher;
use crate::{Template, Templates};
use anyhow::Result;
use assistant_tool::ActionLog;
use cloud_llm_client::CompletionIntent;
use create_file_parser::{CreateFileParser, CreateFileParserEvent};
pub use edit_parser::EditFormat;
use edit_parser::{EditParser, EditParserEvent, EditParserMetrics};
@@ -30,6 +29,7 @@ use std::{cmp, iter, mem, ops::Range, path::PathBuf, pin::Pin, sync::Arc, task::
use streaming_diff::{CharOperation, StreamingDiff};
use streaming_fuzzy_matcher::StreamingFuzzyMatcher;
use util::debug_panic;
use zed_llm_client::CompletionIntent;
#[derive(Serialize)]
struct CreateFilePromptTemplate {

View File

@@ -6,7 +6,6 @@ use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{
ActionLog, Tool, ToolCard, ToolResult, ToolResultContent, ToolResultOutput, ToolUseStatus,
};
use cloud_llm_client::{WebSearchResponse, WebSearchResult};
use futures::{Future, FutureExt, TryFutureExt};
use gpui::{
AnyWindowHandle, App, AppContext, Context, Entity, IntoElement, Task, WeakEntity, Window,
@@ -18,6 +17,7 @@ use serde::{Deserialize, Serialize};
use ui::{IconName, Tooltip, prelude::*};
use web_search::WebSearchRegistry;
use workspace::Workspace;
use zed_llm_client::{WebSearchResponse, WebSearchResult};
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct WebSearchToolInput {

View File

@@ -18,6 +18,6 @@ collections.workspace = true
derive_more.workspace = true
gpui.workspace = true
parking_lot.workspace = true
rodio = { version = "0.21.1", default-features = false, features = ["wav", "playback", "tracing"] }
rodio = { version = "0.20.0", default-features = false, features = ["wav"] }
util.workspace = true
workspace-hack.workspace = true

View File

@@ -3,9 +3,12 @@ use std::{io::Cursor, sync::Arc};
use anyhow::{Context as _, Result};
use collections::HashMap;
use gpui::{App, AssetSource, Global};
use rodio::{Decoder, Source, source::Buffered};
use rodio::{
Decoder, Source,
source::{Buffered, SamplesConverter},
};
type Sound = Buffered<Decoder<Cursor<Vec<u8>>>>;
type Sound = Buffered<SamplesConverter<Decoder<Cursor<Vec<u8>>>, f32>>;
pub struct SoundRegistry {
cache: Arc<parking_lot::Mutex<HashMap<String, Sound>>>,
@@ -45,7 +48,7 @@ impl SoundRegistry {
.with_context(|| format!("No asset available for path {path}"))??
.into_owned();
let cursor = Cursor::new(bytes);
let source = Decoder::new(cursor)?.buffered();
let source = Decoder::new(cursor)?.convert_samples::<f32>().buffered();
self.cache.lock().insert(name.to_string(), source.clone());

View File

@@ -1,7 +1,7 @@
use assets::SoundRegistry;
use derive_more::{Deref, DerefMut};
use gpui::{App, AssetSource, BorrowAppContext, Global};
use rodio::{OutputStream, OutputStreamBuilder};
use rodio::{OutputStream, OutputStreamHandle};
use util::ResultExt;
mod assets;
@@ -37,7 +37,8 @@ impl Sound {
#[derive(Default)]
pub struct Audio {
output_handle: Option<OutputStream>,
_output_stream: Option<OutputStream>,
output_handle: Option<OutputStreamHandle>,
}
#[derive(Deref, DerefMut)]
@@ -50,9 +51,11 @@ impl Audio {
Self::default()
}
fn ensure_output_exists(&mut self) -> Option<&OutputStream> {
fn ensure_output_exists(&mut self) -> Option<&OutputStreamHandle> {
if self.output_handle.is_none() {
self.output_handle = OutputStreamBuilder::open_default_stream().log_err();
let (_output_stream, output_handle) = OutputStream::try_default().log_err().unzip();
self.output_handle = output_handle;
self._output_stream = _output_stream;
}
self.output_handle.as_ref()
@@ -66,7 +69,7 @@ impl Audio {
cx.update_global::<GlobalAudio, _>(|this, cx| {
let output_handle = this.ensure_output_exists()?;
let source = SoundRegistry::global(cx).get(sound.file()).log_err()?;
output_handle.mixer().add(source);
output_handle.play_raw(source).log_err()?;
Some(())
});
}
@@ -77,6 +80,7 @@ impl Audio {
}
cx.update_global::<GlobalAudio, _>(|this, _| {
this._output_stream.take();
this.output_handle.take();
});
}

View File

@@ -22,7 +22,6 @@ async-tungstenite = { workspace = true, features = ["tokio", "tokio-rustls-manua
base64.workspace = true
chrono = { workspace = true, features = ["serde"] }
clock.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
credentials_provider.workspace = true
derive_more.workspace = true
@@ -34,8 +33,8 @@ http_client.workspace = true
http_client_tls.workspace = true
httparse = "1.10"
log.workspace = true
parking_lot.workspace = true
paths.workspace = true
parking_lot.workspace = true
postage.workspace = true
rand.workspace = true
regex.workspace = true
@@ -47,18 +46,19 @@ serde_json.workspace = true
settings.workspace = true
sha2.workspace = true
smol.workspace = true
telemetry.workspace = true
telemetry_events.workspace = true
text.workspace = true
thiserror.workspace = true
time.workspace = true
tiny_http.workspace = true
tokio-socks = { version = "0.5.2", default-features = false, features = ["futures-io"] }
tokio.workspace = true
url.workspace = true
util.workspace = true
workspace-hack.workspace = true
worktree.workspace = true
telemetry.workspace = true
tokio.workspace = true
workspace-hack.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
clock = { workspace = true, features = ["test-support"] }

View File

@@ -21,7 +21,7 @@ use futures::{
channel::oneshot, future::BoxFuture,
};
use gpui::{App, AsyncApp, Entity, Global, Task, WeakEntity, actions};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl, http};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use parking_lot::RwLock;
use postage::watch;
use proxy::connect_proxy_stream;
@@ -31,6 +31,7 @@ use rpc::proto::{AnyTypedEnvelope, EnvelopedMessage, PeerId, RequestMessage};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
use std::pin::Pin;
use std::{
any::TypeId,
convert::TryFrom,
@@ -44,7 +45,6 @@ use std::{
},
time::{Duration, Instant},
};
use std::{cmp, pin::Pin};
use telemetry::Telemetry;
use thiserror::Error;
use tokio::net::TcpStream;
@@ -78,7 +78,7 @@ pub static ZED_ALWAYS_ACTIVE: LazyLock<bool> =
LazyLock::new(|| std::env::var("ZED_ALWAYS_ACTIVE").map_or(false, |e| !e.is_empty()));
pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(500);
pub const MAX_RECONNECTION_DELAY: Duration = Duration::from_secs(30);
pub const MAX_RECONNECTION_DELAY: Duration = Duration::from_secs(10);
pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(20);
actions!(
@@ -727,10 +727,11 @@ impl Client {
},
&cx,
);
let jitter =
Duration::from_millis(rng.gen_range(0..delay.as_millis() as u64));
cx.background_executor().timer(delay + jitter).await;
delay = cmp::min(delay * 2, MAX_RECONNECTION_DELAY);
cx.background_executor().timer(delay).await;
delay = delay
.mul_f32(rng.gen_range(0.5..=2.5))
.max(INITIAL_RECONNECTION_DELAY)
.min(MAX_RECONNECTION_DELAY);
} else {
break;
}
@@ -1137,7 +1138,7 @@ impl Client {
.to_str()
.map_err(EstablishConnectionError::other)?
.to_string();
Url::parse(&collab_url).with_context(|| format!("parsing collab rpc url {collab_url}"))
Url::parse(&collab_url).with_context(|| format!("parsing colab rpc url {collab_url}"))
}
}
@@ -1157,7 +1158,6 @@ impl Client {
let http = self.http.clone();
let proxy = http.proxy().cloned();
let user_agent = http.user_agent().cloned();
let credentials = credentials.clone();
let rpc_url = self.rpc_url(http, release_channel);
let system_id = self.telemetry.system_id();
@@ -1209,7 +1209,7 @@ impl Client {
// We then modify the request to add our desired headers.
let request_headers = request.headers_mut();
request_headers.insert(
http::header::AUTHORIZATION,
"Authorization",
HeaderValue::from_str(&credentials.authorization_header())?,
);
request_headers.insert(
@@ -1221,9 +1221,6 @@ impl Client {
"x-zed-release-channel",
HeaderValue::from_str(release_channel.map(|r| r.dev_name()).unwrap_or("unknown"))?,
);
if let Some(user_agent) = user_agent {
request_headers.insert(http::header::USER_AGENT, user_agent);
}
if let Some(system_id) = system_id {
request_headers.insert("x-zed-system-id", HeaderValue::from_str(&system_id)?);
}

View File

@@ -1,10 +1,6 @@
use super::{Client, Status, TypedEnvelope, proto};
use anyhow::{Context as _, Result, anyhow};
use chrono::{DateTime, Utc};
use cloud_llm_client::{
EDIT_PREDICTIONS_USAGE_AMOUNT_HEADER_NAME, EDIT_PREDICTIONS_USAGE_LIMIT_HEADER_NAME,
MODEL_REQUESTS_USAGE_AMOUNT_HEADER_NAME, MODEL_REQUESTS_USAGE_LIMIT_HEADER_NAME, UsageLimit,
};
use collections::{HashMap, HashSet, hash_map::Entry};
use derive_more::Deref;
use feature_flags::FeatureFlagAppExt;
@@ -21,6 +17,10 @@ use std::{
};
use text::ReplicaId;
use util::{TryFutureExt as _, maybe};
use zed_llm_client::{
EDIT_PREDICTIONS_USAGE_AMOUNT_HEADER_NAME, EDIT_PREDICTIONS_USAGE_LIMIT_HEADER_NAME,
MODEL_REQUESTS_USAGE_AMOUNT_HEADER_NAME, MODEL_REQUESTS_USAGE_LIMIT_HEADER_NAME, UsageLimit,
};
pub type UserId = u64;

View File

@@ -1,23 +0,0 @@
[package]
name = "cloud_llm_client"
version = "0.1.0"
publish.workspace = true
edition.workspace = true
license = "Apache-2.0"
[lints]
workspace = true
[lib]
path = "src/cloud_llm_client.rs"
[dependencies]
anyhow.workspace = true
serde = { workspace = true, features = ["derive", "rc"] }
serde_json.workspace = true
strum = { workspace = true, features = ["derive"] }
uuid = { workspace = true, features = ["serde"] }
workspace-hack.workspace = true
[dev-dependencies]
pretty_assertions.workspace = true

View File

@@ -1 +0,0 @@
../../LICENSE-APACHE

View File

@@ -1,370 +0,0 @@
use std::str::FromStr;
use std::sync::Arc;
use anyhow::Context as _;
use serde::{Deserialize, Serialize};
use strum::{Display, EnumIter, EnumString};
use uuid::Uuid;
/// The name of the header used to indicate which version of Zed the client is running.
pub const ZED_VERSION_HEADER_NAME: &str = "x-zed-version";
/// The name of the header used to indicate when a request failed due to an
/// expired LLM token.
///
/// The client may use this as a signal to refresh the token.
pub const EXPIRED_LLM_TOKEN_HEADER_NAME: &str = "x-zed-expired-token";
/// The name of the header used to indicate what plan the user is currently on.
pub const CURRENT_PLAN_HEADER_NAME: &str = "x-zed-plan";
/// The name of the header used to indicate the usage limit for model requests.
pub const MODEL_REQUESTS_USAGE_LIMIT_HEADER_NAME: &str = "x-zed-model-requests-usage-limit";
/// The name of the header used to indicate the usage amount for model requests.
pub const MODEL_REQUESTS_USAGE_AMOUNT_HEADER_NAME: &str = "x-zed-model-requests-usage-amount";
/// The name of the header used to indicate the usage limit for edit predictions.
pub const EDIT_PREDICTIONS_USAGE_LIMIT_HEADER_NAME: &str = "x-zed-edit-predictions-usage-limit";
/// The name of the header used to indicate the usage amount for edit predictions.
pub const EDIT_PREDICTIONS_USAGE_AMOUNT_HEADER_NAME: &str = "x-zed-edit-predictions-usage-amount";
/// The name of the header used to indicate the resource for which the subscription limit has been reached.
pub const SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME: &str = "x-zed-subscription-limit-resource";
pub const MODEL_REQUESTS_RESOURCE_HEADER_VALUE: &str = "model_requests";
pub const EDIT_PREDICTIONS_RESOURCE_HEADER_VALUE: &str = "edit_predictions";
/// The name of the header used to indicate that the maximum number of consecutive tool uses has been reached.
pub const TOOL_USE_LIMIT_REACHED_HEADER_NAME: &str = "x-zed-tool-use-limit-reached";
/// The name of the header used to indicate the the minimum required Zed version.
///
/// This can be used to force a Zed upgrade in order to continue communicating
/// with the LLM service.
pub const MINIMUM_REQUIRED_VERSION_HEADER_NAME: &str = "x-zed-minimum-required-version";
/// The name of the header used by the client to indicate to the server that it supports receiving status messages.
pub const CLIENT_SUPPORTS_STATUS_MESSAGES_HEADER_NAME: &str =
"x-zed-client-supports-status-messages";
/// The name of the header used by the server to indicate to the client that it supports sending status messages.
pub const SERVER_SUPPORTS_STATUS_MESSAGES_HEADER_NAME: &str =
"x-zed-server-supports-status-messages";
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UsageLimit {
Limited(i32),
Unlimited,
}
impl FromStr for UsageLimit {
type Err = anyhow::Error;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"unlimited" => Ok(Self::Unlimited),
limit => limit
.parse::<i32>()
.map(Self::Limited)
.context("failed to parse limit"),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Plan {
#[default]
#[serde(alias = "Free")]
ZedFree,
#[serde(alias = "ZedPro")]
ZedPro,
#[serde(alias = "ZedProTrial")]
ZedProTrial,
}
impl Plan {
pub fn as_str(&self) -> &'static str {
match self {
Plan::ZedFree => "zed_free",
Plan::ZedPro => "zed_pro",
Plan::ZedProTrial => "zed_pro_trial",
}
}
pub fn model_requests_limit(&self) -> UsageLimit {
match self {
Plan::ZedPro => UsageLimit::Limited(500),
Plan::ZedProTrial => UsageLimit::Limited(150),
Plan::ZedFree => UsageLimit::Limited(50),
}
}
pub fn edit_predictions_limit(&self) -> UsageLimit {
match self {
Plan::ZedPro => UsageLimit::Unlimited,
Plan::ZedProTrial => UsageLimit::Unlimited,
Plan::ZedFree => UsageLimit::Limited(2_000),
}
}
}
impl FromStr for Plan {
type Err = anyhow::Error;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"zed_free" => Ok(Plan::ZedFree),
"zed_pro" => Ok(Plan::ZedPro),
"zed_pro_trial" => Ok(Plan::ZedProTrial),
plan => Err(anyhow::anyhow!("invalid plan: {plan:?}")),
}
}
}
#[derive(
Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize, EnumString, EnumIter, Display,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum LanguageModelProvider {
Anthropic,
OpenAi,
Google,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PredictEditsBody {
#[serde(skip_serializing_if = "Option::is_none", default)]
pub outline: Option<String>,
pub input_events: String,
pub input_excerpt: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub speculated_output: Option<String>,
/// Whether the user provided consent for sampling this interaction.
#[serde(default, alias = "data_collection_permission")]
pub can_collect_data: bool,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub diagnostic_groups: Option<Vec<(String, serde_json::Value)>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PredictEditsResponse {
pub request_id: Uuid,
pub output_excerpt: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AcceptEditPredictionBody {
pub request_id: Uuid,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CompletionMode {
Normal,
Max,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CompletionIntent {
UserPrompt,
ToolResults,
ThreadSummarization,
ThreadContextSummarization,
CreateFile,
EditFile,
InlineAssist,
TerminalInlineAssist,
GenerateGitCommitMessage,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CompletionBody {
#[serde(skip_serializing_if = "Option::is_none", default)]
pub thread_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub prompt_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub intent: Option<CompletionIntent>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub mode: Option<CompletionMode>,
pub provider: LanguageModelProvider,
pub model: String,
pub provider_request: serde_json::Value,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CompletionRequestStatus {
Queued {
position: usize,
},
Started,
Failed {
code: String,
message: String,
request_id: Uuid,
/// Retry duration in seconds.
retry_after: Option<f64>,
},
UsageUpdated {
amount: usize,
limit: UsageLimit,
},
ToolUseLimitReached,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CompletionEvent<T> {
Status(CompletionRequestStatus),
Event(T),
}
impl<T> CompletionEvent<T> {
pub fn into_status(self) -> Option<CompletionRequestStatus> {
match self {
Self::Status(status) => Some(status),
Self::Event(_) => None,
}
}
pub fn into_event(self) -> Option<T> {
match self {
Self::Event(event) => Some(event),
Self::Status(_) => None,
}
}
}
#[derive(Serialize, Deserialize)]
pub struct WebSearchBody {
pub query: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct WebSearchResponse {
pub results: Vec<WebSearchResult>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct WebSearchResult {
pub title: String,
pub url: String,
pub text: String,
}
#[derive(Serialize, Deserialize)]
pub struct CountTokensBody {
pub provider: LanguageModelProvider,
pub model: String,
pub provider_request: serde_json::Value,
}
#[derive(Serialize, Deserialize)]
pub struct CountTokensResponse {
pub tokens: usize,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub struct LanguageModelId(pub Arc<str>);
impl std::fmt::Display for LanguageModelId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LanguageModel {
pub provider: LanguageModelProvider,
pub id: LanguageModelId,
pub display_name: String,
pub max_token_count: usize,
pub max_token_count_in_max_mode: Option<usize>,
pub max_output_tokens: usize,
pub supports_tools: bool,
pub supports_images: bool,
pub supports_thinking: bool,
pub supports_max_mode: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ListModelsResponse {
pub models: Vec<LanguageModel>,
pub default_model: LanguageModelId,
pub default_fast_model: LanguageModelId,
pub recommended_models: Vec<LanguageModelId>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GetSubscriptionResponse {
pub plan: Plan,
pub usage: Option<CurrentUsage>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CurrentUsage {
pub model_requests: UsageData,
pub edit_predictions: UsageData,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct UsageData {
pub used: u32,
pub limit: UsageLimit,
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use serde_json::json;
use super::*;
#[test]
fn test_plan_deserialize_snake_case() {
let plan = serde_json::from_value::<Plan>(json!("zed_free")).unwrap();
assert_eq!(plan, Plan::ZedFree);
let plan = serde_json::from_value::<Plan>(json!("zed_pro")).unwrap();
assert_eq!(plan, Plan::ZedPro);
let plan = serde_json::from_value::<Plan>(json!("zed_pro_trial")).unwrap();
assert_eq!(plan, Plan::ZedProTrial);
}
#[test]
fn test_plan_deserialize_aliases() {
let plan = serde_json::from_value::<Plan>(json!("Free")).unwrap();
assert_eq!(plan, Plan::ZedFree);
let plan = serde_json::from_value::<Plan>(json!("ZedPro")).unwrap();
assert_eq!(plan, Plan::ZedPro);
let plan = serde_json::from_value::<Plan>(json!("ZedProTrial")).unwrap();
assert_eq!(plan, Plan::ZedProTrial);
}
#[test]
fn test_usage_limit_from_str() {
let limit = UsageLimit::from_str("unlimited").unwrap();
assert!(matches!(limit, UsageLimit::Unlimited));
let limit = UsageLimit::from_str(&0.to_string()).unwrap();
assert!(matches!(limit, UsageLimit::Limited(0)));
let limit = UsageLimit::from_str(&50.to_string()).unwrap();
assert!(matches!(limit, UsageLimit::Limited(50)));
for value in ["not_a_number", "50xyz"] {
let limit = UsageLimit::from_str(value);
assert!(limit.is_err());
}
}
}

View File

@@ -23,14 +23,13 @@ async-stripe.workspace = true
async-trait.workspace = true
async-tungstenite.workspace = true
aws-config = { version = "1.1.5" }
aws-sdk-kinesis = "1.51.0"
aws-sdk-s3 = { version = "1.15.0" }
aws-sdk-kinesis = "1.51.0"
axum = { version = "0.6", features = ["json", "headers", "ws"] }
axum-extra = { version = "0.4", features = ["erased-json"] }
base64.workspace = true
chrono.workspace = true
clock.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
dashmap.workspace = true
derive_more.workspace = true
@@ -76,6 +75,7 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json", "re
util.workspace = true
uuid.workspace = true
workspace-hack.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
agent_settings.workspace = true

View File

@@ -100,6 +100,7 @@ impl std::fmt::Display for SystemIdHeader {
pub fn routes(rpc_server: Arc<rpc::Server>) -> Router<(), Body> {
Router::new()
.route("/user", get(update_or_create_authenticated_user))
.route("/users/look_up", get(look_up_user))
.route("/users/:id/access_tokens", post(create_access_token))
.route("/users/:id/refresh_llm_tokens", post(refresh_llm_tokens))
@@ -144,6 +145,48 @@ pub async fn validate_api_token<B>(req: Request<B>, next: Next<B>) -> impl IntoR
Ok::<_, Error>(next.run(req).await)
}
#[derive(Debug, Deserialize)]
struct AuthenticatedUserParams {
github_user_id: i32,
github_login: String,
github_email: Option<String>,
github_name: Option<String>,
github_user_created_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Serialize)]
struct AuthenticatedUserResponse {
user: User,
metrics_id: String,
feature_flags: Vec<String>,
}
async fn update_or_create_authenticated_user(
Query(params): Query<AuthenticatedUserParams>,
Extension(app): Extension<Arc<AppState>>,
) -> Result<Json<AuthenticatedUserResponse>> {
let initial_channel_id = app.config.auto_join_channel_id;
let user = app
.db
.update_or_create_user_by_github_account(
&params.github_login,
params.github_user_id,
params.github_email.as_deref(),
params.github_name.as_deref(),
params.github_user_created_at,
initial_channel_id,
)
.await?;
let metrics_id = app.db.get_user_metrics_id(user.id).await?;
let feature_flags = app.db.get_user_flags(user.id).await?;
Ok(Json(AuthenticatedUserResponse {
user,
metrics_id,
feature_flags,
}))
}
#[derive(Debug, Deserialize)]
struct LookUpUserParams {
identifier: String,
@@ -310,9 +353,9 @@ async fn refresh_llm_tokens(
#[derive(Debug, Serialize, Deserialize)]
struct UpdatePlanBody {
pub plan: cloud_llm_client::Plan,
pub plan: zed_llm_client::Plan,
pub subscription_period: SubscriptionPeriod,
pub usage: cloud_llm_client::CurrentUsage,
pub usage: zed_llm_client::CurrentUsage,
pub trial_started_at: Option<DateTime<Utc>>,
pub is_usage_based_billing_enabled: bool,
pub is_account_too_young: bool,
@@ -334,9 +377,9 @@ async fn update_plan(
extract::Json(body): extract::Json<UpdatePlanBody>,
) -> Result<Json<UpdatePlanResponse>> {
let plan = match body.plan {
cloud_llm_client::Plan::ZedFree => proto::Plan::Free,
cloud_llm_client::Plan::ZedPro => proto::Plan::ZedPro,
cloud_llm_client::Plan::ZedProTrial => proto::Plan::ZedProTrial,
zed_llm_client::Plan::ZedFree => proto::Plan::Free,
zed_llm_client::Plan::ZedPro => proto::Plan::ZedPro,
zed_llm_client::Plan::ZedProTrial => proto::Plan::ZedProTrial,
};
let update_user_plan = proto::UpdateUserPlan {
@@ -368,15 +411,15 @@ async fn update_plan(
Ok(Json(UpdatePlanResponse {}))
}
fn usage_limit_to_proto(limit: cloud_llm_client::UsageLimit) -> proto::UsageLimit {
fn usage_limit_to_proto(limit: zed_llm_client::UsageLimit) -> proto::UsageLimit {
proto::UsageLimit {
variant: Some(match limit {
cloud_llm_client::UsageLimit::Limited(limit) => {
zed_llm_client::UsageLimit::Limited(limit) => {
proto::usage_limit::Variant::Limited(proto::usage_limit::Limited {
limit: limit as u32,
})
}
cloud_llm_client::UsageLimit::Unlimited => {
zed_llm_client::UsageLimit::Unlimited => {
proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {})
}
}),

View File

@@ -1,11 +1,11 @@
use anyhow::{Context as _, bail};
use chrono::{DateTime, Utc};
use cloud_llm_client::LanguageModelProvider;
use collections::{HashMap, HashSet};
use sea_orm::ActiveValue;
use std::{sync::Arc, time::Duration};
use stripe::{CancellationDetailsReason, EventObject, EventType, ListEvents, SubscriptionStatus};
use util::{ResultExt, maybe};
use zed_llm_client::LanguageModelProvider;
use crate::AppState;
use crate::db::billing_subscription::{
@@ -87,14 +87,6 @@ async fn poll_stripe_events(
stripe_client: &Arc<dyn StripeClient>,
real_stripe_client: &stripe::Client,
) -> anyhow::Result<()> {
let feature_flags = app.db.list_feature_flags().await?;
let sync_events_using_cloud = feature_flags
.iter()
.any(|flag| flag.flag == "cloud-stripe-events-polling" && flag.enabled_for_all);
if sync_events_using_cloud {
return Ok(());
}
fn event_type_to_string(event_type: EventType) -> String {
// Calling `to_string` on `stripe::EventType` members gives us a quoted string,
// so we need to unquote it.
@@ -577,14 +569,6 @@ async fn sync_model_request_usage_with_stripe(
llm_db: &Arc<LlmDatabase>,
stripe_billing: &Arc<StripeBilling>,
) -> anyhow::Result<()> {
let feature_flags = app.db.list_feature_flags().await?;
let sync_model_request_usage_using_cloud = feature_flags
.iter()
.any(|flag| flag.flag == "cloud-stripe-usage-meters-sync" && flag.enabled_for_all);
if sync_model_request_usage_using_cloud {
return Ok(());
}
log::info!("Stripe usage sync: Starting");
let started_at = Utc::now();

View File

@@ -8,6 +8,7 @@ use axum::{
use chrono::{NaiveDateTime, SecondsFormat};
use serde::{Deserialize, Serialize};
use crate::api::AuthenticatedUserParams;
use crate::db::ContributorSelector;
use crate::{AppState, Result};
@@ -103,18 +104,9 @@ impl RenovateBot {
}
}
#[derive(Debug, Deserialize)]
struct AddContributorBody {
github_user_id: i32,
github_login: String,
github_email: Option<String>,
github_name: Option<String>,
github_user_created_at: chrono::DateTime<chrono::Utc>,
}
async fn add_contributor(
Extension(app): Extension<Arc<AppState>>,
extract::Json(params): extract::Json<AddContributorBody>,
extract::Json(params): extract::Json<AuthenticatedUserParams>,
) -> Result<()> {
let initial_channel_id = app.config.auto_join_channel_id;
app.db

View File

@@ -95,7 +95,7 @@ pub enum SubscriptionKind {
ZedFree,
}
impl From<SubscriptionKind> for cloud_llm_client::Plan {
impl From<SubscriptionKind> for zed_llm_client::Plan {
fn from(value: SubscriptionKind) -> Self {
match value {
SubscriptionKind::ZedPro => Self::ZedPro,

View File

@@ -6,11 +6,11 @@ mod tables;
#[cfg(test)]
mod tests;
use cloud_llm_client::LanguageModelProvider;
use collections::HashMap;
pub use ids::*;
pub use seed::*;
pub use tables::*;
use zed_llm_client::LanguageModelProvider;
#[cfg(test)]
pub use tests::TestLlmDb;

View File

@@ -1,5 +1,5 @@
use cloud_llm_client::LanguageModelProvider;
use pretty_assertions::assert_eq;
use zed_llm_client::LanguageModelProvider;
use crate::llm::db::LlmDatabase;
use crate::test_llm_db;

View File

@@ -4,12 +4,12 @@ use crate::llm::{AGENT_EXTENDED_TRIAL_FEATURE_FLAG, BYPASS_ACCOUNT_AGE_CHECK_FEA
use crate::{Config, db::billing_preference};
use anyhow::{Context as _, Result};
use chrono::{NaiveDateTime, Utc};
use cloud_llm_client::Plan;
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use thiserror::Error;
use uuid::Uuid;
use zed_llm_client::Plan;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]

View File

@@ -23,7 +23,6 @@ use anyhow::{Context as _, anyhow, bail};
use async_tungstenite::tungstenite::{
Message as TungsteniteMessage, protocol::CloseFrame as TungsteniteCloseFrame,
};
use axum::headers::UserAgent;
use axum::{
Extension, Router, TypedHeader,
body::Body,
@@ -751,7 +750,6 @@ impl Server {
address: String,
principal: Principal,
zed_version: ZedVersion,
user_agent: Option<String>,
geoip_country_code: Option<String>,
system_id: Option<String>,
send_connection_id: Option<oneshot::Sender<ConnectionId>>,
@@ -764,14 +762,9 @@ impl Server {
user_id=field::Empty,
login=field::Empty,
impersonator=field::Empty,
user_agent=field::Empty,
geoip_country_code=field::Empty
);
principal.update_span(&span);
if let Some(user_agent) = user_agent {
span.record("user_agent", user_agent);
}
if let Some(country_code) = geoip_country_code.as_ref() {
span.record("geoip_country_code", country_code);
}
@@ -1179,7 +1172,6 @@ pub async fn handle_websocket_request(
ConnectInfo(socket_address): ConnectInfo<SocketAddr>,
Extension(server): Extension<Arc<Server>>,
Extension(principal): Extension<Principal>,
user_agent: Option<TypedHeader<UserAgent>>,
country_code_header: Option<TypedHeader<CloudflareIpCountryHeader>>,
system_id_header: Option<TypedHeader<SystemIdHeader>>,
ws: WebSocketUpgrade,
@@ -1235,7 +1227,6 @@ pub async fn handle_websocket_request(
socket_address,
principal,
version,
user_agent.map(|header| header.to_string()),
country_code_header.map(|header| header.to_string()),
system_id_header.map(|header| header.to_string()),
None,
@@ -2868,12 +2859,12 @@ async fn make_update_user_plan_message(
}
fn model_requests_limit(
plan: cloud_llm_client::Plan,
plan: zed_llm_client::Plan,
feature_flags: &Vec<String>,
) -> cloud_llm_client::UsageLimit {
) -> zed_llm_client::UsageLimit {
match plan.model_requests_limit() {
cloud_llm_client::UsageLimit::Limited(limit) => {
let limit = if plan == cloud_llm_client::Plan::ZedProTrial
zed_llm_client::UsageLimit::Limited(limit) => {
let limit = if plan == zed_llm_client::Plan::ZedProTrial
&& feature_flags
.iter()
.any(|flag| flag == AGENT_EXTENDED_TRIAL_FEATURE_FLAG)
@@ -2883,9 +2874,9 @@ fn model_requests_limit(
limit
};
cloud_llm_client::UsageLimit::Limited(limit)
zed_llm_client::UsageLimit::Limited(limit)
}
cloud_llm_client::UsageLimit::Unlimited => cloud_llm_client::UsageLimit::Unlimited,
zed_llm_client::UsageLimit::Unlimited => zed_llm_client::UsageLimit::Unlimited,
}
}
@@ -2895,21 +2886,21 @@ fn subscription_usage_to_proto(
feature_flags: &Vec<String>,
) -> proto::SubscriptionUsage {
let plan = match plan {
proto::Plan::Free => cloud_llm_client::Plan::ZedFree,
proto::Plan::ZedPro => cloud_llm_client::Plan::ZedPro,
proto::Plan::ZedProTrial => cloud_llm_client::Plan::ZedProTrial,
proto::Plan::Free => zed_llm_client::Plan::ZedFree,
proto::Plan::ZedPro => zed_llm_client::Plan::ZedPro,
proto::Plan::ZedProTrial => zed_llm_client::Plan::ZedProTrial,
};
proto::SubscriptionUsage {
model_requests_usage_amount: usage.model_requests as u32,
model_requests_usage_limit: Some(proto::UsageLimit {
variant: Some(match model_requests_limit(plan, feature_flags) {
cloud_llm_client::UsageLimit::Limited(limit) => {
zed_llm_client::UsageLimit::Limited(limit) => {
proto::usage_limit::Variant::Limited(proto::usage_limit::Limited {
limit: limit as u32,
})
}
cloud_llm_client::UsageLimit::Unlimited => {
zed_llm_client::UsageLimit::Unlimited => {
proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {})
}
}),
@@ -2917,12 +2908,12 @@ fn subscription_usage_to_proto(
edit_predictions_usage_amount: usage.edit_predictions as u32,
edit_predictions_usage_limit: Some(proto::UsageLimit {
variant: Some(match plan.edit_predictions_limit() {
cloud_llm_client::UsageLimit::Limited(limit) => {
zed_llm_client::UsageLimit::Limited(limit) => {
proto::usage_limit::Variant::Limited(proto::usage_limit::Limited {
limit: limit as u32,
})
}
cloud_llm_client::UsageLimit::Unlimited => {
zed_llm_client::UsageLimit::Unlimited => {
proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {})
}
}),
@@ -2935,21 +2926,21 @@ fn make_default_subscription_usage(
feature_flags: &Vec<String>,
) -> proto::SubscriptionUsage {
let plan = match plan {
proto::Plan::Free => cloud_llm_client::Plan::ZedFree,
proto::Plan::ZedPro => cloud_llm_client::Plan::ZedPro,
proto::Plan::ZedProTrial => cloud_llm_client::Plan::ZedProTrial,
proto::Plan::Free => zed_llm_client::Plan::ZedFree,
proto::Plan::ZedPro => zed_llm_client::Plan::ZedPro,
proto::Plan::ZedProTrial => zed_llm_client::Plan::ZedProTrial,
};
proto::SubscriptionUsage {
model_requests_usage_amount: 0,
model_requests_usage_limit: Some(proto::UsageLimit {
variant: Some(match model_requests_limit(plan, feature_flags) {
cloud_llm_client::UsageLimit::Limited(limit) => {
zed_llm_client::UsageLimit::Limited(limit) => {
proto::usage_limit::Variant::Limited(proto::usage_limit::Limited {
limit: limit as u32,
})
}
cloud_llm_client::UsageLimit::Unlimited => {
zed_llm_client::UsageLimit::Unlimited => {
proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {})
}
}),
@@ -2957,12 +2948,12 @@ fn make_default_subscription_usage(
edit_predictions_usage_amount: 0,
edit_predictions_usage_limit: Some(proto::UsageLimit {
variant: Some(match plan.edit_predictions_limit() {
cloud_llm_client::UsageLimit::Limited(limit) => {
zed_llm_client::UsageLimit::Limited(limit) => {
proto::usage_limit::Variant::Limited(proto::usage_limit::Limited {
limit: limit as u32,
})
}
cloud_llm_client::UsageLimit::Unlimited => {
zed_llm_client::UsageLimit::Unlimited => {
proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {})
}
}),

View File

@@ -842,7 +842,7 @@ async fn test_client_disconnecting_from_room(
// Allow user A to reconnect to the server.
server.allow_connections();
executor.advance_clock(RECONNECT_TIMEOUT);
executor.advance_clock(RECEIVE_TIMEOUT);
// Call user B again from client A.
active_call_a
@@ -1358,7 +1358,7 @@ async fn test_calls_on_multiple_connections(
// User A reconnects automatically, then calls user B again.
server.allow_connections();
executor.advance_clock(RECONNECT_TIMEOUT);
executor.advance_clock(RECEIVE_TIMEOUT);
active_call_a
.update(cx_a, |call, cx| {
call.invite(client_b1.user_id().unwrap(), None, cx)

View File

@@ -256,7 +256,6 @@ impl TestServer {
ZedVersion(SemanticVersion::new(1, 0, 0)),
None,
None,
None,
Some(connection_id_tx),
Executor::Deterministic(cx.background_executor().clone()),
None,

View File

@@ -158,7 +158,6 @@ impl Client {
pub fn stdio(
server_id: ContextServerId,
binary: ModelContextServerBinary,
working_directory: &Option<PathBuf>,
cx: AsyncApp,
) -> Result<Self> {
log::info!(
@@ -173,7 +172,7 @@ impl Client {
.map(|name| name.to_string_lossy().to_string())
.unwrap_or_else(String::new);
let transport = Arc::new(StdioTransport::new(binary, working_directory, &cx)?);
let transport = Arc::new(StdioTransport::new(binary, &cx)?);
Self::new(server_id, server_name.into(), transport, cx)
}

View File

@@ -53,7 +53,7 @@ impl std::fmt::Debug for ContextServerCommand {
}
enum ContextServerTransport {
Stdio(ContextServerCommand, Option<PathBuf>),
Stdio(ContextServerCommand),
Custom(Arc<dyn crate::transport::Transport>),
}
@@ -64,18 +64,11 @@ pub struct ContextServer {
}
impl ContextServer {
pub fn stdio(
id: ContextServerId,
command: ContextServerCommand,
working_directory: Option<Arc<Path>>,
) -> Self {
pub fn stdio(id: ContextServerId, command: ContextServerCommand) -> Self {
Self {
id,
client: RwLock::new(None),
configuration: ContextServerTransport::Stdio(
command,
working_directory.map(|directory| directory.to_path_buf()),
),
configuration: ContextServerTransport::Stdio(command),
}
}
@@ -97,14 +90,13 @@ impl ContextServer {
pub async fn start(self: Arc<Self>, cx: &AsyncApp) -> Result<()> {
let client = match &self.configuration {
ContextServerTransport::Stdio(command, working_directory) => Client::stdio(
ContextServerTransport::Stdio(command) => Client::stdio(
client::ContextServerId(self.id.0.clone()),
client::ModelContextServerBinary {
executable: Path::new(&command.path).to_path_buf(),
args: command.args.clone(),
env: command.env.clone(),
},
working_directory,
cx.clone(),
)?,
ContextServerTransport::Custom(transport) => Client::new(

View File

@@ -1,4 +1,3 @@
use std::path::PathBuf;
use std::pin::Pin;
use anyhow::{Context as _, Result};
@@ -23,11 +22,7 @@ pub struct StdioTransport {
}
impl StdioTransport {
pub fn new(
binary: ModelContextServerBinary,
working_directory: &Option<PathBuf>,
cx: &AsyncApp,
) -> Result<Self> {
pub fn new(binary: ModelContextServerBinary, cx: &AsyncApp) -> Result<Self> {
let mut command = util::command::new_smol_command(&binary.executable);
command
.args(&binary.args)
@@ -37,10 +32,6 @@ impl StdioTransport {
.stderr(std::process::Stdio::piped())
.kill_on_drop(true);
if let Some(working_directory) = working_directory {
command.current_dir(working_directory);
}
let mut server = command.spawn().with_context(|| {
format!(
"failed to spawn command. (path={:?}, args={:?})",

View File

@@ -295,7 +295,7 @@ mod tests {
request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
},
},
Box::new(|_| {}),
Box::new(|_| panic!("Did not expect to hit this code path")),
&mut cx.to_async(),
)
.await

View File

@@ -883,7 +883,6 @@ impl FakeTransport {
break Err(anyhow!("exit in response to request"));
}
};
let success = response.success;
let message =
serde_json::to_string(&Message::Response(response)).unwrap();
@@ -894,25 +893,6 @@ impl FakeTransport {
)
.await
.unwrap();
if request.command == dap_types::requests::Initialize::COMMAND
&& success
{
let message = serde_json::to_string(&Message::Event(Box::new(
dap_types::messages::Events::Initialized(Some(
Default::default(),
)),
)))
.unwrap();
writer
.write_all(
TransportDelegate::build_rpc_message(message)
.as_bytes(),
)
.await
.unwrap();
}
writer.flush().await.unwrap();
}
}

View File

@@ -7,17 +7,17 @@ license = "GPL-3.0-or-later"
[dependencies]
anyhow.workspace = true
command_palette.workspace = true
gpui.workspace = true
clap.workspace = true
mdbook = "0.4.40"
regex.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
regex.workspace = true
util.workspace = true
workspace-hack.workspace = true
zed.workspace = true
zlog.workspace = true
gpui.workspace = true
command_palette.workspace = true
[lints]
workspace = true

View File

@@ -1,15 +1,14 @@
use anyhow::{Context, Result};
use anyhow::Result;
use clap::{Arg, ArgMatches, Command};
use mdbook::BookItem;
use mdbook::book::{Book, Chapter};
use mdbook::preprocess::CmdPreprocessor;
use regex::Regex;
use settings::KeymapFile;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::collections::HashSet;
use std::io::{self, Read};
use std::process;
use std::sync::LazyLock;
use util::paths::PathExt;
static KEYMAP_MACOS: LazyLock<KeymapFile> = LazyLock::new(|| {
load_keymap("keymaps/default-macos.json").expect("Failed to load MacOS keymap")
@@ -21,68 +20,60 @@ static KEYMAP_LINUX: LazyLock<KeymapFile> = LazyLock::new(|| {
static ALL_ACTIONS: LazyLock<Vec<ActionDef>> = LazyLock::new(dump_all_gpui_actions);
const FRONT_MATTER_COMMENT: &'static str = "<!-- ZED_META {} -->";
pub fn make_app() -> Command {
Command::new("zed-docs-preprocessor")
.about("Preprocesses Zed Docs content to provide rich action & keybinding support and more")
.subcommand(
Command::new("supports")
.arg(Arg::new("renderer").required(true))
.about("Check whether a renderer is supported by this preprocessor"),
)
}
fn main() -> Result<()> {
zlog::init();
zlog::init_output_stderr();
let matches = make_app().get_matches();
// call a zed:: function so everything in `zed` crate is linked and
// all actions in the actual app are registered
zed::stdout_is_a_pty();
let args = std::env::args().skip(1).collect::<Vec<_>>();
match args.get(0).map(String::as_str) {
Some("supports") => {
let renderer = args.get(1).expect("Required argument");
let supported = renderer != "not-supported";
if supported {
process::exit(0);
} else {
process::exit(1);
}
}
Some("postprocess") => handle_postprocessing()?,
_ => handle_preprocessing()?,
if let Some(sub_args) = matches.subcommand_matches("supports") {
handle_supports(sub_args);
} else {
handle_preprocessing()?;
}
Ok(())
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum PreprocessorError {
enum Error {
ActionNotFound { action_name: String },
DeprecatedActionUsed { used: String, should_be: String },
InvalidFrontmatterLine(String),
}
impl PreprocessorError {
impl Error {
fn new_for_not_found_action(action_name: String) -> Self {
for action in &*ALL_ACTIONS {
for alias in action.deprecated_aliases {
if alias == &action_name {
return PreprocessorError::DeprecatedActionUsed {
return Error::DeprecatedActionUsed {
used: action_name.clone(),
should_be: action.name.to_string(),
};
}
}
}
PreprocessorError::ActionNotFound {
Error::ActionNotFound {
action_name: action_name.to_string(),
}
}
}
impl std::fmt::Display for PreprocessorError {
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PreprocessorError::InvalidFrontmatterLine(line) => {
write!(f, "Invalid frontmatter line: {}", line)
}
PreprocessorError::ActionNotFound { action_name } => {
write!(f, "Action not found: {}", action_name)
}
PreprocessorError::DeprecatedActionUsed { used, should_be } => write!(
Error::ActionNotFound { action_name } => write!(f, "Action not found: {}", action_name),
Error::DeprecatedActionUsed { used, should_be } => write!(
f,
"Deprecated action used: {} should be {}",
used, should_be
@@ -98,9 +89,8 @@ fn handle_preprocessing() -> Result<()> {
let (_ctx, mut book) = CmdPreprocessor::parse_input(input.as_bytes())?;
let mut errors = HashSet::<PreprocessorError>::new();
let mut errors = HashSet::<Error>::new();
handle_frontmatter(&mut book, &mut errors);
template_and_validate_keybindings(&mut book, &mut errors);
template_and_validate_actions(&mut book, &mut errors);
@@ -118,41 +108,19 @@ fn handle_preprocessing() -> Result<()> {
Ok(())
}
fn handle_frontmatter(book: &mut Book, errors: &mut HashSet<PreprocessorError>) {
let frontmatter_regex = Regex::new(r"(?s)^\s*---(.*?)---").unwrap();
for_each_chapter_mut(book, |chapter| {
let new_content = frontmatter_regex.replace(&chapter.content, |caps: &regex::Captures| {
let frontmatter = caps[1].trim();
let frontmatter = frontmatter.trim_matches(&[' ', '-', '\n']);
let mut metadata = HashMap::<String, String>::default();
for line in frontmatter.lines() {
let Some((name, value)) = line.split_once(':') else {
errors.insert(PreprocessorError::InvalidFrontmatterLine(format!(
"{}: {}",
chapter_breadcrumbs(&chapter),
line
)));
continue;
};
let name = name.trim();
let value = value.trim();
metadata.insert(name.to_string(), value.to_string());
}
FRONT_MATTER_COMMENT.replace(
"{}",
&serde_json::to_string(&metadata).expect("Failed to serialize metadata"),
)
});
match new_content {
Cow::Owned(content) => {
chapter.content = content;
}
Cow::Borrowed(_) => {}
}
});
fn handle_supports(sub_args: &ArgMatches) -> ! {
let renderer = sub_args
.get_one::<String>("renderer")
.expect("Required argument");
let supported = renderer != "not-supported";
if supported {
process::exit(0);
} else {
process::exit(1);
}
}
fn template_and_validate_keybindings(book: &mut Book, errors: &mut HashSet<PreprocessorError>) {
fn template_and_validate_keybindings(book: &mut Book, errors: &mut HashSet<Error>) {
let regex = Regex::new(r"\{#kb (.*?)\}").unwrap();
for_each_chapter_mut(book, |chapter| {
@@ -160,9 +128,7 @@ fn template_and_validate_keybindings(book: &mut Book, errors: &mut HashSet<Prepr
.replace_all(&chapter.content, |caps: &regex::Captures| {
let action = caps[1].trim();
if find_action_by_name(action).is_none() {
errors.insert(PreprocessorError::new_for_not_found_action(
action.to_string(),
));
errors.insert(Error::new_for_not_found_action(action.to_string()));
return String::new();
}
let macos_binding = find_binding("macos", action).unwrap_or_default();
@@ -178,7 +144,7 @@ fn template_and_validate_keybindings(book: &mut Book, errors: &mut HashSet<Prepr
});
}
fn template_and_validate_actions(book: &mut Book, errors: &mut HashSet<PreprocessorError>) {
fn template_and_validate_actions(book: &mut Book, errors: &mut HashSet<Error>) {
let regex = Regex::new(r"\{#action (.*?)\}").unwrap();
for_each_chapter_mut(book, |chapter| {
@@ -186,9 +152,7 @@ fn template_and_validate_actions(book: &mut Book, errors: &mut HashSet<Preproces
.replace_all(&chapter.content, |caps: &regex::Captures| {
let name = caps[1].trim();
let Some(action) = find_action_by_name(name) else {
errors.insert(PreprocessorError::new_for_not_found_action(
name.to_string(),
));
errors.insert(Error::new_for_not_found_action(name.to_string()));
return String::new();
};
format!("<code class=\"hljs\">{}</code>", &action.human_name)
@@ -253,13 +217,6 @@ fn name_for_action(action_as_str: String) -> String {
.unwrap_or(action_as_str)
}
fn chapter_breadcrumbs(chapter: &Chapter) -> String {
let mut breadcrumbs = Vec::with_capacity(chapter.parent_names.len() + 1);
breadcrumbs.extend(chapter.parent_names.iter().map(String::as_str));
breadcrumbs.push(chapter.name.as_str());
format!("[{:?}] {}", chapter.source_path, breadcrumbs.join(" > "))
}
fn load_keymap(asset_path: &str) -> Result<KeymapFile> {
let content = util::asset_str::<settings::SettingsAssets>(asset_path);
KeymapFile::parse(content.as_ref())
@@ -297,126 +254,3 @@ fn dump_all_gpui_actions() -> Vec<ActionDef> {
return actions;
}
fn handle_postprocessing() -> Result<()> {
let logger = zlog::scoped!("render");
let mut ctx = mdbook::renderer::RenderContext::from_json(io::stdin())?;
let output = ctx
.config
.get_mut("output")
.expect("has output")
.as_table_mut()
.expect("output is table");
let zed_html = output.remove("zed-html").expect("zed-html output defined");
let default_description = zed_html
.get("default-description")
.expect("Default description not found")
.as_str()
.expect("Default description not a string")
.to_string();
let default_title = zed_html
.get("default-title")
.expect("Default title not found")
.as_str()
.expect("Default title not a string")
.to_string();
output.insert("html".to_string(), zed_html);
mdbook::Renderer::render(&mdbook::renderer::HtmlHandlebars::new(), &ctx)?;
let ignore_list = ["toc.html"];
let root_dir = ctx.destination.clone();
let mut files = Vec::with_capacity(128);
let mut queue = Vec::with_capacity(64);
queue.push(root_dir.clone());
while let Some(dir) = queue.pop() {
for entry in std::fs::read_dir(&dir).context(dir.to_sanitized_string())? {
let Ok(entry) = entry else {
continue;
};
let file_type = entry.file_type().context("Failed to determine file type")?;
if file_type.is_dir() {
queue.push(entry.path());
}
if file_type.is_file()
&& matches!(
entry.path().extension().and_then(std::ffi::OsStr::to_str),
Some("html")
)
{
if ignore_list.contains(&&*entry.file_name().to_string_lossy()) {
zlog::info!(logger => "Ignoring {}", entry.path().to_string_lossy());
} else {
files.push(entry.path());
}
}
}
}
zlog::info!(logger => "Processing {} `.html` files", files.len());
let meta_regex = Regex::new(&FRONT_MATTER_COMMENT.replace("{}", "(.*)")).unwrap();
for file in files {
let contents = std::fs::read_to_string(&file)?;
let mut meta_description = None;
let mut meta_title = None;
let contents = meta_regex.replace(&contents, |caps: &regex::Captures| {
let metadata: HashMap<String, String> = serde_json::from_str(&caps[1]).with_context(|| format!("JSON Metadata: {:?}", &caps[1])).expect("Failed to deserialize metadata");
for (kind, content) in metadata {
match kind.as_str() {
"description" => {
meta_description = Some(content);
}
"title" => {
meta_title = Some(content);
}
_ => {
zlog::warn!(logger => "Unrecognized frontmatter key: {} in {:?}", kind, pretty_path(&file, &root_dir));
}
}
}
String::new()
});
let meta_description = meta_description.as_ref().unwrap_or_else(|| {
zlog::warn!(logger => "No meta description found for {:?}", pretty_path(&file, &root_dir));
&default_description
});
let page_title = extract_title_from_page(&contents, pretty_path(&file, &root_dir));
let meta_title = meta_title.as_ref().unwrap_or_else(|| {
zlog::debug!(logger => "No meta title found for {:?}", pretty_path(&file, &root_dir));
&default_title
});
let meta_title = format!("{} | {}", page_title, meta_title);
zlog::trace!(logger => "Updating {:?}", pretty_path(&file, &root_dir));
let contents = contents.replace("#description#", meta_description);
let contents = TITLE_REGEX
.replace(&contents, |_: &regex::Captures| {
format!("<title>{}</title>", meta_title)
})
.to_string();
// let contents = contents.replace("#title#", &meta_title);
std::fs::write(file, contents)?;
}
return Ok(());
fn pretty_path<'a>(
path: &'a std::path::PathBuf,
root: &'a std::path::PathBuf,
) -> &'a std::path::Path {
&path.strip_prefix(&root).unwrap_or(&path)
}
const TITLE_REGEX: std::cell::LazyCell<Regex> =
std::cell::LazyCell::new(|| Regex::new(r"<title>\s*(.*?)\s*</title>").unwrap());
fn extract_title_from_page(contents: &str, pretty_path: &std::path::Path) -> String {
let title_tag_contents = &TITLE_REGEX
.captures(&contents)
.with_context(|| format!("Failed to find title in {:?}", pretty_path))
.expect("Page has <title> element")[1];
let title = title_tag_contents
.trim()
.strip_suffix("- Zed")
.unwrap_or(title_tag_contents)
.trim()
.to_string();
title
}
}

View File

@@ -22,7 +22,6 @@ test-support = [
"theme/test-support",
"util/test-support",
"workspace/test-support",
"tree-sitter-c",
"tree-sitter-rust",
"tree-sitter-typescript",
"tree-sitter-html",
@@ -77,7 +76,6 @@ telemetry.workspace = true
text.workspace = true
time.workspace = true
theme.workspace = true
tree-sitter-c = { workspace = true, optional = true }
tree-sitter-html = { workspace = true, optional = true }
tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-typescript = { workspace = true, optional = true }
@@ -108,7 +106,6 @@ settings = { workspace = true, features = ["test-support"] }
tempfile.workspace = true
text = { workspace = true, features = ["test-support"] }
theme = { workspace = true, features = ["test-support"] }
tree-sitter-c.workspace = true
tree-sitter-html.workspace = true
tree-sitter-rust.workspace = true
tree-sitter-typescript.workspace = true

View File

@@ -56,7 +56,7 @@ use aho_corasick::AhoCorasick;
use anyhow::{Context as _, Result, anyhow};
use blink_manager::BlinkManager;
use buffer_diff::DiffHunkStatus;
use client::{Collaborator, DisableAiSettings, ParticipantIndex};
use client::{Collaborator, ParticipantIndex};
use clock::{AGENT_REPLICA_ID, ReplicaId};
use collections::{BTreeMap, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing};
@@ -65,7 +65,7 @@ use display_map::*;
pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
pub use editor_settings::{
CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowScrollbar,
};
use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
pub use editor_settings_controls::*;
@@ -1305,7 +1305,6 @@ impl Default for SelectionHistoryMode {
///
/// Similarly, you might want to disable scrolling if you don't want the viewport to
/// move.
#[derive(Clone)]
pub struct SelectionEffects {
nav_history: Option<bool>,
completions: bool,
@@ -2945,12 +2944,10 @@ impl Editor {
}
}
let selection_anchors = self.selections.disjoint_anchors();
if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
self.buffer.update(cx, |buffer, cx| {
buffer.set_active_selections(
&selection_anchors,
&self.selections.disjoint_anchors(),
self.selections.line_mode,
self.cursor_shape,
cx,
@@ -2967,8 +2964,9 @@ impl Editor {
self.select_next_state = None;
self.select_prev_state = None;
self.select_syntax_node_history.try_clear();
self.invalidate_autoclose_regions(&selection_anchors, buffer);
self.snippet_stack.invalidate(&selection_anchors, buffer);
self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
self.snippet_stack
.invalidate(&self.selections.disjoint_anchors(), buffer);
self.take_rename(false, window, cx);
let newest_selection = self.selections.newest_anchor();
@@ -4049,8 +4047,7 @@ impl Editor {
// then don't insert that closing bracket again; just move the selection
// past the closing bracket.
let should_skip = selection.end == region.range.end.to_point(&snapshot)
&& text.as_ref() == region.pair.end.as_str()
&& snapshot.contains_str_at(region.range.end, text.as_ref());
&& text.as_ref() == region.pair.end.as_str();
if should_skip {
let anchor = snapshot.anchor_after(selection.end);
new_selections
@@ -4976,17 +4973,13 @@ impl Editor {
})
}
/// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
/// Remove any autoclose regions that no longer contain their selection.
fn invalidate_autoclose_regions(
&mut self,
mut selections: &[Selection<Anchor>],
buffer: &MultiBufferSnapshot,
) {
self.autoclose_regions.retain(|state| {
if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
return false;
}
let mut i = 0;
while let Some(selection) = selections.get(i) {
if selection.end.cmp(&state.range.start, buffer).is_lt() {
@@ -5898,20 +5891,18 @@ impl Editor {
text: new_text[common_prefix_len..].into(),
});
self.transact(window, cx, |editor, window, cx| {
self.transact(window, cx, |this, window, cx| {
if let Some(mut snippet) = snippet {
snippet.text = new_text.to_string();
editor
.insert_snippet(&ranges, snippet, window, cx)
.log_err();
this.insert_snippet(&ranges, snippet, window, cx).log_err();
} else {
editor.buffer.update(cx, |multi_buffer, cx| {
this.buffer.update(cx, |buffer, cx| {
let auto_indent = match completion.insert_text_mode {
Some(InsertTextMode::AS_IS) => None,
_ => editor.autoindent_mode.clone(),
_ => this.autoindent_mode.clone(),
};
let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
multi_buffer.edit(edits, auto_indent, cx);
buffer.edit(edits, auto_indent, cx);
});
}
for (buffer, edits) in linked_edits {
@@ -5930,9 +5921,8 @@ impl Editor {
})
}
editor.refresh_inline_completion(true, false, window, cx);
this.refresh_inline_completion(true, false, window, cx);
});
self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
let show_new_completions_on_confirm = completion
.confirm
@@ -7058,7 +7048,7 @@ impl Editor {
}
pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
if self.edit_prediction_provider.is_none() {
self.edit_prediction_settings = EditPredictionSettings::Disabled;
} else {
let selection = self.selections.newest_anchor();
@@ -9572,46 +9562,27 @@ impl Editor {
// Check whether the just-entered snippet ends with an auto-closable bracket.
if self.autoclose_regions.is_empty() {
let snapshot = self.buffer.read(cx).snapshot(cx);
let mut all_selections = self.selections.all::<Point>(cx);
for selection in &mut all_selections {
for selection in &mut self.selections.all::<Point>(cx) {
let selection_head = selection.head();
let Some(scope) = snapshot.language_scope_at(selection_head) else {
continue;
};
let mut bracket_pair = None;
let max_lookup_length = scope
.brackets()
.map(|(pair, _)| {
pair.start
.as_str()
.chars()
.count()
.max(pair.end.as_str().chars().count())
})
.max();
if let Some(max_lookup_length) = max_lookup_length {
let next_text = snapshot
.chars_at(selection_head)
.take(max_lookup_length)
.collect::<String>();
let prev_text = snapshot
.reversed_chars_at(selection_head)
.take(max_lookup_length)
.collect::<String>();
for (pair, enabled) in scope.brackets() {
if enabled
&& pair.close
&& prev_text.starts_with(pair.start.as_str())
&& next_text.starts_with(pair.end.as_str())
{
bracket_pair = Some(pair.clone());
break;
}
let next_chars = snapshot.chars_at(selection_head).collect::<String>();
let prev_chars = snapshot
.reversed_chars_at(selection_head)
.collect::<String>();
for (pair, enabled) in scope.brackets() {
if enabled
&& pair.close
&& prev_chars.starts_with(pair.start.as_str())
&& next_chars.starts_with(pair.end.as_str())
{
bracket_pair = Some(pair.clone());
break;
}
}
if let Some(pair) = bracket_pair {
let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
let autoclose_enabled =
@@ -21863,11 +21834,11 @@ impl CodeActionProvider for Entity<Project> {
cx: &mut App,
) -> Task<Result<Vec<CodeAction>>> {
self.update(cx, |project, cx| {
let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
let code_lens = project.code_lens(buffer, range.clone(), cx);
let code_actions = project.code_actions(buffer, range, None, cx);
cx.background_spawn(async move {
let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
Ok(code_lens_actions
let (code_lens, code_actions) = join(code_lens, code_actions).await;
Ok(code_lens
.context("code lens fetch")?
.into_iter()
.chain(code_actions.context("code action fetch")?)

View File

@@ -10072,14 +10072,8 @@ async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
);
}
async fn setup_range_format_test(
cx: &mut TestAppContext,
) -> (
Entity<Project>,
Entity<Editor>,
&mut gpui::VisualTestContext,
lsp::FakeLanguageServer,
) {
#[gpui::test]
async fn test_range_format_during_save(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let fs = FakeFs::new(cx.executor());
@@ -10094,9 +10088,9 @@ async fn setup_range_format_test(
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
..lsp::ServerCapabilities::default()
..Default::default()
},
..FakeLspAdapter::default()
..Default::default()
},
);
@@ -10111,22 +10105,14 @@ async fn setup_range_format_test(
let (editor, cx) = cx.add_window_view(|window, cx| {
build_editor_with_project(project.clone(), buffer, window, cx)
});
cx.executor().start_waiting();
let fake_server = fake_servers.next().await.unwrap();
(project, editor, cx, fake_server)
}
#[gpui::test]
async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
editor.update_in(cx, |editor, window, cx| {
editor.set_text("one\ntwo\nthree\n", window, cx)
});
assert!(cx.read(|cx| editor.is_dirty(cx)));
cx.executor().start_waiting();
let fake_server = fake_servers.next().await.unwrap();
let save = editor
.update_in(cx, |editor, window, cx| {
editor.save(
@@ -10161,18 +10147,13 @@ async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
"one, two\nthree\n"
);
assert!(!cx.read(|cx| editor.is_dirty(cx)));
}
#[gpui::test]
async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
editor.update_in(cx, |editor, window, cx| {
editor.set_text("one\ntwo\nthree\n", window, cx)
});
assert!(cx.read(|cx| editor.is_dirty(cx)));
// Test that save still works when formatting hangs
// Ensure we can still save even if formatting hangs.
fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
move |params, _| async move {
assert_eq!(
@@ -10204,13 +10185,8 @@ async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
"one\ntwo\nthree\n"
);
assert!(!cx.read(|cx| editor.is_dirty(cx)));
}
#[gpui::test]
async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
// Buffer starts clean, no formatting should be requested
// For non-dirty buffer, no formatting request should be sent
let save = editor
.update_in(cx, |editor, window, cx| {
editor.save(
@@ -10231,12 +10207,6 @@ async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext)
.next();
cx.executor().start_waiting();
save.await;
cx.run_until_parked();
}
#[gpui::test]
async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
// Set Rust language override and assert overridden tabsize is sent to language server
update_test_language_settings(cx, |settings| {
@@ -10250,7 +10220,7 @@ async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppC
});
editor.update_in(cx, |editor, window, cx| {
editor.set_text("something_new\n", window, cx)
editor.set_text("somehting_new\n", window, cx)
});
assert!(cx.read(|cx| editor.is_dirty(cx)));
let save = editor
@@ -13400,178 +13370,6 @@ async fn test_as_is_completions(cx: &mut TestAppContext) {
cx.assert_editor_state("fn a() {}\n unsafeˇ");
}
#[gpui::test]
async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let language =
Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
let mut cx = EditorLspTestContext::new(
language,
lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
..lsp::CompletionOptions::default()
}),
..lsp::ServerCapabilities::default()
},
cx,
)
.await;
cx.set_state(
"#ifndef BAR_H
#define BAR_H
#include <stdbool.h>
int fn_branch(bool do_branch1, bool do_branch2);
#endif // BAR_H
ˇ",
);
cx.executor().run_until_parked();
cx.update_editor(|editor, window, cx| {
editor.handle_input("#", window, cx);
});
cx.executor().run_until_parked();
cx.update_editor(|editor, window, cx| {
editor.handle_input("i", window, cx);
});
cx.executor().run_until_parked();
cx.update_editor(|editor, window, cx| {
editor.handle_input("n", window, cx);
});
cx.executor().run_until_parked();
cx.assert_editor_state(
"#ifndef BAR_H
#define BAR_H
#include <stdbool.h>
int fn_branch(bool do_branch1, bool do_branch2);
#endif // BAR_H
#inˇ",
);
cx.lsp
.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
is_incomplete: false,
item_defaults: None,
items: vec![lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::SNIPPET),
label_details: Some(lsp::CompletionItemLabelDetails {
detail: Some("header".to_string()),
description: None,
}),
label: " include".to_string(),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range {
start: lsp::Position {
line: 8,
character: 1,
},
end: lsp::Position {
line: 8,
character: 1,
},
},
new_text: "include \"$0\"".to_string(),
})),
sort_text: Some("40b67681include".to_string()),
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
filter_text: Some("include".to_string()),
insert_text: Some("include \"$0\"".to_string()),
..lsp::CompletionItem::default()
}],
})))
});
cx.update_editor(|editor, window, cx| {
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
});
cx.executor().run_until_parked();
cx.update_editor(|editor, window, cx| {
editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
});
cx.executor().run_until_parked();
cx.assert_editor_state(
"#ifndef BAR_H
#define BAR_H
#include <stdbool.h>
int fn_branch(bool do_branch1, bool do_branch2);
#endif // BAR_H
#include \"ˇ\"",
);
cx.lsp
.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
is_incomplete: true,
item_defaults: None,
items: vec![lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::FILE),
label: "AGL/".to_string(),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range {
start: lsp::Position {
line: 8,
character: 10,
},
end: lsp::Position {
line: 8,
character: 11,
},
},
new_text: "AGL/".to_string(),
})),
sort_text: Some("40b67681AGL/".to_string()),
insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
filter_text: Some("AGL/".to_string()),
insert_text: Some("AGL/".to_string()),
..lsp::CompletionItem::default()
}],
})))
});
cx.update_editor(|editor, window, cx| {
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
});
cx.executor().run_until_parked();
cx.update_editor(|editor, window, cx| {
editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
});
cx.executor().run_until_parked();
cx.assert_editor_state(
r##"#ifndef BAR_H
#define BAR_H
#include <stdbool.h>
int fn_branch(bool do_branch1, bool do_branch2);
#endif // BAR_H
#include "AGL/ˇ"##,
);
cx.update_editor(|editor, window, cx| {
editor.handle_input("\"", window, cx);
});
cx.executor().run_until_parked();
cx.assert_editor_state(
r##"#ifndef BAR_H
#define BAR_H
#include <stdbool.h>
int fn_branch(bool do_branch1, bool do_branch2);
#endif // BAR_H
#include "AGL/"ˇ"##,
);
}
#[gpui::test]
async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
init_test(cx, |_| {});
@@ -21512,32 +21310,16 @@ async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContex
},
);
let editor = workspace
.update(cx, |workspace, window, cx| {
workspace.open_abs_path(
PathBuf::from(path!("/dir/a.ts")),
OpenOptions::default(),
window,
cx,
)
let (buffer, _handle) = project
.update(cx, |p, cx| {
p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
})
.unwrap()
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
cx.executor().run_until_parked();
let fake_server = fake_language_servers.next().await.unwrap();
let buffer = editor.update(cx, |editor, cx| {
editor
.buffer()
.read(cx)
.as_singleton()
.expect("have opened a single file by path")
});
let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
drop(buffer_snapshot);
@@ -21595,7 +21377,7 @@ async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContex
assert_eq!(
actions.len(),
1,
"Should have only one valid action for the 0..0 range, got: {actions:#?}"
"Should have only one valid action for the 0..0 range"
);
let action = actions[0].clone();
let apply = project.update(cx, |project, cx| {
@@ -21641,7 +21423,7 @@ async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContex
.into_iter()
.collect(),
),
..lsp::WorkspaceEdit::default()
..Default::default()
},
},
)
@@ -21664,38 +21446,6 @@ async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContex
buffer.undo(cx);
assert_eq!(buffer.text(), "a");
});
let actions_after_edits = cx
.update_window(*workspace, |_, window, cx| {
project.code_actions(&buffer, anchor..anchor, window, cx)
})
.unwrap()
.await
.unwrap();
assert_eq!(
actions, actions_after_edits,
"For the same selection, same code lens actions should be returned"
);
let _responses =
fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
panic!("No more code lens requests are expected");
});
editor.update_in(cx, |editor, window, cx| {
editor.select_all(&SelectAll, window, cx);
});
cx.executor().run_until_parked();
let new_actions = cx
.update_window(*workspace, |_, window, cx| {
project.code_actions(&buffer, anchor..anchor, window, cx)
})
.unwrap()
.await
.unwrap();
assert_eq!(
actions, new_actions,
"Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
);
}
#[gpui::test]

View File

@@ -6,7 +6,7 @@ use gpui::{Hsla, Rgba};
use itertools::Itertools;
use language::point_from_lsp;
use multi_buffer::Anchor;
use project::{DocumentColor, lsp_store::LspFetchStrategy};
use project::{DocumentColor, lsp_store::ColorFetchStrategy};
use settings::Settings as _;
use text::{Bias, BufferId, OffsetRangeExt as _};
use ui::{App, Context, Window};
@@ -180,9 +180,9 @@ impl Editor {
.filter_map(|buffer| {
let buffer_id = buffer.read(cx).remote_id();
let fetch_strategy = if ignore_cache {
LspFetchStrategy::IgnoreCache
ColorFetchStrategy::IgnoreCache
} else {
LspFetchStrategy::UseCache {
ColorFetchStrategy::UseCache {
known_cache_version: self.colors.as_ref().and_then(|colors| {
Some(colors.buffer_colors.get(&buffer_id)?.cache_version_used)
}),

View File

@@ -19,8 +19,8 @@ path = "src/explorer.rs"
[dependencies]
agent.workspace = true
agent_settings.workspace = true
agent_ui.workspace = true
agent_settings.workspace = true
anyhow.workspace = true
assistant_tool.workspace = true
assistant_tools.workspace = true
@@ -29,7 +29,6 @@ buffer_diff.workspace = true
chrono.workspace = true
clap.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
debug_adapter_extension.workspace = true
dirs.workspace = true
@@ -69,3 +68,4 @@ util.workspace = true
uuid.workspace = true
watch.workspace = true
workspace-hack.workspace = true
zed_llm_client.workspace = true

View File

@@ -15,11 +15,11 @@ use agent_settings::AgentProfileId;
use anyhow::{Result, anyhow};
use async_trait::async_trait;
use buffer_diff::DiffHunkStatus;
use cloud_llm_client::CompletionIntent;
use collections::HashMap;
use futures::{FutureExt as _, StreamExt, channel::mpsc, select_biased};
use gpui::{App, AppContext, AsyncApp, Entity};
use language_model::{LanguageModel, Role, StopReason};
use zed_llm_client::CompletionIntent;
pub const THREAD_EVENT_TIMEOUT: Duration = Duration::from_secs(60 * 2);

View File

@@ -106,7 +106,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn language_server_initialization_options(
@@ -131,7 +131,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn language_server_workspace_configuration(
@@ -154,7 +154,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn language_server_additional_initialization_options(
@@ -179,7 +179,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn language_server_additional_workspace_configuration(
@@ -204,7 +204,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn labels_for_completions(
@@ -230,7 +230,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn labels_for_symbols(
@@ -256,7 +256,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn complete_slash_command_argument(
@@ -275,7 +275,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn run_slash_command(
@@ -301,7 +301,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn context_server_command(
@@ -320,7 +320,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn context_server_configuration(
@@ -347,7 +347,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>> {
@@ -362,7 +362,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn index_docs(
@@ -388,7 +388,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn get_dap_binary(
@@ -410,7 +410,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn dap_request_kind(
&self,
@@ -427,7 +427,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn dap_config_to_scenario(&self, config: ZedDebugConfig) -> Result<DebugScenario> {
@@ -441,7 +441,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn dap_locator_create_scenario(
@@ -465,7 +465,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
async fn run_dap_locator(
&self,
@@ -481,7 +481,7 @@ impl extension::Extension for WasmExtension {
}
.boxed()
})
.await?
.await
}
}
@@ -761,7 +761,7 @@ impl WasmExtension {
.with_context(|| format!("failed to load wasm extension {}", manifest.id))
}
pub async fn call<T, Fn>(&self, f: Fn) -> Result<T>
pub async fn call<T, Fn>(&self, f: Fn) -> T
where
T: 'static + Send,
Fn: 'static
@@ -777,19 +777,8 @@ impl WasmExtension {
}
.boxed()
}))
.map_err(|_| {
anyhow!(
"wasm extension channel should not be closed yet, extension {} (id {})",
self.manifest.name,
self.manifest.id,
)
})?;
return_rx.await.with_context(|| {
format!(
"wasm extension channel, extension {} (id {})",
self.manifest.name, self.manifest.id,
)
})
.expect("wasm extension channel should not be closed yet");
return_rx.await.expect("wasm extension channel")
}
}
@@ -810,19 +799,8 @@ impl WasmState {
}
.boxed_local()
}))
.unwrap_or_else(|_| {
panic!(
"main thread message channel should not be closed yet, extension {} (id {})",
self.manifest.name, self.manifest.id,
)
});
let name = self.manifest.name.clone();
let id = self.manifest.id.clone();
async move {
return_rx.await.unwrap_or_else(|_| {
panic!("main thread message channel, extension {name} (id {id})")
})
}
.expect("main thread message channel should not be closed yet");
async move { return_rx.await.expect("main thread message channel") }
}
fn work_dir(&self) -> PathBuf {

View File

@@ -1404,21 +1404,14 @@ impl PickerDelegate for FileFinderDelegate {
} else {
let path_position = PathWithPosition::parse_str(&raw_query);
#[cfg(windows)]
let raw_query = raw_query.trim().to_owned().replace("/", "\\");
#[cfg(not(windows))]
let raw_query = raw_query.trim().to_owned();
let file_query_end = if path_position.path.to_str().unwrap_or(&raw_query) == raw_query {
None
} else {
// Safe to unwrap as we won't get here when the unwrap in if fails
Some(path_position.path.to_str().unwrap().len())
};
let query = FileSearchQuery {
raw_query,
file_query_end,
raw_query: raw_query.trim().to_owned(),
file_query_end: if path_position.path.to_str().unwrap_or(raw_query) == raw_query {
None
} else {
// Safe to unwrap as we won't get here when the unwrap in if fails
Some(path_position.path.to_str().unwrap().len())
},
path_position,
};

View File

@@ -24,7 +24,6 @@ buffer_diff.workspace = true
call.workspace = true
chrono.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
component.workspace = true
@@ -63,6 +62,7 @@ watch.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_actions.workspace = true
zed_llm_client.workspace = true
[target.'cfg(windows)'.dependencies]
windows.workspace = true

View File

@@ -71,12 +71,12 @@ use ui::{
use util::{ResultExt, TryFutureExt, maybe};
use workspace::SERIALIZATION_THROTTLE_TIME;
use cloud_llm_client::CompletionIntent;
use workspace::{
Workspace,
dock::{DockPosition, Panel, PanelEvent},
notifications::{DetachAndPromptErr, ErrorMessagePrompt, NotificationId},
};
use zed_llm_client::CompletionIntent;
actions!(
git_panel,

View File

@@ -216,10 +216,6 @@ xim = { git = "https://github.com/XDeme1/xim-rs", rev = "d50d461764c2213655cd9cf
x11-clipboard = { version = "0.9.3", optional = true }
[target.'cfg(target_os = "windows")'.dependencies]
blade-util.workspace = true
bytemuck = "1"
blade-graphics.workspace = true
blade-macros.workspace = true
flume = "0.11"
rand.workspace = true
windows.workspace = true
@@ -240,7 +236,6 @@ util = { workspace = true, features = ["test-support"] }
[target.'cfg(target_os = "windows")'.build-dependencies]
embed-resource = "3.0"
naga.workspace = true
[target.'cfg(target_os = "macos")'.build-dependencies]
bindgen = "0.71"

View File

@@ -9,7 +9,10 @@ fn main() {
let target = env::var("CARGO_CFG_TARGET_OS");
println!("cargo::rustc-check-cfg=cfg(gles)");
#[cfg(any(not(target_os = "macos"), feature = "macos-blade"))]
#[cfg(any(
not(any(target_os = "macos", target_os = "windows")),
all(target_os = "macos", feature = "macos-blade")
))]
check_wgsl_shaders();
match target.as_deref() {
@@ -17,21 +20,18 @@ fn main() {
#[cfg(target_os = "macos")]
macos::build();
}
#[cfg(all(target_os = "windows", feature = "windows-manifest"))]
Ok("windows") => {
let manifest = std::path::Path::new("resources/windows/gpui.manifest.xml");
let rc_file = std::path::Path::new("resources/windows/gpui.rc");
println!("cargo:rerun-if-changed={}", manifest.display());
println!("cargo:rerun-if-changed={}", rc_file.display());
embed_resource::compile(rc_file, embed_resource::NONE)
.manifest_required()
.unwrap();
#[cfg(target_os = "windows")]
windows::build();
}
_ => (),
};
}
#[allow(dead_code)]
#[cfg(any(
not(any(target_os = "macos", target_os = "windows")),
all(target_os = "macos", feature = "macos-blade")
))]
fn check_wgsl_shaders() {
use std::path::PathBuf;
use std::process;
@@ -243,3 +243,215 @@ mod macos {
}
}
}
#[cfg(target_os = "windows")]
mod windows {
use std::{
fs,
io::Write,
path::{Path, PathBuf},
process::{self, Command},
};
pub(super) fn build() {
// Compile HLSL shaders
#[cfg(not(debug_assertions))]
compile_shaders();
// Embed the Windows manifest and resource file
#[cfg(feature = "windows-manifest")]
embed_resource();
}
#[cfg(feature = "windows-manifest")]
fn embed_resource() {
let manifest = std::path::Path::new("resources/windows/gpui.manifest.xml");
let rc_file = std::path::Path::new("resources/windows/gpui.rc");
println!("cargo:rerun-if-changed={}", manifest.display());
println!("cargo:rerun-if-changed={}", rc_file.display());
embed_resource::compile(rc_file, embed_resource::NONE)
.manifest_required()
.unwrap();
}
/// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
fn compile_shaders() {
let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("src/platform/windows/shaders.hlsl");
let out_dir = std::env::var("OUT_DIR").unwrap();
println!("cargo:rerun-if-changed={}", shader_path.display());
// Check if fxc.exe is available
let fxc_path = find_fxc_compiler();
// Define all modules
let modules = [
"quad",
"shadow",
"path_rasterization",
"path_sprite",
"underline",
"monochrome_sprite",
"polychrome_sprite",
];
let rust_binding_path = format!("{}/shaders_bytes.rs", out_dir);
if Path::new(&rust_binding_path).exists() {
fs::remove_file(&rust_binding_path)
.expect("Failed to remove existing Rust binding file");
}
for module in modules {
compile_shader_for_module(
module,
&out_dir,
&fxc_path,
shader_path.to_str().unwrap(),
&rust_binding_path,
);
}
{
let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("src/platform/windows/color_text_raster.hlsl");
compile_shader_for_module(
"emoji_rasterization",
&out_dir,
&fxc_path,
shader_path.to_str().unwrap(),
&rust_binding_path,
);
}
}
/// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
fn find_fxc_compiler() -> String {
// Check environment variable
if let Ok(path) = std::env::var("GPUI_FXC_PATH") {
if Path::new(&path).exists() {
return path;
}
}
// Try to find in PATH
// NOTE: This has to be `where.exe` on Windows, not `where`, it must be ended with `.exe`
if let Ok(output) = std::process::Command::new("where.exe")
.arg("fxc.exe")
.output()
{
if output.status.success() {
let path = String::from_utf8_lossy(&output.stdout);
return path.trim().to_string();
}
}
// Check the default path
if Path::new(r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe")
.exists()
{
return r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe"
.to_string();
}
panic!("Failed to find fxc.exe");
}
fn compile_shader_for_module(
module: &str,
out_dir: &str,
fxc_path: &str,
shader_path: &str,
rust_binding_path: &str,
) {
// Compile vertex shader
let output_file = format!("{}/{}_vs.h", out_dir, module);
let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase());
compile_shader_impl(
fxc_path,
&format!("{module}_vertex"),
&output_file,
&const_name,
shader_path,
"vs_4_1",
);
generate_rust_binding(&const_name, &output_file, &rust_binding_path);
// Compile fragment shader
let output_file = format!("{}/{}_ps.h", out_dir, module);
let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase());
compile_shader_impl(
fxc_path,
&format!("{module}_fragment"),
&output_file,
&const_name,
shader_path,
"ps_4_1",
);
generate_rust_binding(&const_name, &output_file, &rust_binding_path);
}
fn compile_shader_impl(
fxc_path: &str,
entry_point: &str,
output_path: &str,
var_name: &str,
shader_path: &str,
target: &str,
) {
let output = Command::new(fxc_path)
.args([
"/T",
target,
"/E",
entry_point,
"/Fh",
output_path,
"/Vn",
var_name,
"/O3",
shader_path,
])
.output();
match output {
Ok(result) => {
if result.status.success() {
return;
}
eprintln!(
"Shader compilation failed for {}:\n{}",
entry_point,
String::from_utf8_lossy(&result.stderr)
);
process::exit(1);
}
Err(e) => {
eprintln!("Failed to run fxc for {}: {}", entry_point, e);
process::exit(1);
}
}
}
fn generate_rust_binding(const_name: &str, head_file: &str, output_path: &str) {
let header_content = fs::read_to_string(head_file).expect("Failed to read header file");
let const_definition = {
let global_var_start = header_content.find("const BYTE").unwrap();
let global_var = &header_content[global_var_start..];
let equal = global_var.find('=').unwrap();
global_var[equal + 1..].trim()
};
let rust_binding = format!(
"const {}: &[u8] = &{}\n",
const_name,
const_definition.replace('{', "[").replace('}', "]")
);
let mut options = fs::OpenOptions::new()
.create(true)
.append(true)
.open(output_path)
.expect("Failed to open Rust binding file");
options
.write_all(rust_binding.as_bytes())
.expect("Failed to write Rust binding file");
}
}

View File

@@ -6,7 +6,6 @@ use gpui::{
actions!(example, [Tab, TabPrev]);
struct Example {
focus_handle: FocusHandle,
items: Vec<FocusHandle>,
message: SharedString,
}
@@ -21,11 +20,8 @@ impl Example {
cx.focus_handle().tab_index(2).tab_stop(true),
];
let focus_handle = cx.focus_handle();
window.focus(&focus_handle);
window.focus(items.first().unwrap());
Self {
focus_handle,
items,
message: SharedString::from("Press `Tab`, `Shift-Tab` to switch focus."),
}
@@ -44,10 +40,6 @@ impl Example {
impl Render for Example {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn tab_stop_style<T: Styled>(this: T) -> T {
this.border_3().border_color(gpui::blue())
}
fn button(id: impl Into<ElementId>) -> Stateful<Div> {
div()
.id(id)
@@ -60,13 +52,12 @@ impl Render for Example {
.border_color(gpui::black())
.bg(gpui::black())
.text_color(gpui::white())
.focus(tab_stop_style)
.focus(|this| this.border_color(gpui::blue()))
.shadow_sm()
}
div()
.id("app")
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::on_tab))
.on_action(cx.listener(Self::on_tab_prev))
.size_full()
@@ -95,7 +86,7 @@ impl Render for Example {
.border_color(gpui::black())
.when(
item_handle.tab_stop && item_handle.is_focused(window),
tab_stop_style,
|this| this.border_color(gpui::blue()),
)
.map(|this| match item_handle.tab_stop {
true => this

View File

@@ -198,7 +198,7 @@ impl RenderOnce for CharacterGrid {
"χ", "ψ", "", "а", "в", "Ж", "ж", "З", "з", "К", "к", "л", "м", "Н", "н", "Р", "р",
"У", "у", "ф", "ч", "ь", "ы", "Э", "э", "Я", "я", "ij", "öẋ", ".,", "⣝⣑", "~", "*",
"_", "^", "`", "'", "(", "{", "«", "#", "&", "@", "$", "¢", "%", "|", "?", "", "µ",
"", "<=", "!=", "==", "--", "++", "=>", "->",
"", "<=", "!=", "==", "--", "++", "=>", "->", "🏀", "🎊", "😍", "❤️", "👍", "👎",
];
let columns = 11;

View File

@@ -2023,10 +2023,6 @@ impl HttpClient for NullHttpClient {
.boxed()
}
fn user_agent(&self) -> Option<&http_client::http::HeaderValue> {
None
}
fn proxy(&self) -> Option<&Url> {
None
}

View File

@@ -35,6 +35,7 @@ pub(crate) fn swap_rgba_pa_to_bgra(color: &mut [u8]) {
/// An RGBA color
#[derive(PartialEq, Clone, Copy, Default)]
#[repr(C)]
pub struct Rgba {
/// The red component of the color, in the range 0.0 to 1.0
pub r: f32,

View File

@@ -13,8 +13,7 @@ mod mac;
any(target_os = "linux", target_os = "freebsd"),
any(feature = "x11", feature = "wayland")
),
target_os = "windows",
feature = "macos-blade"
all(target_os = "macos", feature = "macos-blade")
))]
mod blade;
@@ -448,6 +447,8 @@ impl Tiling {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub(crate) struct RequestFrameOptions {
pub(crate) require_presentation: bool,
/// Force refresh of all rendering states when true
pub(crate) force_render: bool,
}
pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {

View File

@@ -1004,13 +1004,12 @@ impl X11Client {
let mut keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
let keysym = state.xkb.key_get_one_sym(code);
if keysym.is_modifier_key() {
return Some(());
}
// should be called after key_get_one_sym
state.xkb.update_key(code, xkbc::KeyDirection::Down);
if keysym.is_modifier_key() {
return Some(());
}
if let Some(mut compose_state) = state.compose_state.take() {
compose_state.feed(keysym);
match compose_state.status() {
@@ -1068,13 +1067,12 @@ impl X11Client {
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
let keysym = state.xkb.key_get_one_sym(code);
if keysym.is_modifier_key() {
return Some(());
}
// should be called after key_get_one_sym
state.xkb.update_key(code, xkbc::KeyDirection::Up);
if keysym.is_modifier_key() {
return Some(());
}
keystroke
};
drop(state);
@@ -1795,6 +1793,7 @@ impl X11ClientState {
drop(state);
window.refresh(RequestFrameOptions {
require_presentation: expose_event_received,
force_render: false,
});
}
xcb_connection

View File

@@ -1,6 +1,8 @@
mod clipboard;
mod destination_list;
mod direct_write;
mod directx_atlas;
mod directx_renderer;
mod dispatcher;
mod display;
mod events;
@@ -14,6 +16,8 @@ mod wrapper;
pub(crate) use clipboard::*;
pub(crate) use destination_list::*;
pub(crate) use direct_write::*;
pub(crate) use directx_atlas::*;
pub(crate) use directx_renderer::*;
pub(crate) use dispatcher::*;
pub(crate) use display::*;
pub(crate) use events::*;

View File

@@ -0,0 +1,39 @@
struct RasterVertexOutput {
float4 position : SV_Position;
float2 texcoord : TEXCOORD0;
};
RasterVertexOutput emoji_rasterization_vertex(uint vertexID : SV_VERTEXID)
{
RasterVertexOutput output;
output.texcoord = float2((vertexID << 1) & 2, vertexID & 2);
output.position = float4(output.texcoord * 2.0f - 1.0f, 0.0f, 1.0f);
output.position.y = -output.position.y;
return output;
}
struct PixelInput {
float4 position: SV_Position;
float2 texcoord : TEXCOORD0;
};
struct Bounds {
int2 origin;
int2 size;
};
Texture2D<float4> t_layer : register(t0);
SamplerState s_layer : register(s0);
cbuffer GlyphLayerTextureParams : register(b0) {
Bounds bounds;
float4 run_color;
};
float4 emoji_rasterization_fragment(PixelInput input): SV_Target {
float3 sampled = t_layer.Sample(s_layer, input.texcoord.xy).rgb;
float alpha = (sampled.r + sampled.g + sampled.b) / 3;
return float4(run_color.rgb, alpha);
}

View File

@@ -1,4 +1,4 @@
use std::{borrow::Cow, sync::Arc};
use std::{borrow::Cow, mem::ManuallyDrop, sync::Arc};
use ::util::ResultExt;
use anyhow::Result;
@@ -10,11 +10,8 @@ use windows::{
Foundation::*,
Globalization::GetUserDefaultLocaleName,
Graphics::{
Direct2D::{Common::*, *},
DirectWrite::*,
Dxgi::Common::*,
Gdi::LOGFONTW,
Imaging::*,
Direct3D::D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP, Direct3D11::*, DirectWrite::*,
Dxgi::Common::*, Gdi::LOGFONTW, Imaging::*,
},
System::SystemServices::LOCALE_NAME_MAX_LENGTH,
UI::WindowsAndMessaging::*,
@@ -40,16 +37,21 @@ struct DirectWriteComponent {
locale: String,
factory: IDWriteFactory5,
bitmap_factory: AgileReference<IWICImagingFactory>,
d2d1_factory: ID2D1Factory,
in_memory_loader: IDWriteInMemoryFontFileLoader,
builder: IDWriteFontSetBuilder1,
text_renderer: Arc<TextRendererWrapper>,
render_context: GlyphRenderContext,
render_params: IDWriteRenderingParams3,
gpu_state: GPUState,
}
struct GlyphRenderContext {
params: IDWriteRenderingParams3,
dc_target: ID2D1DeviceContext4,
struct GPUState {
device: ID3D11Device,
device_context: ID3D11DeviceContext,
sampler: [Option<ID3D11SamplerState>; 1],
blend_state: ID3D11BlendState,
vertex_shader: ID3D11VertexShader,
pixel_shader: ID3D11PixelShader,
}
struct DirectWriteState {
@@ -70,12 +72,11 @@ struct FontIdentifier {
}
impl DirectWriteComponent {
pub fn new(bitmap_factory: &IWICImagingFactory) -> Result<Self> {
pub fn new(bitmap_factory: &IWICImagingFactory, gpu_context: &DirectXDevices) -> Result<Self> {
// todo: ideally this would not be a large unsafe block but smaller isolated ones for easier auditing
unsafe {
let factory: IDWriteFactory5 = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED)?;
let bitmap_factory = AgileReference::new(bitmap_factory)?;
let d2d1_factory: ID2D1Factory =
D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, None)?;
// The `IDWriteInMemoryFontFileLoader` here is supported starting from
// Windows 10 Creators Update, which consequently requires the entire
// `DirectWriteTextSystem` to run on `win10 1703`+.
@@ -86,60 +87,133 @@ impl DirectWriteComponent {
GetUserDefaultLocaleName(&mut locale_vec);
let locale = String::from_utf16_lossy(&locale_vec);
let text_renderer = Arc::new(TextRendererWrapper::new(&locale));
let render_context = GlyphRenderContext::new(&factory, &d2d1_factory)?;
let render_params = {
let default_params: IDWriteRenderingParams3 =
factory.CreateRenderingParams()?.cast()?;
let gamma = default_params.GetGamma();
let enhanced_contrast = default_params.GetEnhancedContrast();
let gray_contrast = default_params.GetGrayscaleEnhancedContrast();
let cleartype_level = default_params.GetClearTypeLevel();
let grid_fit_mode = default_params.GetGridFitMode();
factory.CreateCustomRenderingParams(
gamma,
enhanced_contrast,
gray_contrast,
cleartype_level,
DWRITE_PIXEL_GEOMETRY_RGB,
DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC,
grid_fit_mode,
)?
};
let gpu_state = GPUState::new(gpu_context)?;
Ok(DirectWriteComponent {
locale,
factory,
bitmap_factory,
d2d1_factory,
in_memory_loader,
builder,
text_renderer,
render_context,
render_params,
gpu_state,
})
}
}
}
impl GlyphRenderContext {
pub fn new(factory: &IDWriteFactory5, d2d1_factory: &ID2D1Factory) -> Result<Self> {
unsafe {
let default_params: IDWriteRenderingParams3 =
factory.CreateRenderingParams()?.cast()?;
let gamma = default_params.GetGamma();
let enhanced_contrast = default_params.GetEnhancedContrast();
let gray_contrast = default_params.GetGrayscaleEnhancedContrast();
let cleartype_level = default_params.GetClearTypeLevel();
let grid_fit_mode = default_params.GetGridFitMode();
impl GPUState {
fn new(gpu_context: &DirectXDevices) -> Result<Self> {
// todo: safety comments
let device = gpu_context.device.clone();
let device_context = gpu_context.device_context.clone();
let params = factory.CreateCustomRenderingParams(
gamma,
enhanced_contrast,
gray_contrast,
cleartype_level,
DWRITE_PIXEL_GEOMETRY_RGB,
DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC,
grid_fit_mode,
)?;
let dc_target = {
let target = d2d1_factory.CreateDCRenderTarget(&get_render_target_property(
DXGI_FORMAT_B8G8R8A8_UNORM,
D2D1_ALPHA_MODE_PREMULTIPLIED,
))?;
let target = target.cast::<ID2D1DeviceContext4>()?;
target.SetTextRenderingParams(&params);
target
let blend_state = {
let mut blend_state = None;
let desc = D3D11_BLEND_DESC {
AlphaToCoverageEnable: false.into(),
IndependentBlendEnable: false.into(),
RenderTarget: [
D3D11_RENDER_TARGET_BLEND_DESC {
BlendEnable: true.into(),
SrcBlend: D3D11_BLEND_SRC_ALPHA,
DestBlend: D3D11_BLEND_INV_SRC_ALPHA,
BlendOp: D3D11_BLEND_OP_ADD,
SrcBlendAlpha: D3D11_BLEND_SRC_ALPHA,
DestBlendAlpha: D3D11_BLEND_INV_SRC_ALPHA,
BlendOpAlpha: D3D11_BLEND_OP_ADD,
RenderTargetWriteMask: D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8,
},
Default::default(),
Default::default(),
Default::default(),
Default::default(),
Default::default(),
Default::default(),
Default::default(),
],
};
unsafe { device.CreateBlendState(&desc, Some(&mut blend_state)) }?;
blend_state.unwrap()
};
Ok(Self { params, dc_target })
}
let sampler = {
let mut sampler = None;
let desc = D3D11_SAMPLER_DESC {
Filter: D3D11_FILTER_MIN_MAG_MIP_POINT,
AddressU: D3D11_TEXTURE_ADDRESS_BORDER,
AddressV: D3D11_TEXTURE_ADDRESS_BORDER,
AddressW: D3D11_TEXTURE_ADDRESS_BORDER,
MipLODBias: 0.0,
MaxAnisotropy: 1,
ComparisonFunc: D3D11_COMPARISON_ALWAYS,
BorderColor: [0.0, 0.0, 0.0, 0.0],
MinLOD: 0.0,
MaxLOD: 0.0,
};
unsafe { device.CreateSamplerState(&desc, Some(&mut sampler)) }?;
[sampler]
};
let vertex_shader = {
let source = shader_resources::RawShaderBytes::new(
shader_resources::ShaderModule::EmojiRasterization,
shader_resources::ShaderTarget::Vertex,
)?;
let mut shader = None;
unsafe { device.CreateVertexShader(source.as_bytes(), None, Some(&mut shader)) }?;
shader.unwrap()
};
let pixel_shader = {
let source = shader_resources::RawShaderBytes::new(
shader_resources::ShaderModule::EmojiRasterization,
shader_resources::ShaderTarget::Fragment,
)?;
let mut shader = None;
unsafe { device.CreatePixelShader(source.as_bytes(), None, Some(&mut shader)) }?;
shader.unwrap()
};
Ok(Self {
device,
device_context,
sampler,
blend_state,
vertex_shader,
pixel_shader,
})
}
}
impl DirectWriteTextSystem {
pub(crate) fn new(bitmap_factory: &IWICImagingFactory) -> Result<Self> {
let components = DirectWriteComponent::new(bitmap_factory)?;
pub(crate) fn new(
gpu_context: &DirectXDevices,
bitmap_factory: &IWICImagingFactory,
) -> Result<Self> {
let components = DirectWriteComponent::new(bitmap_factory, gpu_context)?;
let system_font_collection = unsafe {
let mut result = std::mem::zeroed();
components
@@ -649,11 +723,6 @@ impl DirectWriteState {
}
fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
let render_target = &self.components.render_context.dc_target;
unsafe {
render_target.SetUnitMode(D2D1_UNIT_MODE_DIPS);
render_target.SetDpi(96.0 * params.scale_factor, 96.0 * params.scale_factor);
}
let font = &self.fonts[params.font_id.0];
let glyph_id = [params.glyph_id.0 as u16];
let advance = [0.0f32];
@@ -668,25 +737,36 @@ impl DirectWriteState {
isSideways: BOOL(0),
bidiLevel: 0,
};
let bounds = unsafe {
render_target.GetGlyphRunWorldBounds(
Vector2 { X: 0.0, Y: 0.0 },
let rendering_mode = DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC;
let measuring_mode = DWRITE_MEASURING_MODE_NATURAL;
let baseline_origin_x = 0.0;
let baseline_origin_y = 0.0;
let transform = DWRITE_MATRIX {
m11: params.scale_factor,
m12: 0.0,
m21: 0.0,
m22: params.scale_factor,
dx: 0.0,
dy: 0.0,
};
let glyph_analysis = unsafe {
self.components.factory.CreateGlyphRunAnalysis(
&glyph_run,
DWRITE_MEASURING_MODE_NATURAL,
Some(&transform),
rendering_mode,
measuring_mode,
DWRITE_GRID_FIT_MODE_DEFAULT,
DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE,
baseline_origin_x,
baseline_origin_y,
)?
};
// todo(windows)
// This is a walkaround, deleted when figured out.
let y_offset;
let extra_height;
if params.is_emoji {
y_offset = 0;
extra_height = 0;
} else {
// make some room for scaler.
y_offset = -1;
extra_height = 2;
}
let texture_type = DWRITE_TEXTURE_CLEARTYPE_3x1;
let bounds = unsafe { glyph_analysis.GetAlphaTextureBounds(texture_type)? };
if bounds.right < bounds.left {
Ok(Bounds {
@@ -695,15 +775,10 @@ impl DirectWriteState {
})
} else {
Ok(Bounds {
origin: point(
((bounds.left * params.scale_factor).ceil() as i32).into(),
((bounds.top * params.scale_factor).ceil() as i32 + y_offset).into(),
),
origin: point((bounds.left as i32).into(), (bounds.top as i32).into()),
size: size(
(((bounds.right - bounds.left) * params.scale_factor).ceil() as i32).into(),
(((bounds.bottom - bounds.top) * params.scale_factor).ceil() as i32
+ extra_height)
.into(),
(bounds.right - bounds.left).into(),
(bounds.bottom - bounds.top).into(),
),
})
}
@@ -739,7 +814,7 @@ impl DirectWriteState {
ascenderOffset: glyph_bounds.origin.y.0 as f32 / params.scale_factor,
}];
let glyph_run = DWRITE_GLYPH_RUN {
fontFace: unsafe { std::mem::transmute_copy(&font_info.font_face) },
fontFace: ManuallyDrop::new(Some(font_info.font_face.cast()?)),
fontEmSize: params.font_size.0,
glyphCount: 1,
glyphIndices: glyph_id.as_ptr(),
@@ -759,150 +834,398 @@ impl DirectWriteState {
}
let bitmap_size = bitmap_size;
let total_bytes;
let bitmap_format;
let render_target_property;
let bitmap_width;
let bitmap_height;
let bitmap_stride;
let bitmap_dpi;
if params.is_emoji {
total_bytes = bitmap_size.height.0 as usize * bitmap_size.width.0 as usize * 4;
bitmap_format = &GUID_WICPixelFormat32bppPBGRA;
render_target_property = get_render_target_property(
DXGI_FORMAT_B8G8R8A8_UNORM,
D2D1_ALPHA_MODE_PREMULTIPLIED,
);
bitmap_width = bitmap_size.width.0 as u32;
bitmap_height = bitmap_size.height.0 as u32;
bitmap_stride = bitmap_size.width.0 as u32 * 4;
bitmap_dpi = 96.0;
let subpixel_shift = params
.subpixel_variant
.map(|v| v as f32 / SUBPIXEL_VARIANTS as f32);
let baseline_origin_x = subpixel_shift.x / params.scale_factor;
let baseline_origin_y = subpixel_shift.y / params.scale_factor;
let transform = DWRITE_MATRIX {
m11: params.scale_factor,
m12: 0.0,
m21: 0.0,
m22: params.scale_factor,
dx: 0.0,
dy: 0.0,
};
let rendering_mode = if params.is_emoji {
DWRITE_RENDERING_MODE1_NATURAL
} else {
total_bytes = bitmap_size.height.0 as usize * bitmap_size.width.0 as usize;
bitmap_format = &GUID_WICPixelFormat8bppAlpha;
render_target_property =
get_render_target_property(DXGI_FORMAT_A8_UNORM, D2D1_ALPHA_MODE_STRAIGHT);
bitmap_width = bitmap_size.width.0 as u32 * 2;
bitmap_height = bitmap_size.height.0 as u32 * 2;
bitmap_stride = bitmap_size.width.0 as u32;
bitmap_dpi = 192.0;
DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC
};
let measuring_mode = DWRITE_MEASURING_MODE_NATURAL;
let glyph_analysis = unsafe {
self.components.factory.CreateGlyphRunAnalysis(
&glyph_run,
Some(&transform),
rendering_mode,
measuring_mode,
DWRITE_GRID_FIT_MODE_DEFAULT,
DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE,
baseline_origin_x,
baseline_origin_y,
)?
};
let texture_type = DWRITE_TEXTURE_CLEARTYPE_3x1;
let texture_bounds = unsafe { glyph_analysis.GetAlphaTextureBounds(texture_type)? };
let texture_width = (texture_bounds.right - texture_bounds.left) as u32;
let texture_height = (texture_bounds.bottom - texture_bounds.top) as u32;
if texture_width == 0 || texture_height == 0 {
return Ok((
bitmap_size,
vec![
0u8;
bitmap_size.width.0 as usize
* bitmap_size.height.0 as usize
* if params.is_emoji { 4 } else { 1 }
],
));
}
let bitmap_factory = self.components.bitmap_factory.resolve()?;
unsafe {
let bitmap = bitmap_factory.CreateBitmap(
bitmap_width,
bitmap_height,
bitmap_format,
WICBitmapCacheOnLoad,
)?;
let render_target = self
.components
.d2d1_factory
.CreateWicBitmapRenderTarget(&bitmap, &render_target_property)?;
let brush = render_target.CreateSolidColorBrush(&BRUSH_COLOR, None)?;
let subpixel_shift = params
.subpixel_variant
.map(|v| v as f32 / SUBPIXEL_VARIANTS as f32);
let baseline_origin = Vector2 {
X: subpixel_shift.x / params.scale_factor,
Y: subpixel_shift.y / params.scale_factor,
};
// This `cast()` action here should never fail since we are running on Win10+, and
// ID2D1DeviceContext4 requires Win8+
let render_target = render_target.cast::<ID2D1DeviceContext4>().unwrap();
render_target.SetUnitMode(D2D1_UNIT_MODE_DIPS);
render_target.SetDpi(
bitmap_dpi * params.scale_factor,
bitmap_dpi * params.scale_factor,
);
render_target.SetTextRenderingParams(&self.components.render_context.params);
render_target.BeginDraw();
if params.is_emoji {
// WARN: only DWRITE_GLYPH_IMAGE_FORMATS_COLR has been tested
let enumerator = self.components.factory.TranslateColorGlyphRun(
baseline_origin,
&glyph_run as _,
None,
DWRITE_GLYPH_IMAGE_FORMATS_COLR
| DWRITE_GLYPH_IMAGE_FORMATS_SVG
| DWRITE_GLYPH_IMAGE_FORMATS_PNG
| DWRITE_GLYPH_IMAGE_FORMATS_JPEG
| DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8,
DWRITE_MEASURING_MODE_NATURAL,
None,
0,
let mut bitmap_data: Vec<u8>;
if params.is_emoji {
if let Ok(color) = self.rasterize_color(
&glyph_run,
rendering_mode,
measuring_mode,
&transform,
point(baseline_origin_x, baseline_origin_y),
bitmap_size,
) {
bitmap_data = color;
} else {
let monochrome = Self::rasterize_monochrome(
&glyph_analysis,
bitmap_size,
size(texture_width, texture_height),
&texture_bounds,
)?;
while enumerator.MoveNext().is_ok() {
let Ok(color_glyph) = enumerator.GetCurrentRun() else {
break;
};
let color_glyph = &*color_glyph;
let brush_color = translate_color(&color_glyph.Base.runColor);
brush.SetColor(&brush_color);
match color_glyph.glyphImageFormat {
DWRITE_GLYPH_IMAGE_FORMATS_PNG
| DWRITE_GLYPH_IMAGE_FORMATS_JPEG
| DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8 => render_target
.DrawColorBitmapGlyphRun(
color_glyph.glyphImageFormat,
baseline_origin,
&color_glyph.Base.glyphRun,
color_glyph.measuringMode,
D2D1_COLOR_BITMAP_GLYPH_SNAP_OPTION_DEFAULT,
),
DWRITE_GLYPH_IMAGE_FORMATS_SVG => render_target.DrawSvgGlyphRun(
baseline_origin,
&color_glyph.Base.glyphRun,
&brush,
None,
color_glyph.Base.paletteIndex as u32,
color_glyph.measuringMode,
),
_ => render_target.DrawGlyphRun(
baseline_origin,
&color_glyph.Base.glyphRun,
Some(color_glyph.Base.glyphRunDescription as *const _),
&brush,
color_glyph.measuringMode,
),
bitmap_data = monochrome
.into_iter()
.flat_map(|pixel| [0, 0, 0, pixel])
.collect::<Vec<_>>();
}
} else {
bitmap_data = Self::rasterize_monochrome(
&glyph_analysis,
bitmap_size,
size(texture_width, texture_height),
&texture_bounds,
)?;
}
Ok((bitmap_size, bitmap_data))
}
fn rasterize_monochrome(
glyph_analysis: &IDWriteGlyphRunAnalysis,
bitmap_size: Size<DevicePixels>,
texture_size: Size<u32>,
texture_bounds: &RECT,
) -> Result<Vec<u8>> {
let mut bitmap_data =
vec![0u8; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize];
let mut alpha_data = vec![0u8; (texture_size.width * texture_size.height * 3) as usize];
unsafe {
glyph_analysis.CreateAlphaTexture(
DWRITE_TEXTURE_CLEARTYPE_3x1,
texture_bounds,
&mut alpha_data,
)?;
}
// Convert ClearType RGB data to grayscale and place in bitmap
let offset_x = texture_bounds.left.max(0) as usize;
let offset_y = texture_bounds.top.max(0) as usize;
for y in 0..texture_size.height as usize {
for x in 0..texture_size.width as usize {
let bitmap_x = offset_x + x;
let bitmap_y = offset_y + y;
if bitmap_x < bitmap_size.width.0 as usize
&& bitmap_y < bitmap_size.height.0 as usize
{
let texture_idx = (y * texture_size.width as usize + x) * 3;
let bitmap_idx = bitmap_y * bitmap_size.width.0 as usize + bitmap_x;
if texture_idx + 2 < alpha_data.len() && bitmap_idx < bitmap_data.len() {
let max_value = alpha_data[texture_idx]
.max(alpha_data[texture_idx + 1])
.max(alpha_data[texture_idx + 2]);
bitmap_data[bitmap_idx] = max_value;
}
}
} else {
render_target.DrawGlyphRun(
baseline_origin,
&glyph_run,
None,
&brush,
DWRITE_MEASURING_MODE_NATURAL,
);
}
render_target.EndDraw(None, None)?;
let mut raw_data = vec![0u8; total_bytes];
if params.is_emoji {
bitmap.CopyPixels(std::ptr::null() as _, bitmap_stride, &mut raw_data)?;
// Convert from BGRA with premultiplied alpha to BGRA with straight alpha.
for pixel in raw_data.chunks_exact_mut(4) {
let a = pixel[3] as f32 / 255.;
pixel[0] = (pixel[0] as f32 / a) as u8;
pixel[1] = (pixel[1] as f32 / a) as u8;
pixel[2] = (pixel[2] as f32 / a) as u8;
}
} else {
let scaler = bitmap_factory.CreateBitmapScaler()?;
scaler.Initialize(
&bitmap,
bitmap_size.width.0 as u32,
bitmap_size.height.0 as u32,
WICBitmapInterpolationModeHighQualityCubic,
)?;
scaler.CopyPixels(std::ptr::null() as _, bitmap_stride, &mut raw_data)?;
}
Ok((bitmap_size, raw_data))
}
Ok(bitmap_data)
}
fn rasterize_color(
&self,
glyph_run: &DWRITE_GLYPH_RUN,
rendering_mode: DWRITE_RENDERING_MODE1,
measuring_mode: DWRITE_MEASURING_MODE,
transform: &DWRITE_MATRIX,
baseline_origin: Point<f32>,
bitmap_size: Size<DevicePixels>,
) -> Result<Vec<u8>> {
// todo: support formats other than COLR
let color_enumerator = unsafe {
self.components.factory.TranslateColorGlyphRun(
Vector2::new(baseline_origin.x, baseline_origin.y),
glyph_run,
None,
DWRITE_GLYPH_IMAGE_FORMATS_COLR,
measuring_mode,
Some(transform),
0,
)
}?;
let mut glyph_layers = Vec::new();
loop {
let color_run = unsafe { color_enumerator.GetCurrentRun() }?;
let color_run = unsafe { &*color_run };
let image_format = color_run.glyphImageFormat & !DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE;
if image_format == DWRITE_GLYPH_IMAGE_FORMATS_COLR {
let color_analysis = unsafe {
self.components.factory.CreateGlyphRunAnalysis(
&color_run.Base.glyphRun as *const _,
Some(transform),
rendering_mode,
measuring_mode,
DWRITE_GRID_FIT_MODE_DEFAULT,
DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE,
baseline_origin.x,
baseline_origin.y,
)
}?;
let color_bounds =
unsafe { color_analysis.GetAlphaTextureBounds(DWRITE_TEXTURE_CLEARTYPE_3x1) }?;
let color_size = size(
color_bounds.right - color_bounds.left,
color_bounds.bottom - color_bounds.top,
);
if color_size.width > 0 && color_size.height > 0 {
let mut alpha_data =
vec![0u8; (color_size.width * color_size.height * 3) as usize];
unsafe {
color_analysis.CreateAlphaTexture(
DWRITE_TEXTURE_CLEARTYPE_3x1,
&color_bounds,
&mut alpha_data,
)
}?;
let run_color = {
let run_color = color_run.Base.runColor;
Rgba {
r: run_color.r,
g: run_color.g,
b: run_color.b,
a: run_color.a,
}
};
let bounds = bounds(point(color_bounds.left, color_bounds.top), color_size);
let alpha_data = alpha_data
.chunks_exact(3)
.flat_map(|chunk| [chunk[0], chunk[1], chunk[2], 255])
.collect::<Vec<_>>();
glyph_layers.push(GlyphLayerTexture::new(
&self.components.gpu_state,
run_color,
bounds,
&alpha_data,
)?);
}
}
let has_next = unsafe { color_enumerator.MoveNext() }
.map(|e| e.as_bool())
.unwrap_or(false);
if !has_next {
break;
}
}
let gpu_state = &self.components.gpu_state;
let params_buffer = {
let desc = D3D11_BUFFER_DESC {
ByteWidth: std::mem::size_of::<GlyphLayerTextureParams>() as u32,
Usage: D3D11_USAGE_DYNAMIC,
BindFlags: D3D11_BIND_CONSTANT_BUFFER.0 as u32,
CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
MiscFlags: 0,
StructureByteStride: 0,
};
let mut buffer = None;
unsafe {
gpu_state
.device
.CreateBuffer(&desc, None, Some(&mut buffer))
}?;
[buffer]
};
let render_target_texture = {
let mut texture = None;
let desc = D3D11_TEXTURE2D_DESC {
Width: bitmap_size.width.0 as u32,
Height: bitmap_size.height.0 as u32,
MipLevels: 1,
ArraySize: 1,
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Usage: D3D11_USAGE_DEFAULT,
BindFlags: D3D11_BIND_RENDER_TARGET.0 as u32,
CPUAccessFlags: 0,
MiscFlags: 0,
};
unsafe {
gpu_state
.device
.CreateTexture2D(&desc, None, Some(&mut texture))
}?;
texture.unwrap()
};
let render_target_view = {
let desc = D3D11_RENDER_TARGET_VIEW_DESC {
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
ViewDimension: D3D11_RTV_DIMENSION_TEXTURE2D,
Anonymous: D3D11_RENDER_TARGET_VIEW_DESC_0 {
Texture2D: D3D11_TEX2D_RTV { MipSlice: 0 },
},
};
let mut rtv = None;
unsafe {
gpu_state.device.CreateRenderTargetView(
&render_target_texture,
Some(&desc),
Some(&mut rtv),
)
}?;
[rtv]
};
let staging_texture = {
let mut texture = None;
let desc = D3D11_TEXTURE2D_DESC {
Width: bitmap_size.width.0 as u32,
Height: bitmap_size.height.0 as u32,
MipLevels: 1,
ArraySize: 1,
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Usage: D3D11_USAGE_STAGING,
BindFlags: 0,
CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32,
MiscFlags: 0,
};
unsafe {
gpu_state
.device
.CreateTexture2D(&desc, None, Some(&mut texture))
}?;
texture.unwrap()
};
let device_context = &gpu_state.device_context;
unsafe { device_context.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP) };
unsafe { device_context.VSSetShader(&gpu_state.vertex_shader, None) };
unsafe { device_context.PSSetShader(&gpu_state.pixel_shader, None) };
unsafe { device_context.VSSetConstantBuffers(0, Some(&params_buffer)) };
unsafe { device_context.PSSetConstantBuffers(0, Some(&params_buffer)) };
unsafe { device_context.OMSetRenderTargets(Some(&render_target_view), None) };
unsafe { device_context.PSSetSamplers(0, Some(&gpu_state.sampler)) };
unsafe { device_context.OMSetBlendState(&gpu_state.blend_state, None, 0xffffffff) };
for layer in glyph_layers {
let params = GlyphLayerTextureParams {
run_color: layer.run_color,
bounds: layer.bounds,
};
unsafe {
let mut dest = std::mem::zeroed();
gpu_state.device_context.Map(
params_buffer[0].as_ref().unwrap(),
0,
D3D11_MAP_WRITE_DISCARD,
0,
Some(&mut dest),
)?;
std::ptr::copy_nonoverlapping(&params as *const _, dest.pData as *mut _, 1);
gpu_state
.device_context
.Unmap(params_buffer[0].as_ref().unwrap(), 0);
};
let texture = [Some(layer.texture_view)];
unsafe { device_context.PSSetShaderResources(0, Some(&texture)) };
let viewport = [D3D11_VIEWPORT {
TopLeftX: layer.bounds.origin.x as f32,
TopLeftY: layer.bounds.origin.y as f32,
Width: layer.bounds.size.width as f32,
Height: layer.bounds.size.height as f32,
MinDepth: 0.0,
MaxDepth: 1.0,
}];
unsafe { device_context.RSSetViewports(Some(&viewport)) };
unsafe { device_context.Draw(4, 0) };
}
unsafe { device_context.CopyResource(&staging_texture, &render_target_texture) };
let mapped_data = {
let mut mapped_data = D3D11_MAPPED_SUBRESOURCE::default();
unsafe {
device_context.Map(
&staging_texture,
0,
D3D11_MAP_READ,
0,
Some(&mut mapped_data),
)
}?;
mapped_data
};
let mut rasterized =
vec![0u8; (bitmap_size.width.0 as u32 * bitmap_size.height.0 as u32 * 4) as usize];
for y in 0..bitmap_size.height.0 as usize {
let width = bitmap_size.width.0 as usize;
unsafe {
std::ptr::copy_nonoverlapping::<u8>(
(mapped_data.pData as *const u8).byte_add(mapped_data.RowPitch as usize * y),
rasterized
.as_mut_ptr()
.byte_add(width * y * std::mem::size_of::<u32>()),
width * std::mem::size_of::<u32>(),
)
};
}
Ok(rasterized)
}
fn get_typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {
@@ -976,6 +1299,84 @@ impl Drop for DirectWriteState {
}
}
struct GlyphLayerTexture {
run_color: Rgba,
bounds: Bounds<i32>,
texture: ID3D11Texture2D,
texture_view: ID3D11ShaderResourceView,
}
impl GlyphLayerTexture {
pub fn new(
gpu_state: &GPUState,
run_color: Rgba,
bounds: Bounds<i32>,
alpha_data: &[u8],
) -> Result<Self> {
// todo: ideally we would have a nice texture wrapper
let texture_size = bounds.size;
let desc = D3D11_TEXTURE2D_DESC {
Width: texture_size.width as u32,
Height: texture_size.height as u32,
MipLevels: 1,
ArraySize: 1,
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Usage: D3D11_USAGE_DEFAULT,
BindFlags: D3D11_BIND_SHADER_RESOURCE.0 as u32,
CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
MiscFlags: 0,
};
let texture = {
let mut texture: Option<ID3D11Texture2D> = None;
unsafe {
gpu_state
.device
.CreateTexture2D(&desc, None, Some(&mut texture))?
};
texture.unwrap()
};
let texture_view = {
let mut view: Option<ID3D11ShaderResourceView> = None;
unsafe {
gpu_state
.device
.CreateShaderResourceView(&texture, None, Some(&mut view))?
};
view.unwrap()
};
unsafe {
gpu_state.device_context.UpdateSubresource(
&texture,
0,
None,
alpha_data.as_ptr() as _,
(texture_size.width * 4) as u32,
0,
)
};
Ok(GlyphLayerTexture {
run_color,
bounds,
texture,
texture_view,
})
}
}
#[repr(C)]
struct GlyphLayerTextureParams {
bounds: Bounds<i32>,
run_color: Rgba,
}
struct TextRendererWrapper(pub IDWriteTextRenderer);
impl TextRendererWrapper {
@@ -1470,16 +1871,6 @@ fn get_name(string: IDWriteLocalizedStrings, locale: &str) -> Result<String> {
Ok(String::from_utf16_lossy(&name_vec[..name_length]))
}
#[inline]
fn translate_color(color: &DWRITE_COLOR_F) -> D2D1_COLOR_F {
D2D1_COLOR_F {
r: color.r,
g: color.g,
b: color.b,
a: color.a,
}
}
fn get_system_ui_font_name() -> SharedString {
unsafe {
let mut info: LOGFONTW = std::mem::zeroed();
@@ -1504,24 +1895,6 @@ fn get_system_ui_font_name() -> SharedString {
}
}
#[inline]
fn get_render_target_property(
pixel_format: DXGI_FORMAT,
alpha_mode: D2D1_ALPHA_MODE,
) -> D2D1_RENDER_TARGET_PROPERTIES {
D2D1_RENDER_TARGET_PROPERTIES {
r#type: D2D1_RENDER_TARGET_TYPE_DEFAULT,
pixelFormat: D2D1_PIXEL_FORMAT {
format: pixel_format,
alphaMode: alpha_mode,
},
dpiX: 96.0,
dpiY: 96.0,
usage: D2D1_RENDER_TARGET_USAGE_NONE,
minLevel: D2D1_FEATURE_LEVEL_DEFAULT,
}
}
// One would think that with newer DirectWrite method: IDWriteFontFace4::GetGlyphImageFormats
// but that doesn't seem to work for some glyphs, say ❤
fn is_color_glyph(
@@ -1561,12 +1934,6 @@ fn is_color_glyph(
}
const DEFAULT_LOCALE_NAME: PCWSTR = windows::core::w!("en-US");
const BRUSH_COLOR: D2D1_COLOR_F = D2D1_COLOR_F {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
};
#[cfg(test)]
mod tests {

View File

@@ -0,0 +1,310 @@
use collections::FxHashMap;
use etagere::BucketedAtlasAllocator;
use parking_lot::Mutex;
use windows::Win32::Graphics::{
Direct3D11::{
D3D11_BIND_SHADER_RESOURCE, D3D11_BOX, D3D11_CPU_ACCESS_WRITE, D3D11_TEXTURE2D_DESC,
D3D11_USAGE_DEFAULT, ID3D11Device, ID3D11DeviceContext, ID3D11ShaderResourceView,
ID3D11Texture2D,
},
Dxgi::Common::*,
};
use crate::{
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
Point, Size, platform::AtlasTextureList,
};
pub(crate) struct DirectXAtlas(Mutex<DirectXAtlasState>);
struct DirectXAtlasState {
device: ID3D11Device,
device_context: ID3D11DeviceContext,
monochrome_textures: AtlasTextureList<DirectXAtlasTexture>,
polychrome_textures: AtlasTextureList<DirectXAtlasTexture>,
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
}
struct DirectXAtlasTexture {
id: AtlasTextureId,
bytes_per_pixel: u32,
allocator: BucketedAtlasAllocator,
texture: ID3D11Texture2D,
view: [Option<ID3D11ShaderResourceView>; 1],
live_atlas_keys: u32,
}
impl DirectXAtlas {
pub(crate) fn new(device: &ID3D11Device, device_context: &ID3D11DeviceContext) -> Self {
DirectXAtlas(Mutex::new(DirectXAtlasState {
device: device.clone(),
device_context: device_context.clone(),
monochrome_textures: Default::default(),
polychrome_textures: Default::default(),
tiles_by_key: Default::default(),
}))
}
pub(crate) fn get_texture_view(
&self,
id: AtlasTextureId,
) -> [Option<ID3D11ShaderResourceView>; 1] {
let lock = self.0.lock();
let tex = lock.texture(id);
tex.view.clone()
}
pub(crate) fn handle_device_lost(
&self,
device: &ID3D11Device,
device_context: &ID3D11DeviceContext,
) {
let mut lock = self.0.lock();
lock.device = device.clone();
lock.device_context = device_context.clone();
lock.monochrome_textures = AtlasTextureList::default();
lock.polychrome_textures = AtlasTextureList::default();
lock.tiles_by_key.clear();
}
}
impl PlatformAtlas for DirectXAtlas {
fn get_or_insert_with<'a>(
&self,
key: &AtlasKey,
build: &mut dyn FnMut() -> anyhow::Result<
Option<(Size<DevicePixels>, std::borrow::Cow<'a, [u8]>)>,
>,
) -> anyhow::Result<Option<AtlasTile>> {
let mut lock = self.0.lock();
if let Some(tile) = lock.tiles_by_key.get(key) {
Ok(Some(tile.clone()))
} else {
let Some((size, bytes)) = build()? else {
return Ok(None);
};
let tile = lock
.allocate(size, key.texture_kind())
.ok_or_else(|| anyhow::anyhow!("failed to allocate"))?;
let texture = lock.texture(tile.texture_id);
texture.upload(&lock.device_context, tile.bounds, &bytes);
lock.tiles_by_key.insert(key.clone(), tile.clone());
Ok(Some(tile))
}
}
fn remove(&self, key: &AtlasKey) {
let mut lock = self.0.lock();
let Some(id) = lock.tiles_by_key.remove(key).map(|tile| tile.texture_id) else {
return;
};
let textures = match id.kind {
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
};
let Some(texture_slot) = textures.textures.get_mut(id.index as usize) else {
return;
};
if let Some(mut texture) = texture_slot.take() {
texture.decrement_ref_count();
if texture.is_unreferenced() {
textures.free_list.push(texture.id.index as usize);
lock.tiles_by_key.remove(key);
} else {
*texture_slot = Some(texture);
}
}
}
}
impl DirectXAtlasState {
fn allocate(
&mut self,
size: Size<DevicePixels>,
texture_kind: AtlasTextureKind,
) -> Option<AtlasTile> {
{
let textures = match texture_kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
};
if let Some(tile) = textures
.iter_mut()
.rev()
.find_map(|texture| texture.allocate(size))
{
return Some(tile);
}
}
let texture = self.push_texture(size, texture_kind)?;
texture.allocate(size)
}
fn push_texture(
&mut self,
min_size: Size<DevicePixels>,
kind: AtlasTextureKind,
) -> Option<&mut DirectXAtlasTexture> {
const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
width: DevicePixels(1024),
height: DevicePixels(1024),
};
// Max texture size for DirectX. See:
// https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-limits
const MAX_ATLAS_SIZE: Size<DevicePixels> = Size {
width: DevicePixels(16384),
height: DevicePixels(16384),
};
let size = min_size.min(&MAX_ATLAS_SIZE).max(&DEFAULT_ATLAS_SIZE);
let pixel_format;
let bind_flag;
let bytes_per_pixel;
match kind {
AtlasTextureKind::Monochrome => {
pixel_format = DXGI_FORMAT_R8_UNORM;
bind_flag = D3D11_BIND_SHADER_RESOURCE;
bytes_per_pixel = 1;
}
AtlasTextureKind::Polychrome => {
pixel_format = DXGI_FORMAT_B8G8R8A8_UNORM;
bind_flag = D3D11_BIND_SHADER_RESOURCE;
bytes_per_pixel = 4;
}
}
let texture_desc = D3D11_TEXTURE2D_DESC {
Width: size.width.0 as u32,
Height: size.height.0 as u32,
MipLevels: 1,
ArraySize: 1,
Format: pixel_format,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Usage: D3D11_USAGE_DEFAULT,
BindFlags: bind_flag.0 as u32,
CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
MiscFlags: 0,
};
let mut texture: Option<ID3D11Texture2D> = None;
unsafe {
// This only returns None if the device is lost, which we will recreate later.
// So it's ok to return None here.
self.device
.CreateTexture2D(&texture_desc, None, Some(&mut texture))
.ok()?;
}
let texture = texture.unwrap();
let texture_list = match kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
};
let index = texture_list.free_list.pop();
let view = unsafe {
let mut view = None;
self.device
.CreateShaderResourceView(&texture, None, Some(&mut view))
.ok()?;
[view]
};
let atlas_texture = DirectXAtlasTexture {
id: AtlasTextureId {
index: index.unwrap_or(texture_list.textures.len()) as u32,
kind,
},
bytes_per_pixel,
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
texture,
view,
live_atlas_keys: 0,
};
if let Some(ix) = index {
texture_list.textures[ix] = Some(atlas_texture);
texture_list.textures.get_mut(ix).unwrap().as_mut()
} else {
texture_list.textures.push(Some(atlas_texture));
texture_list.textures.last_mut().unwrap().as_mut()
}
}
fn texture(&self, id: AtlasTextureId) -> &DirectXAtlasTexture {
let textures = match id.kind {
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
};
textures[id.index as usize].as_ref().unwrap()
}
}
impl DirectXAtlasTexture {
fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
let allocation = self.allocator.allocate(size.into())?;
let tile = AtlasTile {
texture_id: self.id,
tile_id: allocation.id.into(),
bounds: Bounds {
origin: allocation.rectangle.min.into(),
size,
},
padding: 0,
};
self.live_atlas_keys += 1;
Some(tile)
}
fn upload(
&self,
device_context: &ID3D11DeviceContext,
bounds: Bounds<DevicePixels>,
bytes: &[u8],
) {
println!("{:?}", bounds);
unsafe {
device_context.UpdateSubresource(
&self.texture,
0,
Some(&D3D11_BOX {
left: bounds.left().0 as u32,
top: bounds.top().0 as u32,
front: 0,
right: bounds.right().0 as u32,
bottom: bounds.bottom().0 as u32,
back: 1,
}),
bytes.as_ptr() as _,
bounds.size.width.to_bytes(self.bytes_per_pixel as u8),
0,
);
}
}
fn decrement_ref_count(&mut self) {
self.live_atlas_keys -= 1;
}
fn is_unreferenced(&mut self) -> bool {
self.live_atlas_keys == 0
}
}
impl From<Size<DevicePixels>> for etagere::Size {
fn from(size: Size<DevicePixels>) -> Self {
etagere::Size::new(size.width.into(), size.height.into())
}
}
impl From<etagere::Point> for Point<DevicePixels> {
fn from(value: etagere::Point) -> Self {
Point {
x: DevicePixels::from(value.x),
y: DevicePixels::from(value.y),
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -23,6 +23,7 @@ pub(crate) const WM_GPUI_CURSOR_STYLE_CHANGED: u32 = WM_USER + 1;
pub(crate) const WM_GPUI_CLOSE_ONE_WINDOW: u32 = WM_USER + 2;
pub(crate) const WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD: u32 = WM_USER + 3;
pub(crate) const WM_GPUI_DOCK_MENU_ACTION: u32 = WM_USER + 4;
pub(crate) const WM_GPUI_FORCE_UPDATE_WINDOW: u32 = WM_USER + 5;
const SIZE_MOVE_LOOP_TIMER_ID: usize = 1;
const AUTO_HIDE_TASKBAR_THICKNESS_PX: i32 = 1;
@@ -37,6 +38,7 @@ pub(crate) fn handle_msg(
let handled = match msg {
WM_ACTIVATE => handle_activate_msg(wparam, state_ptr),
WM_CREATE => handle_create_msg(handle, state_ptr),
WM_DEVICECHANGE => handle_device_change_msg(handle, wparam, state_ptr),
WM_MOVE => handle_move_msg(handle, lparam, state_ptr),
WM_SIZE => handle_size_msg(wparam, lparam, state_ptr),
WM_GETMINMAXINFO => handle_get_min_max_info_msg(lparam, state_ptr),
@@ -48,7 +50,7 @@ pub(crate) fn handle_msg(
WM_DISPLAYCHANGE => handle_display_change_msg(handle, state_ptr),
WM_NCHITTEST => handle_hit_test_msg(handle, msg, wparam, lparam, state_ptr),
WM_PAINT => handle_paint_msg(handle, state_ptr),
WM_CLOSE => handle_close_msg(handle, state_ptr),
WM_CLOSE => handle_close_msg(state_ptr),
WM_DESTROY => handle_destroy_msg(handle, state_ptr),
WM_MOUSEMOVE => handle_mouse_move_msg(handle, lparam, wparam, state_ptr),
WM_MOUSELEAVE | WM_NCMOUSELEAVE => handle_mouse_leave_msg(state_ptr),
@@ -96,6 +98,7 @@ pub(crate) fn handle_msg(
WM_SETTINGCHANGE => handle_system_settings_changed(handle, wparam, lparam, state_ptr),
WM_INPUTLANGCHANGE => handle_input_language_changed(lparam, state_ptr),
WM_GPUI_CURSOR_STYLE_CHANGED => handle_cursor_changed(lparam, state_ptr),
WM_GPUI_FORCE_UPDATE_WINDOW => draw_window(handle, true, state_ptr),
_ => None,
};
if let Some(n) = handled {
@@ -181,11 +184,9 @@ fn handle_size_msg(
let new_size = size(DevicePixels(width), DevicePixels(height));
let scale_factor = lock.scale_factor;
if lock.restore_from_minimized.is_some() {
lock.renderer
.update_drawable_size_even_if_unchanged(new_size);
lock.callbacks.request_frame = lock.restore_from_minimized.take();
} else {
lock.renderer.update_drawable_size(new_size);
lock.renderer.resize(new_size).log_err();
}
let new_size = new_size.to_pixels(scale_factor);
lock.logical_size = new_size;
@@ -238,40 +239,14 @@ fn handle_timer_msg(
}
fn handle_paint_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
let mut lock = state_ptr.state.borrow_mut();
if let Some(mut request_frame) = lock.callbacks.request_frame.take() {
drop(lock);
request_frame(Default::default());
state_ptr.state.borrow_mut().callbacks.request_frame = Some(request_frame);
}
unsafe { ValidateRect(Some(handle), None).ok().log_err() };
Some(0)
draw_window(handle, false, state_ptr)
}
fn handle_close_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
let mut lock = state_ptr.state.borrow_mut();
let output = if let Some(mut callback) = lock.callbacks.should_close.take() {
drop(lock);
let should_close = callback();
state_ptr.state.borrow_mut().callbacks.should_close = Some(callback);
if should_close { None } else { Some(0) }
} else {
None
};
// Workaround as window close animation is not played with `WS_EX_LAYERED` enabled.
if output.is_none() {
unsafe {
let current_style = get_window_long(handle, GWL_EXSTYLE);
set_window_long(
handle,
GWL_EXSTYLE,
current_style & !WS_EX_LAYERED.0 as isize,
);
}
}
output
fn handle_close_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
let mut callback = state_ptr.state.borrow_mut().callbacks.should_close.take()?;
let should_close = callback();
state_ptr.state.borrow_mut().callbacks.should_close = Some(callback);
if should_close { None } else { Some(0) }
}
fn handle_destroy_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
@@ -1223,6 +1198,53 @@ fn handle_input_language_changed(
Some(0)
}
fn handle_device_change_msg(
handle: HWND,
wparam: WPARAM,
state_ptr: Rc<WindowsWindowStatePtr>,
) -> Option<isize> {
if wparam.0 == DBT_DEVNODES_CHANGED as usize {
// The reason for sending this message is to actually trigger a redraw of the window.
unsafe {
PostMessageW(
Some(handle),
WM_GPUI_FORCE_UPDATE_WINDOW,
WPARAM(0),
LPARAM(0),
)
.log_err();
}
// If the GPU device is lost, this redraw will take care of recreating the device context.
// The WM_GPUI_FORCE_UPDATE_WINDOW message will take care of redrawing the window, after
// the device context has been recreated.
draw_window(handle, true, state_ptr)
} else {
// Other device change messages are not handled.
None
}
}
#[inline]
fn draw_window(
handle: HWND,
force_render: bool,
state_ptr: Rc<WindowsWindowStatePtr>,
) -> Option<isize> {
let mut request_frame = state_ptr
.state
.borrow_mut()
.callbacks
.request_frame
.take()?;
request_frame(RequestFrameOptions {
require_presentation: false,
force_render,
});
state_ptr.state.borrow_mut().callbacks.request_frame = Some(request_frame);
unsafe { ValidateRect(Some(handle), None).ok().log_err() };
Some(0)
}
#[inline]
fn parse_char_message(wparam: WPARAM, state_ptr: &Rc<WindowsWindowStatePtr>) -> Option<String> {
let code_point = wparam.loword();

View File

@@ -28,13 +28,12 @@ use windows::{
core::*,
};
use crate::{platform::blade::BladeContext, *};
use crate::*;
pub(crate) struct WindowsPlatform {
state: RefCell<WindowsPlatformState>,
raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
// The below members will never change throughout the entire lifecycle of the app.
gpu_context: BladeContext,
icon: HICON,
main_receiver: flume::Receiver<Runnable>,
background_executor: BackgroundExecutor,
@@ -45,6 +44,7 @@ pub(crate) struct WindowsPlatform {
drop_target_helper: IDropTargetHelper,
validation_number: usize,
main_thread_id_win32: u32,
disable_direct_composition: bool,
}
pub(crate) struct WindowsPlatformState {
@@ -94,14 +94,18 @@ impl WindowsPlatform {
main_thread_id_win32,
validation_number,
));
let disable_direct_composition = std::env::var(DISABLE_DIRECT_COMPOSITION)
.is_ok_and(|value| value == "true" || value == "1");
let background_executor = BackgroundExecutor::new(dispatcher.clone());
let foreground_executor = ForegroundExecutor::new(dispatcher);
let directx_devices = DirectXDevices::new(disable_direct_composition)
.context("Unable to init directx devices.")?;
let bitmap_factory = ManuallyDrop::new(unsafe {
CoCreateInstance(&CLSID_WICImagingFactory, None, CLSCTX_INPROC_SERVER)
.context("Error creating bitmap factory.")?
});
let text_system = Arc::new(
DirectWriteTextSystem::new(&bitmap_factory)
DirectWriteTextSystem::new(&directx_devices, &bitmap_factory)
.context("Error creating DirectWriteTextSystem")?,
);
let drop_target_helper: IDropTargetHelper = unsafe {
@@ -111,18 +115,17 @@ impl WindowsPlatform {
let icon = load_icon().unwrap_or_default();
let state = RefCell::new(WindowsPlatformState::new());
let raw_window_handles = RwLock::new(SmallVec::new());
let gpu_context = BladeContext::new().context("Unable to init GPU context")?;
let windows_version = WindowsVersion::new().context("Error retrieve windows version")?;
Ok(Self {
state,
raw_window_handles,
gpu_context,
icon,
main_receiver,
background_executor,
foreground_executor,
text_system,
disable_direct_composition,
windows_version,
bitmap_factory,
drop_target_helper,
@@ -187,6 +190,7 @@ impl WindowsPlatform {
validation_number: self.validation_number,
main_receiver: self.main_receiver.clone(),
main_thread_id_win32: self.main_thread_id_win32,
disable_direct_composition: self.disable_direct_composition,
}
}
@@ -343,27 +347,11 @@ impl Platform for WindowsPlatform {
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
on_finish_launching();
let vsync_event = unsafe { Owned::new(CreateEventW(None, false, false, None).unwrap()) };
begin_vsync(*vsync_event);
'a: loop {
let wait_result = unsafe {
MsgWaitForMultipleObjects(Some(&[*vsync_event]), false, INFINITE, QS_ALLINPUT)
};
match wait_result {
// compositor clock ticked so we should draw a frame
WAIT_EVENT(0) => self.redraw_all(),
// Windows thread messages are posted
WAIT_EVENT(1) => {
if self.handle_events() {
break 'a;
}
}
_ => {
log::error!("Something went wrong while waiting {:?}", wait_result);
break;
}
loop {
if self.handle_events() {
break;
}
self.redraw_all();
}
if let Some(ref mut callback) = self.state.borrow_mut().callbacks.quit {
@@ -455,12 +443,7 @@ impl Platform for WindowsPlatform {
handle: AnyWindowHandle,
options: WindowParams,
) -> Result<Box<dyn PlatformWindow>> {
let window = WindowsWindow::new(
handle,
options,
self.generate_creation_info(),
&self.gpu_context,
)?;
let window = WindowsWindow::new(handle, options, self.generate_creation_info())?;
let handle = window.get_raw_handle();
self.raw_window_handles.write().push(handle);
@@ -739,6 +722,7 @@ pub(crate) struct WindowCreationInfo {
pub(crate) validation_number: usize,
pub(crate) main_receiver: flume::Receiver<Runnable>,
pub(crate) main_thread_id_win32: u32,
pub(crate) disable_direct_composition: bool,
}
fn open_target(target: &str) {
@@ -846,16 +830,6 @@ fn file_save_dialog(directory: PathBuf, window: Option<HWND>) -> Result<Option<P
Ok(Some(PathBuf::from(file_path_string)))
}
fn begin_vsync(vsync_event: HANDLE) {
let event: SafeHandle = vsync_event.into();
std::thread::spawn(move || unsafe {
loop {
windows::Win32::Graphics::Dwm::DwmFlush().log_err();
SetEvent(*event).log_err();
}
});
}
fn load_icon() -> Result<HICON> {
let module = unsafe { GetModuleHandleW(None).context("unable to get module handle")? };
let handle = unsafe {

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,6 @@ use windows::{
core::*,
};
use crate::platform::blade::{BladeContext, BladeRenderer};
use crate::*;
pub(crate) struct WindowsWindow(pub Rc<WindowsWindowStatePtr>);
@@ -49,7 +48,7 @@ pub struct WindowsWindowState {
pub system_key_handled: bool,
pub hovered: bool,
pub renderer: BladeRenderer,
pub renderer: DirectXRenderer,
pub click_state: ClickState,
pub system_settings: WindowsSystemSettings,
@@ -80,13 +79,12 @@ pub(crate) struct WindowsWindowStatePtr {
impl WindowsWindowState {
fn new(
hwnd: HWND,
transparent: bool,
cs: &CREATESTRUCTW,
current_cursor: Option<HCURSOR>,
display: WindowsDisplay,
gpu_context: &BladeContext,
min_size: Option<Size<Pixels>>,
appearance: WindowAppearance,
disable_direct_composition: bool,
) -> Result<Self> {
let scale_factor = {
let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
@@ -103,7 +101,8 @@ impl WindowsWindowState {
};
let border_offset = WindowBorderOffset::default();
let restore_from_minimized = None;
let renderer = windows_renderer::init(gpu_context, hwnd, transparent)?;
let renderer = DirectXRenderer::new(hwnd, disable_direct_composition)
.context("Creating DirectX renderer")?;
let callbacks = Callbacks::default();
let input_handler = None;
let pending_surrogate = None;
@@ -206,13 +205,12 @@ impl WindowsWindowStatePtr {
fn new(context: &WindowCreateContext, hwnd: HWND, cs: &CREATESTRUCTW) -> Result<Rc<Self>> {
let state = RefCell::new(WindowsWindowState::new(
hwnd,
context.transparent,
cs,
context.current_cursor,
context.display,
context.gpu_context,
context.min_size,
context.appearance,
context.disable_direct_composition,
)?);
Ok(Rc::new_cyclic(|this| Self {
@@ -329,12 +327,11 @@ pub(crate) struct Callbacks {
pub(crate) appearance_changed: Option<Box<dyn FnMut()>>,
}
struct WindowCreateContext<'a> {
struct WindowCreateContext {
inner: Option<Result<Rc<WindowsWindowStatePtr>>>,
handle: AnyWindowHandle,
hide_title_bar: bool,
display: WindowsDisplay,
transparent: bool,
is_movable: bool,
min_size: Option<Size<Pixels>>,
executor: ForegroundExecutor,
@@ -343,9 +340,9 @@ struct WindowCreateContext<'a> {
drop_target_helper: IDropTargetHelper,
validation_number: usize,
main_receiver: flume::Receiver<Runnable>,
gpu_context: &'a BladeContext,
main_thread_id_win32: u32,
appearance: WindowAppearance,
disable_direct_composition: bool,
}
impl WindowsWindow {
@@ -353,7 +350,6 @@ impl WindowsWindow {
handle: AnyWindowHandle,
params: WindowParams,
creation_info: WindowCreationInfo,
gpu_context: &BladeContext,
) -> Result<Self> {
let WindowCreationInfo {
icon,
@@ -364,6 +360,7 @@ impl WindowsWindow {
validation_number,
main_receiver,
main_thread_id_win32,
disable_direct_composition,
} = creation_info;
let classname = register_wnd_class(icon);
let hide_title_bar = params
@@ -379,14 +376,18 @@ impl WindowsWindow {
.map(|title| title.as_ref())
.unwrap_or(""),
);
let (dwexstyle, mut dwstyle) = if params.kind == WindowKind::PopUp {
(WS_EX_TOOLWINDOW | WS_EX_LAYERED, WINDOW_STYLE(0x0))
let (mut dwexstyle, dwstyle) = if params.kind == WindowKind::PopUp {
(WS_EX_TOOLWINDOW, WINDOW_STYLE(0x0))
} else {
(
WS_EX_APPWINDOW | WS_EX_LAYERED,
WS_EX_APPWINDOW,
WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX,
)
};
if !disable_direct_composition {
dwexstyle |= WS_EX_NOREDIRECTIONBITMAP;
}
let hinstance = get_module_handle();
let display = if let Some(display_id) = params.display_id {
@@ -401,7 +402,6 @@ impl WindowsWindow {
handle,
hide_title_bar,
display,
transparent: true,
is_movable: params.is_movable,
min_size: params.window_min_size,
executor,
@@ -410,9 +410,9 @@ impl WindowsWindow {
drop_target_helper,
validation_number,
main_receiver,
gpu_context,
main_thread_id_win32,
appearance,
disable_direct_composition,
};
let lpparam = Some(&context as *const _ as *const _);
let creation_result = unsafe {
@@ -453,14 +453,6 @@ impl WindowsWindow {
state: WindowOpenState::Windowed,
});
}
// The render pipeline will perform compositing on the GPU when the
// swapchain is configured correctly (see downstream of
// update_transparency).
// The following configuration is a one-time setup to ensure that the
// window is going to be composited with per-pixel alpha, but the render
// pipeline is responsible for effectively calling UpdateLayeredWindow
// at the appropriate time.
unsafe { SetLayeredWindowAttributes(hwnd, COLORREF(0), 255, LWA_ALPHA)? };
Ok(Self(state_ptr))
}
@@ -485,7 +477,6 @@ impl rwh::HasDisplayHandle for WindowsWindow {
impl Drop for WindowsWindow {
fn drop(&mut self) {
self.0.state.borrow_mut().renderer.destroy();
// clone this `Rc` to prevent early release of the pointer
let this = self.0.clone();
self.0
@@ -705,24 +696,21 @@ impl PlatformWindow for WindowsWindow {
}
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
let mut window_state = self.0.state.borrow_mut();
window_state
.renderer
.update_transparency(background_appearance != WindowBackgroundAppearance::Opaque);
let hwnd = self.0.hwnd;
match background_appearance {
WindowBackgroundAppearance::Opaque => {
// ACCENT_DISABLED
set_window_composition_attribute(window_state.hwnd, None, 0);
set_window_composition_attribute(hwnd, None, 0);
}
WindowBackgroundAppearance::Transparent => {
// Use ACCENT_ENABLE_TRANSPARENTGRADIENT for transparent background
set_window_composition_attribute(window_state.hwnd, None, 2);
set_window_composition_attribute(hwnd, None, 2);
}
WindowBackgroundAppearance::Blurred => {
// Enable acrylic blur
// ACCENT_ENABLE_ACRYLICBLURBEHIND
set_window_composition_attribute(window_state.hwnd, Some((0, 0, 0, 0)), 4);
set_window_composition_attribute(hwnd, Some((0, 0, 0, 0)), 4);
}
}
}
@@ -794,11 +782,11 @@ impl PlatformWindow for WindowsWindow {
}
fn draw(&self, scene: &Scene) {
self.0.state.borrow_mut().renderer.draw(scene)
self.0.state.borrow_mut().renderer.draw(scene).log_err();
}
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
self.0.state.borrow().renderer.sprite_atlas().clone()
self.0.state.borrow().renderer.sprite_atlas()
}
fn get_raw_handle(&self) -> HWND {
@@ -806,11 +794,11 @@ impl PlatformWindow for WindowsWindow {
}
fn gpu_specs(&self) -> Option<GpuSpecs> {
Some(self.0.state.borrow().renderer.gpu_specs())
self.0.state.borrow().renderer.gpu_specs().log_err()
}
fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>) {
// todo(windows)
// There is no such thing on Windows.
}
}
@@ -1306,52 +1294,6 @@ fn set_window_composition_attribute(hwnd: HWND, color: Option<Color>, state: u32
}
}
mod windows_renderer {
use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};
use raw_window_handle as rwh;
use std::num::NonZeroIsize;
use windows::Win32::{Foundation::HWND, UI::WindowsAndMessaging::GWLP_HINSTANCE};
use crate::{get_window_long, show_error};
pub(super) fn init(
context: &BladeContext,
hwnd: HWND,
transparent: bool,
) -> anyhow::Result<BladeRenderer> {
let raw = RawWindow { hwnd };
let config = BladeSurfaceConfig {
size: Default::default(),
transparent,
};
BladeRenderer::new(context, &raw, config)
.inspect_err(|err| show_error("Failed to initialize BladeRenderer", err.to_string()))
}
struct RawWindow {
hwnd: HWND,
}
impl rwh::HasWindowHandle for RawWindow {
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
Ok(unsafe {
let hwnd = NonZeroIsize::new_unchecked(self.hwnd.0 as isize);
let mut handle = rwh::Win32WindowHandle::new(hwnd);
let hinstance = get_window_long(self.hwnd, GWLP_HINSTANCE);
handle.hinstance = NonZeroIsize::new(hinstance);
rwh::WindowHandle::borrow_raw(handle.into())
})
}
}
impl rwh::HasDisplayHandle for RawWindow {
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
let handle = rwh::WindowsDisplayHandle::new();
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
}
}
}
#[cfg(test)]
mod tests {
use super::ClickState;

View File

@@ -32,18 +32,20 @@ impl TabHandles {
self.handles.clear();
}
fn current_index(&self, focused_id: Option<&FocusId>) -> Option<usize> {
self.handles.iter().position(|h| Some(&h.id) == focused_id)
fn current_index(&self, focused_id: Option<&FocusId>) -> usize {
self.handles
.iter()
.position(|h| Some(&h.id) == focused_id)
.unwrap_or_default()
}
pub(crate) fn next(&self, focused_id: Option<&FocusId>) -> Option<FocusHandle> {
let next_ix = self
.current_index(focused_id)
.and_then(|ix| {
let next_ix = ix + 1;
(next_ix < self.handles.len()).then_some(next_ix)
})
.unwrap_or_default();
let ix = self.current_index(focused_id);
let mut next_ix = ix + 1;
if next_ix + 1 > self.handles.len() {
next_ix = 0;
}
if let Some(next_handle) = self.handles.get(next_ix) {
Some(next_handle.clone())
@@ -53,7 +55,7 @@ impl TabHandles {
}
pub(crate) fn prev(&self, focused_id: Option<&FocusId>) -> Option<FocusHandle> {
let ix = self.current_index(focused_id).unwrap_or_default();
let ix = self.current_index(focused_id);
let prev_ix;
if ix == 0 {
prev_ix = self.handles.len().saturating_sub(1);
@@ -106,14 +108,8 @@ mod tests {
]
);
// Select first tab index if no handle is currently focused.
assert_eq!(tab.next(None), Some(tab.handles[0].clone()));
// Select last tab index if no handle is currently focused.
assert_eq!(
tab.prev(None),
Some(tab.handles[tab.handles.len() - 1].clone())
);
// next
assert_eq!(tab.next(None), Some(tab.handles[1].clone()));
assert_eq!(
tab.next(Some(&tab.handles[0].id)),
Some(tab.handles[1].clone())

View File

@@ -1020,7 +1020,7 @@ impl Window {
|| (active.get()
&& last_input_timestamp.get().elapsed() < Duration::from_secs(1));
if invalidator.is_dirty() {
if invalidator.is_dirty() || request_frame_options.force_render {
measure("frame duration", || {
handle
.update(&mut cx, |_, window, cx| {

View File

@@ -4,7 +4,6 @@ pub mod github;
pub use anyhow::{Result, anyhow};
pub use async_body::{AsyncBody, Inner};
use derive_more::Deref;
use http::HeaderValue;
pub use http::{self, Method, Request, Response, StatusCode, Uri};
use futures::future::BoxFuture;
@@ -40,8 +39,6 @@ impl HttpRequestExt for http::request::Builder {
pub trait HttpClient: 'static + Send + Sync {
fn type_name(&self) -> &'static str;
fn user_agent(&self) -> Option<&HeaderValue>;
fn send(
&self,
req: http::Request<AsyncBody>,
@@ -121,10 +118,6 @@ impl HttpClient for HttpClientWithProxy {
self.client.send(req)
}
fn user_agent(&self) -> Option<&HeaderValue> {
self.client.user_agent()
}
fn proxy(&self) -> Option<&Url> {
self.proxy.as_ref()
}
@@ -142,10 +135,6 @@ impl HttpClient for Arc<HttpClientWithProxy> {
self.client.send(req)
}
fn user_agent(&self) -> Option<&HeaderValue> {
self.client.user_agent()
}
fn proxy(&self) -> Option<&Url> {
self.proxy.as_ref()
}
@@ -261,10 +250,6 @@ impl HttpClient for Arc<HttpClientWithUrl> {
self.client.send(req)
}
fn user_agent(&self) -> Option<&HeaderValue> {
self.client.user_agent()
}
fn proxy(&self) -> Option<&Url> {
self.client.proxy.as_ref()
}
@@ -282,10 +267,6 @@ impl HttpClient for HttpClientWithUrl {
self.client.send(req)
}
fn user_agent(&self) -> Option<&HeaderValue> {
self.client.user_agent()
}
fn proxy(&self) -> Option<&Url> {
self.client.proxy.as_ref()
}
@@ -333,10 +314,6 @@ impl HttpClient for BlockedHttpClient {
})
}
fn user_agent(&self) -> Option<&HeaderValue> {
None
}
fn proxy(&self) -> Option<&Url> {
None
}
@@ -357,7 +334,6 @@ type FakeHttpHandler = Box<
#[cfg(feature = "test-support")]
pub struct FakeHttpClient {
handler: FakeHttpHandler,
user_agent: HeaderValue,
}
#[cfg(feature = "test-support")]
@@ -372,7 +348,6 @@ impl FakeHttpClient {
client: HttpClientWithProxy {
client: Arc::new(Self {
handler: Box::new(move |req| Box::pin(handler(req))),
user_agent: HeaderValue::from_static(type_name::<Self>()),
}),
proxy: None,
},
@@ -415,10 +390,6 @@ impl HttpClient for FakeHttpClient {
future
}
fn user_agent(&self) -> Option<&HeaderValue> {
Some(&self.user_agent)
}
fn proxy(&self) -> Option<&Url> {
None
}

View File

@@ -15,7 +15,6 @@ doctest = false
[dependencies]
anyhow.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
copilot.workspace = true
editor.workspace = true
feature_flags.workspace = true
@@ -33,6 +32,7 @@ ui.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_actions.workspace = true
zed_llm_client.workspace = true
zeta.workspace = true
[dev-dependencies]

View File

@@ -1,6 +1,5 @@
use anyhow::Result;
use client::{DisableAiSettings, UserStore, zed_urls};
use cloud_llm_client::UsageLimit;
use copilot::{Copilot, Status};
use editor::{
Editor, SelectionEffects,
@@ -35,6 +34,7 @@ use workspace::{
notifications::NotificationId,
};
use zed_actions::OpenBrowser;
use zed_llm_client::UsageLimit;
use zeta::RateCompletions;
actions!(

View File

@@ -166,6 +166,7 @@ pub struct CachedLspAdapter {
pub reinstall_attempt_count: AtomicU64,
cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>,
manifest_name: OnceLock<Option<ManifestName>>,
attach_kind: OnceLock<Attach>,
}
impl Debug for CachedLspAdapter {
@@ -201,6 +202,7 @@ impl CachedLspAdapter {
adapter,
cached_binary: Default::default(),
reinstall_attempt_count: AtomicU64::new(0),
attach_kind: Default::default(),
manifest_name: Default::default(),
})
}
@@ -286,15 +288,29 @@ impl CachedLspAdapter {
.get_or_init(|| self.adapter.manifest_name())
.clone()
}
pub fn attach_kind(&self) -> Attach {
*self.attach_kind.get_or_init(|| self.adapter.attach_kind())
}
}
/// Determines what gets sent out as a workspace folders content
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum WorkspaceFoldersContent {
/// Send out a single entry with the root of the workspace.
WorktreeRoot,
/// Send out a list of subproject roots.
SubprojectRoots,
pub enum Attach {
/// Create a single language server instance per subproject root.
InstancePerRoot,
/// Use one shared language server instance for all subprojects within a project.
Shared,
}
impl Attach {
pub fn root_path(
&self,
root_subproject_path: (WorktreeId, Arc<Path>),
) -> (WorktreeId, Arc<Path>) {
match self {
Attach::InstancePerRoot => root_subproject_path,
Attach::Shared => (root_subproject_path.0, Arc::from(Path::new(""))),
}
}
}
/// [`LspAdapterDelegate`] allows [`LspAdapter]` implementations to interface with the application
@@ -586,11 +602,8 @@ pub trait LspAdapter: 'static + Send + Sync {
Ok(original)
}
/// Determines whether a language server supports workspace folders.
///
/// And does not trip over itself in the process.
fn workspace_folders_content(&self) -> WorkspaceFoldersContent {
WorkspaceFoldersContent::SubprojectRoots
fn attach_kind(&self) -> Attach {
Attach::Shared
}
fn manifest_name(&self) -> Option<ManifestName> {

View File

@@ -20,7 +20,6 @@ anthropic = { workspace = true, features = ["schemars"] }
anyhow.workspace = true
base64.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
futures.workspace = true
gpui.workspace = true
@@ -38,6 +37,7 @@ telemetry_events.workspace = true
thiserror.workspace = true
util.workspace = true
workspace-hack.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
gpui = { workspace = true, features = ["test-support"] }

View File

@@ -11,7 +11,6 @@ pub mod fake_provider;
use anthropic::{AnthropicError, parse_prompt_too_long};
use anyhow::{Result, anyhow};
use client::Client;
use cloud_llm_client::{CompletionMode, CompletionRequestStatus};
use futures::FutureExt;
use futures::{StreamExt, future::BoxFuture, stream::BoxStream};
use gpui::{AnyElement, AnyView, App, AsyncApp, SharedString, Task, Window};
@@ -27,6 +26,7 @@ use std::time::Duration;
use std::{fmt, io};
use thiserror::Error;
use util::serde::is_default;
use zed_llm_client::{CompletionMode, CompletionRequestStatus};
pub use crate::model::*;
pub use crate::rate_limiter::*;

View File

@@ -1,9 +1,10 @@
use std::io::{Cursor, Write};
use std::sync::Arc;
use crate::role::Role;
use crate::{LanguageModelToolUse, LanguageModelToolUseId};
use anyhow::Result;
use base64::write::EncoderWriter;
use cloud_llm_client::{CompletionIntent, CompletionMode};
use gpui::{
App, AppContext as _, DevicePixels, Image, ImageFormat, ObjectFit, SharedString, Size, Task,
point, px, size,
@@ -11,9 +12,7 @@ use gpui::{
use image::codecs::png::PngEncoder;
use serde::{Deserialize, Serialize};
use util::ResultExt;
use crate::role::Role;
use crate::{LanguageModelToolUse, LanguageModelToolUseId};
use zed_llm_client::{CompletionIntent, CompletionMode};
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct LanguageModelImage {

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