Compare commits
97 Commits
git-panel-
...
new-diff-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df81a91bf9 | ||
|
|
7adcf903cc | ||
|
|
062b4a2dd8 | ||
|
|
81c118d67d | ||
|
|
e1ca5ed836 | ||
|
|
fa1b1c6aff | ||
|
|
2cca833d7b | ||
|
|
0511f9268b | ||
|
|
70f82f84c6 | ||
|
|
0b3715ae44 | ||
|
|
1c4868979d | ||
|
|
c86cf2c3e1 | ||
|
|
7425d242bc | ||
|
|
b17f2089a2 | ||
|
|
68e3d79847 | ||
|
|
ed3e647ed7 | ||
|
|
6fa5a17586 | ||
|
|
a6b717b97b | ||
|
|
228c89a78a | ||
|
|
3978937457 | ||
|
|
95334cb0ad | ||
|
|
cc56ed7a88 | ||
|
|
4878b9bbed | ||
|
|
e1bc48c554 | ||
|
|
9082a006d6 | ||
|
|
ebf6804afd | ||
|
|
a062c0f1bc | ||
|
|
2b68817ed8 | ||
|
|
fc5a810408 | ||
|
|
d1239f6cdb | ||
|
|
d600c58d3c | ||
|
|
3052fc2565 | ||
|
|
c160ce6a24 | ||
|
|
80431e5518 | ||
|
|
9450de73b3 | ||
|
|
28087934d1 | ||
|
|
5cc3bd1585 | ||
|
|
87d50ee54c | ||
|
|
5558b04223 | ||
|
|
0ca0433912 | ||
|
|
d11deff3c2 | ||
|
|
8e71e46867 | ||
|
|
2f5ea92fe1 | ||
|
|
ac24f074df | ||
|
|
ccf2a60039 | ||
|
|
db2aa0bca5 | ||
|
|
2625719ca2 | ||
|
|
373854be46 | ||
|
|
eb74332e96 | ||
|
|
1932c04b84 | ||
|
|
97d9567188 | ||
|
|
53c8b48647 | ||
|
|
88b4628c00 | ||
|
|
92fb38acb6 | ||
|
|
84392fbc2f | ||
|
|
91fdb5d2a9 | ||
|
|
8127decd2d | ||
|
|
4bf005ef52 | ||
|
|
082469e173 | ||
|
|
241c7a6645 | ||
|
|
f6aa7df14f | ||
|
|
472fee6b62 | ||
|
|
093c3d3652 | ||
|
|
1f28437c82 | ||
|
|
2266b3cb96 | ||
|
|
4369890646 | ||
|
|
82fbe27c08 | ||
|
|
41c5e45ca2 | ||
|
|
243d7c6d5a | ||
|
|
9025fb226a | ||
|
|
07eb573a2c | ||
|
|
b552e32b18 | ||
|
|
9b526a4d42 | ||
|
|
cc4ecacc04 | ||
|
|
dc814cb3af | ||
|
|
ad87dfe908 | ||
|
|
99edc9583c | ||
|
|
c015553b1f | ||
|
|
e53d8bb8d2 | ||
|
|
6e724c1168 | ||
|
|
a8a9177878 | ||
|
|
9dc5ebab85 | ||
|
|
2630dfdd23 | ||
|
|
20ec116932 | ||
|
|
dac0a07770 | ||
|
|
61e42b986a | ||
|
|
a20715d663 | ||
|
|
33833533bd | ||
|
|
023f9239b4 | ||
|
|
946ae93f68 | ||
|
|
011880c48c | ||
|
|
440b632b09 | ||
|
|
d0a8b0f8b1 | ||
|
|
09082d103e | ||
|
|
90c9ad4fd6 | ||
|
|
67ffe999e5 | ||
|
|
159c2239cc |
18
Cargo.lock
generated
@@ -2547,6 +2547,7 @@ dependencies = [
|
||||
"settings",
|
||||
"sha2",
|
||||
"smol",
|
||||
"telemetry",
|
||||
"telemetry_events",
|
||||
"text",
|
||||
"thiserror 1.0.69",
|
||||
@@ -2841,6 +2842,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"telemetry",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
@@ -3938,6 +3940,7 @@ dependencies = [
|
||||
"snippet",
|
||||
"sum_tree",
|
||||
"task",
|
||||
"telemetry",
|
||||
"tempfile",
|
||||
"text",
|
||||
"theme",
|
||||
@@ -4373,6 +4376,7 @@ dependencies = [
|
||||
"serde_json_lenient",
|
||||
"settings",
|
||||
"task",
|
||||
"telemetry",
|
||||
"tempfile",
|
||||
"theme",
|
||||
"theme_extension",
|
||||
@@ -7698,11 +7702,13 @@ dependencies = [
|
||||
"ctor",
|
||||
"env_logger 0.11.5",
|
||||
"futures 0.3.31",
|
||||
"git",
|
||||
"gpui",
|
||||
"itertools 0.13.0",
|
||||
"language",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"project",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"settings",
|
||||
@@ -10399,6 +10405,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
"telemetry",
|
||||
"terminal",
|
||||
"terminal_view",
|
||||
"theme",
|
||||
@@ -12663,12 +12670,23 @@ dependencies = [
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "telemetry"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures 0.3.31",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"telemetry_events",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "telemetry_events"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"semantic_version",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -117,6 +117,7 @@ members = [
|
||||
"crates/tab_switcher",
|
||||
"crates/task",
|
||||
"crates/tasks_ui",
|
||||
"crates/telemetry",
|
||||
"crates/telemetry_events",
|
||||
"crates/terminal",
|
||||
"crates/terminal_view",
|
||||
@@ -305,6 +306,7 @@ supermaven_api = { path = "crates/supermaven_api" }
|
||||
tab_switcher = { path = "crates/tab_switcher" }
|
||||
task = { path = "crates/task" }
|
||||
tasks_ui = { path = "crates/tasks_ui" }
|
||||
telemetry = { path = "crates/telemetry" }
|
||||
telemetry_events = { path = "crates/telemetry_events" }
|
||||
terminal = { path = "crates/terminal" }
|
||||
terminal_view = { path = "crates/terminal_view" }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.5 6C1.5 6.89002 1.76392 7.76004 2.25839 8.50007C2.75285 9.24009 3.45566 9.81686 4.27792 10.1575C5.10019 10.4981 6.00499 10.5872 6.87791 10.4135C7.75082 10.2399 8.55264 9.81132 9.18198 9.18198C9.81132 8.55264 10.2399 7.75082 10.4135 6.87791C10.5872 6.00499 10.4981 5.10019 10.1575 4.27792C9.81686 3.45566 9.24009 2.75285 8.50007 2.25839C7.76004 1.76392 6.89002 1.5 6 1.5C4.74198 1.50473 3.53448 1.99561 2.63 2.87L1.5 4" stroke="#919081" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M1.5 1.5V4H4" stroke="#919081" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 3.5V6L8 7" stroke="#919081" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 8C2 9.18669 2.35189 10.3467 3.01118 11.3334C3.67047 12.3201 4.60754 13.0892 5.7039 13.5433C6.80026 13.9974 8.00666 14.1162 9.17054 13.8847C10.3344 13.6532 11.4035 13.0818 12.2426 12.2426C13.0818 11.4035 13.6532 10.3344 13.8847 9.17054C14.1162 8.00666 13.9974 6.80026 13.5433 5.7039C13.0892 4.60754 12.3201 3.67047 11.3334 3.01118C10.3467 2.35189 9.18669 2 8 2C6.32263 2.00631 4.71265 2.66082 3.50667 3.82667L2 5.33333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2 2V5.33333H5.33333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 5V8.5L10 9.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 778 B After Width: | Height: | Size: 840 B |
1
assets/icons/message_circle.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle-more"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/><path d="M8 12h.01"/><path d="M12 12h.01"/><path d="M16 12h.01"/></svg>
|
||||
|
After Width: | Height: | Size: 337 B |
@@ -1,3 +1,4 @@
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 0.875C5.49797 0.875 3.875 2.49797 3.875 4.5C3.875 6.15288 4.98124 7.54738 6.49373 7.98351C5.2997 8.12901 4.27557 8.55134 3.50407 9.31167C2.52216 10.2794 2.02502 11.72 2.02502 13.5999C2.02502 13.8623 2.23769 14.0749 2.50002 14.0749C2.76236 14.0749 2.97502 13.8623 2.97502 13.5999C2.97502 11.8799 3.42786 10.7206 4.17091 9.9883C4.91536 9.25463 6.02674 8.87499 7.49995 8.87499C8.97317 8.87499 10.0846 9.25463 10.8291 9.98831C11.5721 10.7206 12.025 11.8799 12.025 13.5999C12.025 13.8623 12.2376 14.0749 12.5 14.0749C12.7623 14.075 12.975 13.8623 12.975 13.6C12.975 11.72 12.4778 10.2794 11.4959 9.31166C10.7244 8.55135 9.70025 8.12903 8.50625 7.98352C10.0187 7.5474 11.125 6.15289 11.125 4.5C11.125 2.49797 9.50203 0.875 7.5 0.875ZM4.825 4.5C4.825 3.02264 6.02264 1.825 7.5 1.825C8.97736 1.825 10.175 3.02264 10.175 4.5C10.175 5.97736 8.97736 7.175 7.5 7.175C6.02264 7.175 4.825 5.97736 4.825 4.5Z" fill="black"/>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.6666 14V12.6667C12.6666 11.9594 12.3856 11.2811 11.8855 10.781C11.3854 10.281 10.7072 10 9.99992 10H5.99992C5.29267 10 4.6144 10.281 4.1143 10.781C3.6142 11.2811 3.33325 11.9594 3.33325 12.6667V14" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.99992 7.33333C9.47268 7.33333 10.6666 6.13943 10.6666 4.66667C10.6666 3.19391 9.47268 2 7.99992 2C6.52716 2 5.33325 3.19391 5.33325 4.66667C5.33325 6.13943 6.52716 7.33333 7.99992 7.33333Z" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 690 B |
@@ -1,8 +1,4 @@
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M8 2.75C8 2.47386 7.77614 2.25 7.5 2.25C7.22386 2.25 7 2.47386 7 2.75V7H2.75C2.47386 7 2.25 7.22386 2.25 7.5C2.25 7.77614 2.47386 8 2.75 8H7V12.25C7 12.5261 7.22386 12.75 7.5 12.75C7.77614 12.75 8 12.5261 8 12.25V8H12.25C12.5261 8 12.75 7.77614 12.75 7.5C12.75 7.22386 12.5261 7 12.25 7H8V2.75Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.33325 8H12.6666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 3.33333V12.6667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 491 B After Width: | Height: | Size: 327 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.16089 10.2476L3.99598 10.3784C4.61244 10.4749 5.05269 11.0395 5.00728 11.6755L4.94576 12.5377C4.92784 12.789 5.06165 13.0255 5.28326 13.1348L5.90091 13.4391C6.12253 13.5485 6.38717 13.5075 6.56817 13.3371L7.1888 12.7505C7.64641 12.3178 8.35245 12.3178 8.81059 12.7505L9.43121 13.3371C9.61222 13.5081 9.87629 13.5485 10.0985 13.4391L10.7173 13.1341C10.9384 13.0255 11.0716 12.7895 11.0537 12.539L10.9921 11.6755C10.9467 11.0395 11.3869 10.4749 12.0033 10.3784L12.8385 10.2476C13.0817 10.2097 13.2776 10.0233 13.3325 9.77768L13.4848 9.09455C13.5398 8.8489 13.4425 8.59408 13.2393 8.45229L12.5422 7.96404C12.0279 7.60355 11.8708 6.89963 12.1814 6.34659L12.6025 5.59745C12.7249 5.3793 12.7047 5.10616 12.5511 4.9094L12.1241 4.36128C11.9706 4.16451 11.7149 4.08325 11.4795 4.15719L10.6719 4.41016C10.0752 4.59714 9.43903 4.28367 9.20962 3.69035L8.90017 2.88803C8.80937 2.65339 8.58777 2.4994 8.34108 2.5L7.65649 2.50184C7.40979 2.50244 7.1888 2.65766 7.09921 2.89291L6.79751 3.68607C6.57053 4.28307 5.93138 4.59898 5.33284 4.41077L4.49178 4.1468C4.25583 4.07225 3.99897 4.15413 3.84545 4.35212L3.42133 4.90084C3.26781 5.09943 3.2493 5.37319 3.37414 5.59133L3.80483 6.34232C4.12201 6.89591 3.96671 7.60659 3.44941 7.96897L2.76065 8.45169C2.55756 8.59408 2.4602 8.84891 2.51516 9.09393L2.66747 9.77708C2.72184 10.0233 2.91777 10.2097 3.16089 10.2476Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.41432 6.83576C8.63332 6.05481 7.36676 6.05476 6.58575 6.83571C5.8048 7.61672 5.80476 8.88327 6.58571 9.66427C7.36671 10.4452 8.63326 10.4452 9.41426 9.66432C10.1952 8.88332 10.1952 7.61676 9.41432 6.83576Z" fill="black"/>
|
||||
<path d="M8.14667 1.33334H7.85333C7.49971 1.33334 7.16057 1.47382 6.91053 1.72387C6.66048 1.97392 6.52 2.31305 6.52 2.66668V2.78668C6.51976 3.02049 6.45804 3.25014 6.34103 3.45257C6.22401 3.655 6.05583 3.8231 5.85333 3.94001L5.56667 4.10668C5.36398 4.2237 5.13405 4.28531 4.9 4.28531C4.66595 4.28531 4.43603 4.2237 4.23333 4.10668L4.13333 4.05334C3.82738 3.87685 3.46389 3.82897 3.12267 3.92022C2.78145 4.01146 2.49037 4.23437 2.31333 4.54001L2.16667 4.79334C1.99018 5.0993 1.9423 5.46279 2.03354 5.80401C2.12478 6.14523 2.34769 6.43631 2.65333 6.61334L2.75333 6.68001C2.95485 6.79635 3.12241 6.9634 3.23937 7.16456C3.35632 7.36573 3.4186 7.59399 3.42 7.82668V8.16668C3.42093 8.40162 3.35977 8.63265 3.2427 8.83635C3.12563 9.04005 2.95681 9.2092 2.75333 9.32668L2.65333 9.38668C2.34769 9.56371 2.12478 9.85479 2.03354 10.196C1.9423 10.5372 1.99018 10.9007 2.16667 11.2067L2.31333 11.46C2.49037 11.7657 2.78145 11.9886 3.12267 12.0798C3.46389 12.171 3.82738 12.1232 4.13333 11.9467L4.23333 11.8933C4.43603 11.7763 4.66595 11.7147 4.9 11.7147C5.13405 11.7147 5.36398 11.7763 5.56667 11.8933L5.85333 12.06C6.05583 12.1769 6.22401 12.345 6.34103 12.5475C6.45804 12.7499 6.51976 12.9795 6.52 13.2133V13.3333C6.52 13.687 6.66048 14.0261 6.91053 14.2762C7.16057 14.5262 7.49971 14.6667 7.85333 14.6667H8.14667C8.50029 14.6667 8.83943 14.5262 9.08948 14.2762C9.33953 14.0261 9.48 13.687 9.48 13.3333V13.2133C9.48024 12.9795 9.54196 12.7499 9.65898 12.5475C9.77599 12.345 9.94418 12.1769 10.1467 12.06L10.4333 11.8933C10.636 11.7763 10.866 11.7147 11.1 11.7147C11.3341 11.7147 11.564 11.7763 11.7667 11.8933L11.8667 11.9467C12.1726 12.1232 12.5361 12.171 12.8773 12.0798C13.2186 11.9886 13.5096 11.7657 13.6867 11.46L13.8333 11.2C14.0098 10.8941 14.0577 10.5306 13.9665 10.1893C13.8752 9.84812 13.6523 9.55704 13.3467 9.38001L13.2467 9.32668C13.0432 9.2092 12.8744 9.04005 12.7573 8.83635C12.6402 8.63265 12.5791 8.40162 12.58 8.16668V7.83334C12.5791 7.5984 12.6402 7.36738 12.7573 7.16367C12.8744 6.95997 13.0432 6.79082 13.2467 6.67334L13.3467 6.61334C13.6523 6.43631 13.8752 6.14523 13.9665 5.80401C14.0577 5.46279 14.0098 5.0993 13.8333 4.79334L13.6867 4.54001C13.5096 4.23437 13.2186 4.01146 12.8773 3.92022C12.5361 3.82897 12.1726 3.87685 11.8667 4.05334L11.7667 4.10668C11.564 4.2237 11.3341 4.28531 11.1 4.28531C10.866 4.28531 10.636 4.2237 10.4333 4.10668L10.1467 3.94001C9.94418 3.8231 9.77599 3.655 9.65898 3.45257C9.54196 3.25014 9.48024 3.02049 9.48 2.78668V2.66668C9.48 2.31305 9.33953 1.97392 9.08948 1.72387C8.83943 1.47382 8.50029 1.33334 8.14667 1.33334Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10Z" fill="black"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.8 KiB |
@@ -1,4 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 8.9V11C5.93097 11 5.06903 11 3 11V10.4L8 5.6V5H3V7.1" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M11 5L13 8L11 11" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M7 8.9V11C5.34478 11 4.65522 11 3 11V10.4L7 5.6V5H3V7.1" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M12 5L14 8L12 11" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M10 6.5L11 8L10 9.5" stroke="black" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 334 B |
@@ -471,22 +471,13 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && !inline_completion && showing_completions",
|
||||
"context": "Editor && showing_completions",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmCompletion",
|
||||
"tab": "editor::ComposeCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && inline_completion && showing_completions",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmCompletion",
|
||||
"tab": "editor::ComposeCompletion",
|
||||
"shift-tab": "editor::AcceptInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && inline_completion && !showing_completions",
|
||||
"use_key_equivalents": true,
|
||||
|
||||
@@ -224,7 +224,9 @@
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-n": "assistant2::NewThread",
|
||||
"cmd-shift-h": "assistant2::OpenHistory"
|
||||
"cmd-shift-h": "assistant2::OpenHistory",
|
||||
"cmd-shift-m": "assistant2::ToggleModelSelector",
|
||||
"cmd-shift-a": "assistant2::ToggleContextPicker"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -540,22 +542,13 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && !inline_completion && showing_completions",
|
||||
"context": "Editor && showing_completions",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmCompletion",
|
||||
"tab": "editor::ComposeCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && inline_completion && showing_completions",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmCompletion",
|
||||
"tab": "editor::ComposeCompletion",
|
||||
"shift-tab": "editor::AcceptInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && inline_completion && !showing_completions",
|
||||
"use_key_equivalents": true,
|
||||
@@ -617,6 +610,7 @@
|
||||
"context": "PromptEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-a": "assistant2::ToggleContextPicker",
|
||||
"ctrl-[": "assistant::CyclePreviousInlineAssist",
|
||||
"ctrl-]": "assistant::CycleNextInlineAssist"
|
||||
}
|
||||
|
||||
@@ -4,12 +4,32 @@
|
||||
"ctrl-shift-[": "pane::ActivatePrevItem",
|
||||
"ctrl-shift-]": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem"
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-1": ["workspace::ActivatePane", 0],
|
||||
"ctrl-2": ["workspace::ActivatePane", 1],
|
||||
"ctrl-3": ["workspace::ActivatePane", 2],
|
||||
"ctrl-4": ["workspace::ActivatePane", 3],
|
||||
"ctrl-5": ["workspace::ActivatePane", 4],
|
||||
"ctrl-6": ["workspace::ActivatePane", 5],
|
||||
"ctrl-7": ["workspace::ActivatePane", 6],
|
||||
"ctrl-8": ["workspace::ActivatePane", 7],
|
||||
"ctrl-9": ["workspace::ActivatePane", 8],
|
||||
"ctrl-shift-1": ["workspace::MoveItemToPane", { "destination": 0, "focus": true }],
|
||||
"ctrl-shift-2": ["workspace::MoveItemToPane", { "destination": 1 }],
|
||||
"ctrl-shift-3": ["workspace::MoveItemToPane", { "destination": 2 }],
|
||||
"ctrl-shift-4": ["workspace::MoveItemToPane", { "destination": 3 }],
|
||||
"ctrl-shift-5": ["workspace::MoveItemToPane", { "destination": 4 }],
|
||||
"ctrl-shift-6": ["workspace::MoveItemToPane", { "destination": 5 }],
|
||||
"ctrl-shift-7": ["workspace::MoveItemToPane", { "destination": 6 }],
|
||||
"ctrl-shift-8": ["workspace::MoveItemToPane", { "destination": 7 }],
|
||||
"ctrl-shift-9": ["workspace::MoveItemToPane", { "destination": 8 }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-alt-up": "editor::AddSelectionAbove",
|
||||
"ctrl-alt-down": "editor::AddSelectionBelow",
|
||||
"ctrl-shift-up": "editor::MoveLineUp",
|
||||
"ctrl-shift-down": "editor::MoveLineDown",
|
||||
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
|
||||
@@ -17,6 +37,8 @@
|
||||
"ctrl-shift-a": "editor::SelectLargerSyntaxNode",
|
||||
"ctrl-shift-d": "editor::DuplicateSelection",
|
||||
"alt-f3": "editor::SelectAllMatches", // find_all_under
|
||||
"f9": "editor::SortLinesCaseSensitive",
|
||||
"ctrl-f9": "editor::SortLinesCaseInsensitive",
|
||||
"f12": "editor::GoToDefinition",
|
||||
"ctrl-f12": "editor::GoToDefinitionSplit",
|
||||
"shift-f12": "editor::FindAllReferences",
|
||||
|
||||
@@ -4,7 +4,25 @@
|
||||
"cmd-shift-[": "pane::ActivatePrevItem",
|
||||
"cmd-shift-]": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem"
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-1": ["workspace::ActivatePane", 0],
|
||||
"ctrl-2": ["workspace::ActivatePane", 1],
|
||||
"ctrl-3": ["workspace::ActivatePane", 2],
|
||||
"ctrl-4": ["workspace::ActivatePane", 3],
|
||||
"ctrl-5": ["workspace::ActivatePane", 4],
|
||||
"ctrl-6": ["workspace::ActivatePane", 5],
|
||||
"ctrl-7": ["workspace::ActivatePane", 6],
|
||||
"ctrl-8": ["workspace::ActivatePane", 7],
|
||||
"ctrl-9": ["workspace::ActivatePane", 8],
|
||||
"ctrl-shift-1": ["workspace::MoveItemToPane", { "destination": 0, "focus": true }],
|
||||
"ctrl-shift-2": ["workspace::MoveItemToPane", { "destination": 1 }],
|
||||
"ctrl-shift-3": ["workspace::MoveItemToPane", { "destination": 2 }],
|
||||
"ctrl-shift-4": ["workspace::MoveItemToPane", { "destination": 3 }],
|
||||
"ctrl-shift-5": ["workspace::MoveItemToPane", { "destination": 4 }],
|
||||
"ctrl-shift-6": ["workspace::MoveItemToPane", { "destination": 5 }],
|
||||
"ctrl-shift-7": ["workspace::MoveItemToPane", { "destination": 6 }],
|
||||
"ctrl-shift-8": ["workspace::MoveItemToPane", { "destination": 7 }],
|
||||
"ctrl-shift-9": ["workspace::MoveItemToPane", { "destination": 8 }]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -20,6 +38,8 @@
|
||||
"cmd-shift-a": "editor::SelectLargerSyntaxNode",
|
||||
"cmd-shift-d": "editor::DuplicateSelection",
|
||||
"ctrl-cmd-g": "editor::SelectAllMatches", // find_all_under
|
||||
"f5": "editor::SortLinesCaseSensitive",
|
||||
"ctrl-f5": "editor::SortLinesCaseInsensitive",
|
||||
"shift-f12": "editor::FindAllReferences",
|
||||
"alt-cmd-down": "editor::GoToDefinition",
|
||||
"ctrl-alt-cmd-down": "editor::GoToDefinitionSplit",
|
||||
|
||||
@@ -254,7 +254,14 @@
|
||||
// Whether to show selected symbol occurrences in the scrollbar.
|
||||
"selected_symbol": true,
|
||||
// Whether to show diagnostic indicators in the scrollbar.
|
||||
"diagnostics": true
|
||||
"diagnostics": true,
|
||||
/// Forcefully enable or disable the scrollbar for each axis
|
||||
"axes": {
|
||||
/// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
|
||||
"horizontal": true,
|
||||
/// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
|
||||
"vertical": true
|
||||
}
|
||||
},
|
||||
// Enable middle-click paste on Linux.
|
||||
"middle_click_paste": true,
|
||||
@@ -304,6 +311,8 @@
|
||||
"vertical_scroll_margin": 3,
|
||||
// Whether to scroll when clicking near the edge of the visible text area.
|
||||
"autoscroll_on_clicks": false,
|
||||
// The number of characters to keep on either side when scrolling with the mouse
|
||||
"horizontal_scroll_margin": 5,
|
||||
// Scroll sensitivity multiplier. This multiplier is applied
|
||||
// to both the horizontal and vertical delta values while scrolling.
|
||||
"scroll_sensitivity": 1.0,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"style": {
|
||||
"border": "#464b57ff",
|
||||
"border.variant": "#363c46ff",
|
||||
"border.focused": "#293b5bff",
|
||||
"border.focused": "#47679eff",
|
||||
"border.selected": "#293b5bff",
|
||||
"border.transparent": "#00000000",
|
||||
"border.disabled": "#414754ff",
|
||||
@@ -384,7 +384,7 @@
|
||||
"style": {
|
||||
"border": "#c9c9caff",
|
||||
"border.variant": "#dfdfe0ff",
|
||||
"border.focused": "#cbcdf6ff",
|
||||
"border.focused": "#7d82e8ff",
|
||||
"border.selected": "#cbcdf6ff",
|
||||
"border.transparent": "#00000000",
|
||||
"border.disabled": "#d3d3d4ff",
|
||||
|
||||
@@ -493,7 +493,7 @@ impl Render for ActivityIndicator {
|
||||
}),
|
||||
),
|
||||
)
|
||||
.anchor(gpui::AnchorCorner::BottomLeft)
|
||||
.anchor(gpui::Corner::BottomLeft)
|
||||
.menu(move |cx| {
|
||||
let strong_this = this.upgrade()?;
|
||||
let mut has_work = false;
|
||||
|
||||
@@ -108,7 +108,6 @@ pub fn init(cx: &mut AppContext) {
|
||||
|
||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||
})
|
||||
.register_action(AssistantPanel::inline_assist)
|
||||
.register_action(ContextEditor::quote_selection)
|
||||
.register_action(ContextEditor::insert_selection)
|
||||
.register_action(ContextEditor::copy_code)
|
||||
|
||||
@@ -1556,7 +1556,7 @@ impl Render for PromptEditor {
|
||||
anchored()
|
||||
.position_mode(gpui::AnchoredPositionMode::Local)
|
||||
.position(point(px(0.), px(24.)))
|
||||
.anchor(gpui::AnchorCorner::TopLeft)
|
||||
.anchor(gpui::Corner::TopLeft)
|
||||
.child(self.render_rate_limit_notice(cx)),
|
||||
)
|
||||
})),
|
||||
|
||||
@@ -217,11 +217,10 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
div().overflow_hidden().text_ellipsis().child(
|
||||
Label::new(info.description.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
Label::new(info.description.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.text_ellipsis(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -317,8 +316,8 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
||||
PopoverMenu::new("model-switcher")
|
||||
.menu(move |_cx| Some(picker_view.clone()))
|
||||
.trigger(self.trigger)
|
||||
.attach(gpui::AnchorCorner::TopLeft)
|
||||
.anchor(gpui::AnchorCorner::BottomLeft)
|
||||
.attach(gpui::Corner::TopLeft)
|
||||
.anchor(gpui::Corner::BottomLeft)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(-16.0),
|
||||
|
||||
@@ -213,26 +213,33 @@ impl ActiveThread {
|
||||
|
||||
div()
|
||||
.id(("message-container", ix))
|
||||
.p_2()
|
||||
.py_1()
|
||||
.px_2()
|
||||
.child(
|
||||
v_flex()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.rounded_md()
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.p_1p5()
|
||||
.py_1()
|
||||
.px_2()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Icon::new(role_icon).size(IconSize::Small))
|
||||
.child(Label::new(role_name).size(LabelSize::Small)),
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Icon::new(role_icon)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(Label::new(role_name).size(LabelSize::XSmall)),
|
||||
),
|
||||
)
|
||||
.child(v_flex().p_1p5().text_ui(cx).child(markdown.clone()))
|
||||
.child(v_flex().px_2().py_1().text_ui(cx).child(markdown.clone()))
|
||||
.when_some(context, |parent, context| {
|
||||
parent.child(
|
||||
h_flex().flex_wrap().gap_2().p_1p5().children(
|
||||
@@ -249,6 +256,6 @@ impl ActiveThread {
|
||||
|
||||
impl Render for ActiveThread {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
list(self.list_state.clone()).flex_1()
|
||||
list(self.list_state.clone()).flex_1().py_1()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ mod assistant_panel;
|
||||
mod assistant_settings;
|
||||
mod context;
|
||||
mod context_picker;
|
||||
mod context_store;
|
||||
mod context_strip;
|
||||
mod inline_assistant;
|
||||
mod message_editor;
|
||||
@@ -16,7 +17,6 @@ mod ui;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_settings::AssistantSettings;
|
||||
use client::Client;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
|
||||
@@ -27,16 +27,18 @@ use settings::Settings as _;
|
||||
use util::ResultExt;
|
||||
|
||||
pub use crate::assistant_panel::AssistantPanel;
|
||||
use crate::assistant_settings::AssistantSettings;
|
||||
pub use crate::inline_assistant::InlineAssistant;
|
||||
|
||||
actions!(
|
||||
assistant2,
|
||||
[
|
||||
ToggleFocus,
|
||||
NewThread,
|
||||
ToggleContextPicker,
|
||||
ToggleModelSelector,
|
||||
OpenHistory,
|
||||
Chat,
|
||||
ToggleInlineAssist,
|
||||
CycleNextInlineAssist,
|
||||
CyclePreviousInlineAssist
|
||||
]
|
||||
@@ -76,8 +78,6 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, stdout_is_a_pty: bool, cx: &mu
|
||||
}
|
||||
|
||||
fn feature_gate_assistant2_actions(cx: &mut AppContext) {
|
||||
const ASSISTANT1_NAMESPACE: &str = "assistant";
|
||||
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.hide_namespace(NAMESPACE);
|
||||
});
|
||||
@@ -86,12 +86,10 @@ fn feature_gate_assistant2_actions(cx: &mut AppContext) {
|
||||
if is_enabled {
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.show_namespace(NAMESPACE);
|
||||
filter.hide_namespace(ASSISTANT1_NAMESPACE);
|
||||
});
|
||||
} else {
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.hide_namespace(NAMESPACE);
|
||||
filter.show_namespace(ASSISTANT1_NAMESPACE);
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,18 +3,21 @@ use std::sync::Arc;
|
||||
use anyhow::Result;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use client::zed_urls;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
prelude::*, px, svg, Action, AnyElement, AppContext, AsyncWindowContext, EventEmitter,
|
||||
FocusHandle, FocusableView, FontWeight, Model, Pixels, Task, View, ViewContext, WeakView,
|
||||
WindowContext,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use settings::Settings;
|
||||
use time::UtcOffset;
|
||||
use ui::{prelude::*, Divider, IconButtonShape, KeyBinding, Tab, Tooltip};
|
||||
use ui::{prelude::*, KeyBinding, Tab, Tooltip};
|
||||
use workspace::dock::{DockPosition, Panel, PanelEvent};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::active_thread::ActiveThread;
|
||||
use crate::assistant_settings::{AssistantDockPosition, AssistantSettings};
|
||||
use crate::message_editor::MessageEditor;
|
||||
use crate::thread::{ThreadError, ThreadId};
|
||||
use crate::thread_history::{PastThread, ThreadHistory};
|
||||
@@ -39,6 +42,7 @@ enum ActiveView {
|
||||
|
||||
pub struct AssistantPanel {
|
||||
workspace: WeakView<Workspace>,
|
||||
fs: Arc<dyn Fs>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
thread_store: Model<ThreadStore>,
|
||||
thread: View<ActiveThread>,
|
||||
@@ -47,6 +51,8 @@ pub struct AssistantPanel {
|
||||
local_timezone: UtcOffset,
|
||||
active_view: ActiveView,
|
||||
history: View<ThreadHistory>,
|
||||
width: Option<Pixels>,
|
||||
height: Option<Pixels>,
|
||||
}
|
||||
|
||||
impl AssistantPanel {
|
||||
@@ -76,6 +82,7 @@ impl AssistantPanel {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
let language_registry = workspace.project().read(cx).languages().clone();
|
||||
let workspace = workspace.weak_handle();
|
||||
let weak_self = cx.view().downgrade();
|
||||
@@ -83,6 +90,7 @@ impl AssistantPanel {
|
||||
Self {
|
||||
active_view: ActiveView::Thread,
|
||||
workspace: workspace.clone(),
|
||||
fs: fs.clone(),
|
||||
language_registry: language_registry.clone(),
|
||||
thread_store: thread_store.clone(),
|
||||
thread: cx.new_view(|cx| {
|
||||
@@ -95,7 +103,13 @@ impl AssistantPanel {
|
||||
)
|
||||
}),
|
||||
message_editor: cx.new_view(|cx| {
|
||||
MessageEditor::new(workspace, thread_store.downgrade(), thread.clone(), cx)
|
||||
MessageEditor::new(
|
||||
fs.clone(),
|
||||
workspace,
|
||||
thread_store.downgrade(),
|
||||
thread.clone(),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
tools,
|
||||
local_timezone: UtcOffset::from_whole_seconds(
|
||||
@@ -103,6 +117,8 @@ impl AssistantPanel {
|
||||
)
|
||||
.unwrap(),
|
||||
history: cx.new_view(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
|
||||
width: None,
|
||||
height: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +126,10 @@ impl AssistantPanel {
|
||||
self.local_timezone
|
||||
}
|
||||
|
||||
pub(crate) fn thread_store(&self) -> &Model<ThreadStore> {
|
||||
&self.thread_store
|
||||
}
|
||||
|
||||
fn new_thread(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let thread = self
|
||||
.thread_store
|
||||
@@ -127,6 +147,7 @@ impl AssistantPanel {
|
||||
});
|
||||
self.message_editor = cx.new_view(|cx| {
|
||||
MessageEditor::new(
|
||||
self.fs.clone(),
|
||||
self.workspace.clone(),
|
||||
self.thread_store.downgrade(),
|
||||
thread,
|
||||
@@ -156,6 +177,7 @@ impl AssistantPanel {
|
||||
});
|
||||
self.message_editor = cx.new_view(|cx| {
|
||||
MessageEditor::new(
|
||||
self.fs.clone(),
|
||||
self.workspace.clone(),
|
||||
self.thread_store.downgrade(),
|
||||
thread,
|
||||
@@ -195,13 +217,38 @@ impl Panel for AssistantPanel {
|
||||
true
|
||||
}
|
||||
|
||||
fn set_position(&mut self, _position: DockPosition, _cx: &mut ViewContext<Self>) {}
|
||||
|
||||
fn size(&self, _cx: &WindowContext) -> Pixels {
|
||||
px(640.)
|
||||
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
|
||||
settings::update_settings_file::<AssistantSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
move |settings, _| {
|
||||
let dock = match position {
|
||||
DockPosition::Left => AssistantDockPosition::Left,
|
||||
DockPosition::Bottom => AssistantDockPosition::Bottom,
|
||||
DockPosition::Right => AssistantDockPosition::Right,
|
||||
};
|
||||
settings.set_dock(dock);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn set_size(&mut self, _size: Option<Pixels>, _cx: &mut ViewContext<Self>) {}
|
||||
fn size(&self, cx: &WindowContext) -> Pixels {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
match self.position(cx) {
|
||||
DockPosition::Left | DockPosition::Right => {
|
||||
self.width.unwrap_or(settings.default_width)
|
||||
}
|
||||
DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
|
||||
match self.position(cx) {
|
||||
DockPosition::Left | DockPosition::Right => self.width = size,
|
||||
DockPosition::Bottom => self.height = size,
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
|
||||
|
||||
@@ -234,15 +281,17 @@ impl AssistantPanel {
|
||||
.px(DynamicSpacing::Base08.rems(cx))
|
||||
.bg(cx.theme().colors().tab_bar_background)
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(h_flex().children(self.thread.read(cx).summary(cx).map(Label::new)))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap(DynamicSpacing::Base08.rems(cx))
|
||||
.child(Divider::vertical())
|
||||
.h_full()
|
||||
.pl_1()
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.gap(DynamicSpacing::Base02.rems(cx))
|
||||
.child(
|
||||
IconButton::new("new-thread", IconName::Plus)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip({
|
||||
@@ -262,7 +311,6 @@ impl AssistantPanel {
|
||||
)
|
||||
.child(
|
||||
IconButton::new("open-history", IconName::HistoryRerun)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip({
|
||||
@@ -282,7 +330,6 @@ impl AssistantPanel {
|
||||
)
|
||||
.child(
|
||||
IconButton::new("configure-assistant", IconName::Settings)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| Tooltip::text("Configure Assistant", cx))
|
||||
@@ -308,7 +355,6 @@ impl AssistantPanel {
|
||||
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.mx_auto()
|
||||
.child(
|
||||
v_flex().w_full().child(
|
||||
svg()
|
||||
@@ -330,7 +376,7 @@ impl AssistantPanel {
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex().gap_2().children(
|
||||
v_flex().mx_auto().w_4_5().gap_2().children(
|
||||
recent_threads
|
||||
.into_iter()
|
||||
.map(|thread| PastThread::new(thread, cx.view().downgrade())),
|
||||
@@ -537,7 +583,7 @@ impl Render for AssistantPanel {
|
||||
.child(
|
||||
h_flex()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(self.message_editor.clone()),
|
||||
)
|
||||
.children(self.render_last_error(cx)),
|
||||
|
||||
@@ -157,6 +157,22 @@ impl AssistantSettingsContent {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_dock(&mut self, dock: AssistantDockPosition) {
|
||||
match self {
|
||||
AssistantSettingsContent::Versioned(settings) => match settings {
|
||||
VersionedAssistantSettingsContent::V1(settings) => {
|
||||
settings.dock = Some(dock);
|
||||
}
|
||||
VersionedAssistantSettingsContent::V2(settings) => {
|
||||
settings.dock = Some(dock);
|
||||
}
|
||||
},
|
||||
AssistantSettingsContent::Legacy(settings) => {
|
||||
settings.dock = Some(dock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_model(&mut self, language_model: Arc<dyn LanguageModel>) {
|
||||
let model = language_model.id().0.to_string();
|
||||
let provider = language_model.provider_id().0.to_string();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use gpui::SharedString;
|
||||
use language_model::{LanguageModelRequestMessage, MessageContent};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use util::post_inc;
|
||||
|
||||
@@ -23,6 +24,67 @@ pub struct Context {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ContextKind {
|
||||
File,
|
||||
Directory,
|
||||
FetchedUrl,
|
||||
Thread,
|
||||
}
|
||||
|
||||
pub fn attach_context_to_message(
|
||||
message: &mut LanguageModelRequestMessage,
|
||||
context: impl IntoIterator<Item = Context>,
|
||||
) {
|
||||
let mut file_context = String::new();
|
||||
let mut directory_context = String::new();
|
||||
let mut fetch_context = String::new();
|
||||
let mut thread_context = String::new();
|
||||
|
||||
for context in context.into_iter() {
|
||||
match context.kind {
|
||||
ContextKind::File => {
|
||||
file_context.push_str(&context.text);
|
||||
file_context.push('\n');
|
||||
}
|
||||
ContextKind::Directory => {
|
||||
directory_context.push_str(&context.text);
|
||||
directory_context.push('\n');
|
||||
}
|
||||
ContextKind::FetchedUrl => {
|
||||
fetch_context.push_str(&context.name);
|
||||
fetch_context.push('\n');
|
||||
fetch_context.push_str(&context.text);
|
||||
fetch_context.push('\n');
|
||||
}
|
||||
ContextKind::Thread => {
|
||||
thread_context.push_str(&context.name);
|
||||
thread_context.push('\n');
|
||||
thread_context.push_str(&context.text);
|
||||
thread_context.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut context_text = String::new();
|
||||
if !file_context.is_empty() {
|
||||
context_text.push_str("The following files are available:\n");
|
||||
context_text.push_str(&file_context);
|
||||
}
|
||||
|
||||
if !directory_context.is_empty() {
|
||||
context_text.push_str("The following directories are available:\n");
|
||||
context_text.push_str(&directory_context);
|
||||
}
|
||||
|
||||
if !fetch_context.is_empty() {
|
||||
context_text.push_str("The following fetched results are available\n");
|
||||
context_text.push_str(&fetch_context);
|
||||
}
|
||||
|
||||
if !thread_context.is_empty() {
|
||||
context_text.push_str("The following previous conversation threads are available\n");
|
||||
context_text.push_str(&thread_context);
|
||||
}
|
||||
|
||||
if !context_text.is_empty() {
|
||||
message.content.push(MessageContent::Text(context_text));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
mod directory_context_picker;
|
||||
mod fetch_context_picker;
|
||||
mod file_context_picker;
|
||||
mod thread_context_picker;
|
||||
@@ -9,20 +10,23 @@ use gpui::{
|
||||
WeakModel, WeakView,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing, Tooltip};
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::directory_context_picker::DirectoryContextPicker;
|
||||
use crate::context_picker::fetch_context_picker::FetchContextPicker;
|
||||
use crate::context_picker::file_context_picker::FileContextPicker;
|
||||
use crate::context_picker::thread_context_picker::ThreadContextPicker;
|
||||
use crate::context_strip::ContextStrip;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::thread_store::ThreadStore;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum ContextPickerMode {
|
||||
Default,
|
||||
File(View<FileContextPicker>),
|
||||
Directory(View<DirectoryContextPicker>),
|
||||
Fetch(View<FetchContextPicker>),
|
||||
Thread(View<ThreadContextPicker>),
|
||||
}
|
||||
@@ -35,37 +39,42 @@ pub(super) struct ContextPicker {
|
||||
impl ContextPicker {
|
||||
pub fn new(
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: WeakModel<ThreadStore>,
|
||||
context_strip: WeakView<ContextStrip>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let mut entries = vec![
|
||||
ContextPickerEntry {
|
||||
name: "File".into(),
|
||||
kind: ContextKind::File,
|
||||
icon: IconName::File,
|
||||
},
|
||||
ContextPickerEntry {
|
||||
name: "Folder".into(),
|
||||
kind: ContextKind::Directory,
|
||||
icon: IconName::Folder,
|
||||
},
|
||||
ContextPickerEntry {
|
||||
name: "Fetch".into(),
|
||||
kind: ContextKind::FetchedUrl,
|
||||
icon: IconName::Globe,
|
||||
},
|
||||
];
|
||||
|
||||
if thread_store.is_some() {
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "Thread".into(),
|
||||
kind: ContextKind::Thread,
|
||||
icon: IconName::MessageCircle,
|
||||
});
|
||||
}
|
||||
|
||||
let delegate = ContextPickerDelegate {
|
||||
context_picker: cx.view().downgrade(),
|
||||
workspace,
|
||||
thread_store,
|
||||
context_strip,
|
||||
entries: vec![
|
||||
ContextPickerEntry {
|
||||
name: "directory".into(),
|
||||
description: "Insert any directory".into(),
|
||||
icon: IconName::Folder,
|
||||
},
|
||||
ContextPickerEntry {
|
||||
name: "file".into(),
|
||||
description: "Insert any file".into(),
|
||||
icon: IconName::File,
|
||||
},
|
||||
ContextPickerEntry {
|
||||
name: "fetch".into(),
|
||||
description: "Fetch content from URL".into(),
|
||||
icon: IconName::Globe,
|
||||
},
|
||||
ContextPickerEntry {
|
||||
name: "thread".into(),
|
||||
description: "Insert any thread".into(),
|
||||
icon: IconName::MessageBubbles,
|
||||
},
|
||||
],
|
||||
context_store,
|
||||
entries,
|
||||
selected_ix: 0,
|
||||
};
|
||||
|
||||
@@ -91,6 +100,7 @@ impl FocusableView for ContextPicker {
|
||||
match &self.mode {
|
||||
ContextPickerMode::Default => self.picker.focus_handle(cx),
|
||||
ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx),
|
||||
ContextPickerMode::Directory(directory_picker) => directory_picker.focus_handle(cx),
|
||||
ContextPickerMode::Fetch(fetch_picker) => fetch_picker.focus_handle(cx),
|
||||
ContextPickerMode::Thread(thread_picker) => thread_picker.focus_handle(cx),
|
||||
}
|
||||
@@ -105,6 +115,9 @@ impl Render for ContextPicker {
|
||||
.map(|parent| match &self.mode {
|
||||
ContextPickerMode::Default => parent.child(self.picker.clone()),
|
||||
ContextPickerMode::File(file_picker) => parent.child(file_picker.clone()),
|
||||
ContextPickerMode::Directory(directory_picker) => {
|
||||
parent.child(directory_picker.clone())
|
||||
}
|
||||
ContextPickerMode::Fetch(fetch_picker) => parent.child(fetch_picker.clone()),
|
||||
ContextPickerMode::Thread(thread_picker) => parent.child(thread_picker.clone()),
|
||||
})
|
||||
@@ -114,15 +127,15 @@ impl Render for ContextPicker {
|
||||
#[derive(Clone)]
|
||||
struct ContextPickerEntry {
|
||||
name: SharedString,
|
||||
description: SharedString,
|
||||
kind: ContextKind,
|
||||
icon: IconName,
|
||||
}
|
||||
|
||||
pub(crate) struct ContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: WeakModel<ThreadStore>,
|
||||
context_strip: WeakView<ContextStrip>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
entries: Vec<ContextPickerEntry>,
|
||||
selected_ix: usize,
|
||||
}
|
||||
@@ -155,38 +168,49 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
if let Some(entry) = self.entries.get(self.selected_ix) {
|
||||
self.context_picker
|
||||
.update(cx, |this, cx| {
|
||||
match entry.name.to_string().as_str() {
|
||||
"file" => {
|
||||
match entry.kind {
|
||||
ContextKind::File => {
|
||||
this.mode = ContextPickerMode::File(cx.new_view(|cx| {
|
||||
FileContextPicker::new(
|
||||
self.context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.context_strip.clone(),
|
||||
self.context_store.clone(),
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
"fetch" => {
|
||||
ContextKind::Directory => {
|
||||
this.mode = ContextPickerMode::Directory(cx.new_view(|cx| {
|
||||
DirectoryContextPicker::new(
|
||||
self.context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.context_store.clone(),
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::FetchedUrl => {
|
||||
this.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
|
||||
FetchContextPicker::new(
|
||||
self.context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.context_strip.clone(),
|
||||
self.context_store.clone(),
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
"thread" => {
|
||||
this.mode = ContextPickerMode::Thread(cx.new_view(|cx| {
|
||||
ThreadContextPicker::new(
|
||||
self.thread_store.clone(),
|
||||
self.context_picker.clone(),
|
||||
self.context_strip.clone(),
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
ContextKind::Thread => {
|
||||
if let Some(thread_store) = self.thread_store.as_ref() {
|
||||
this.mode = ContextPickerMode::Thread(cx.new_view(|cx| {
|
||||
ThreadContextPicker::new(
|
||||
thread_store.clone(),
|
||||
self.context_picker.clone(),
|
||||
self.context_store.clone(),
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
cx.focus_self();
|
||||
@@ -200,6 +224,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
.update(cx, |this, cx| match this.mode {
|
||||
ContextPickerMode::Default => cx.emit(DismissEvent),
|
||||
ContextPickerMode::File(_)
|
||||
| ContextPickerMode::Directory(_)
|
||||
| ContextPickerMode::Fetch(_)
|
||||
| ContextPickerMode::Thread(_) => {}
|
||||
})
|
||||
@@ -219,34 +244,13 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Dense)
|
||||
.toggle_state(selected)
|
||||
.tooltip({
|
||||
let description = entry.description.clone();
|
||||
move |cx| cx.new_view(|_cx| Tooltip::new(description.clone())).into()
|
||||
})
|
||||
.child(
|
||||
v_flex()
|
||||
.group(format!("context-entry-label-{ix}"))
|
||||
.w_full()
|
||||
.py_0p5()
|
||||
h_flex()
|
||||
.min_w(px(250.))
|
||||
.max_w(px(400.))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(Icon::new(entry.icon).size(IconSize::XSmall))
|
||||
.child(
|
||||
Label::new(entry.name.clone())
|
||||
.single_line()
|
||||
.size(LabelSize::Small),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().overflow_hidden().text_ellipsis().child(
|
||||
Label::new(entry.description.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
),
|
||||
.gap_2()
|
||||
.child(Icon::new(entry.icon).size(IconSize::Small))
|
||||
.child(Label::new(entry.name.clone()).single_line()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
117
crates/assistant2/src/context_picker/directory_context_picker.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
// TODO: Remove this once we've implemented the functionality.
|
||||
#![allow(unused)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{PathMatchCandidateSet, WorktreeId};
|
||||
use ui::{prelude::*, ListItem};
|
||||
use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
|
||||
pub struct DirectoryContextPicker {
|
||||
picker: View<Picker<DirectoryContextPickerDelegate>>,
|
||||
}
|
||||
|
||||
impl DirectoryContextPicker {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let delegate =
|
||||
DirectoryContextPickerDelegate::new(context_picker, workspace, context_store);
|
||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
|
||||
|
||||
Self { picker }
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for DirectoryContextPicker {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
self.picker.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for DirectoryContextPicker {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
self.picker.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirectoryContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
matches: Vec<PathMatch>,
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
impl DirectoryContextPickerDelegate {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
) -> Self {
|
||||
Self {
|
||||
context_picker,
|
||||
workspace,
|
||||
context_store,
|
||||
matches: Vec::new(),
|
||||
selected_index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for DirectoryContextPickerDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn match_count(&self) -> usize {
|
||||
self.matches.len()
|
||||
}
|
||||
|
||||
fn selected_index(&self) -> usize {
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.selected_index = ix;
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
"Search folders…".into()
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, _query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
// TODO: Implement this once we fix the issues with the file context picker.
|
||||
Task::ready(())
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, _cx: &mut ViewContext<Picker<Self>>) {
|
||||
// TODO: Implement this once we fix the issues with the file context picker.
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.context_picker
|
||||
.update(cx, |this, cx| {
|
||||
this.reset_mode();
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
_ix: usize,
|
||||
_selected: bool,
|
||||
_cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use futures::AsyncReadExt as _;
|
||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakView};
|
||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
|
||||
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
||||
use http_client::{AsyncBody, HttpClientWithUrl};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
@@ -13,7 +13,7 @@ use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_strip::ContextStrip;
|
||||
use crate::context_store::ContextStore;
|
||||
|
||||
pub struct FetchContextPicker {
|
||||
picker: View<Picker<FetchContextPickerDelegate>>,
|
||||
@@ -23,10 +23,10 @@ impl FetchContextPicker {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_strip: WeakView<ContextStrip>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let delegate = FetchContextPickerDelegate::new(context_picker, workspace, context_strip);
|
||||
let delegate = FetchContextPickerDelegate::new(context_picker, workspace, context_store);
|
||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
|
||||
|
||||
Self { picker }
|
||||
@@ -55,7 +55,7 @@ enum ContentType {
|
||||
pub struct FetchContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_strip: WeakView<ContextStrip>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
url: String,
|
||||
}
|
||||
|
||||
@@ -63,12 +63,12 @@ impl FetchContextPickerDelegate {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_strip: WeakView<ContextStrip>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
) -> Self {
|
||||
FetchContextPickerDelegate {
|
||||
context_picker,
|
||||
workspace,
|
||||
context_strip,
|
||||
context_store,
|
||||
url: String::new(),
|
||||
}
|
||||
}
|
||||
@@ -189,9 +189,9 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.delegate
|
||||
.context_strip
|
||||
.update(cx, |context_strip, _cx| {
|
||||
context_strip.insert_context(ContextKind::FetchedUrl, url, text);
|
||||
.context_store
|
||||
.update(cx, |context_store, _cx| {
|
||||
context_store.insert_context(ContextKind::FetchedUrl, url, text);
|
||||
})
|
||||
})??;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakView};
|
||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{PathMatchCandidateSet, WorktreeId};
|
||||
use ui::{prelude::*, ListItem};
|
||||
@@ -14,7 +14,7 @@ use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_strip::ContextStrip;
|
||||
use crate::context_store::ContextStore;
|
||||
|
||||
pub struct FileContextPicker {
|
||||
picker: View<Picker<FileContextPickerDelegate>>,
|
||||
@@ -24,10 +24,10 @@ impl FileContextPicker {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_strip: WeakView<ContextStrip>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let delegate = FileContextPickerDelegate::new(context_picker, workspace, context_strip);
|
||||
let delegate = FileContextPickerDelegate::new(context_picker, workspace, context_store);
|
||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
|
||||
|
||||
Self { picker }
|
||||
@@ -49,7 +49,7 @@ impl Render for FileContextPicker {
|
||||
pub struct FileContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_strip: WeakView<ContextStrip>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
matches: Vec<PathMatch>,
|
||||
selected_index: usize,
|
||||
}
|
||||
@@ -58,12 +58,12 @@ impl FileContextPickerDelegate {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_strip: WeakView<ContextStrip>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
) -> Self {
|
||||
Self {
|
||||
context_picker,
|
||||
workspace,
|
||||
context_strip,
|
||||
context_store,
|
||||
matches: Vec::new(),
|
||||
selected_index: 0,
|
||||
}
|
||||
@@ -214,7 +214,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
let buffer = open_buffer_task.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.delegate.context_strip.update(cx, |context_strip, cx| {
|
||||
this.delegate.context_store.update(cx, |context_store, cx| {
|
||||
let mut text = String::new();
|
||||
text.push_str(&codeblock_fence_for_path(Some(&path), None));
|
||||
text.push_str(&buffer.read(cx).text());
|
||||
@@ -224,7 +224,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
|
||||
text.push_str("```\n");
|
||||
|
||||
context_strip.insert_context(
|
||||
context_store.insert_context(
|
||||
ContextKind::File,
|
||||
path.to_string_lossy().to_string(),
|
||||
text,
|
||||
|
||||
@@ -7,7 +7,7 @@ use ui::{prelude::*, ListItem};
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_strip::ContextStrip;
|
||||
use crate::context_store;
|
||||
use crate::thread::ThreadId;
|
||||
use crate::thread_store::ThreadStore;
|
||||
|
||||
@@ -19,11 +19,11 @@ impl ThreadContextPicker {
|
||||
pub fn new(
|
||||
thread_store: WeakModel<ThreadStore>,
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
context_strip: WeakView<ContextStrip>,
|
||||
context_store: WeakModel<context_store::ContextStore>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let delegate =
|
||||
ThreadContextPickerDelegate::new(thread_store, context_picker, context_strip);
|
||||
ThreadContextPickerDelegate::new(thread_store, context_picker, context_store);
|
||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
|
||||
|
||||
ThreadContextPicker { picker }
|
||||
@@ -51,7 +51,7 @@ struct ThreadContextEntry {
|
||||
pub struct ThreadContextPickerDelegate {
|
||||
thread_store: WeakModel<ThreadStore>,
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
context_strip: WeakView<ContextStrip>,
|
||||
context_store: WeakModel<context_store::ContextStore>,
|
||||
matches: Vec<ThreadContextEntry>,
|
||||
selected_index: usize,
|
||||
}
|
||||
@@ -60,12 +60,12 @@ impl ThreadContextPickerDelegate {
|
||||
pub fn new(
|
||||
thread_store: WeakModel<ThreadStore>,
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
context_strip: WeakView<ContextStrip>,
|
||||
context_store: WeakModel<context_store::ContextStore>,
|
||||
) -> Self {
|
||||
ThreadContextPickerDelegate {
|
||||
thread_store,
|
||||
context_picker,
|
||||
context_strip,
|
||||
context_store,
|
||||
matches: Vec::new(),
|
||||
selected_index: 0,
|
||||
}
|
||||
@@ -157,8 +157,8 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||
return;
|
||||
};
|
||||
|
||||
self.context_strip
|
||||
.update(cx, |context_strip, cx| {
|
||||
self.context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
let text = thread.update(cx, |thread, _cx| {
|
||||
let mut text = String::new();
|
||||
|
||||
@@ -177,7 +177,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||
text
|
||||
});
|
||||
|
||||
context_strip.insert_context(ContextKind::Thread, entry.summary.clone(), text);
|
||||
context_store.insert_context(ContextKind::Thread, entry.summary.clone(), text);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
47
crates/assistant2/src/context_store.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use gpui::SharedString;
|
||||
|
||||
use crate::context::{Context, ContextId, ContextKind};
|
||||
|
||||
pub struct ContextStore {
|
||||
context: Vec<Context>,
|
||||
next_context_id: ContextId,
|
||||
}
|
||||
|
||||
impl ContextStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
context: Vec::new(),
|
||||
next_context_id: ContextId(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context(&self) -> &Vec<Context> {
|
||||
&self.context
|
||||
}
|
||||
|
||||
pub fn drain(&mut self) -> Vec<Context> {
|
||||
self.context.drain(..).collect()
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.context.clear();
|
||||
}
|
||||
|
||||
pub fn insert_context(
|
||||
&mut self,
|
||||
kind: ContextKind,
|
||||
name: impl Into<SharedString>,
|
||||
text: impl Into<SharedString>,
|
||||
) {
|
||||
self.context.push(Context {
|
||||
id: self.next_context_id.post_inc(),
|
||||
name: name.into(),
|
||||
kind,
|
||||
text: text.into(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn remove_context(&mut self, id: &ContextId) {
|
||||
self.context.retain(|context| context.id != *id);
|
||||
}
|
||||
}
|
||||
@@ -1,100 +1,104 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{View, WeakModel, WeakView};
|
||||
use ui::{prelude::*, IconButtonShape, PopoverMenu, PopoverMenuHandle, Tooltip};
|
||||
use gpui::{FocusHandle, Model, View, WeakModel, WeakView};
|
||||
use ui::{prelude::*, PopoverMenu, PopoverMenuHandle, Tooltip};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::{Context, ContextId, ContextKind};
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::ui::ContextPill;
|
||||
use crate::ToggleContextPicker;
|
||||
|
||||
pub struct ContextStrip {
|
||||
context: Vec<Context>,
|
||||
next_context_id: ContextId,
|
||||
context_store: Model<ContextStore>,
|
||||
context_picker: View<ContextPicker>,
|
||||
pub(crate) context_picker_handle: PopoverMenuHandle<ContextPicker>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl ContextStrip {
|
||||
pub fn new(
|
||||
context_store: Model<ContextStore>,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: WeakModel<ThreadStore>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
focus_handle: FocusHandle,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let weak_self = cx.view().downgrade();
|
||||
|
||||
Self {
|
||||
context: Vec::new(),
|
||||
next_context_id: ContextId(0),
|
||||
context_store: context_store.clone(),
|
||||
context_picker: cx.new_view(|cx| {
|
||||
ContextPicker::new(workspace.clone(), thread_store.clone(), weak_self, cx)
|
||||
ContextPicker::new(
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
context_store.downgrade(),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
context_picker_handle: PopoverMenuHandle::default(),
|
||||
context_picker_menu_handle,
|
||||
focus_handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drain(&mut self) -> Vec<Context> {
|
||||
self.context.drain(..).collect()
|
||||
}
|
||||
|
||||
pub fn insert_context(
|
||||
&mut self,
|
||||
kind: ContextKind,
|
||||
name: impl Into<SharedString>,
|
||||
text: impl Into<SharedString>,
|
||||
) {
|
||||
self.context.push(Context {
|
||||
id: self.next_context_id.post_inc(),
|
||||
name: name.into(),
|
||||
kind,
|
||||
text: text.into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ContextStrip {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let context = self.context_store.read(cx).context();
|
||||
let context_picker = self.context_picker.clone();
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
h_flex()
|
||||
.flex_wrap()
|
||||
.gap_2()
|
||||
.gap_1()
|
||||
.child(
|
||||
PopoverMenu::new("context-picker")
|
||||
.menu(move |_cx| Some(context_picker.clone()))
|
||||
.trigger(
|
||||
IconButton::new("add-context", IconName::Plus)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small),
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ui::ButtonStyle::Filled)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Add Context",
|
||||
&ToggleContextPicker,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
)
|
||||
.attach(gpui::AnchorCorner::TopLeft)
|
||||
.anchor(gpui::AnchorCorner::BottomLeft)
|
||||
.attach(gpui::Corner::TopLeft)
|
||||
.anchor(gpui::Corner::BottomLeft)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(-16.0),
|
||||
})
|
||||
.with_handle(self.context_picker_handle.clone()),
|
||||
.with_handle(self.context_picker_menu_handle.clone()),
|
||||
)
|
||||
.children(self.context.iter().map(|context| {
|
||||
.children(context.iter().map(|context| {
|
||||
ContextPill::new(context.clone()).on_remove({
|
||||
let context = context.clone();
|
||||
Rc::new(cx.listener(move |this, _event, cx| {
|
||||
this.context.retain(|other| other.id != context.id);
|
||||
let context_store = self.context_store.clone();
|
||||
Rc::new(cx.listener(move |_this, _event, cx| {
|
||||
context_store.update(cx, |this, _cx| {
|
||||
this.remove_context(&context.id);
|
||||
});
|
||||
cx.notify();
|
||||
}))
|
||||
})
|
||||
}))
|
||||
.when(!self.context.is_empty(), |parent| {
|
||||
.when(!context.is_empty(), |parent| {
|
||||
parent.child(
|
||||
IconButton::new("remove-all-context", IconName::Eraser)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(move |cx| Tooltip::text("Remove All Context", cx))
|
||||
.on_click(cx.listener(|this, _event, cx| {
|
||||
this.context.clear();
|
||||
cx.notify();
|
||||
})),
|
||||
.on_click({
|
||||
let context_store = self.context_store.clone();
|
||||
cx.listener(move |_this, _event, cx| {
|
||||
context_store.update(cx, |this, _cx| this.clear());
|
||||
cx.notify();
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
use crate::context::attach_context_to_message;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::ContextStrip;
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{
|
||||
assistant_settings::AssistantSettings,
|
||||
prompts::PromptBuilder,
|
||||
streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff},
|
||||
terminal_inline_assistant::TerminalInlineAssistant,
|
||||
CycleNextInlineAssist, CyclePreviousInlineAssist, ToggleInlineAssist,
|
||||
CycleNextInlineAssist, CyclePreviousInlineAssist,
|
||||
};
|
||||
use crate::{AssistantPanel, ToggleContextPicker};
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::{telemetry::Telemetry, ErrorExt};
|
||||
use collections::{hash_map, HashMap, HashSet, VecDeque};
|
||||
@@ -24,7 +30,8 @@ use futures::{channel::mpsc, future::LocalBoxFuture, join, SinkExt, Stream, Stre
|
||||
use gpui::{
|
||||
anchored, deferred, point, AnyElement, AppContext, ClickEvent, CursorStyle, EventEmitter,
|
||||
FocusHandle, FocusableView, FontWeight, Global, HighlightStyle, Model, ModelContext,
|
||||
Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView, WindowContext,
|
||||
Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakModel, WeakView,
|
||||
WindowContext,
|
||||
};
|
||||
use language::{Buffer, IndentKind, Point, Selection, TransactionId};
|
||||
use language_model::{
|
||||
@@ -54,7 +61,9 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
||||
use text::{OffsetRangeExt, ToPoint as _};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip};
|
||||
use ui::{
|
||||
prelude::*, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, PopoverMenuHandle, Tooltip,
|
||||
};
|
||||
use util::{RangeExt, ResultExt};
|
||||
use workspace::{dock::Panel, ShowConfiguration};
|
||||
use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
|
||||
@@ -66,9 +75,7 @@ pub fn init(
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
cx.set_global(InlineAssistant::new(fs, prompt_builder, telemetry));
|
||||
cx.observe_new_views(|workspace: &mut Workspace, cx| {
|
||||
workspace.register_action(InlineAssistant::toggle_inline_assist);
|
||||
|
||||
cx.observe_new_views(|_workspace: &mut Workspace, cx| {
|
||||
let workspace = cx.view().clone();
|
||||
InlineAssistant::update_global(cx, |inline_assistant, cx| {
|
||||
inline_assistant.register_workspace(&workspace, cx)
|
||||
@@ -178,10 +185,16 @@ impl InlineAssistant {
|
||||
) {
|
||||
if let Some(editor) = item.act_as::<Editor>(cx) {
|
||||
editor.update(cx, |editor, cx| {
|
||||
let thread_store = workspace
|
||||
.read(cx)
|
||||
.panel::<AssistantPanel>(cx)
|
||||
.map(|assistant_panel| assistant_panel.read(cx).thread_store().downgrade());
|
||||
|
||||
editor.push_code_action_provider(
|
||||
Rc::new(AssistantCodeActionProvider {
|
||||
editor: cx.view().downgrade(),
|
||||
workspace: workspace.downgrade(),
|
||||
thread_store,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
@@ -189,9 +202,9 @@ impl InlineAssistant {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_inline_assist(
|
||||
pub fn inline_assist(
|
||||
workspace: &mut Workspace,
|
||||
_action: &ToggleInlineAssist,
|
||||
_action: &zed_actions::InlineAssist,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
@@ -209,15 +222,19 @@ impl InlineAssistant {
|
||||
.map_or(false, |provider| provider.is_authenticated(cx))
|
||||
};
|
||||
|
||||
let thread_store = workspace
|
||||
.panel::<AssistantPanel>(cx)
|
||||
.map(|assistant_panel| assistant_panel.read(cx).thread_store().downgrade());
|
||||
|
||||
let handle_assist = |cx: &mut ViewContext<Workspace>| match inline_assist_target {
|
||||
InlineAssistTarget::Editor(active_editor) => {
|
||||
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||
assistant.assist(&active_editor, cx.view().downgrade(), cx)
|
||||
assistant.assist(&active_editor, cx.view().downgrade(), thread_store, cx)
|
||||
})
|
||||
}
|
||||
InlineAssistTarget::Terminal(active_terminal) => {
|
||||
TerminalInlineAssistant::update_global(cx, |assistant, cx| {
|
||||
assistant.assist(&active_terminal, cx.view().downgrade(), cx)
|
||||
assistant.assist(&active_terminal, cx.view().downgrade(), thread_store, cx)
|
||||
})
|
||||
}
|
||||
};
|
||||
@@ -265,6 +282,7 @@ impl InlineAssistant {
|
||||
&mut self,
|
||||
editor: &View<Editor>,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let (snapshot, initial_selections) = editor.update(cx, |editor, cx| {
|
||||
@@ -343,11 +361,13 @@ impl InlineAssistant {
|
||||
let mut assist_to_focus = None;
|
||||
for range in codegen_ranges {
|
||||
let assist_id = self.next_assist_id.post_inc();
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new());
|
||||
let codegen = cx.new_model(|cx| {
|
||||
Codegen::new(
|
||||
editor.read(cx).buffer().clone(),
|
||||
range.clone(),
|
||||
None,
|
||||
context_store.clone(),
|
||||
self.telemetry.clone(),
|
||||
self.prompt_builder.clone(),
|
||||
cx,
|
||||
@@ -363,6 +383,9 @@ impl InlineAssistant {
|
||||
prompt_buffer.clone(),
|
||||
codegen.clone(),
|
||||
self.fs.clone(),
|
||||
context_store,
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -430,6 +453,7 @@ impl InlineAssistant {
|
||||
initial_transaction_id: Option<TransactionId>,
|
||||
focus: bool,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> InlineAssistId {
|
||||
let assist_group_id = self.next_assist_group_id.post_inc();
|
||||
@@ -445,11 +469,14 @@ impl InlineAssistant {
|
||||
range.end = range.end.bias_right(&snapshot);
|
||||
}
|
||||
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new());
|
||||
|
||||
let codegen = cx.new_model(|cx| {
|
||||
Codegen::new(
|
||||
editor.read(cx).buffer().clone(),
|
||||
range.clone(),
|
||||
initial_transaction_id,
|
||||
context_store.clone(),
|
||||
self.telemetry.clone(),
|
||||
self.prompt_builder.clone(),
|
||||
cx,
|
||||
@@ -465,6 +492,9 @@ impl InlineAssistant {
|
||||
prompt_buffer.clone(),
|
||||
codegen.clone(),
|
||||
self.fs.clone(),
|
||||
context_store,
|
||||
workspace.clone(),
|
||||
thread_store,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1456,6 +1486,8 @@ enum PromptEditorEvent {
|
||||
struct PromptEditor {
|
||||
id: InlineAssistId,
|
||||
editor: View<Editor>,
|
||||
context_strip: View<ContextStrip>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
language_model_selector: View<LanguageModelSelector>,
|
||||
edited_since_done: bool,
|
||||
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
||||
@@ -1473,11 +1505,7 @@ impl EventEmitter<PromptEditorEvent> for PromptEditor {}
|
||||
impl Render for PromptEditor {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let gutter_dimensions = *self.gutter_dimensions.lock();
|
||||
let mut buttons = vec![Button::new("add-context", "Add Context")
|
||||
.style(ButtonStyle::Filled)
|
||||
.icon(IconName::Plus)
|
||||
.icon_position(IconPosition::Start)
|
||||
.into_any_element()];
|
||||
let mut buttons = Vec::new();
|
||||
let codegen = self.codegen.read(cx);
|
||||
if codegen.alternative_count(cx) > 1 {
|
||||
buttons.push(self.render_cycle_controls(cx));
|
||||
@@ -1570,91 +1598,115 @@ impl Render for PromptEditor {
|
||||
}
|
||||
});
|
||||
|
||||
h_flex()
|
||||
.key_context("PromptEditor")
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.block_mouse_down()
|
||||
.cursor(CursorStyle::Arrow)
|
||||
v_flex()
|
||||
.border_y_1()
|
||||
.border_color(cx.theme().status().info_border)
|
||||
.size_full()
|
||||
.py(cx.line_height() / 2.5)
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::move_up))
|
||||
.on_action(cx.listener(Self::move_down))
|
||||
.capture_action(cx.listener(Self::cycle_prev))
|
||||
.capture_action(cx.listener(Self::cycle_next))
|
||||
.child(
|
||||
h_flex()
|
||||
.w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.child(LanguageModelSelectorPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Using {}",
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|model| model.name().0)
|
||||
.unwrap_or_else(|| "No model selected".into()),
|
||||
),
|
||||
None,
|
||||
"Change Model",
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
))
|
||||
.map(|el| {
|
||||
let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
|
||||
return el;
|
||||
};
|
||||
|
||||
let error_message = SharedString::from(error.to_string());
|
||||
if error.error_code() == proto::ErrorCode::RateLimitExceeded
|
||||
&& cx.has_flag::<ZedPro>()
|
||||
{
|
||||
el.child(
|
||||
v_flex()
|
||||
.child(
|
||||
IconButton::new("rate-limit-error", IconName::XCircle)
|
||||
.toggle_state(self.show_rate_limit_notice)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(cx.listener(Self::toggle_rate_limit_notice)),
|
||||
)
|
||||
.children(self.show_rate_limit_notice.then(|| {
|
||||
deferred(
|
||||
anchored()
|
||||
.position_mode(gpui::AnchoredPositionMode::Local)
|
||||
.position(point(px(0.), px(24.)))
|
||||
.anchor(gpui::AnchorCorner::TopLeft)
|
||||
.child(self.render_rate_limit_notice(cx)),
|
||||
.key_context("PromptEditor")
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.block_mouse_down()
|
||||
.cursor(CursorStyle::Arrow)
|
||||
.on_action(cx.listener(Self::toggle_context_picker))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::move_up))
|
||||
.on_action(cx.listener(Self::move_down))
|
||||
.capture_action(cx.listener(Self::cycle_prev))
|
||||
.capture_action(cx.listener(Self::cycle_next))
|
||||
.child(
|
||||
h_flex()
|
||||
.w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.child(LanguageModelSelectorPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Using {}",
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|model| model.name().0)
|
||||
.unwrap_or_else(|| "No model selected".into()),
|
||||
),
|
||||
None,
|
||||
"Change Model",
|
||||
cx,
|
||||
)
|
||||
})),
|
||||
)
|
||||
} else {
|
||||
el.child(
|
||||
div()
|
||||
.id("error")
|
||||
.tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
|
||||
.child(
|
||||
Icon::new(IconName::XCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
),
|
||||
)
|
||||
}
|
||||
}),
|
||||
}),
|
||||
))
|
||||
.map(|el| {
|
||||
let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx)
|
||||
else {
|
||||
return el;
|
||||
};
|
||||
|
||||
let error_message = SharedString::from(error.to_string());
|
||||
if error.error_code() == proto::ErrorCode::RateLimitExceeded
|
||||
&& cx.has_flag::<ZedPro>()
|
||||
{
|
||||
el.child(
|
||||
v_flex()
|
||||
.child(
|
||||
IconButton::new(
|
||||
"rate-limit-error",
|
||||
IconName::XCircle,
|
||||
)
|
||||
.toggle_state(self.show_rate_limit_notice)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(
|
||||
cx.listener(Self::toggle_rate_limit_notice),
|
||||
),
|
||||
)
|
||||
.children(self.show_rate_limit_notice.then(|| {
|
||||
deferred(
|
||||
anchored()
|
||||
.position_mode(
|
||||
gpui::AnchoredPositionMode::Local,
|
||||
)
|
||||
.position(point(px(0.), px(24.)))
|
||||
.anchor(gpui::Corner::TopLeft)
|
||||
.child(self.render_rate_limit_notice(cx)),
|
||||
)
|
||||
})),
|
||||
)
|
||||
} else {
|
||||
el.child(
|
||||
div()
|
||||
.id("error")
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(error_message.clone(), cx)
|
||||
})
|
||||
.child(
|
||||
Icon::new(IconName::XCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
),
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(div().flex_1().child(self.render_editor(cx)))
|
||||
.child(h_flex().gap_2().pr_6().children(buttons)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.child(
|
||||
h_flex()
|
||||
.w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
|
||||
.justify_center()
|
||||
.gap_2(),
|
||||
)
|
||||
.child(self.context_strip.clone()),
|
||||
)
|
||||
.child(div().flex_1().child(self.render_editor(cx)))
|
||||
.child(h_flex().gap_2().pr_6().children(buttons))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1675,6 +1727,9 @@ impl PromptEditor {
|
||||
prompt_buffer: Model<MultiBuffer>,
|
||||
codegen: Model<Codegen>,
|
||||
fs: Arc<dyn Fs>,
|
||||
context_store: Model<ContextStore>,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let prompt_editor = cx.new_view(|cx| {
|
||||
@@ -1695,10 +1750,22 @@ impl PromptEditor {
|
||||
editor.set_placeholder_text(Self::placeholder_text(codegen.read(cx)), cx);
|
||||
editor
|
||||
});
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let mut this = Self {
|
||||
id,
|
||||
editor: prompt_editor,
|
||||
editor: prompt_editor.clone(),
|
||||
context_strip: cx.new_view(|cx| {
|
||||
ContextStrip::new(
|
||||
context_store,
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
context_picker_menu_handle,
|
||||
language_model_selector: cx.new_view(|cx| {
|
||||
let fs = fs.clone();
|
||||
LanguageModelSelector::new(
|
||||
@@ -1856,6 +1923,10 @@ impl PromptEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
|
||||
self.context_picker_menu_handle.toggle(cx);
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
|
||||
match self.codegen.read(cx).status(cx) {
|
||||
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||
@@ -2293,6 +2364,7 @@ pub struct Codegen {
|
||||
buffer: Model<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
initial_transaction_id: Option<TransactionId>,
|
||||
context_store: Model<ContextStore>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
is_insertion: bool,
|
||||
@@ -2303,6 +2375,7 @@ impl Codegen {
|
||||
buffer: Model<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
initial_transaction_id: Option<TransactionId>,
|
||||
context_store: Model<ContextStore>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
@@ -2312,6 +2385,7 @@ impl Codegen {
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
false,
|
||||
Some(context_store.clone()),
|
||||
Some(telemetry.clone()),
|
||||
builder.clone(),
|
||||
cx,
|
||||
@@ -2326,6 +2400,7 @@ impl Codegen {
|
||||
buffer,
|
||||
range,
|
||||
initial_transaction_id,
|
||||
context_store,
|
||||
telemetry,
|
||||
builder,
|
||||
};
|
||||
@@ -2398,6 +2473,7 @@ impl Codegen {
|
||||
self.buffer.clone(),
|
||||
self.range.clone(),
|
||||
false,
|
||||
Some(self.context_store.clone()),
|
||||
Some(self.telemetry.clone()),
|
||||
self.builder.clone(),
|
||||
cx,
|
||||
@@ -2477,6 +2553,7 @@ pub struct CodegenAlternative {
|
||||
status: CodegenStatus,
|
||||
generation: Task<()>,
|
||||
diff: Diff,
|
||||
context_store: Option<Model<ContextStore>>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
_subscription: gpui::Subscription,
|
||||
builder: Arc<PromptBuilder>,
|
||||
@@ -2515,6 +2592,7 @@ impl CodegenAlternative {
|
||||
buffer: Model<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
active: bool,
|
||||
context_store: Option<Model<ContextStore>>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
@@ -2552,6 +2630,7 @@ impl CodegenAlternative {
|
||||
status: CodegenStatus::Idle,
|
||||
generation: Task::ready(()),
|
||||
diff: Diff::default(),
|
||||
context_store,
|
||||
telemetry,
|
||||
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
|
||||
builder,
|
||||
@@ -2637,7 +2716,11 @@ impl CodegenAlternative {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_request(&self, user_prompt: String, cx: &AppContext) -> Result<LanguageModelRequest> {
|
||||
fn build_request(
|
||||
&self,
|
||||
user_prompt: String,
|
||||
cx: &mut AppContext,
|
||||
) -> Result<LanguageModelRequest> {
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let language = buffer.language_at(self.range.start);
|
||||
let language_name = if let Some(language) = language.as_ref() {
|
||||
@@ -2670,15 +2753,24 @@ impl CodegenAlternative {
|
||||
.generate_inline_transformation_prompt(user_prompt, language_name, buffer, range)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to generate content prompt: {}", e))?;
|
||||
|
||||
let mut request_message = LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: Vec::new(),
|
||||
cache: false,
|
||||
};
|
||||
|
||||
if let Some(context_store) = &self.context_store {
|
||||
let context = context_store.update(cx, |this, _cx| this.context().clone());
|
||||
attach_context_to_message(&mut request_message, context);
|
||||
}
|
||||
|
||||
request_message.content.push(prompt.into());
|
||||
|
||||
Ok(LanguageModelRequest {
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: None,
|
||||
messages: vec![LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![prompt.into()],
|
||||
cache: false,
|
||||
}],
|
||||
messages: vec![request_message],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3273,6 +3365,7 @@ where
|
||||
struct AssistantCodeActionProvider {
|
||||
editor: WeakView<Editor>,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
}
|
||||
|
||||
impl CodeActionProvider for AssistantCodeActionProvider {
|
||||
@@ -3337,6 +3430,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
||||
) -> Task<Result<ProjectTransaction>> {
|
||||
let editor = self.editor.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
let thread_store = self.thread_store.clone();
|
||||
cx.spawn(|mut cx| async move {
|
||||
let editor = editor.upgrade().context("editor was released")?;
|
||||
let range = editor
|
||||
@@ -3384,6 +3478,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
||||
None,
|
||||
true,
|
||||
workspace,
|
||||
thread_store,
|
||||
cx,
|
||||
);
|
||||
assistant.start_assist(assist_id, cx);
|
||||
@@ -3469,6 +3564,7 @@ mod tests {
|
||||
range.clone(),
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
prompt_builder,
|
||||
cx,
|
||||
)
|
||||
@@ -3533,6 +3629,7 @@ mod tests {
|
||||
range.clone(),
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
prompt_builder,
|
||||
cx,
|
||||
)
|
||||
@@ -3600,6 +3697,7 @@ mod tests {
|
||||
range.clone(),
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
prompt_builder,
|
||||
cx,
|
||||
)
|
||||
@@ -3666,6 +3764,7 @@ mod tests {
|
||||
range.clone(),
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
prompt_builder,
|
||||
cx,
|
||||
)
|
||||
@@ -3721,6 +3820,7 @@ mod tests {
|
||||
range.clone(),
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
prompt_builder,
|
||||
cx,
|
||||
)
|
||||
|
||||
@@ -1,54 +1,97 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use fs::Fs;
|
||||
use gpui::{AppContext, FocusableView, Model, TextStyle, View, WeakModel, WeakView};
|
||||
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use settings::Settings;
|
||||
use settings::{update_settings_file, Settings};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, KeyBinding, Tooltip};
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, KeyBinding, PopoverMenuHandle,
|
||||
Tooltip,
|
||||
};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::assistant_settings::AssistantSettings;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::ContextStrip;
|
||||
use crate::thread::{RequestKind, Thread};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{Chat, ToggleModelSelector};
|
||||
use crate::{Chat, ToggleContextPicker, ToggleModelSelector};
|
||||
|
||||
pub struct MessageEditor {
|
||||
thread: Model<Thread>,
|
||||
editor: View<Editor>,
|
||||
context_store: Model<ContextStore>,
|
||||
context_strip: View<ContextStrip>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
language_model_selector: View<LanguageModelSelector>,
|
||||
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
use_tools: bool,
|
||||
}
|
||||
|
||||
impl MessageEditor {
|
||||
pub fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: WeakModel<ThreadStore>,
|
||||
thread: Model<Thread>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new());
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::auto_height(80, cx);
|
||||
editor.set_placeholder_text("Ask anything, @ to add context", cx);
|
||||
editor.set_show_indent_guides(false, cx);
|
||||
|
||||
editor
|
||||
});
|
||||
|
||||
Self {
|
||||
thread,
|
||||
editor: cx.new_view(|cx| {
|
||||
let mut editor = Editor::auto_height(80, cx);
|
||||
editor.set_placeholder_text("Ask anything or type @ to add context", cx);
|
||||
|
||||
editor
|
||||
editor: editor.clone(),
|
||||
context_store: context_store.clone(),
|
||||
context_strip: cx.new_view(|cx| {
|
||||
ContextStrip::new(
|
||||
context_store,
|
||||
workspace.clone(),
|
||||
Some(thread_store.clone()),
|
||||
editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
context_strip: cx
|
||||
.new_view(|cx| ContextStrip::new(workspace.clone(), thread_store.clone(), cx)),
|
||||
context_picker_menu_handle,
|
||||
language_model_selector: cx.new_view(|cx| {
|
||||
let fs = fs.clone();
|
||||
LanguageModelSelector::new(
|
||||
|model, _cx| {
|
||||
println!("Selected {:?}", model.name());
|
||||
move |model, cx| {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _cx| settings.set_model(model.clone()),
|
||||
);
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
language_model_selector_menu_handle: PopoverMenuHandle::default(),
|
||||
use_tools: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
|
||||
self.language_model_selector_menu_handle.toggle(cx);
|
||||
}
|
||||
|
||||
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
|
||||
self.context_picker_menu_handle.toggle(cx);
|
||||
}
|
||||
|
||||
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
|
||||
self.send_to_model(RequestKind::Chat, cx);
|
||||
}
|
||||
@@ -75,7 +118,7 @@ impl MessageEditor {
|
||||
editor.clear(cx);
|
||||
text
|
||||
});
|
||||
let context = self.context_strip.update(cx, |this, _cx| this.drain());
|
||||
let context = self.context_store.update(cx, |this, _cx| this.drain());
|
||||
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
thread.insert_user_message(user_message, context, cx);
|
||||
@@ -101,8 +144,8 @@ impl MessageEditor {
|
||||
}
|
||||
|
||||
fn render_language_model_selector(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
|
||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||
let focus_handle = self.language_model_selector.focus_handle(cx).clone();
|
||||
|
||||
LanguageModelSelectorPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
@@ -117,16 +160,8 @@ impl MessageEditor {
|
||||
.overflow_x_hidden()
|
||||
.flex_grow()
|
||||
.whitespace_nowrap()
|
||||
.child(match (active_provider, active_model) {
|
||||
(Some(provider), Some(model)) => h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(
|
||||
model.icon().unwrap_or_else(|| provider.icon()),
|
||||
)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
)
|
||||
.child(match active_model {
|
||||
Some(model) => h_flex()
|
||||
.child(
|
||||
Label::new(model.name().0)
|
||||
.size(LabelSize::Small)
|
||||
@@ -145,8 +180,11 @@ impl MessageEditor {
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
)
|
||||
.tooltip(move |cx| Tooltip::for_action("Change Model", &ToggleModelSelector, cx)),
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
||||
}),
|
||||
)
|
||||
.with_handle(self.language_model_selector_menu_handle.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,18 +197,21 @@ impl FocusableView for MessageEditor {
|
||||
impl Render for MessageEditor {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let font_size = TextSize::Default.rems(cx);
|
||||
let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
|
||||
let line_height = font_size.to_pixels(cx.rem_size()) * 1.5;
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
let bg_color = cx.theme().colors().editor_background;
|
||||
|
||||
v_flex()
|
||||
.key_context("MessageEditor")
|
||||
.on_action(cx.listener(Self::chat))
|
||||
.on_action(cx.listener(Self::toggle_model_selector))
|
||||
.on_action(cx.listener(Self::toggle_context_picker))
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.p_2()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.bg(bg_color)
|
||||
.child(self.context_strip.clone())
|
||||
.child({
|
||||
.child(div().id("thread_editor").overflow_y_scroll().h_12().child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().editor_foreground,
|
||||
@@ -185,17 +226,17 @@ impl Render for MessageEditor {
|
||||
EditorElement::new(
|
||||
&self.editor,
|
||||
EditorStyle {
|
||||
background: cx.theme().colors().editor_background,
|
||||
background: bg_color,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: text_style,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
})
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(h_flex().gap_2().child(CheckboxWithLabel::new(
|
||||
.child(CheckboxWithLabel::new(
|
||||
"use-tools",
|
||||
Label::new("Tools"),
|
||||
self.use_tools.into(),
|
||||
@@ -205,10 +246,10 @@ impl Render for MessageEditor {
|
||||
ToggleState::Unselected | ToggleState::Indeterminate => false,
|
||||
};
|
||||
}),
|
||||
)))
|
||||
))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.gap_1()
|
||||
.child(self.render_language_model_selector(cx))
|
||||
.child(
|
||||
ButtonLike::new("chat")
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
use crate::assistant_settings::AssistantSettings;
|
||||
use crate::context::attach_context_to_message;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::ContextStrip;
|
||||
use crate::prompts::PromptBuilder;
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::ToggleContextPicker;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, VecDeque};
|
||||
@@ -11,7 +17,7 @@ use fs::Fs;
|
||||
use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
use gpui::{
|
||||
AppContext, Context, EventEmitter, FocusHandle, FocusableView, Global, Model, ModelContext,
|
||||
Subscription, Task, TextStyle, UpdateGlobal, View, WeakView,
|
||||
Subscription, Task, TextStyle, UpdateGlobal, View, WeakModel, WeakView,
|
||||
};
|
||||
use language::Buffer;
|
||||
use language_model::{
|
||||
@@ -25,7 +31,7 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use terminal::Terminal;
|
||||
use terminal_view::TerminalView;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, text_for_action, IconButtonShape, Tooltip};
|
||||
use ui::{prelude::*, text_for_action, IconButtonShape, PopoverMenuHandle, Tooltip};
|
||||
use util::ResultExt;
|
||||
use workspace::{notifications::NotificationId, Toast, Workspace};
|
||||
|
||||
@@ -83,6 +89,7 @@ impl TerminalInlineAssistant {
|
||||
&mut self,
|
||||
terminal_view: &View<TerminalView>,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let terminal = terminal_view.read(cx).terminal().clone();
|
||||
@@ -90,6 +97,7 @@ impl TerminalInlineAssistant {
|
||||
let prompt_buffer = cx.new_model(|cx| {
|
||||
MultiBuffer::singleton(cx.new_model(|cx| Buffer::local(String::new(), cx)), cx)
|
||||
});
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new());
|
||||
let codegen = cx.new_model(|_| Codegen::new(terminal, self.telemetry.clone()));
|
||||
|
||||
let prompt_editor = cx.new_view(|cx| {
|
||||
@@ -99,6 +107,9 @@ impl TerminalInlineAssistant {
|
||||
prompt_buffer.clone(),
|
||||
codegen,
|
||||
self.fs.clone(),
|
||||
context_store.clone(),
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -116,6 +127,7 @@ impl TerminalInlineAssistant {
|
||||
terminal_view,
|
||||
prompt_editor,
|
||||
workspace.clone(),
|
||||
context_store,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -246,12 +258,21 @@ impl TerminalInlineAssistant {
|
||||
&latest_output,
|
||||
)?;
|
||||
|
||||
let mut request_message = LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![],
|
||||
cache: false,
|
||||
};
|
||||
|
||||
let context = assist
|
||||
.context_store
|
||||
.update(cx, |this, _cx| this.context().clone());
|
||||
attach_context_to_message(&mut request_message, context);
|
||||
|
||||
request_message.content.push(prompt.into());
|
||||
|
||||
Ok(LanguageModelRequest {
|
||||
messages: vec![LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![prompt.into()],
|
||||
cache: false,
|
||||
}],
|
||||
messages: vec![request_message],
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: None,
|
||||
@@ -362,6 +383,7 @@ struct TerminalInlineAssist {
|
||||
prompt_editor: Option<View<PromptEditor>>,
|
||||
codegen: Model<Codegen>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: Model<ContextStore>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
@@ -371,6 +393,7 @@ impl TerminalInlineAssist {
|
||||
terminal: &View<TerminalView>,
|
||||
prompt_editor: View<PromptEditor>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: Model<ContextStore>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self {
|
||||
let codegen = prompt_editor.read(cx).codegen.clone();
|
||||
@@ -379,6 +402,7 @@ impl TerminalInlineAssist {
|
||||
prompt_editor: Some(prompt_editor.clone()),
|
||||
codegen: codegen.clone(),
|
||||
workspace: workspace.clone(),
|
||||
context_store,
|
||||
_subscriptions: vec![
|
||||
cx.subscribe(&prompt_editor, |prompt_editor, event, cx| {
|
||||
TerminalInlineAssistant::update_global(cx, |this, cx| {
|
||||
@@ -437,6 +461,8 @@ struct PromptEditor {
|
||||
id: TerminalInlineAssistId,
|
||||
height_in_lines: u8,
|
||||
editor: View<Editor>,
|
||||
context_strip: View<ContextStrip>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
language_model_selector: View<LanguageModelSelector>,
|
||||
edited_since_done: bool,
|
||||
prompt_history: VecDeque<String>,
|
||||
@@ -452,11 +478,7 @@ impl EventEmitter<PromptEditorEvent> for PromptEditor {}
|
||||
impl Render for PromptEditor {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let status = &self.codegen.read(cx).status;
|
||||
let mut buttons = vec![Button::new("add-context", "Add Context")
|
||||
.style(ButtonStyle::Filled)
|
||||
.icon(IconName::Plus)
|
||||
.icon_position(IconPosition::Start)
|
||||
.into_any_element()];
|
||||
let mut buttons = Vec::new();
|
||||
|
||||
buttons.extend(match status {
|
||||
CodegenStatus::Idle => vec![
|
||||
@@ -554,64 +576,71 @@ impl Render for PromptEditor {
|
||||
}
|
||||
});
|
||||
|
||||
h_flex()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
v_flex()
|
||||
.border_y_1()
|
||||
.border_color(cx.theme().status().info_border)
|
||||
.py_2()
|
||||
.h_full()
|
||||
.w_full()
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::secondary_confirm))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::move_up))
|
||||
.on_action(cx.listener(Self::move_down))
|
||||
.size_full()
|
||||
.child(
|
||||
h_flex()
|
||||
.w_12()
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.child(LanguageModelSelectorPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Using {}",
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|model| model.name().0)
|
||||
.unwrap_or_else(|| "No model selected".into()),
|
||||
),
|
||||
None,
|
||||
"Change Model",
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
))
|
||||
.children(
|
||||
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
|
||||
let error_message = SharedString::from(error.to_string());
|
||||
Some(
|
||||
div()
|
||||
.id("error")
|
||||
.tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
|
||||
.child(
|
||||
Icon::new(IconName::XCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
),
|
||||
.key_context("PromptEditor")
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.on_action(cx.listener(Self::toggle_context_picker))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::secondary_confirm))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::move_up))
|
||||
.on_action(cx.listener(Self::move_down))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_12()
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.child(LanguageModelSelectorPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Using {}",
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|model| model.name().0)
|
||||
.unwrap_or_else(|| "No model selected".into()),
|
||||
),
|
||||
None,
|
||||
"Change Model",
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
))
|
||||
.children(
|
||||
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
|
||||
let error_message = SharedString::from(error.to_string());
|
||||
Some(
|
||||
div()
|
||||
.id("error")
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(error_message.clone(), cx)
|
||||
})
|
||||
.child(
|
||||
Icon::new(IconName::XCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(div().flex_1().child(self.render_prompt_editor(cx)))
|
||||
.child(h_flex().gap_1().pr_4().children(buttons)),
|
||||
)
|
||||
.child(div().flex_1().child(self.render_prompt_editor(cx)))
|
||||
.child(h_flex().gap_1().pr_4().children(buttons))
|
||||
.child(h_flex().child(self.context_strip.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -631,6 +660,9 @@ impl PromptEditor {
|
||||
prompt_buffer: Model<MultiBuffer>,
|
||||
codegen: Model<Codegen>,
|
||||
fs: Arc<dyn Fs>,
|
||||
context_store: Model<ContextStore>,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let prompt_editor = cx.new_view(|cx| {
|
||||
@@ -647,11 +679,23 @@ impl PromptEditor {
|
||||
editor.set_placeholder_text(Self::placeholder_text(cx), cx);
|
||||
editor
|
||||
});
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let mut this = Self {
|
||||
id,
|
||||
height_in_lines: 1,
|
||||
editor: prompt_editor,
|
||||
editor: prompt_editor.clone(),
|
||||
context_strip: cx.new_view(|cx| {
|
||||
ContextStrip::new(
|
||||
context_store,
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
context_picker_menu_handle,
|
||||
language_model_selector: cx.new_view(|cx| {
|
||||
let fs = fs.clone();
|
||||
LanguageModelSelector::new(
|
||||
@@ -760,6 +804,10 @@ impl PromptEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
|
||||
self.context_picker_menu_handle.toggle(cx);
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
|
||||
match &self.codegen.read(cx).status {
|
||||
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||
|
||||
@@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize};
|
||||
use util::{post_inc, TryFutureExt as _};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::context::{Context, ContextKind};
|
||||
use crate::context::{attach_context_to_message, Context};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum RequestKind {
|
||||
@@ -192,51 +192,7 @@ impl Thread {
|
||||
}
|
||||
|
||||
if let Some(context) = self.context_for_message(message.id) {
|
||||
let mut file_context = String::new();
|
||||
let mut fetch_context = String::new();
|
||||
let mut thread_context = String::new();
|
||||
|
||||
for context in context.iter() {
|
||||
match context.kind {
|
||||
ContextKind::File => {
|
||||
file_context.push_str(&context.text);
|
||||
file_context.push('\n');
|
||||
}
|
||||
ContextKind::FetchedUrl => {
|
||||
fetch_context.push_str(&context.name);
|
||||
fetch_context.push('\n');
|
||||
fetch_context.push_str(&context.text);
|
||||
fetch_context.push('\n');
|
||||
}
|
||||
ContextKind::Thread => {
|
||||
thread_context.push_str(&context.name);
|
||||
thread_context.push('\n');
|
||||
thread_context.push_str(&context.text);
|
||||
thread_context.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut context_text = String::new();
|
||||
if !file_context.is_empty() {
|
||||
context_text.push_str("The following files are available:\n");
|
||||
context_text.push_str(&file_context);
|
||||
}
|
||||
|
||||
if !fetch_context.is_empty() {
|
||||
context_text.push_str("The following fetched results are available\n");
|
||||
context_text.push_str(&fetch_context);
|
||||
}
|
||||
|
||||
if !thread_context.is_empty() {
|
||||
context_text
|
||||
.push_str("The following previous conversation threads are available\n");
|
||||
context_text.push_str(&thread_context);
|
||||
}
|
||||
|
||||
request_message
|
||||
.content
|
||||
.push(MessageContent::Text(context_text))
|
||||
attach_context_to_message(&mut request_message, context.clone());
|
||||
}
|
||||
|
||||
if !message.text.is_empty() {
|
||||
|
||||
@@ -66,10 +66,10 @@ impl Render for ThreadHistory {
|
||||
threads[range]
|
||||
.iter()
|
||||
.map(|thread| {
|
||||
PastThread::new(
|
||||
h_flex().w_full().pb_1().child(PastThread::new(
|
||||
thread.clone(),
|
||||
history.assistant_panel.clone(),
|
||||
)
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
@@ -120,9 +120,13 @@ impl RenderOnce for PastThread {
|
||||
|
||||
ListItem::new(("past-thread", self.thread.entity_id()))
|
||||
.outlined()
|
||||
.start_slot(Icon::new(IconName::MessageBubbles))
|
||||
.start_slot(
|
||||
Icon::new(IconName::MessageCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.child(Label::new(summary).size(LabelSize::Small))
|
||||
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
|
||||
.end_slot(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
|
||||
@@ -29,14 +29,17 @@ impl RenderOnce for ContextPill {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.px_1()
|
||||
.pl_1p5()
|
||||
.pr_0p5()
|
||||
.pb(px(1.))
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.border_color(cx.theme().colors().border.opacity(0.5))
|
||||
.bg(cx.theme().colors().element_background)
|
||||
.rounded_md()
|
||||
.child(Label::new(self.context.name.clone()).size(LabelSize::Small))
|
||||
.when_some(self.on_remove, |parent, on_remove| {
|
||||
parent.child(
|
||||
IconButton::new("remove", IconName::Close)
|
||||
IconButton::new(("remove", self.context.id.0), IconName::Close)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click({
|
||||
|
||||
@@ -51,6 +51,7 @@ tokio-socks = { version = "0.5.2", default-features = false, features = ["future
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
worktree.workspace = true
|
||||
telemetry.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
clock = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -4,7 +4,8 @@ use crate::{ChannelId, TelemetrySettings};
|
||||
use anyhow::Result;
|
||||
use clock::SystemClock;
|
||||
use collections::{HashMap, HashSet};
|
||||
use futures::Future;
|
||||
use futures::channel::mpsc;
|
||||
use futures::{Future, StreamExt};
|
||||
use gpui::{AppContext, BackgroundExecutor, Task};
|
||||
use http_client::{self, AsyncBody, HttpClient, HttpClientWithUrl, Method, Request};
|
||||
use once_cell::sync::Lazy;
|
||||
@@ -17,9 +18,8 @@ use std::io::Write;
|
||||
use std::time::Instant;
|
||||
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||
use telemetry_events::{
|
||||
ActionEvent, AppEvent, AssistantEvent, CallEvent, EditEvent, EditorEvent, Event,
|
||||
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, InlineCompletionRating,
|
||||
InlineCompletionRatingEvent, ReplEvent, SettingEvent,
|
||||
AppEvent, AssistantEvent, CallEvent, EditEvent, Event, EventRequestBody, EventWrapper,
|
||||
InlineCompletionEvent, InlineCompletionRating, InlineCompletionRatingEvent, SettingEvent,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use worktree::{UpdatedEntriesSet, WorktreeId};
|
||||
@@ -245,7 +245,6 @@ impl Telemetry {
|
||||
})
|
||||
.detach();
|
||||
|
||||
// TODO: Replace all hardware stuff with nested SystemSpecs json
|
||||
let this = Arc::new(Self {
|
||||
clock,
|
||||
http_client: client,
|
||||
@@ -253,6 +252,21 @@ impl Telemetry {
|
||||
state,
|
||||
});
|
||||
|
||||
let (tx, mut rx) = mpsc::unbounded();
|
||||
::telemetry::init(tx);
|
||||
|
||||
cx.background_executor()
|
||||
.spawn({
|
||||
let this = Arc::downgrade(&this);
|
||||
async move {
|
||||
while let Some(event) = rx.next().await {
|
||||
let Some(state) = this.upgrade() else { break };
|
||||
state.report_event(Event::Flexible(event))
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
// We should only ever have one instance of Telemetry, leak the subscription to keep it alive
|
||||
// rather than store in TelemetryState, complicating spawn as subscriptions are not Send
|
||||
std::mem::forget(cx.on_app_quit({
|
||||
@@ -320,27 +334,6 @@ impl Telemetry {
|
||||
drop(state);
|
||||
}
|
||||
|
||||
pub fn report_editor_event(
|
||||
self: &Arc<Self>,
|
||||
file_extension: Option<String>,
|
||||
vim_mode: bool,
|
||||
operation: &'static str,
|
||||
copilot_enabled: bool,
|
||||
copilot_enabled_for_language: bool,
|
||||
is_via_ssh: bool,
|
||||
) {
|
||||
let event = Event::Editor(EditorEvent {
|
||||
file_extension,
|
||||
vim_mode,
|
||||
operation: operation.into(),
|
||||
copilot_enabled,
|
||||
copilot_enabled_for_language,
|
||||
is_via_ssh,
|
||||
});
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
|
||||
pub fn report_inline_completion_event(
|
||||
self: &Arc<Self>,
|
||||
provider: String,
|
||||
@@ -410,13 +403,6 @@ impl Telemetry {
|
||||
self.report_event(event)
|
||||
}
|
||||
|
||||
pub fn report_extension_event(self: &Arc<Self>, extension_id: Arc<str>, version: Arc<str>) {
|
||||
self.report_event(Event::Extension(ExtensionEvent {
|
||||
extension_id,
|
||||
version,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn log_edit_event(self: &Arc<Self>, environment: &'static str, is_via_ssh: bool) {
|
||||
let mut state = self.state.lock();
|
||||
let period_data = state.event_coalescer.log_event(environment);
|
||||
@@ -436,15 +422,6 @@ impl Telemetry {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_action_event(self: &Arc<Self>, source: &'static str, action: String) {
|
||||
let event = Event::Action(ActionEvent {
|
||||
source: source.to_string(),
|
||||
action,
|
||||
});
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
|
||||
pub fn report_discovered_project_events(
|
||||
self: &Arc<Self>,
|
||||
worktree_id: WorktreeId,
|
||||
@@ -491,21 +468,6 @@ impl Telemetry {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_repl_event(
|
||||
self: &Arc<Self>,
|
||||
kernel_language: String,
|
||||
kernel_status: String,
|
||||
repl_session_id: String,
|
||||
) {
|
||||
let event = Event::Repl(ReplEvent {
|
||||
kernel_language,
|
||||
kernel_status,
|
||||
repl_session_id,
|
||||
});
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
|
||||
fn report_event(self: &Arc<Self>, event: Event) {
|
||||
let mut state = self.state.lock();
|
||||
|
||||
|
||||
@@ -610,6 +610,10 @@ fn for_snowflake(
|
||||
"Kernel Status Changed".to_string(),
|
||||
serde_json::to_value(e).unwrap(),
|
||||
),
|
||||
Event::Flexible(e) => (
|
||||
e.event_type.clone(),
|
||||
serde_json::to_value(&e.event_properties).unwrap(),
|
||||
),
|
||||
};
|
||||
|
||||
if let serde_json::Value::Object(ref mut map) = event_properties {
|
||||
|
||||
@@ -9,6 +9,7 @@ use editor::{
|
||||
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, Redo, Rename,
|
||||
ToggleCodeActions, Undo,
|
||||
},
|
||||
display_map::RowInfo,
|
||||
test::editor_test_context::{AssertionContextManager, EditorTestContext},
|
||||
Editor,
|
||||
};
|
||||
@@ -20,7 +21,6 @@ use language::{
|
||||
language_settings::{AllLanguageSettings, InlayHintSettings},
|
||||
FakeLspAdapter,
|
||||
};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use project::{
|
||||
project_settings::{InlineBlameSettings, ProjectSettings},
|
||||
SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
||||
@@ -2075,7 +2075,15 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
let blame = editor_b.blame().expect("editor_b should have blame now");
|
||||
let entries = blame.update(cx, |blame, cx| {
|
||||
blame
|
||||
.blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
|
||||
.blame_for_rows(
|
||||
&(0..4)
|
||||
.map(|row| RowInfo {
|
||||
buffer_row: Some(row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx,
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
@@ -2114,7 +2122,15 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
let blame = editor_b.blame().expect("editor_b should have blame now");
|
||||
let entries = blame.update(cx, |blame, cx| {
|
||||
blame
|
||||
.blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
|
||||
.blame_for_rows(
|
||||
&(0..4)
|
||||
.map(|row| RowInfo {
|
||||
buffer_row: Some(row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx,
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
@@ -2141,7 +2157,15 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
let blame = editor_b.blame().expect("editor_b should have blame now");
|
||||
let entries = blame.update(cx, |blame, cx| {
|
||||
blame
|
||||
.blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
|
||||
.blame_for_rows(
|
||||
&(0..4)
|
||||
.map(|row| RowInfo {
|
||||
buffer_row: Some(row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx,
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
|
||||
@@ -2708,7 +2708,7 @@ impl Render for CollabPanel {
|
||||
deferred(
|
||||
anchored()
|
||||
.position(*position)
|
||||
.anchor(gpui::AnchorCorner::TopLeft)
|
||||
.anchor(gpui::Corner::TopLeft)
|
||||
.child(menu.clone()),
|
||||
)
|
||||
.with_priority(1)
|
||||
|
||||
@@ -409,7 +409,7 @@ impl PickerDelegate for ChannelModalDelegate {
|
||||
Some(
|
||||
deferred(
|
||||
anchored()
|
||||
.anchor(gpui::AnchorCorner::TopRight)
|
||||
.anchor(gpui::Corner::TopRight)
|
||||
.child(menu.clone()),
|
||||
)
|
||||
.with_priority(1),
|
||||
|
||||
@@ -44,7 +44,7 @@ fn notification_window_options(
|
||||
let notification_margin_height = px(-48.);
|
||||
|
||||
let bounds = gpui::Bounds::<Pixels> {
|
||||
origin: screen.bounds().upper_right()
|
||||
origin: screen.bounds().top_right()
|
||||
- point(
|
||||
size.width + notification_margin_width,
|
||||
notification_margin_height,
|
||||
|
||||
@@ -25,6 +25,7 @@ settings.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
telemetry.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use client::{parse_zed_link, telemetry::Telemetry};
|
||||
use client::parse_zed_link;
|
||||
use collections::HashMap;
|
||||
use command_palette_hooks::{
|
||||
CommandInterceptResult, CommandPaletteFilter, CommandPaletteInterceptor,
|
||||
@@ -63,18 +63,12 @@ impl CommandPalette {
|
||||
let Some(previous_focus_handle) = cx.focused() else {
|
||||
return;
|
||||
};
|
||||
let telemetry = workspace.client().telemetry().clone();
|
||||
workspace.toggle_modal(cx, move |cx| {
|
||||
CommandPalette::new(previous_focus_handle, telemetry, query, cx)
|
||||
CommandPalette::new(previous_focus_handle, query, cx)
|
||||
});
|
||||
}
|
||||
|
||||
fn new(
|
||||
previous_focus_handle: FocusHandle,
|
||||
telemetry: Arc<Telemetry>,
|
||||
query: &str,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
fn new(previous_focus_handle: FocusHandle, query: &str, cx: &mut ViewContext<Self>) -> Self {
|
||||
let filter = CommandPaletteFilter::try_global(cx);
|
||||
|
||||
let commands = cx
|
||||
@@ -92,12 +86,8 @@ impl CommandPalette {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let delegate = CommandPaletteDelegate::new(
|
||||
cx.view().downgrade(),
|
||||
commands,
|
||||
telemetry,
|
||||
previous_focus_handle,
|
||||
);
|
||||
let delegate =
|
||||
CommandPaletteDelegate::new(cx.view().downgrade(), commands, previous_focus_handle);
|
||||
|
||||
let picker = cx.new_view(|cx| {
|
||||
let picker = Picker::uniform_list(delegate, cx);
|
||||
@@ -133,7 +123,6 @@ pub struct CommandPaletteDelegate {
|
||||
commands: Vec<Command>,
|
||||
matches: Vec<StringMatch>,
|
||||
selected_ix: usize,
|
||||
telemetry: Arc<Telemetry>,
|
||||
previous_focus_handle: FocusHandle,
|
||||
updating_matches: Option<(
|
||||
Task<()>,
|
||||
@@ -167,7 +156,6 @@ impl CommandPaletteDelegate {
|
||||
fn new(
|
||||
command_palette: WeakView<CommandPalette>,
|
||||
commands: Vec<Command>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
previous_focus_handle: FocusHandle,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -176,7 +164,6 @@ impl CommandPaletteDelegate {
|
||||
matches: vec![],
|
||||
commands,
|
||||
selected_ix: 0,
|
||||
telemetry,
|
||||
previous_focus_handle,
|
||||
updating_matches: None,
|
||||
}
|
||||
@@ -367,9 +354,11 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
let action_ix = self.matches[self.selected_ix].candidate_id;
|
||||
let command = self.commands.swap_remove(action_ix);
|
||||
|
||||
self.telemetry
|
||||
.report_action_event("command palette", command.name.clone());
|
||||
|
||||
telemetry::event!(
|
||||
"Action Invoked",
|
||||
source = "command palette",
|
||||
action = command.name
|
||||
);
|
||||
self.matches.clear();
|
||||
self.commands.clear();
|
||||
HitCounts::update_global(cx, |hit_counts, _cx| {
|
||||
|
||||
@@ -19,6 +19,9 @@ pub fn init(cx: &mut AppContext) {
|
||||
pub struct CommandPaletteFilter {
|
||||
hidden_namespaces: HashSet<&'static str>,
|
||||
hidden_action_types: HashSet<TypeId>,
|
||||
/// Actions that have explicitly been shown. These should be shown even if
|
||||
/// they are in a hidden namespace.
|
||||
shown_action_types: HashSet<TypeId>,
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut, Default)]
|
||||
@@ -53,6 +56,11 @@ impl CommandPaletteFilter {
|
||||
let name = action.name();
|
||||
let namespace = name.split("::").next().unwrap_or("malformed action name");
|
||||
|
||||
// If this action has specifically been shown then it should be visible.
|
||||
if self.shown_action_types.contains(&action.type_id()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.hidden_namespaces.contains(namespace)
|
||||
|| self.hidden_action_types.contains(&action.type_id())
|
||||
}
|
||||
@@ -69,12 +77,16 @@ impl CommandPaletteFilter {
|
||||
|
||||
/// Hides all actions with the given types.
|
||||
pub fn hide_action_types(&mut self, action_types: &[TypeId]) {
|
||||
self.hidden_action_types.extend(action_types);
|
||||
for action_type in action_types {
|
||||
self.hidden_action_types.insert(*action_type);
|
||||
self.shown_action_types.remove(action_type);
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows all actions with the given types.
|
||||
pub fn show_action_types<'a>(&mut self, action_types: impl Iterator<Item = &'a TypeId>) {
|
||||
for action_type in action_types {
|
||||
self.shown_action_types.insert(*action_type);
|
||||
self.hidden_action_types.remove(action_type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,10 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
"copilot"
|
||||
}
|
||||
|
||||
fn display_name() -> &'static str {
|
||||
"Copilot"
|
||||
}
|
||||
|
||||
fn is_enabled(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
@@ -324,10 +328,15 @@ mod tests {
|
||||
cx.update_editor(|editor, cx| {
|
||||
// We want to show both: the inline completion and the completion menu
|
||||
assert!(editor.context_menu_visible());
|
||||
assert!(editor.context_menu_contains_inline_completion());
|
||||
assert!(editor.has_active_inline_completion());
|
||||
// Since we have both, the copilot suggestion is not shown inline
|
||||
assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
|
||||
assert_eq!(editor.display_text(cx), "one.\ntwo\nthree\n");
|
||||
|
||||
// Confirming a completion inserts it and hides the context menu, without showing
|
||||
// Confirming a non-copilot completion inserts it and hides the context menu, without showing
|
||||
// the copilot suggestion afterwards.
|
||||
editor.context_menu_next(&Default::default(), cx);
|
||||
editor
|
||||
.confirm_completion(&Default::default(), cx)
|
||||
.unwrap()
|
||||
@@ -338,13 +347,14 @@ mod tests {
|
||||
assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
|
||||
});
|
||||
|
||||
// Reset editor and test that accepting completions works
|
||||
// Reset editor and only return copilot suggestions
|
||||
cx.set_state(indoc! {"
|
||||
oneˇ
|
||||
two
|
||||
three
|
||||
"});
|
||||
cx.simulate_keystroke(".");
|
||||
|
||||
drop(handle_completion_request(
|
||||
&mut cx,
|
||||
indoc! {"
|
||||
@@ -352,7 +362,7 @@ mod tests {
|
||||
two
|
||||
three
|
||||
"},
|
||||
vec!["completion_a", "completion_b"],
|
||||
vec![],
|
||||
));
|
||||
handle_copilot_completion_request(
|
||||
&copilot_lsp,
|
||||
@@ -365,16 +375,15 @@ mod tests {
|
||||
);
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(editor.context_menu_visible());
|
||||
assert!(!editor.context_menu_visible());
|
||||
assert!(editor.has_active_inline_completion());
|
||||
// Since only the copilot is available, it's shown inline
|
||||
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
|
||||
});
|
||||
|
||||
// Ensure existing inline completion is interpolated when inserting again.
|
||||
cx.simulate_keystroke("c");
|
||||
// We still request a normal LSP completion, but we interpolate the
|
||||
// existing inline completion.
|
||||
drop(handle_completion_request(
|
||||
&mut cx,
|
||||
indoc! {"
|
||||
@@ -382,13 +391,16 @@ mod tests {
|
||||
two
|
||||
three
|
||||
"},
|
||||
vec!["ompletion_a", "ompletion_b"],
|
||||
vec!["completion_a", "completion_b"],
|
||||
));
|
||||
executor.run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(!editor.context_menu_visible());
|
||||
// Since we have an LSP completion too, the inline completion is
|
||||
// shown in the menu now
|
||||
assert!(editor.context_menu_visible());
|
||||
assert!(editor.context_menu_contains_inline_completion());
|
||||
assert!(editor.has_active_inline_completion());
|
||||
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
||||
assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
||||
});
|
||||
|
||||
@@ -404,6 +416,14 @@ mod tests {
|
||||
);
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(editor.context_menu_visible());
|
||||
assert!(editor.has_active_inline_completion());
|
||||
assert!(editor.context_menu_contains_inline_completion());
|
||||
assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
||||
|
||||
// Canceling should first hide the menu and make Copilot suggestion visible.
|
||||
editor.cancel(&Default::default(), cx);
|
||||
assert!(!editor.context_menu_visible());
|
||||
assert!(editor.has_active_inline_completion());
|
||||
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
||||
@@ -908,8 +928,8 @@ mod tests {
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(editor.context_menu_visible());
|
||||
assert!(editor.context_menu_contains_inline_completion());
|
||||
assert!(editor.has_active_inline_completion(),);
|
||||
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one\ntwo.\nthree\n");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ smol.workspace = true
|
||||
snippet.workspace = true
|
||||
sum_tree.workspace = true
|
||||
task.workspace = true
|
||||
telemetry.workspace = true
|
||||
text.workspace = true
|
||||
time.workspace = true
|
||||
time_format.workspace = true
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::{cell::Cell, cmp::Reverse, ops::Range, rc::Rc};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
div, px, uniform_list, AnyElement, BackgroundExecutor, Div, FontWeight, ListSizingBehavior,
|
||||
Model, MouseButton, Pixels, ScrollStrategy, SharedString, StrikethroughStyle, StyledText,
|
||||
UniformListScrollHandle, ViewContext, WeakView,
|
||||
Model, ScrollStrategy, SharedString, StrikethroughStyle, StyledText, UniformListScrollHandle,
|
||||
ViewContext, WeakView,
|
||||
};
|
||||
use language::Buffer;
|
||||
use language::{CodeLabel, Documentation};
|
||||
@@ -14,11 +14,7 @@ use multi_buffer::{Anchor, ExcerptId};
|
||||
use ordered_float::OrderedFloat;
|
||||
use project::{CodeAction, Completion, TaskSourceKind};
|
||||
use task::ResolvedTask;
|
||||
use ui::{
|
||||
h_flex, ActiveTheme as _, Color, FluentBuilder as _, InteractiveElement as _, IntoElement,
|
||||
Label, LabelCommon as _, LabelSize, ListItem, ParentElement as _, Popover,
|
||||
StatefulInteractiveElement as _, Styled, StyledExt as _, Toggleable as _,
|
||||
};
|
||||
use ui::{prelude::*, Color, IntoElement, ListItem, Popover, Styled};
|
||||
use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -28,6 +24,7 @@ use crate::{
|
||||
render_parsed_markdown, split_words, styled_runs_for_code_label, CodeActionProvider,
|
||||
CompletionId, CompletionProvider, DisplayRow, Editor, EditorStyle, ResolvedTasks,
|
||||
};
|
||||
use crate::{AcceptInlineCompletion, InlineCompletionMenuHint, InlineCompletionText};
|
||||
|
||||
pub enum CodeContextMenu {
|
||||
Completions(CompletionsMenu),
|
||||
@@ -106,22 +103,24 @@ impl CodeContextMenu {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn origin(&self, cursor_position: DisplayPoint) -> ContextMenuOrigin {
|
||||
match self {
|
||||
CodeContextMenu::Completions(menu) => menu.origin(cursor_position),
|
||||
CodeContextMenu::CodeActions(menu) => menu.origin(cursor_position),
|
||||
}
|
||||
}
|
||||
pub fn render(
|
||||
&self,
|
||||
cursor_position: DisplayPoint,
|
||||
style: &EditorStyle,
|
||||
max_height: Pixels,
|
||||
max_height_in_lines: u32,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> (ContextMenuOrigin, AnyElement) {
|
||||
) -> AnyElement {
|
||||
match self {
|
||||
CodeContextMenu::Completions(menu) => (
|
||||
ContextMenuOrigin::EditorPoint(cursor_position),
|
||||
menu.render(style, max_height, workspace, cx),
|
||||
),
|
||||
CodeContextMenu::CodeActions(menu) => {
|
||||
menu.render(cursor_position, style, max_height, cx)
|
||||
CodeContextMenu::Completions(menu) => {
|
||||
menu.render(style, max_height_in_lines, workspace, cx)
|
||||
}
|
||||
CodeContextMenu::CodeActions(menu) => menu.render(style, max_height_in_lines, cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,7 +138,7 @@ pub struct CompletionsMenu {
|
||||
pub buffer: Model<Buffer>,
|
||||
pub completions: Rc<RefCell<Box<[Completion]>>>,
|
||||
match_candidates: Rc<[StringMatchCandidate]>,
|
||||
pub matches: Rc<[StringMatch]>,
|
||||
pub entries: Rc<[CompletionEntry]>,
|
||||
pub selected_item: usize,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
resolve_completions: bool,
|
||||
@@ -147,6 +146,12 @@ pub struct CompletionsMenu {
|
||||
show_completion_documentation: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum CompletionEntry {
|
||||
Match(StringMatch),
|
||||
InlineCompletionHint(InlineCompletionMenuHint),
|
||||
}
|
||||
|
||||
impl CompletionsMenu {
|
||||
pub fn new(
|
||||
id: CompletionId,
|
||||
@@ -171,7 +176,7 @@ impl CompletionsMenu {
|
||||
show_completion_documentation,
|
||||
completions: RefCell::new(completions).into(),
|
||||
match_candidates,
|
||||
matches: Vec::new().into(),
|
||||
entries: Vec::new().into(),
|
||||
selected_item: 0,
|
||||
scroll_handle: UniformListScrollHandle::new(),
|
||||
resolve_completions: true,
|
||||
@@ -208,14 +213,16 @@ impl CompletionsMenu {
|
||||
.enumerate()
|
||||
.map(|(id, completion)| StringMatchCandidate::new(id, &completion))
|
||||
.collect();
|
||||
let matches = choices
|
||||
let entries = choices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(id, completion)| StringMatch {
|
||||
candidate_id: id,
|
||||
score: 1.,
|
||||
positions: vec![],
|
||||
string: completion.clone(),
|
||||
.map(|(id, completion)| {
|
||||
CompletionEntry::Match(StringMatch {
|
||||
candidate_id: id,
|
||||
score: 1.,
|
||||
positions: vec![],
|
||||
string: completion.clone(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
Self {
|
||||
@@ -225,7 +232,7 @@ impl CompletionsMenu {
|
||||
buffer,
|
||||
completions: RefCell::new(completions).into(),
|
||||
match_candidates,
|
||||
matches,
|
||||
entries,
|
||||
selected_item: 0,
|
||||
scroll_handle: UniformListScrollHandle::new(),
|
||||
resolve_completions: false,
|
||||
@@ -254,7 +261,7 @@ impl CompletionsMenu {
|
||||
if self.selected_item > 0 {
|
||||
self.selected_item -= 1;
|
||||
} else {
|
||||
self.selected_item = self.matches.len() - 1;
|
||||
self.selected_item = self.entries.len() - 1;
|
||||
}
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
@@ -267,7 +274,7 @@ impl CompletionsMenu {
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
if self.selected_item + 1 < self.matches.len() {
|
||||
if self.selected_item + 1 < self.entries.len() {
|
||||
self.selected_item += 1;
|
||||
} else {
|
||||
self.selected_item = 0;
|
||||
@@ -283,13 +290,33 @@ impl CompletionsMenu {
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
self.selected_item = self.matches.len() - 1;
|
||||
self.selected_item = self.entries.len() - 1;
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.resolve_selected_completion(provider, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn show_inline_completion_hint(&mut self, hint: InlineCompletionMenuHint) {
|
||||
let hint = CompletionEntry::InlineCompletionHint(hint);
|
||||
|
||||
self.entries = match self.entries.first() {
|
||||
Some(CompletionEntry::InlineCompletionHint { .. }) => {
|
||||
let mut entries = Vec::from(&*self.entries);
|
||||
entries[0] = hint;
|
||||
entries
|
||||
}
|
||||
_ => {
|
||||
let mut entries = Vec::with_capacity(self.entries.len() + 1);
|
||||
entries.push(hint);
|
||||
entries.extend_from_slice(&self.entries);
|
||||
entries
|
||||
}
|
||||
}
|
||||
.into();
|
||||
self.selected_item = 0;
|
||||
}
|
||||
|
||||
pub fn resolve_selected_completion(
|
||||
&mut self,
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
@@ -302,84 +329,114 @@ impl CompletionsMenu {
|
||||
return;
|
||||
};
|
||||
|
||||
let completion_index = self.matches[self.selected_item].candidate_id;
|
||||
let resolve_task = provider.resolve_completions(
|
||||
self.buffer.clone(),
|
||||
vec![completion_index],
|
||||
self.completions.clone(),
|
||||
cx,
|
||||
);
|
||||
match &self.entries[self.selected_item] {
|
||||
CompletionEntry::Match(entry) => {
|
||||
let completion_index = entry.candidate_id;
|
||||
let resolve_task = provider.resolve_completions(
|
||||
self.buffer.clone(),
|
||||
vec![completion_index],
|
||||
self.completions.clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
cx.spawn(move |editor, mut cx| async move {
|
||||
if let Some(true) = resolve_task.await.log_err() {
|
||||
editor.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||
cx.spawn(move |editor, mut cx| async move {
|
||||
if let Some(true) = resolve_task.await.log_err() {
|
||||
editor.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
CompletionEntry::InlineCompletionHint { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visible(&self) -> bool {
|
||||
!self.matches.is_empty()
|
||||
pub fn visible(&self) -> bool {
|
||||
!self.entries.is_empty()
|
||||
}
|
||||
|
||||
fn origin(&self, cursor_position: DisplayPoint) -> ContextMenuOrigin {
|
||||
ContextMenuOrigin::EditorPoint(cursor_position)
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
style: &EditorStyle,
|
||||
max_height: Pixels,
|
||||
max_height_in_lines: u32,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> AnyElement {
|
||||
let max_height = max_height_in_lines as f32 * cx.line_height();
|
||||
|
||||
let completions = self.completions.borrow_mut();
|
||||
let show_completion_documentation = self.show_completion_documentation;
|
||||
let widest_completion_ix = self
|
||||
.matches
|
||||
.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.max_by_key(|(_, mat)| {
|
||||
let completion = &completions[mat.candidate_id];
|
||||
let documentation = &completion.documentation;
|
||||
.max_by_key(|(_, mat)| match mat {
|
||||
CompletionEntry::Match(mat) => {
|
||||
let completion = &completions[mat.candidate_id];
|
||||
let documentation = &completion.documentation;
|
||||
|
||||
let mut len = completion.label.text.chars().count();
|
||||
if let Some(Documentation::SingleLine(text)) = documentation {
|
||||
if show_completion_documentation {
|
||||
len += text.chars().count();
|
||||
let mut len = completion.label.text.chars().count();
|
||||
if let Some(Documentation::SingleLine(text)) = documentation {
|
||||
if show_completion_documentation {
|
||||
len += text.chars().count();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
len
|
||||
len
|
||||
}
|
||||
CompletionEntry::InlineCompletionHint(InlineCompletionMenuHint {
|
||||
provider_name,
|
||||
..
|
||||
}) => provider_name.len(),
|
||||
})
|
||||
.map(|(ix, _)| ix);
|
||||
|
||||
let selected_item = self.selected_item;
|
||||
let style = style.clone();
|
||||
|
||||
let multiline_docs = if show_completion_documentation {
|
||||
let mat = &self.matches[selected_item];
|
||||
match &completions[mat.candidate_id].documentation {
|
||||
Some(Documentation::MultiLinePlainText(text)) => {
|
||||
Some(div().child(SharedString::from(text.clone())))
|
||||
let multiline_docs = match &self.entries[selected_item] {
|
||||
CompletionEntry::Match(mat) if show_completion_documentation => {
|
||||
match &completions[mat.candidate_id].documentation {
|
||||
Some(Documentation::MultiLinePlainText(text)) => {
|
||||
Some(div().child(SharedString::from(text.clone())))
|
||||
}
|
||||
Some(Documentation::MultiLineMarkdown(parsed)) if !parsed.text.is_empty() => {
|
||||
Some(div().child(render_parsed_markdown(
|
||||
"completions_markdown",
|
||||
parsed,
|
||||
&style,
|
||||
workspace,
|
||||
cx,
|
||||
)))
|
||||
}
|
||||
Some(Documentation::Undocumented) if self.aside_was_displayed.get() => {
|
||||
Some(div().child("No documentation"))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
Some(Documentation::MultiLineMarkdown(parsed)) if !parsed.text.is_empty() => {
|
||||
Some(div().child(render_parsed_markdown(
|
||||
"completions_markdown",
|
||||
parsed,
|
||||
&style,
|
||||
workspace,
|
||||
cx,
|
||||
)))
|
||||
}
|
||||
Some(Documentation::Undocumented) if self.aside_was_displayed.get() => {
|
||||
Some(div().child("No documentation"))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
CompletionEntry::InlineCompletionHint(hint) => Some(match &hint.text {
|
||||
InlineCompletionText::Edit { text, highlights } => div()
|
||||
.my_1()
|
||||
.rounded(px(6.))
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(
|
||||
gpui::StyledText::new(text.clone())
|
||||
.with_highlights(&style.text, highlights.clone()),
|
||||
),
|
||||
InlineCompletionText::Move(text) => div().child(text.clone()),
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let aside_contents = if let Some(multiline_docs) = multiline_docs {
|
||||
Some(multiline_docs)
|
||||
} else if self.aside_was_displayed.get() {
|
||||
} else if show_completion_documentation && self.aside_was_displayed.get() {
|
||||
Some(div().child("Fetching documentation..."))
|
||||
} else {
|
||||
None
|
||||
@@ -392,16 +449,15 @@ impl CompletionsMenu {
|
||||
.flex_1()
|
||||
.px_1p5()
|
||||
.py_1()
|
||||
.min_w(px(260.))
|
||||
.max_w(px(640.))
|
||||
.w(px(500.))
|
||||
.w(px(450.))
|
||||
.overflow_y_scroll()
|
||||
.occlude()
|
||||
});
|
||||
|
||||
drop(completions);
|
||||
let completions = self.completions.clone();
|
||||
let matches = self.matches.clone();
|
||||
let matches = self.entries.clone();
|
||||
let list = uniform_list(
|
||||
cx.view().clone(),
|
||||
"completions",
|
||||
@@ -415,88 +471,119 @@ impl CompletionsMenu {
|
||||
.enumerate()
|
||||
.map(|(ix, mat)| {
|
||||
let item_ix = start_ix + ix;
|
||||
let candidate_id = mat.candidate_id;
|
||||
let completion = &completions_guard[candidate_id];
|
||||
match mat {
|
||||
CompletionEntry::Match(mat) => {
|
||||
let candidate_id = mat.candidate_id;
|
||||
let completion = &completions_guard[candidate_id];
|
||||
|
||||
let documentation = if show_completion_documentation {
|
||||
&completion.documentation
|
||||
} else {
|
||||
&None
|
||||
};
|
||||
|
||||
let filter_start = completion.label.filter_range.start;
|
||||
let highlights = gpui::combine_highlights(
|
||||
mat.ranges().map(|range| {
|
||||
(
|
||||
filter_start + range.start..filter_start + range.end,
|
||||
FontWeight::BOLD.into(),
|
||||
)
|
||||
}),
|
||||
styled_runs_for_code_label(&completion.label, &style.syntax).map(
|
||||
|(range, mut highlight)| {
|
||||
// Ignore font weight for syntax highlighting, as we'll use it
|
||||
// for fuzzy matches.
|
||||
highlight.font_weight = None;
|
||||
|
||||
if completion.lsp_completion.deprecated.unwrap_or(false) {
|
||||
highlight.strikethrough = Some(StrikethroughStyle {
|
||||
thickness: 1.0.into(),
|
||||
..Default::default()
|
||||
});
|
||||
highlight.color = Some(cx.theme().colors().text_muted);
|
||||
}
|
||||
|
||||
(range, highlight)
|
||||
},
|
||||
),
|
||||
);
|
||||
let completion_label = StyledText::new(completion.label.text.clone())
|
||||
.with_highlights(&style.text, highlights);
|
||||
let documentation_label =
|
||||
if let Some(Documentation::SingleLine(text)) = documentation {
|
||||
if text.trim().is_empty() {
|
||||
None
|
||||
let documentation = if show_completion_documentation {
|
||||
&completion.documentation
|
||||
} else {
|
||||
Some(
|
||||
Label::new(text.clone())
|
||||
.ml_4()
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
&None
|
||||
};
|
||||
|
||||
let filter_start = completion.label.filter_range.start;
|
||||
let highlights = gpui::combine_highlights(
|
||||
mat.ranges().map(|range| {
|
||||
(
|
||||
filter_start + range.start..filter_start + range.end,
|
||||
FontWeight::BOLD.into(),
|
||||
)
|
||||
}),
|
||||
styled_runs_for_code_label(&completion.label, &style.syntax)
|
||||
.map(|(range, mut highlight)| {
|
||||
// Ignore font weight for syntax highlighting, as we'll use it
|
||||
// for fuzzy matches.
|
||||
highlight.font_weight = None;
|
||||
|
||||
if completion.lsp_completion.deprecated.unwrap_or(false)
|
||||
{
|
||||
highlight.strikethrough =
|
||||
Some(StrikethroughStyle {
|
||||
thickness: 1.0.into(),
|
||||
..Default::default()
|
||||
});
|
||||
highlight.color =
|
||||
Some(cx.theme().colors().text_muted);
|
||||
}
|
||||
|
||||
(range, highlight)
|
||||
}),
|
||||
);
|
||||
|
||||
let completion_label =
|
||||
StyledText::new(completion.label.text.clone())
|
||||
.with_highlights(&style.text, highlights);
|
||||
let documentation_label =
|
||||
if let Some(Documentation::SingleLine(text)) = documentation {
|
||||
if text.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
Label::new(text.clone())
|
||||
.ml_4()
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let color_swatch = completion
|
||||
.color()
|
||||
.map(|color| div().size_4().bg(color).rounded_sm());
|
||||
|
||||
div().min_w(px(220.)).max_w(px(540.)).child(
|
||||
ListItem::new(mat.candidate_id)
|
||||
.inset(true)
|
||||
.toggle_state(item_ix == selected_item)
|
||||
.on_click(cx.listener(move |editor, _event, cx| {
|
||||
cx.stop_propagation();
|
||||
if let Some(task) = editor.confirm_completion(
|
||||
&ConfirmCompletion {
|
||||
item_ix: Some(item_ix),
|
||||
},
|
||||
cx,
|
||||
) {
|
||||
task.detach_and_log_err(cx)
|
||||
}
|
||||
}))
|
||||
.start_slot::<Div>(color_swatch)
|
||||
.child(h_flex().overflow_hidden().child(completion_label))
|
||||
.end_slot::<Label>(documentation_label),
|
||||
)
|
||||
}
|
||||
CompletionEntry::InlineCompletionHint(InlineCompletionMenuHint {
|
||||
provider_name,
|
||||
..
|
||||
}) => div().min_w(px(250.)).max_w(px(500.)).child(
|
||||
ListItem::new("inline-completion")
|
||||
.inset(true)
|
||||
.toggle_state(item_ix == selected_item)
|
||||
.start_slot(Icon::new(IconName::ZedPredict))
|
||||
.child(
|
||||
StyledText::new(format!(
|
||||
"{} Completion",
|
||||
SharedString::new_static(provider_name)
|
||||
))
|
||||
.with_highlights(&style.text, None),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let color_swatch = completion
|
||||
.color()
|
||||
.map(|color| div().size_4().bg(color).rounded_sm());
|
||||
|
||||
div().min_w(px(220.)).max_w(px(540.)).child(
|
||||
ListItem::new(mat.candidate_id)
|
||||
.inset(true)
|
||||
.toggle_state(item_ix == selected_item)
|
||||
.on_click(cx.listener(move |editor, _event, cx| {
|
||||
cx.stop_propagation();
|
||||
if let Some(task) = editor.confirm_completion(
|
||||
&ConfirmCompletion {
|
||||
item_ix: Some(item_ix),
|
||||
},
|
||||
cx,
|
||||
) {
|
||||
task.detach_and_log_err(cx)
|
||||
}
|
||||
}))
|
||||
.start_slot::<Div>(color_swatch)
|
||||
.child(h_flex().overflow_hidden().child(completion_label))
|
||||
.end_slot::<Label>(documentation_label),
|
||||
)
|
||||
.on_click(cx.listener(move |editor, _event, cx| {
|
||||
cx.stop_propagation();
|
||||
editor.accept_inline_completion(
|
||||
&AcceptInlineCompletion {},
|
||||
cx,
|
||||
);
|
||||
})),
|
||||
),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
)
|
||||
.occlude()
|
||||
.max_h(max_height)
|
||||
.max_h(max_height_in_lines as f32 * cx.line_height())
|
||||
.track_scroll(self.scroll_handle.clone())
|
||||
.with_width_from_item(widest_completion_ix)
|
||||
.with_sizing_behavior(ListSizingBehavior::Infer);
|
||||
@@ -603,7 +690,12 @@ impl CompletionsMenu {
|
||||
}
|
||||
drop(completions);
|
||||
|
||||
self.matches = matches.into();
|
||||
let mut new_entries: Vec<_> = matches.into_iter().map(CompletionEntry::Match).collect();
|
||||
if let Some(CompletionEntry::InlineCompletionHint(hint)) = self.entries.first() {
|
||||
new_entries.insert(0, CompletionEntry::InlineCompletionHint(hint.clone()));
|
||||
}
|
||||
|
||||
self.entries = new_entries.into();
|
||||
self.selected_item = 0;
|
||||
}
|
||||
}
|
||||
@@ -779,16 +871,23 @@ impl CodeActionsMenu {
|
||||
!self.actions.is_empty()
|
||||
}
|
||||
|
||||
fn origin(&self, cursor_position: DisplayPoint) -> ContextMenuOrigin {
|
||||
if let Some(row) = self.deployed_from_indicator {
|
||||
ContextMenuOrigin::GutterIndicator(row)
|
||||
} else {
|
||||
ContextMenuOrigin::EditorPoint(cursor_position)
|
||||
}
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
cursor_position: DisplayPoint,
|
||||
_style: &EditorStyle,
|
||||
max_height: Pixels,
|
||||
max_height_in_lines: u32,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> (ContextMenuOrigin, AnyElement) {
|
||||
) -> AnyElement {
|
||||
let actions = self.actions.clone();
|
||||
let selected_item = self.selected_item;
|
||||
let element = uniform_list(
|
||||
let list = uniform_list(
|
||||
cx.view().clone(),
|
||||
"code_actions_menu",
|
||||
self.actions.len(),
|
||||
@@ -800,27 +899,14 @@ impl CodeActionsMenu {
|
||||
.enumerate()
|
||||
.map(|(ix, action)| {
|
||||
let item_ix = range.start + ix;
|
||||
let selected = selected_item == item_ix;
|
||||
let selected = item_ix == selected_item;
|
||||
let colors = cx.theme().colors();
|
||||
div()
|
||||
.px_1()
|
||||
.rounded_md()
|
||||
.text_color(colors.text)
|
||||
.when(selected, |style| {
|
||||
style
|
||||
.bg(colors.element_active)
|
||||
.text_color(colors.text_accent)
|
||||
})
|
||||
.hover(|style| {
|
||||
style
|
||||
.bg(colors.element_hover)
|
||||
.text_color(colors.text_accent)
|
||||
})
|
||||
.whitespace_nowrap()
|
||||
.when_some(action.as_code_action(), |this, action| {
|
||||
this.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(move |editor, _, cx| {
|
||||
div().min_w(px(220.)).max_w(px(540.)).child(
|
||||
ListItem::new(item_ix)
|
||||
.inset(true)
|
||||
.toggle_state(selected)
|
||||
.when_some(action.as_code_action(), |this, action| {
|
||||
this.on_click(cx.listener(move |editor, _, cx| {
|
||||
cx.stop_propagation();
|
||||
if let Some(task) = editor.confirm_code_action(
|
||||
&ConfirmCodeAction {
|
||||
@@ -830,17 +916,21 @@ impl CodeActionsMenu {
|
||||
) {
|
||||
task.detach_and_log_err(cx)
|
||||
}
|
||||
}),
|
||||
)
|
||||
// TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here.
|
||||
.child(SharedString::from(
|
||||
action.lsp_action.title.replace("\n", ""),
|
||||
))
|
||||
})
|
||||
.when_some(action.as_task(), |this, task| {
|
||||
this.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(move |editor, _, cx| {
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.overflow_hidden()
|
||||
.child(
|
||||
// TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here.
|
||||
action.lsp_action.title.replace("\n", ""),
|
||||
)
|
||||
.when(selected, |this| {
|
||||
this.text_color(colors.text_accent)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when_some(action.as_task(), |this, task| {
|
||||
this.on_click(cx.listener(move |editor, _, cx| {
|
||||
cx.stop_propagation();
|
||||
if let Some(task) = editor.confirm_code_action(
|
||||
&ConfirmCodeAction {
|
||||
@@ -850,18 +940,23 @@ impl CodeActionsMenu {
|
||||
) {
|
||||
task.detach_and_log_err(cx)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(SharedString::from(task.resolved_label.replace("\n", "")))
|
||||
})
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.overflow_hidden()
|
||||
.child(task.resolved_label.replace("\n", ""))
|
||||
.when(selected, |this| {
|
||||
this.text_color(colors.text_accent)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
)
|
||||
.elevation_1(cx)
|
||||
.p_1()
|
||||
.max_h(max_height)
|
||||
.occlude()
|
||||
.max_h(max_height_in_lines as f32 * cx.line_height())
|
||||
.track_scroll(self.scroll_handle.clone())
|
||||
.with_width_from_item(
|
||||
self.actions
|
||||
@@ -875,15 +970,8 @@ impl CodeActionsMenu {
|
||||
})
|
||||
.map(|(ix, _)| ix),
|
||||
)
|
||||
.with_sizing_behavior(ListSizingBehavior::Infer)
|
||||
.into_any_element();
|
||||
.with_sizing_behavior(ListSizingBehavior::Infer);
|
||||
|
||||
let cursor_position = if let Some(row) = self.deployed_from_indicator {
|
||||
ContextMenuOrigin::GutterIndicator(row)
|
||||
} else {
|
||||
ContextMenuOrigin::EditorPoint(cursor_position)
|
||||
};
|
||||
|
||||
(cursor_position, element)
|
||||
Popover::new().child(list).into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
mod block_map;
|
||||
mod crease_map;
|
||||
mod custom_highlights;
|
||||
mod diff_map;
|
||||
mod fold_map;
|
||||
mod inlay_map;
|
||||
pub(crate) mod invisibles;
|
||||
@@ -29,16 +31,19 @@ use crate::{
|
||||
hover_links::InlayHighlight, movement::TextLayoutDetails, EditorStyle, InlayId, RowExt,
|
||||
};
|
||||
pub use block_map::{
|
||||
Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap,
|
||||
BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
||||
Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement,
|
||||
BlockPoint, BlockProperties, BlockRows, BlockStyle, CustomBlockId, RenderBlock,
|
||||
};
|
||||
use block_map::{BlockRow, BlockSnapshot};
|
||||
use collections::{HashMap, HashSet};
|
||||
pub use crease_map::*;
|
||||
use diff_map::{DiffMap, DiffMapSnapshot, DiffOffset, DiffPoint};
|
||||
pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
|
||||
use fold_map::{FoldMap, FoldSnapshot};
|
||||
use git::diff::DiffHunkStatus;
|
||||
use gpui::{
|
||||
AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
|
||||
AnyElement, AppContext, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels,
|
||||
UnderlineStyle,
|
||||
};
|
||||
pub(crate) use inlay_map::Inlay;
|
||||
use inlay_map::{InlayMap, InlaySnapshot};
|
||||
@@ -50,9 +55,10 @@ use language::{
|
||||
};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
|
||||
ToOffset, ToPoint,
|
||||
Anchor, AnchorRangeExt, MultiBuffer, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
|
||||
MultiBufferSnapshot, RowInfo, ToOffset, ToPoint,
|
||||
};
|
||||
use project::buffer_store::BufferChangeSet;
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
@@ -65,7 +71,7 @@ use std::{
|
||||
};
|
||||
use sum_tree::{Bias, TreeMap};
|
||||
use tab_map::{TabMap, TabSnapshot};
|
||||
use text::LineIndent;
|
||||
use text::{BufferId, LineIndent};
|
||||
use ui::{px, SharedString, WindowContext};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use wrap_map::{WrapMap, WrapSnapshot};
|
||||
@@ -95,6 +101,8 @@ pub struct DisplayMap {
|
||||
buffer_subscription: BufferSubscription,
|
||||
/// Decides where the [`Inlay`]s should be displayed.
|
||||
inlay_map: InlayMap,
|
||||
/// Decides where diff hunks should be.
|
||||
diff_map: Model<DiffMap>,
|
||||
/// Decides where the fold indicators should be and tracks parts of a source file that are currently folded.
|
||||
fold_map: FoldMap,
|
||||
/// Keeps track of hard tabs in a buffer.
|
||||
@@ -133,7 +141,8 @@ impl DisplayMap {
|
||||
let tab_size = Self::tab_size(&buffer, cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let crease_map = CreaseMap::new(&buffer_snapshot);
|
||||
let (inlay_map, snapshot) = InlayMap::new(buffer_snapshot);
|
||||
let (diff_map, snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (inlay_map, snapshot) = InlayMap::new(snapshot);
|
||||
let (fold_map, snapshot) = FoldMap::new(snapshot);
|
||||
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
|
||||
let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
|
||||
@@ -152,6 +161,7 @@ impl DisplayMap {
|
||||
buffer_subscription,
|
||||
fold_map,
|
||||
inlay_map,
|
||||
diff_map,
|
||||
tab_map,
|
||||
wrap_map,
|
||||
block_map,
|
||||
@@ -167,7 +177,10 @@ impl DisplayMap {
|
||||
pub fn snapshot(&mut self, cx: &mut ModelContext<Self>) -> DisplaySnapshot {
|
||||
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
|
||||
let (diff_snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), edits, cx)
|
||||
});
|
||||
let (inlay_snapshot, edits) = self.inlay_map.sync(diff_snapshot, edits);
|
||||
let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits);
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size);
|
||||
@@ -216,7 +229,10 @@ impl DisplayMap {
|
||||
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot.clone(), edits);
|
||||
let (snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), edits, cx)
|
||||
});
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
@@ -289,6 +305,9 @@ impl DisplayMap {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
@@ -318,6 +337,9 @@ impl DisplayMap {
|
||||
.collect::<Vec<_>>();
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
@@ -340,6 +362,9 @@ impl DisplayMap {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
@@ -354,6 +379,9 @@ impl DisplayMap {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
@@ -386,6 +414,71 @@ impl DisplayMap {
|
||||
self.crease_map.remove(crease_ids, &snapshot)
|
||||
}
|
||||
|
||||
pub fn add_change_set(
|
||||
&mut self,
|
||||
change_set: Model<BufferChangeSet>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.add_change_set(change_set, cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn has_multiple_hunks(&self, cx: &AppContext) -> bool {
|
||||
self.diff_map.read(cx).has_multiple_hunks()
|
||||
}
|
||||
|
||||
pub fn has_expanded_diff_hunks_in_ranges(
|
||||
&mut self,
|
||||
ranges: &[Range<multi_buffer::Anchor>],
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> bool {
|
||||
self.diff_map
|
||||
.read(cx)
|
||||
.has_expanded_diff_hunks_in_ranges(ranges)
|
||||
}
|
||||
|
||||
pub fn set_all_hunks_expanded(&mut self, cx: &mut ModelContext<Self>) {
|
||||
self.update_diff_map(cx, |diff_map, cx| diff_map.set_all_hunks_expanded(cx))
|
||||
}
|
||||
|
||||
pub fn expand_diff_hunks(&mut self, ranges: Vec<Range<Anchor>>, cx: &mut ModelContext<Self>) {
|
||||
self.update_diff_map(cx, |diff_map, cx| diff_map.expand_diff_hunks(ranges, cx))
|
||||
}
|
||||
|
||||
pub fn collapse_diff_hunks(&mut self, ranges: Vec<Range<Anchor>>, cx: &mut ModelContext<Self>) {
|
||||
self.update_diff_map(cx, |diff_map, cx| diff_map.collapse_diff_hunks(ranges, cx))
|
||||
}
|
||||
|
||||
pub fn diff_base_for<'a>(
|
||||
&'a self,
|
||||
buffer_id: BufferId,
|
||||
cx: &'a AppContext,
|
||||
) -> Option<&'a Model<BufferChangeSet>> {
|
||||
self.diff_map.read(cx).diff_base_for(buffer_id)
|
||||
}
|
||||
|
||||
fn update_diff_map(
|
||||
&mut self,
|
||||
cx: &mut ModelContext<Self>,
|
||||
f: impl FnOnce(&mut DiffMap, &mut ModelContext<DiffMap>),
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let (snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| {
|
||||
f(diff_map, cx);
|
||||
diff_map.sync(snapshot, edits, cx)
|
||||
});
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.write(snapshot, edits);
|
||||
}
|
||||
|
||||
pub fn insert_blocks(
|
||||
&mut self,
|
||||
blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
|
||||
@@ -394,6 +487,9 @@ impl DisplayMap {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
@@ -412,6 +508,9 @@ impl DisplayMap {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
@@ -430,6 +529,9 @@ impl DisplayMap {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
@@ -448,6 +550,9 @@ impl DisplayMap {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
@@ -523,7 +628,10 @@ impl DisplayMap {
|
||||
}
|
||||
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
|
||||
let (snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), edits, cx)
|
||||
});
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot.clone(), edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
@@ -709,17 +817,35 @@ impl DisplaySnapshot {
|
||||
self.fold_snapshot.fold_count()
|
||||
}
|
||||
|
||||
pub fn diff_hunks<'a>(&'a self) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
|
||||
self.diff_snapshot()
|
||||
.diff_hunks_in_range(0..self.buffer_snapshot.len())
|
||||
}
|
||||
|
||||
pub fn diff_hunks_in_range<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
|
||||
self.diff_snapshot().diff_hunks_in_range(range)
|
||||
}
|
||||
|
||||
pub fn diff_hunks_in_range_rev<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
|
||||
self.diff_snapshot().diff_hunks_in_range_rev(range)
|
||||
}
|
||||
|
||||
pub fn has_diff_hunks(&self) -> bool {
|
||||
self.diff_snapshot().has_diff_hunks()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.buffer_snapshot.len() == 0
|
||||
}
|
||||
|
||||
pub fn buffer_rows(
|
||||
&self,
|
||||
start_row: DisplayRow,
|
||||
) -> impl Iterator<Item = Option<MultiBufferRow>> + '_ {
|
||||
self.block_snapshot
|
||||
.buffer_rows(BlockRow(start_row.0))
|
||||
.map(|row| row.map(MultiBufferRow))
|
||||
pub fn row_infos(&self, start_row: DisplayRow) -> impl Iterator<Item = RowInfo> + '_ {
|
||||
self.block_snapshot.row_infos(BlockRow(start_row.0))
|
||||
}
|
||||
|
||||
pub fn widest_line_number(&self) -> u32 {
|
||||
@@ -728,7 +854,7 @@ impl DisplaySnapshot {
|
||||
|
||||
pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
|
||||
loop {
|
||||
let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
|
||||
let mut inlay_point = self.inlay_snapshot.make_inlay_point(point);
|
||||
let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left);
|
||||
fold_point.0.column = 0;
|
||||
inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
|
||||
@@ -750,7 +876,7 @@ impl DisplaySnapshot {
|
||||
) -> (MultiBufferPoint, DisplayPoint) {
|
||||
let original_point = point;
|
||||
loop {
|
||||
let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
|
||||
let mut inlay_point = self.inlay_snapshot.make_inlay_point(point);
|
||||
let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right);
|
||||
fold_point.0.column = self.fold_snapshot.line_len(fold_point.row());
|
||||
inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
|
||||
@@ -794,7 +920,7 @@ impl DisplaySnapshot {
|
||||
}
|
||||
|
||||
pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
|
||||
let inlay_point = self.inlay_snapshot.to_inlay_point(point);
|
||||
let inlay_point = self.inlay_snapshot.make_inlay_point(point);
|
||||
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
|
||||
let tab_point = self.tab_snapshot.to_tab_point(fold_point);
|
||||
let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
|
||||
@@ -812,9 +938,15 @@ impl DisplaySnapshot {
|
||||
.to_offset(self.display_point_to_inlay_point(point, bias))
|
||||
}
|
||||
|
||||
pub fn display_point_to_diff_offset(&self, point: DisplayPoint, bias: Bias) -> DiffOffset {
|
||||
self.diff_snapshot()
|
||||
.point_to_offset(self.display_point_to_diff_point(point, bias))
|
||||
}
|
||||
|
||||
pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset {
|
||||
self.inlay_snapshot
|
||||
.to_inlay_offset(anchor.to_offset(&self.buffer_snapshot))
|
||||
let multibuffer_offset = anchor.to_offset(&self.buffer_snapshot);
|
||||
let diff_offset = self.diff_snapshot().to_diff_offset(multibuffer_offset);
|
||||
self.inlay_snapshot.to_inlay_offset(diff_offset)
|
||||
}
|
||||
|
||||
pub fn display_point_to_anchor(&self, point: DisplayPoint, bias: Bias) -> Anchor {
|
||||
@@ -830,6 +962,17 @@ impl DisplaySnapshot {
|
||||
fold_point.to_inlay_point(&self.fold_snapshot)
|
||||
}
|
||||
|
||||
fn diff_point_to_display_point(&self, diff_point: DiffPoint, bias: Bias) -> DisplayPoint {
|
||||
let inlay_point = self.inlay_snapshot.to_inlay_point(diff_point);
|
||||
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
|
||||
self.fold_point_to_display_point(fold_point)
|
||||
}
|
||||
|
||||
fn display_point_to_diff_point(&self, point: DisplayPoint, bias: Bias) -> DiffPoint {
|
||||
self.inlay_snapshot
|
||||
.to_diff_point(self.display_point_to_inlay_point(point, bias))
|
||||
}
|
||||
|
||||
pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
|
||||
let block_point = point.0;
|
||||
let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
|
||||
@@ -1067,6 +1210,22 @@ impl DisplaySnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn anchor_before(&self, point: DisplayPoint) -> DisplayAnchor {
|
||||
let diff_point = self.display_point_to_diff_point(point, Bias::Left);
|
||||
self.diff_snapshot().point_to_anchor(diff_point, Bias::Left)
|
||||
}
|
||||
|
||||
pub fn anchor_after(&self, point: DisplayPoint) -> DisplayAnchor {
|
||||
let diff_point = self.display_point_to_diff_point(point, Bias::Left);
|
||||
self.diff_snapshot()
|
||||
.point_to_anchor(diff_point, Bias::Right)
|
||||
}
|
||||
|
||||
pub fn anchor_to_point(&self, anchor: DisplayAnchor) -> DisplayPoint {
|
||||
let diff_point = self.diff_snapshot().anchor_to_point(anchor);
|
||||
self.diff_point_to_display_point(diff_point, Bias::Left)
|
||||
}
|
||||
|
||||
pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
|
||||
let mut clipped = self.block_snapshot.clip_point(point.0, bias);
|
||||
if self.clip_at_line_ends {
|
||||
@@ -1306,6 +1465,51 @@ impl DisplaySnapshot {
|
||||
pub fn excerpt_header_height(&self) -> u32 {
|
||||
self.block_snapshot.excerpt_header_height
|
||||
}
|
||||
|
||||
pub(crate) fn diff_snapshot(&self) -> &DiffMapSnapshot {
|
||||
&self.inlay_snapshot.diff_map_snapshot
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
|
||||
pub struct DisplayAnchor {
|
||||
pub anchor: multi_buffer::Anchor,
|
||||
pub diff_base_anchor: Option<text::Anchor>,
|
||||
}
|
||||
|
||||
impl DisplayAnchor {
|
||||
pub fn min() -> Self {
|
||||
Self {
|
||||
anchor: multi_buffer::Anchor::min(),
|
||||
diff_base_anchor: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max() -> Self {
|
||||
Self {
|
||||
anchor: multi_buffer::Anchor::max(),
|
||||
diff_base_anchor: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cmp(&self, other: &DisplayAnchor, map: &DisplaySnapshot) -> std::cmp::Ordering {
|
||||
map.diff_snapshot().compare_anchors(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DisplayCoordinate {
|
||||
fn to_display_anchor(self, map: &DisplaySnapshot) -> DisplayAnchor;
|
||||
}
|
||||
|
||||
impl DisplayCoordinate for DisplayAnchor {
|
||||
fn to_display_anchor(self, _: &DisplaySnapshot) -> DisplayAnchor {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl DisplayCoordinate for DisplayPoint {
|
||||
fn to_display_anchor(self, map: &DisplaySnapshot) -> DisplayAnchor {
|
||||
map.anchor_before(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::{
|
||||
wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
|
||||
Highlights,
|
||||
};
|
||||
use crate::{EditorStyle, GutterDimensions};
|
||||
use crate::{EditorStyle, GutterDimensions, RowInfo};
|
||||
use collections::{Bound, HashMap, HashSet};
|
||||
use gpui::{AnyElement, AppContext, EntityId, Pixels, WindowContext};
|
||||
use language::{Chunk, Patch, Point};
|
||||
@@ -399,9 +399,9 @@ pub struct BlockChunks<'a> {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BlockBufferRows<'a> {
|
||||
pub struct BlockRows<'a> {
|
||||
transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
|
||||
input_buffer_rows: wrap_map::WrapBufferRows<'a>,
|
||||
input_rows: wrap_map::WrapRows<'a>,
|
||||
output_row: BlockRow,
|
||||
started: bool,
|
||||
}
|
||||
@@ -1360,7 +1360,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn buffer_rows(&self, start_row: BlockRow) -> BlockBufferRows {
|
||||
pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows {
|
||||
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
|
||||
cursor.seek(&start_row, Bias::Right, &());
|
||||
let (output_start, input_start) = cursor.start();
|
||||
@@ -1373,9 +1373,9 @@ impl BlockSnapshot {
|
||||
0
|
||||
};
|
||||
let input_start_row = input_start.0 + overshoot;
|
||||
BlockBufferRows {
|
||||
BlockRows {
|
||||
transforms: cursor,
|
||||
input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row),
|
||||
input_rows: self.wrap_snapshot.row_infos(input_start_row),
|
||||
output_row: start_row,
|
||||
started: false,
|
||||
}
|
||||
@@ -1766,8 +1766,8 @@ impl<'a> Iterator for BlockChunks<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BlockBufferRows<'a> {
|
||||
type Item = Option<u32>;
|
||||
impl<'a> Iterator for BlockRows<'a> {
|
||||
type Item = RowInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.started {
|
||||
@@ -1796,7 +1796,7 @@ impl<'a> Iterator for BlockBufferRows<'a> {
|
||||
.as_ref()
|
||||
.map_or(true, |block| block.is_replacement())
|
||||
{
|
||||
self.input_buffer_rows.seek(self.transforms.start().1 .0);
|
||||
self.input_rows.seek(self.transforms.start().1 .0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1804,15 +1804,15 @@ impl<'a> Iterator for BlockBufferRows<'a> {
|
||||
if let Some(block) = transform.block.as_ref() {
|
||||
if block.is_replacement() && self.transforms.start().0 == self.output_row {
|
||||
if matches!(block, Block::FoldedBuffer { .. }) {
|
||||
Some(None)
|
||||
Some(RowInfo::default())
|
||||
} else {
|
||||
Some(self.input_buffer_rows.next().unwrap())
|
||||
Some(self.input_rows.next().unwrap())
|
||||
}
|
||||
} else {
|
||||
Some(None)
|
||||
Some(RowInfo::default())
|
||||
}
|
||||
} else {
|
||||
Some(self.input_buffer_rows.next().unwrap())
|
||||
Some(self.input_rows.next().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1928,7 +1928,8 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::display_map::{
|
||||
fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
|
||||
diff_map::DiffMap, fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap,
|
||||
wrap_map::WrapMap,
|
||||
};
|
||||
use gpui::{div, font, px, AppContext, Context as _, Element};
|
||||
use itertools::Itertools;
|
||||
@@ -1962,7 +1963,8 @@ mod tests {
|
||||
let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
|
||||
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
|
||||
let (wrap_map, wraps_snapshot) =
|
||||
@@ -2087,7 +2089,10 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|row_info| row_info.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
Some(0),
|
||||
None,
|
||||
@@ -2108,8 +2113,10 @@ mod tests {
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) =
|
||||
tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
|
||||
@@ -2171,8 +2178,8 @@ mod tests {
|
||||
.width;
|
||||
}
|
||||
|
||||
let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot.clone());
|
||||
let (_, diff_snapshot) = DiffMap::new(multi_buffer.clone(), cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot);
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
|
||||
@@ -2210,7 +2217,8 @@ mod tests {
|
||||
let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
|
||||
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
let (_inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
|
||||
let (_wrap_map, wraps_snapshot) =
|
||||
@@ -2312,7 +2320,8 @@ mod tests {
|
||||
|
||||
let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
|
||||
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, wraps_snapshot) = cx.update(|cx| {
|
||||
@@ -2356,7 +2365,8 @@ mod tests {
|
||||
let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
|
||||
let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
|
||||
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let tab_size = 1.try_into().unwrap();
|
||||
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
|
||||
@@ -2383,10 +2393,14 @@ mod tests {
|
||||
buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(
|
||||
buffer_snapshot.clone(),
|
||||
buffer_subscription.consume().into_inner(),
|
||||
);
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(
|
||||
buffer_snapshot,
|
||||
buffer_subscription.consume().into_inner(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot.clone(), diff_edits);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
|
||||
@@ -2406,10 +2420,14 @@ mod tests {
|
||||
);
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(
|
||||
buffer_snapshot.clone(),
|
||||
buffer_subscription.consume().into_inner(),
|
||||
);
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(
|
||||
buffer_snapshot.clone(),
|
||||
buffer_subscription.consume().into_inner(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
|
||||
@@ -2524,7 +2542,8 @@ mod tests {
|
||||
let buffer_id_2 = buffer_ids[1];
|
||||
let buffer_id_3 = buffer_ids[2];
|
||||
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, wrap_snapshot) =
|
||||
@@ -2537,7 +2556,10 @@ mod tests {
|
||||
"\n\n\n111\n\n\n\n\n222\n\n\n333\n\n\n444\n\n\n\n\n555\n\n\n666\n"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
@@ -2613,7 +2635,10 @@ mod tests {
|
||||
"\n\n\n111\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
@@ -2688,7 +2713,10 @@ mod tests {
|
||||
"\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
@@ -2753,7 +2781,10 @@ mod tests {
|
||||
);
|
||||
assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n\n555\n\n\n666\n\n");
|
||||
assert_eq!(
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
@@ -2807,7 +2838,10 @@ mod tests {
|
||||
"Should have extra newline for 111 buffer, due to a new block added when it was folded"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
@@ -2861,7 +2895,10 @@ mod tests {
|
||||
"Should have a single, first buffer left after folding"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
@@ -2895,7 +2932,8 @@ mod tests {
|
||||
assert_eq!(buffer_ids.len(), 1);
|
||||
let buffer_id = buffer_ids[0];
|
||||
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, wrap_snapshot) =
|
||||
@@ -2931,7 +2969,10 @@ mod tests {
|
||||
);
|
||||
assert_eq!(blocks_snapshot.text(), "\n");
|
||||
assert_eq!(
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![None, None],
|
||||
"When fully folded, should be no buffer rows"
|
||||
);
|
||||
@@ -2977,7 +3018,8 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (wrap_map, wraps_snapshot) = cx
|
||||
@@ -3035,8 +3077,10 @@ mod tests {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), vec![]);
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), vec![], cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) =
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
@@ -3073,8 +3117,10 @@ mod tests {
|
||||
.map(|block| block.id)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), vec![]);
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), vec![], cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) =
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
@@ -3094,8 +3140,11 @@ mod tests {
|
||||
log::info!("Noop fold/unfold operation on a singleton buffer");
|
||||
continue;
|
||||
}
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), vec![], cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), vec![]);
|
||||
inlay_map.sync(diff_snapshot.clone(), diff_edits);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) =
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
@@ -3180,8 +3229,10 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), buffer_edits, cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
|
||||
@@ -3384,7 +3435,8 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot
|
||||
.buffer_rows(BlockRow(start_row as u32))
|
||||
.row_infos(BlockRow(start_row as u32))
|
||||
.map(|row_info| row_info.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
&expected_buffer_rows[start_row..],
|
||||
"incorrect buffer_rows starting at row {:?}",
|
||||
|
||||
174
crates/editor/src/display_map/custom_highlights.rs
Normal file
@@ -0,0 +1,174 @@
|
||||
use collections::BTreeMap;
|
||||
use gpui::HighlightStyle;
|
||||
use language::Chunk;
|
||||
use multi_buffer::{Anchor, MultiBufferChunks, MultiBufferSnapshot, ToOffset as _};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
cmp,
|
||||
iter::{self, Peekable},
|
||||
ops::Range,
|
||||
sync::Arc,
|
||||
vec,
|
||||
};
|
||||
use sum_tree::TreeMap;
|
||||
|
||||
pub struct CustomHighlightsChunks<'a> {
|
||||
buffer_chunks: MultiBufferChunks<'a>,
|
||||
buffer_chunk: Option<Chunk<'a>>,
|
||||
offset: usize,
|
||||
multibuffer_snapshot: &'a MultiBufferSnapshot,
|
||||
|
||||
highlight_endpoints: Peekable<vec::IntoIter<HighlightEndpoint>>,
|
||||
active_highlights: BTreeMap<TypeId, HighlightStyle>,
|
||||
text_highlights: Option<&'a TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
struct HighlightEndpoint {
|
||||
offset: usize,
|
||||
is_start: bool,
|
||||
tag: TypeId,
|
||||
style: HighlightStyle,
|
||||
}
|
||||
|
||||
impl<'a> CustomHighlightsChunks<'a> {
|
||||
pub fn new(
|
||||
range: Range<usize>,
|
||||
language_aware: bool,
|
||||
text_highlights: Option<&'a TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>,
|
||||
multibuffer_snapshot: &'a MultiBufferSnapshot,
|
||||
) -> Self {
|
||||
Self {
|
||||
buffer_chunks: multibuffer_snapshot.chunks(range.clone(), language_aware),
|
||||
buffer_chunk: None,
|
||||
offset: range.start,
|
||||
|
||||
text_highlights,
|
||||
highlight_endpoints: create_highlight_endpoints(
|
||||
&range,
|
||||
text_highlights,
|
||||
multibuffer_snapshot,
|
||||
),
|
||||
active_highlights: Default::default(),
|
||||
multibuffer_snapshot,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn seek(&mut self, new_range: Range<usize>) {
|
||||
self.highlight_endpoints =
|
||||
create_highlight_endpoints(&new_range, self.text_highlights, self.multibuffer_snapshot);
|
||||
self.offset = new_range.start;
|
||||
self.buffer_chunks.seek(new_range);
|
||||
self.buffer_chunk.take();
|
||||
self.active_highlights.clear()
|
||||
}
|
||||
}
|
||||
|
||||
fn create_highlight_endpoints(
|
||||
range: &Range<usize>,
|
||||
text_highlights: Option<&TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>,
|
||||
buffer: &MultiBufferSnapshot,
|
||||
) -> iter::Peekable<vec::IntoIter<HighlightEndpoint>> {
|
||||
let mut highlight_endpoints = Vec::new();
|
||||
if let Some(text_highlights) = text_highlights {
|
||||
let start = buffer.anchor_after(range.start);
|
||||
let end = buffer.anchor_after(range.end);
|
||||
for (&tag, text_highlights) in text_highlights.iter() {
|
||||
let style = text_highlights.0;
|
||||
let ranges = &text_highlights.1;
|
||||
|
||||
let start_ix = match ranges.binary_search_by(|probe| {
|
||||
let cmp = probe.end.cmp(&start, &buffer);
|
||||
if cmp.is_gt() {
|
||||
cmp::Ordering::Greater
|
||||
} else {
|
||||
cmp::Ordering::Less
|
||||
}
|
||||
}) {
|
||||
Ok(i) | Err(i) => i,
|
||||
};
|
||||
|
||||
for range in &ranges[start_ix..] {
|
||||
if range.start.cmp(&end, &buffer).is_ge() {
|
||||
break;
|
||||
}
|
||||
|
||||
highlight_endpoints.push(HighlightEndpoint {
|
||||
offset: range.start.to_offset(&buffer),
|
||||
is_start: true,
|
||||
tag,
|
||||
style,
|
||||
});
|
||||
highlight_endpoints.push(HighlightEndpoint {
|
||||
offset: range.end.to_offset(&buffer),
|
||||
is_start: false,
|
||||
tag,
|
||||
style,
|
||||
});
|
||||
}
|
||||
}
|
||||
highlight_endpoints.sort();
|
||||
}
|
||||
highlight_endpoints.into_iter().peekable()
|
||||
}
|
||||
|
||||
impl<'a> Iterator for CustomHighlightsChunks<'a> {
|
||||
type Item = Chunk<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut next_highlight_endpoint = usize::MAX;
|
||||
while let Some(endpoint) = self.highlight_endpoints.peek().copied() {
|
||||
if endpoint.offset <= self.offset {
|
||||
if endpoint.is_start {
|
||||
self.active_highlights.insert(endpoint.tag, endpoint.style);
|
||||
} else {
|
||||
self.active_highlights.remove(&endpoint.tag);
|
||||
}
|
||||
self.highlight_endpoints.next();
|
||||
} else {
|
||||
next_highlight_endpoint = endpoint.offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let chunk = self
|
||||
.buffer_chunk
|
||||
.get_or_insert_with(|| self.buffer_chunks.next().unwrap());
|
||||
if chunk.text.is_empty() {
|
||||
*chunk = self.buffer_chunks.next().unwrap();
|
||||
}
|
||||
|
||||
let (prefix, suffix) = chunk
|
||||
.text
|
||||
.split_at(chunk.text.len().min(next_highlight_endpoint - self.offset));
|
||||
|
||||
chunk.text = suffix;
|
||||
self.offset += prefix.len();
|
||||
let mut prefix = Chunk {
|
||||
text: prefix,
|
||||
..chunk.clone()
|
||||
};
|
||||
if !self.active_highlights.is_empty() {
|
||||
let mut highlight_style = HighlightStyle::default();
|
||||
for active_highlight in self.active_highlights.values() {
|
||||
highlight_style.highlight(*active_highlight);
|
||||
}
|
||||
prefix.highlight_style = Some(highlight_style);
|
||||
}
|
||||
Some(prefix)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for HighlightEndpoint {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for HighlightEndpoint {
|
||||
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||
self.offset
|
||||
.cmp(&other.offset)
|
||||
.then_with(|| other.is_start.cmp(&self.is_start))
|
||||
}
|
||||
}
|
||||
2562
crates/editor/src/display_map/diff_map.rs
Normal file
@@ -1,3 +1,5 @@
|
||||
use crate::RowInfo;
|
||||
|
||||
use super::{
|
||||
inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
|
||||
Highlights,
|
||||
@@ -139,7 +141,7 @@ impl<'a> FoldMapWriter<'a> {
|
||||
let mut folds = Vec::new();
|
||||
let snapshot = self.0.snapshot.inlay_snapshot.clone();
|
||||
for (range, fold_text) in ranges.into_iter() {
|
||||
let buffer = &snapshot.buffer;
|
||||
let buffer = snapshot.buffer();
|
||||
let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
|
||||
|
||||
// Ignore any empty ranges.
|
||||
@@ -161,14 +163,14 @@ impl<'a> FoldMapWriter<'a> {
|
||||
});
|
||||
|
||||
let inlay_range =
|
||||
snapshot.to_inlay_offset(range.start)..snapshot.to_inlay_offset(range.end);
|
||||
snapshot.make_inlay_offset(range.start)..snapshot.make_inlay_offset(range.end);
|
||||
edits.push(InlayEdit {
|
||||
old: inlay_range.clone(),
|
||||
new: inlay_range,
|
||||
});
|
||||
}
|
||||
|
||||
let buffer = &snapshot.buffer;
|
||||
let buffer = snapshot.buffer();
|
||||
folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(&a.range, &b.range, buffer));
|
||||
|
||||
self.0.snapshot.folds = {
|
||||
@@ -220,7 +222,7 @@ impl<'a> FoldMapWriter<'a> {
|
||||
let mut edits = Vec::new();
|
||||
let mut fold_ixs_to_delete = Vec::new();
|
||||
let snapshot = self.0.snapshot.inlay_snapshot.clone();
|
||||
let buffer = &snapshot.buffer;
|
||||
let buffer = snapshot.buffer();
|
||||
for range in ranges.into_iter() {
|
||||
let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
|
||||
let mut folds_cursor =
|
||||
@@ -230,8 +232,8 @@ impl<'a> FoldMapWriter<'a> {
|
||||
fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer);
|
||||
if should_unfold(fold) {
|
||||
if offset_range.end > offset_range.start {
|
||||
let inlay_range = snapshot.to_inlay_offset(offset_range.start)
|
||||
..snapshot.to_inlay_offset(offset_range.end);
|
||||
let inlay_range = snapshot.make_inlay_offset(offset_range.start)
|
||||
..snapshot.make_inlay_offset(offset_range.end);
|
||||
edits.push(InlayEdit {
|
||||
old: inlay_range.clone(),
|
||||
new: inlay_range,
|
||||
@@ -275,7 +277,7 @@ impl FoldMap {
|
||||
pub(crate) fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) {
|
||||
let this = Self {
|
||||
snapshot: FoldSnapshot {
|
||||
folds: SumTree::new(&inlay_snapshot.buffer),
|
||||
folds: SumTree::new(inlay_snapshot.buffer()),
|
||||
transforms: SumTree::from_item(
|
||||
Transform {
|
||||
summary: TransformSummary {
|
||||
@@ -336,9 +338,7 @@ impl FoldMap {
|
||||
let mut folds = self.snapshot.folds.iter().peekable();
|
||||
while let Some(fold) = folds.next() {
|
||||
if let Some(next_fold) = folds.peek() {
|
||||
let comparison = fold
|
||||
.range
|
||||
.cmp(&next_fold.range, &self.snapshot.inlay_snapshot.buffer);
|
||||
let comparison = fold.range.cmp(&next_fold.range, self.snapshot.buffer());
|
||||
assert!(comparison.is_le());
|
||||
}
|
||||
}
|
||||
@@ -410,31 +410,31 @@ impl FoldMap {
|
||||
InlayOffset(((edit.new.start + edit.old_len()).0 as isize + delta) as usize);
|
||||
|
||||
let anchor = inlay_snapshot
|
||||
.buffer
|
||||
.buffer()
|
||||
.anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start));
|
||||
let mut folds_cursor = self
|
||||
.snapshot
|
||||
.folds
|
||||
.cursor::<FoldRange>(&inlay_snapshot.buffer);
|
||||
.cursor::<FoldRange>(&inlay_snapshot.buffer());
|
||||
folds_cursor.seek(
|
||||
&FoldRange(anchor..Anchor::max()),
|
||||
Bias::Left,
|
||||
&inlay_snapshot.buffer,
|
||||
&inlay_snapshot.buffer(),
|
||||
);
|
||||
|
||||
let mut folds = iter::from_fn({
|
||||
let inlay_snapshot = &inlay_snapshot;
|
||||
move || {
|
||||
let item = folds_cursor.item().map(|fold| {
|
||||
let buffer_start = fold.range.start.to_offset(&inlay_snapshot.buffer);
|
||||
let buffer_end = fold.range.end.to_offset(&inlay_snapshot.buffer);
|
||||
let buffer_start = fold.range.start.to_offset(&inlay_snapshot.buffer());
|
||||
let buffer_end = fold.range.end.to_offset(&inlay_snapshot.buffer());
|
||||
(
|
||||
fold.clone(),
|
||||
inlay_snapshot.to_inlay_offset(buffer_start)
|
||||
..inlay_snapshot.to_inlay_offset(buffer_end),
|
||||
inlay_snapshot.make_inlay_offset(buffer_start)
|
||||
..inlay_snapshot.make_inlay_offset(buffer_end),
|
||||
)
|
||||
});
|
||||
folds_cursor.next(&inlay_snapshot.buffer);
|
||||
folds_cursor.next(&inlay_snapshot.buffer());
|
||||
item
|
||||
}
|
||||
})
|
||||
@@ -578,6 +578,10 @@ pub struct FoldSnapshot {
|
||||
}
|
||||
|
||||
impl FoldSnapshot {
|
||||
pub fn buffer(&self) -> &MultiBufferSnapshot {
|
||||
self.inlay_snapshot.buffer()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn text(&self) -> String {
|
||||
self.chunks(FoldOffset(0)..self.len(), false, Highlights::default())
|
||||
@@ -587,7 +591,7 @@ impl FoldSnapshot {
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn fold_count(&self) -> usize {
|
||||
self.folds.items(&self.inlay_snapshot.buffer).len()
|
||||
self.folds.items(self.inlay_snapshot.buffer()).len()
|
||||
}
|
||||
|
||||
pub fn text_summary_for_range(&self, range: Range<FoldPoint>) -> TextSummary {
|
||||
@@ -641,6 +645,32 @@ impl FoldSnapshot {
|
||||
summary
|
||||
}
|
||||
|
||||
pub fn make_fold_point(&self, point: Point, bias: Bias) -> FoldPoint {
|
||||
self.to_fold_point(self.inlay_snapshot.make_inlay_point(point), bias)
|
||||
}
|
||||
|
||||
pub fn make_fold_offset(&self, buffer_offset: usize, bias: Bias) -> FoldOffset {
|
||||
self.to_fold_offset(self.inlay_snapshot.make_inlay_offset(buffer_offset), bias)
|
||||
}
|
||||
|
||||
pub fn to_fold_offset(&self, inlay_offset: InlayOffset, bias: Bias) -> FoldOffset {
|
||||
let mut cursor = self.transforms.cursor::<(InlayOffset, FoldOffset)>(&());
|
||||
cursor.seek(&inlay_offset, Bias::Right, &());
|
||||
if cursor.item().map_or(false, |t| t.is_fold()) {
|
||||
if bias == Bias::Left || inlay_offset == cursor.start().0 {
|
||||
cursor.start().1
|
||||
} else {
|
||||
cursor.end(&()).1
|
||||
}
|
||||
} else {
|
||||
let overshoot = inlay_offset.0 - cursor.start().0 .0;
|
||||
FoldOffset(cmp::min(
|
||||
cursor.start().1 .0 + overshoot,
|
||||
cursor.end(&()).1 .0,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint {
|
||||
let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
@@ -673,7 +703,7 @@ impl FoldSnapshot {
|
||||
(line_end - line_start) as u32
|
||||
}
|
||||
|
||||
pub fn buffer_rows(&self, start_row: u32) -> FoldBufferRows {
|
||||
pub fn row_infos(&self, start_row: u32) -> FoldRows {
|
||||
if start_row > self.transforms.summary().output.lines.row {
|
||||
panic!("invalid display row {}", start_row);
|
||||
}
|
||||
@@ -684,11 +714,11 @@ impl FoldSnapshot {
|
||||
|
||||
let overshoot = fold_point.0 - cursor.start().0 .0;
|
||||
let inlay_point = InlayPoint(cursor.start().1 .0 + overshoot);
|
||||
let input_buffer_rows = self.inlay_snapshot.buffer_rows(inlay_point.row());
|
||||
let input_rows = self.inlay_snapshot.row_infos(inlay_point.row());
|
||||
|
||||
FoldBufferRows {
|
||||
FoldRows {
|
||||
fold_point,
|
||||
input_buffer_rows,
|
||||
input_rows,
|
||||
cursor,
|
||||
}
|
||||
}
|
||||
@@ -706,12 +736,12 @@ impl FoldSnapshot {
|
||||
where
|
||||
T: ToOffset,
|
||||
{
|
||||
let buffer = &self.inlay_snapshot.buffer;
|
||||
let buffer = self.inlay_snapshot.buffer();
|
||||
let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
|
||||
let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false);
|
||||
iter::from_fn(move || {
|
||||
let item = folds.item();
|
||||
folds.next(&self.inlay_snapshot.buffer);
|
||||
folds.next(buffer);
|
||||
item
|
||||
})
|
||||
}
|
||||
@@ -720,8 +750,8 @@ impl FoldSnapshot {
|
||||
where
|
||||
T: ToOffset,
|
||||
{
|
||||
let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer);
|
||||
let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset);
|
||||
let buffer_offset = offset.to_offset(self.inlay_snapshot.buffer());
|
||||
let inlay_offset = self.inlay_snapshot.make_inlay_offset(buffer_offset);
|
||||
let mut cursor = self.transforms.cursor::<InlayOffset>(&());
|
||||
cursor.seek(&inlay_offset, Bias::Right, &());
|
||||
cursor.item().map_or(false, |t| t.placeholder.is_some())
|
||||
@@ -730,7 +760,7 @@ impl FoldSnapshot {
|
||||
pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
|
||||
let mut inlay_point = self
|
||||
.inlay_snapshot
|
||||
.to_inlay_point(Point::new(buffer_row.0, 0));
|
||||
.make_inlay_point(Point::new(buffer_row.0, 0));
|
||||
let mut cursor = self.transforms.cursor::<InlayPoint>(&());
|
||||
cursor.seek(&inlay_point, Bias::Right, &());
|
||||
loop {
|
||||
@@ -870,7 +900,7 @@ fn intersecting_folds<'a>(
|
||||
range: Range<usize>,
|
||||
inclusive: bool,
|
||||
) -> FilterCursor<'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize> {
|
||||
let buffer = &inlay_snapshot.buffer;
|
||||
let buffer = inlay_snapshot.buffer();
|
||||
let start = buffer.anchor_before(range.start.to_offset(buffer));
|
||||
let end = buffer.anchor_after(range.end.to_offset(buffer));
|
||||
let mut cursor = folds.filter::<_, usize>(buffer, move |summary| {
|
||||
@@ -1134,25 +1164,25 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FoldBufferRows<'a> {
|
||||
pub struct FoldRows<'a> {
|
||||
cursor: Cursor<'a, Transform, (FoldPoint, InlayPoint)>,
|
||||
input_buffer_rows: InlayBufferRows<'a>,
|
||||
input_rows: InlayBufferRows<'a>,
|
||||
fold_point: FoldPoint,
|
||||
}
|
||||
|
||||
impl<'a> FoldBufferRows<'a> {
|
||||
impl<'a> FoldRows<'a> {
|
||||
pub(crate) fn seek(&mut self, row: u32) {
|
||||
let fold_point = FoldPoint::new(row, 0);
|
||||
self.cursor.seek(&fold_point, Bias::Left, &());
|
||||
let overshoot = fold_point.0 - self.cursor.start().0 .0;
|
||||
let inlay_point = InlayPoint(self.cursor.start().1 .0 + overshoot);
|
||||
self.input_buffer_rows.seek(inlay_point.row());
|
||||
self.input_rows.seek(inlay_point.row());
|
||||
self.fold_point = fold_point;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for FoldBufferRows<'a> {
|
||||
type Item = Option<u32>;
|
||||
impl<'a> Iterator for FoldRows<'a> {
|
||||
type Item = RowInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut traversed_fold = false;
|
||||
@@ -1166,11 +1196,11 @@ impl<'a> Iterator for FoldBufferRows<'a> {
|
||||
|
||||
if self.cursor.item().is_some() {
|
||||
if traversed_fold {
|
||||
self.input_buffer_rows.seek(self.cursor.start().1.row());
|
||||
self.input_buffer_rows.next();
|
||||
self.input_rows.seek(self.cursor.start().1 .0.row);
|
||||
self.input_rows.next();
|
||||
}
|
||||
*self.fold_point.row_mut() += 1;
|
||||
self.input_buffer_rows.next()
|
||||
self.input_rows.next()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -1380,7 +1410,10 @@ pub type FoldEdit = Edit<FoldOffset>;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{display_map::inlay_map::InlayMap, MultiBuffer, ToPoint};
|
||||
use crate::{
|
||||
display_map::{diff_map::DiffMap, inlay_map::InlayMap},
|
||||
MultiBuffer, ToPoint,
|
||||
};
|
||||
use collections::HashSet;
|
||||
use rand::prelude::*;
|
||||
use settings::SettingsStore;
|
||||
@@ -1395,8 +1428,8 @@ mod tests {
|
||||
init_test(cx);
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||
|
||||
let (mut writer, _, _) = map.write(inlay_snapshot, vec![]);
|
||||
@@ -1431,8 +1464,14 @@ mod tests {
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(
|
||||
buffer_snapshot.clone(),
|
||||
subscription.consume().into_inner(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (snapshot3, edits) = map.read(inlay_snapshot, inlay_edits);
|
||||
assert_eq!(snapshot3.text(), "123a⋯c123c⋯eeeee");
|
||||
assert_eq!(
|
||||
@@ -1453,8 +1492,11 @@ mod tests {
|
||||
buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], None, cx);
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx)
|
||||
});
|
||||
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (snapshot4, _) = map.read(inlay_snapshot.clone(), inlay_edits);
|
||||
assert_eq!(snapshot4.text(), "123a⋯c123456eee");
|
||||
|
||||
@@ -1474,8 +1516,8 @@ mod tests {
|
||||
init_test(cx);
|
||||
let buffer = MultiBuffer::build_simple("abcdefghijkl", cx);
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
|
||||
{
|
||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||
@@ -1521,8 +1563,10 @@ mod tests {
|
||||
buffer.edit([(0..1, "12345")], None, cx);
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
|
||||
assert_eq!(snapshot.text(), "12345⋯fghijkl");
|
||||
}
|
||||
@@ -1531,8 +1575,8 @@ mod tests {
|
||||
#[gpui::test]
|
||||
fn test_overlapping_folds(cx: &mut gpui::AppContext) {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot);
|
||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||
writer.fold(vec![
|
||||
@@ -1550,8 +1594,8 @@ mod tests {
|
||||
init_test(cx);
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||
|
||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||
@@ -1566,8 +1610,10 @@ mod tests {
|
||||
buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx);
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
|
||||
assert_eq!(snapshot.text(), "aa⋯eeeee");
|
||||
}
|
||||
@@ -1576,7 +1622,8 @@ mod tests {
|
||||
fn test_folds_in_range(cx: &mut gpui::AppContext) {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||
|
||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||
@@ -1618,7 +1665,8 @@ mod tests {
|
||||
MultiBuffer::build_random(&mut rng, cx)
|
||||
};
|
||||
let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||
|
||||
let (mut initial_snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
||||
@@ -1648,8 +1696,10 @@ mod tests {
|
||||
}),
|
||||
};
|
||||
|
||||
let (inlay_snapshot, new_inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), buffer_edits, cx)
|
||||
});
|
||||
let (inlay_snapshot, new_inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
log::info!("inlay text {:?}", inlay_snapshot.text());
|
||||
|
||||
let inlay_edits = Patch::new(inlay_edits)
|
||||
@@ -1660,8 +1710,8 @@ mod tests {
|
||||
|
||||
let mut expected_text: String = inlay_snapshot.text().to_string();
|
||||
for fold_range in map.merged_folds().into_iter().rev() {
|
||||
let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start);
|
||||
let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end);
|
||||
let fold_inlay_start = inlay_snapshot.make_inlay_offset(fold_range.start);
|
||||
let fold_inlay_end = inlay_snapshot.make_inlay_offset(fold_range.end);
|
||||
expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "⋯");
|
||||
}
|
||||
|
||||
@@ -1676,19 +1726,19 @@ mod tests {
|
||||
let mut expected_buffer_rows = Vec::new();
|
||||
for fold_range in map.merged_folds() {
|
||||
let fold_start = inlay_snapshot
|
||||
.to_point(inlay_snapshot.to_inlay_offset(fold_range.start))
|
||||
.to_point(inlay_snapshot.make_inlay_offset(fold_range.start))
|
||||
.row();
|
||||
let fold_end = inlay_snapshot
|
||||
.to_point(inlay_snapshot.to_inlay_offset(fold_range.end))
|
||||
.to_point(inlay_snapshot.make_inlay_offset(fold_range.end))
|
||||
.row();
|
||||
expected_buffer_rows.extend(
|
||||
inlay_snapshot
|
||||
.buffer_rows(prev_row)
|
||||
.row_infos(prev_row)
|
||||
.take((1 + fold_start - prev_row) as usize),
|
||||
);
|
||||
prev_row = 1 + fold_end;
|
||||
}
|
||||
expected_buffer_rows.extend(inlay_snapshot.buffer_rows(prev_row));
|
||||
expected_buffer_rows.extend(inlay_snapshot.row_infos(prev_row));
|
||||
|
||||
assert_eq!(
|
||||
expected_buffer_rows.len(),
|
||||
@@ -1777,7 +1827,7 @@ mod tests {
|
||||
let mut fold_row = 0;
|
||||
while fold_row < expected_buffer_rows.len() as u32 {
|
||||
assert_eq!(
|
||||
snapshot.buffer_rows(fold_row).collect::<Vec<_>>(),
|
||||
snapshot.row_infos(fold_row).collect::<Vec<_>>(),
|
||||
expected_buffer_rows[(fold_row as usize)..],
|
||||
"wrong buffer rows starting at fold row {}",
|
||||
fold_row,
|
||||
@@ -1879,8 +1929,8 @@ mod tests {
|
||||
let text = sample_text(6, 6, 'a') + "\n";
|
||||
let buffer = MultiBuffer::build_simple(&text, cx);
|
||||
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot);
|
||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||
|
||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||
@@ -1892,10 +1942,19 @@ mod tests {
|
||||
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
|
||||
assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n");
|
||||
assert_eq!(
|
||||
snapshot.buffer_rows(0).collect::<Vec<_>>(),
|
||||
snapshot
|
||||
.row_infos(0)
|
||||
.map(|info| info.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
[Some(0), Some(3), Some(5), Some(6)]
|
||||
);
|
||||
assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [Some(6)]);
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.row_infos(3)
|
||||
.map(|info| info.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
[Some(6)]
|
||||
);
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut gpui::AppContext) {
|
||||
@@ -1906,7 +1965,7 @@ mod tests {
|
||||
impl FoldMap {
|
||||
fn merged_folds(&self) -> Vec<Range<usize>> {
|
||||
let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
|
||||
let buffer = &inlay_snapshot.buffer;
|
||||
let buffer = inlay_snapshot.buffer();
|
||||
let mut folds = self.snapshot.folds.items(buffer);
|
||||
// Ensure sorting doesn't change how folds get merged and displayed.
|
||||
folds.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
||||
@@ -1942,7 +2001,7 @@ mod tests {
|
||||
match rng.gen_range(0..=100) {
|
||||
0..=39 if !self.snapshot.folds.is_empty() => {
|
||||
let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
|
||||
let buffer = &inlay_snapshot.buffer;
|
||||
let buffer = inlay_snapshot.buffer();
|
||||
let mut to_unfold = Vec::new();
|
||||
for _ in 0..rng.gen_range(1..=3) {
|
||||
let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
|
||||
@@ -1958,7 +2017,7 @@ mod tests {
|
||||
}
|
||||
_ => {
|
||||
let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
|
||||
let buffer = &inlay_snapshot.buffer;
|
||||
let buffer = inlay_snapshot.buffer();
|
||||
let mut to_fold = Vec::new();
|
||||
for _ in 0..rng.gen_range(1..=2) {
|
||||
let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
|
||||
|
||||
@@ -164,7 +164,7 @@ pub struct TabSnapshot {
|
||||
|
||||
impl TabSnapshot {
|
||||
pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
|
||||
&self.fold_snapshot.inlay_snapshot.buffer
|
||||
self.fold_snapshot.inlay_snapshot.buffer()
|
||||
}
|
||||
|
||||
pub fn line_len(&self, row: u32) -> u32 {
|
||||
@@ -272,8 +272,8 @@ impl TabSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buffer_rows(&self, row: u32) -> fold_map::FoldBufferRows<'_> {
|
||||
self.fold_snapshot.buffer_rows(row)
|
||||
pub fn rows(&self, row: u32) -> fold_map::FoldRows<'_> {
|
||||
self.fold_snapshot.row_infos(row)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -317,8 +317,7 @@ impl TabSnapshot {
|
||||
}
|
||||
|
||||
pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint {
|
||||
let inlay_point = self.fold_snapshot.inlay_snapshot.to_inlay_point(point);
|
||||
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
|
||||
let fold_point = self.fold_snapshot.make_fold_point(point, bias);
|
||||
self.to_tab_point(fold_point)
|
||||
}
|
||||
|
||||
@@ -602,7 +601,7 @@ impl<'a> Iterator for TabChunks<'a> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
display_map::{fold_map::FoldMap, inlay_map::InlayMap},
|
||||
display_map::{diff_map::DiffMap, fold_map::FoldMap, inlay_map::InlayMap},
|
||||
MultiBuffer,
|
||||
};
|
||||
use rand::{prelude::StdRng, Rng};
|
||||
@@ -610,8 +609,8 @@ mod tests {
|
||||
#[gpui::test]
|
||||
fn test_expand_tabs(cx: &mut gpui::AppContext) {
|
||||
let buffer = MultiBuffer::build_simple("", cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer, cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
@@ -627,8 +626,8 @@ mod tests {
|
||||
let output = "A BC DEF G HI J K L M";
|
||||
|
||||
let buffer = MultiBuffer::build_simple(input, cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer, cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
@@ -674,8 +673,8 @@ mod tests {
|
||||
let input = "abcdefg⋯hij";
|
||||
|
||||
let buffer = MultiBuffer::build_simple(input, cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
@@ -688,8 +687,8 @@ mod tests {
|
||||
let input = "\t \thello";
|
||||
|
||||
let buffer = MultiBuffer::build_simple(input, cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot);
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
@@ -749,8 +748,11 @@ mod tests {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
log::info!("Buffer text: {:?}", buffer_snapshot.text());
|
||||
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
|
||||
log::info!("DiffMap text: {:?}", diff_snapshot.text());
|
||||
let (mut fold_map, _) = FoldMap::new(inlay_snapshot.clone());
|
||||
fold_map.randomly_mutate(&mut rng);
|
||||
let (fold_snapshot, _) = fold_map.read(inlay_snapshot, vec![]);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::RowInfo;
|
||||
|
||||
use super::{
|
||||
fold_map::FoldBufferRows,
|
||||
fold_map::FoldRows,
|
||||
tab_map::{self, TabEdit, TabPoint, TabSnapshot},
|
||||
Highlights,
|
||||
};
|
||||
@@ -60,16 +62,16 @@ pub struct WrapChunks<'a> {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WrapBufferRows<'a> {
|
||||
input_buffer_rows: FoldBufferRows<'a>,
|
||||
input_buffer_row: Option<u32>,
|
||||
pub struct WrapRows<'a> {
|
||||
input_buffer_rows: FoldRows<'a>,
|
||||
input_buffer_row: RowInfo,
|
||||
output_row: u32,
|
||||
soft_wrapped: bool,
|
||||
max_output_row: u32,
|
||||
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
|
||||
}
|
||||
|
||||
impl<'a> WrapBufferRows<'a> {
|
||||
impl<'a> WrapRows<'a> {
|
||||
pub(crate) fn seek(&mut self, start_row: u32) {
|
||||
self.transforms
|
||||
.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
|
||||
@@ -717,7 +719,7 @@ impl WrapSnapshot {
|
||||
self.transforms.summary().output.longest_row
|
||||
}
|
||||
|
||||
pub fn buffer_rows(&self, start_row: u32) -> WrapBufferRows {
|
||||
pub fn row_infos(&self, start_row: u32) -> WrapRows {
|
||||
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||
transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
|
||||
let mut input_row = transforms.start().1.row();
|
||||
@@ -725,9 +727,9 @@ impl WrapSnapshot {
|
||||
input_row += start_row - transforms.start().0.row();
|
||||
}
|
||||
let soft_wrapped = transforms.item().map_or(false, |t| !t.is_isomorphic());
|
||||
let mut input_buffer_rows = self.tab_snapshot.buffer_rows(input_row);
|
||||
let mut input_buffer_rows = self.tab_snapshot.rows(input_row);
|
||||
let input_buffer_row = input_buffer_rows.next().unwrap();
|
||||
WrapBufferRows {
|
||||
WrapRows {
|
||||
transforms,
|
||||
input_buffer_row,
|
||||
input_buffer_rows,
|
||||
@@ -847,7 +849,7 @@ impl WrapSnapshot {
|
||||
}
|
||||
|
||||
let text = language::Rope::from(self.text().as_str());
|
||||
let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0);
|
||||
let mut input_buffer_rows = self.tab_snapshot.rows(0);
|
||||
let mut expected_buffer_rows = Vec::new();
|
||||
let mut prev_tab_row = 0;
|
||||
for display_row in 0..=self.max_point().row() {
|
||||
@@ -855,7 +857,7 @@ impl WrapSnapshot {
|
||||
if tab_point.row() == prev_tab_row && display_row != 0 {
|
||||
expected_buffer_rows.push(None);
|
||||
} else {
|
||||
expected_buffer_rows.push(input_buffer_rows.next().unwrap());
|
||||
expected_buffer_rows.push(input_buffer_rows.next().unwrap().buffer_row);
|
||||
}
|
||||
|
||||
prev_tab_row = tab_point.row();
|
||||
@@ -864,7 +866,8 @@ impl WrapSnapshot {
|
||||
|
||||
for start_display_row in 0..expected_buffer_rows.len() {
|
||||
assert_eq!(
|
||||
self.buffer_rows(start_display_row as u32)
|
||||
self.row_infos(start_display_row as u32)
|
||||
.map(|row_info| row_info.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
&expected_buffer_rows[start_display_row..],
|
||||
"invalid buffer_rows({}..)",
|
||||
@@ -958,8 +961,8 @@ impl<'a> Iterator for WrapChunks<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for WrapBufferRows<'a> {
|
||||
type Item = Option<u32>;
|
||||
impl<'a> Iterator for WrapRows<'a> {
|
||||
type Item = RowInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.output_row > self.max_output_row {
|
||||
@@ -979,7 +982,11 @@ impl<'a> Iterator for WrapBufferRows<'a> {
|
||||
self.soft_wrapped = true;
|
||||
}
|
||||
|
||||
Some(if soft_wrapped { None } else { buffer_row })
|
||||
Some(if soft_wrapped {
|
||||
RowInfo::default()
|
||||
} else {
|
||||
buffer_row
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1161,7 +1168,7 @@ fn consolidate_wrap_edits(edits: Vec<WrapEdit>) -> Vec<WrapEdit> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
|
||||
display_map::{diff_map::DiffMap, fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
|
||||
MultiBuffer,
|
||||
};
|
||||
use gpui::{font, px, test::observe};
|
||||
@@ -1209,9 +1216,11 @@ mod tests {
|
||||
});
|
||||
let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
log::info!("Buffer text: {:?}", buffer_snapshot.text());
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
log::info!("DiffMap text: {:?}", diff_snapshot.text());
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot);
|
||||
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
log::info!("FoldMap text: {:?}", fold_snapshot.text());
|
||||
let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
|
||||
let tabs_snapshot = tab_map.set_max_expansion_column(32);
|
||||
@@ -1293,8 +1302,10 @@ mod tests {
|
||||
}
|
||||
|
||||
log::info!("Buffer text: {:?}", buffer_snapshot.text());
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), buffer_edits, cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
log::info!("FoldMap text: {:?}", fold_snapshot.text());
|
||||
|
||||
@@ -73,7 +73,7 @@ use fuzzy::StringMatchCandidate;
|
||||
|
||||
use code_context_menus::{
|
||||
AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
|
||||
CompletionsMenu, ContextMenuOrigin,
|
||||
CompletionEntry, CompletionsMenu, ContextMenuOrigin,
|
||||
};
|
||||
use git::blame::GitBlame;
|
||||
use gpui::{
|
||||
@@ -89,7 +89,7 @@ use gpui::{
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
use hover_popover::{hide_hover, HoverState};
|
||||
pub(crate) use hunk_diff::HoveredHunk;
|
||||
use hunk_diff::{diff_hunk_to_display, DiffMap, DiffMapSnapshot};
|
||||
use hunk_diff::{diff_hunk_to_display, DiffMap};
|
||||
use indent_guides::ActiveIndentGuidesState;
|
||||
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
|
||||
pub use inline_completion::Direction;
|
||||
@@ -457,6 +457,21 @@ pub fn make_suggestion_styles(cx: &WindowContext) -> InlineCompletionStyles {
|
||||
|
||||
type CompletionId = usize;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct InlineCompletionMenuHint {
|
||||
provider_name: &'static str,
|
||||
text: InlineCompletionText,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum InlineCompletionText {
|
||||
Move(SharedString),
|
||||
Edit {
|
||||
text: SharedString,
|
||||
highlights: Vec<(Range<usize>, HighlightStyle)>,
|
||||
},
|
||||
}
|
||||
|
||||
enum InlineCompletion {
|
||||
Edit(Vec<(Range<Anchor>, String)>),
|
||||
Move(Anchor),
|
||||
@@ -710,7 +725,6 @@ pub struct EditorSnapshot {
|
||||
git_blame_gutter_max_author_length: Option<usize>,
|
||||
pub display_snapshot: DisplaySnapshot,
|
||||
pub placeholder_text: Option<Arc<str>>,
|
||||
diff_map: DiffMapSnapshot,
|
||||
is_focused: bool,
|
||||
scroll_anchor: ScrollAnchor,
|
||||
ongoing_scroll: OngoingScroll,
|
||||
@@ -1190,7 +1204,12 @@ impl Editor {
|
||||
|
||||
let mut code_action_providers = Vec::new();
|
||||
if let Some(project) = project.clone() {
|
||||
get_unstaged_changes_for_buffers(&project, buffer.read(cx).all_buffers(), cx);
|
||||
get_unstaged_changes_for_buffers(
|
||||
&project,
|
||||
buffer.read(cx).all_buffers(),
|
||||
display_map.clone(),
|
||||
cx,
|
||||
);
|
||||
code_action_providers.push(Rc::new(project) as Rc<_>);
|
||||
}
|
||||
|
||||
@@ -1355,7 +1374,7 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
this.report_editor_event("open", None, cx);
|
||||
this.report_editor_event("Editor Opened", None, cx);
|
||||
this
|
||||
}
|
||||
|
||||
@@ -1382,18 +1401,16 @@ impl Editor {
|
||||
if self.pending_rename.is_some() {
|
||||
key_context.add("renaming");
|
||||
}
|
||||
if self.context_menu_visible() {
|
||||
match self.context_menu.borrow().as_ref() {
|
||||
Some(CodeContextMenu::Completions(_)) => {
|
||||
key_context.add("menu");
|
||||
key_context.add("showing_completions")
|
||||
}
|
||||
Some(CodeContextMenu::CodeActions(_)) => {
|
||||
key_context.add("menu");
|
||||
key_context.add("showing_code_actions")
|
||||
}
|
||||
None => {}
|
||||
match self.context_menu.borrow().as_ref() {
|
||||
Some(CodeContextMenu::Completions(_)) => {
|
||||
key_context.add("menu");
|
||||
key_context.add("showing_completions")
|
||||
}
|
||||
Some(CodeContextMenu::CodeActions(_)) => {
|
||||
key_context.add("menu");
|
||||
key_context.add("showing_code_actions")
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
// Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
|
||||
@@ -1555,7 +1572,6 @@ impl Editor {
|
||||
scroll_anchor: self.scroll_manager.anchor(),
|
||||
ongoing_scroll: self.scroll_manager.ongoing_scroll(),
|
||||
placeholder_text: self.placeholder_text.clone(),
|
||||
diff_map: self.diff_map.snapshot(),
|
||||
is_focused: self.focus_handle.is_focused(cx),
|
||||
current_line_highlight: self
|
||||
.current_line_highlight
|
||||
@@ -2429,7 +2445,7 @@ impl Editor {
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
if self.dismiss_menus_and_popups(false, true, cx) {
|
||||
if self.dismiss_menus_and_popups(true, cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2444,7 +2460,6 @@ impl Editor {
|
||||
|
||||
pub fn dismiss_menus_and_popups(
|
||||
&mut self,
|
||||
keep_inline_completion: bool,
|
||||
should_report_inline_completion_event: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> bool {
|
||||
@@ -2461,6 +2476,9 @@ impl Editor {
|
||||
}
|
||||
|
||||
if self.hide_context_menu(cx).is_some() {
|
||||
if self.has_active_inline_completion() {
|
||||
self.update_visible_inline_completion(cx);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2468,9 +2486,7 @@ impl Editor {
|
||||
return true;
|
||||
}
|
||||
|
||||
if !keep_inline_completion
|
||||
&& self.discard_inline_completion(should_report_inline_completion_event, cx)
|
||||
{
|
||||
if self.discard_inline_completion(should_report_inline_completion_event, cx) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3709,21 +3725,17 @@ impl Editor {
|
||||
completions.into(),
|
||||
aside_was_displayed,
|
||||
);
|
||||
|
||||
menu.filter(query.as_deref(), cx.background_executor().clone())
|
||||
.await;
|
||||
|
||||
if menu.matches.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(menu)
|
||||
}
|
||||
menu.visible().then_some(menu)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
let mut context_menu = editor.context_menu.borrow_mut();
|
||||
match context_menu.as_ref() {
|
||||
match editor.context_menu.borrow().as_ref() {
|
||||
None => {}
|
||||
Some(CodeContextMenu::Completions(prev_menu)) => {
|
||||
if prev_menu.id > id {
|
||||
@@ -3736,14 +3748,20 @@ impl Editor {
|
||||
if editor.focus_handle.is_focused(cx) && menu.is_some() {
|
||||
let mut menu = menu.unwrap();
|
||||
menu.resolve_selected_completion(editor.completion_provider.as_deref(), cx);
|
||||
*context_menu = Some(CodeContextMenu::Completions(menu));
|
||||
drop(context_menu);
|
||||
|
||||
if let Some(hint) = editor.inline_completion_menu_hint(cx) {
|
||||
editor.hide_active_inline_completion(cx);
|
||||
menu.show_inline_completion_hint(hint);
|
||||
}
|
||||
|
||||
*editor.context_menu.borrow_mut() =
|
||||
Some(CodeContextMenu::Completions(menu));
|
||||
|
||||
cx.notify();
|
||||
} else if editor.completion_tasks.len() <= 1 {
|
||||
// If there are no more completion tasks and the last menu was
|
||||
// empty, we should hide it. If it was already hidden, we should
|
||||
// also show the copilot completion when available.
|
||||
drop(context_menu);
|
||||
editor.hide_context_menu(cx);
|
||||
}
|
||||
})?;
|
||||
@@ -3780,7 +3798,6 @@ impl Editor {
|
||||
) -> Option<Task<std::result::Result<(), anyhow::Error>>> {
|
||||
use language::ToOffset as _;
|
||||
|
||||
self.discard_inline_completion(true, cx);
|
||||
let completions_menu =
|
||||
if let CodeContextMenu::Completions(menu) = self.hide_context_menu(cx)? {
|
||||
menu
|
||||
@@ -3789,8 +3806,21 @@ impl Editor {
|
||||
};
|
||||
|
||||
let mat = completions_menu
|
||||
.matches
|
||||
.entries
|
||||
.get(item_ix.unwrap_or(completions_menu.selected_item))?;
|
||||
|
||||
let mat = match mat {
|
||||
CompletionEntry::InlineCompletionHint { .. } => {
|
||||
self.accept_inline_completion(&AcceptInlineCompletion, cx);
|
||||
cx.stop_propagation();
|
||||
return Some(Task::ready(Ok(())));
|
||||
}
|
||||
CompletionEntry::Match(mat) => {
|
||||
self.discard_inline_completion(true, cx);
|
||||
mat
|
||||
}
|
||||
};
|
||||
|
||||
let buffer_handle = completions_menu.buffer;
|
||||
let completions = completions_menu.completions.borrow_mut();
|
||||
let completion = completions.get(mat.candidate_id)?;
|
||||
@@ -4673,6 +4703,17 @@ impl Editor {
|
||||
Some(active_inline_completion.completion)
|
||||
}
|
||||
|
||||
fn hide_active_inline_completion(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(active_inline_completion) = self.active_inline_completion.as_ref() {
|
||||
self.splice_inlays(
|
||||
active_inline_completion.inlay_ids.clone(),
|
||||
Default::default(),
|
||||
cx,
|
||||
);
|
||||
self.clear_highlights::<InlineCompletionHighlight>(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_visible_inline_completion(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
|
||||
let selection = self.selections.newest_anchor();
|
||||
let cursor = selection.head();
|
||||
@@ -4744,32 +4785,34 @@ impl Editor {
|
||||
invalidation_row_range = edit_start_row..cursor_row;
|
||||
completion = InlineCompletion::Move(first_edit_start);
|
||||
} else {
|
||||
if edits
|
||||
.iter()
|
||||
.all(|(range, _)| range.to_offset(&multibuffer).is_empty())
|
||||
{
|
||||
let mut inlays = Vec::new();
|
||||
for (range, new_text) in &edits {
|
||||
let inlay = Inlay::inline_completion(
|
||||
post_inc(&mut self.next_inlay_id),
|
||||
range.start,
|
||||
new_text.as_str(),
|
||||
);
|
||||
inlay_ids.push(inlay.id);
|
||||
inlays.push(inlay);
|
||||
}
|
||||
if !self.has_active_completions_menu() {
|
||||
if edits
|
||||
.iter()
|
||||
.all(|(range, _)| range.to_offset(&multibuffer).is_empty())
|
||||
{
|
||||
let mut inlays = Vec::new();
|
||||
for (range, new_text) in &edits {
|
||||
let inlay = Inlay::inline_completion(
|
||||
post_inc(&mut self.next_inlay_id),
|
||||
range.start,
|
||||
new_text.as_str(),
|
||||
);
|
||||
inlay_ids.push(inlay.id);
|
||||
inlays.push(inlay);
|
||||
}
|
||||
|
||||
self.splice_inlays(vec![], inlays, cx);
|
||||
} else {
|
||||
let background_color = cx.theme().status().deleted_background;
|
||||
self.highlight_text::<InlineCompletionHighlight>(
|
||||
edits.iter().map(|(range, _)| range.clone()).collect(),
|
||||
HighlightStyle {
|
||||
background_color: Some(background_color),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
);
|
||||
self.splice_inlays(vec![], inlays, cx);
|
||||
} else {
|
||||
let background_color = cx.theme().status().deleted_background;
|
||||
self.highlight_text::<InlineCompletionHighlight>(
|
||||
edits.iter().map(|(range, _)| range.clone()).collect(),
|
||||
HighlightStyle {
|
||||
background_color: Some(background_color),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
invalidation_row_range = edit_start_row..edit_end_row;
|
||||
@@ -4788,11 +4831,54 @@ impl Editor {
|
||||
completion,
|
||||
invalidation_range,
|
||||
});
|
||||
|
||||
if self.has_active_completions_menu() {
|
||||
if let Some(hint) = self.inline_completion_menu_hint(cx) {
|
||||
match self.context_menu.borrow_mut().as_mut() {
|
||||
Some(CodeContextMenu::Completions(menu)) => {
|
||||
menu.show_inline_completion_hint(hint);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn inline_completion_menu_hint(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<InlineCompletionMenuHint> {
|
||||
if self.has_active_inline_completion() {
|
||||
let provider_name = self.inline_completion_provider()?.display_name();
|
||||
let editor_snapshot = self.snapshot(cx);
|
||||
|
||||
let text = match &self.active_inline_completion.as_ref()?.completion {
|
||||
InlineCompletion::Edit(edits) => {
|
||||
inline_completion_edit_text(&editor_snapshot, edits, cx)
|
||||
}
|
||||
InlineCompletion::Move(target) => {
|
||||
let target_point =
|
||||
target.to_point(&editor_snapshot.display_snapshot.buffer_snapshot);
|
||||
let target_line = target_point.row + 1;
|
||||
InlineCompletionText::Move(
|
||||
format!("Jump to edit in line {}", target_line).into(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
Some(InlineCompletionMenuHint {
|
||||
provider_name,
|
||||
text,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn inline_completion_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
|
||||
Some(self.inline_completion_provider.as_ref()?.provider.clone())
|
||||
}
|
||||
@@ -4999,6 +5085,7 @@ impl Editor {
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn context_menu_visible(&self) -> bool {
|
||||
self.context_menu
|
||||
.borrow()
|
||||
@@ -5006,21 +5093,43 @@ impl Editor {
|
||||
.map_or(false, |menu| menu.visible())
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
pub fn context_menu_contains_inline_completion(&self) -> bool {
|
||||
self.context_menu
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map_or(false, |menu| match menu {
|
||||
CodeContextMenu::Completions(menu) => menu.entries.first().map_or(false, |entry| {
|
||||
matches!(entry, CompletionEntry::InlineCompletionHint(_))
|
||||
}),
|
||||
CodeContextMenu::CodeActions(_) => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn context_menu_origin(&self, cursor_position: DisplayPoint) -> Option<ContextMenuOrigin> {
|
||||
self.context_menu
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|menu| menu.origin(cursor_position))
|
||||
}
|
||||
|
||||
fn render_context_menu(
|
||||
&self,
|
||||
cursor_position: DisplayPoint,
|
||||
style: &EditorStyle,
|
||||
max_height: Pixels,
|
||||
max_height_in_lines: u32,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Option<(ContextMenuOrigin, AnyElement)> {
|
||||
self.context_menu.borrow().as_ref().map(|menu| {
|
||||
menu.render(
|
||||
cursor_position,
|
||||
style,
|
||||
max_height,
|
||||
self.workspace.as_ref().map(|(w, _)| w.clone()),
|
||||
cx,
|
||||
)
|
||||
) -> Option<AnyElement> {
|
||||
self.context_menu.borrow().as_ref().and_then(|menu| {
|
||||
if menu.visible() {
|
||||
Some(menu.render(
|
||||
style,
|
||||
max_height_in_lines,
|
||||
self.workspace.as_ref().map(|(w, _)| w.clone()),
|
||||
cx,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5812,10 +5921,9 @@ impl Editor {
|
||||
pub fn revert_file(&mut self, _: &RevertFile, cx: &mut ViewContext<Self>) {
|
||||
let mut revert_changes = HashMap::default();
|
||||
let snapshot = self.snapshot(cx);
|
||||
for hunk in hunks_for_ranges(
|
||||
Some(Point::zero()..snapshot.buffer_snapshot.max_point()).into_iter(),
|
||||
&snapshot,
|
||||
) {
|
||||
for hunk in snapshot
|
||||
.hunks_for_ranges(Some(Point::zero()..snapshot.buffer_snapshot.max_point()).into_iter())
|
||||
{
|
||||
self.prepare_revert_change(&mut revert_changes, &hunk, cx);
|
||||
}
|
||||
if !revert_changes.is_empty() {
|
||||
@@ -5833,7 +5941,12 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn revert_selected_hunks(&mut self, _: &RevertSelectedHunks, cx: &mut ViewContext<Self>) {
|
||||
let revert_changes = self.gather_revert_changes(&self.selections.all(cx), cx);
|
||||
let selections = self.selections.all(cx).into_iter().map(|s| s.range());
|
||||
let mut revert_changes = HashMap::default();
|
||||
let snapshot = self.snapshot(cx);
|
||||
for hunk in &snapshot.hunks_for_ranges(selections) {
|
||||
self.prepare_revert_change(&mut revert_changes, &hunk, cx);
|
||||
}
|
||||
if !revert_changes.is_empty() {
|
||||
self.transact(cx, |editor, cx| {
|
||||
editor.revert(revert_changes, cx);
|
||||
@@ -5870,28 +5983,18 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn gather_revert_changes(
|
||||
&mut self,
|
||||
selections: &[Selection<Point>],
|
||||
cx: &mut ViewContext<'_, Editor>,
|
||||
) -> HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>> {
|
||||
let mut revert_changes = HashMap::default();
|
||||
let snapshot = self.snapshot(cx);
|
||||
for hunk in hunks_for_selections(&snapshot, selections) {
|
||||
self.prepare_revert_change(&mut revert_changes, &hunk, cx);
|
||||
}
|
||||
revert_changes
|
||||
}
|
||||
|
||||
pub fn prepare_revert_change(
|
||||
&mut self,
|
||||
revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
|
||||
hunk: &MultiBufferDiffHunk,
|
||||
cx: &AppContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<()> {
|
||||
let change_set = self
|
||||
.display_map
|
||||
.read(cx)
|
||||
.diff_base_for(hunk.buffer_id, cx)?;
|
||||
let buffer = self.buffer.read(cx).buffer(hunk.buffer_id)?;
|
||||
let buffer = buffer.read(cx);
|
||||
let change_set = &self.diff_map.diff_bases.get(&hunk.buffer_id)?.change_set;
|
||||
let original_text = change_set
|
||||
.read(cx)
|
||||
.base_text
|
||||
@@ -9107,9 +9210,8 @@ impl Editor {
|
||||
snapshot,
|
||||
position,
|
||||
ix > 0,
|
||||
snapshot.diff_map.diff_hunks_in_range(
|
||||
snapshot.diff_hunks_in_range(
|
||||
position + Point::new(1, 0)..snapshot.buffer_snapshot.max_point(),
|
||||
&snapshot.buffer_snapshot,
|
||||
),
|
||||
cx,
|
||||
) {
|
||||
@@ -9139,9 +9241,7 @@ impl Editor {
|
||||
snapshot,
|
||||
position,
|
||||
ix > 0,
|
||||
snapshot
|
||||
.diff_map
|
||||
.diff_hunks_in_range_rev(Point::zero()..position, &snapshot.buffer_snapshot),
|
||||
snapshot.diff_hunks_in_range_rev(Point::zero()..position),
|
||||
cx,
|
||||
) {
|
||||
return Some(hunk);
|
||||
@@ -10329,31 +10429,20 @@ impl Editor {
|
||||
self.fold(&Default::default(), cx)
|
||||
}
|
||||
} else {
|
||||
let (display_snapshot, selections) = self.selections.all_adjusted_display(cx);
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut toggled_buffers = HashSet::default();
|
||||
for selection in selections {
|
||||
if let Some(buffer_id) = display_snapshot
|
||||
.display_point_to_anchor(selection.head(), Bias::Right)
|
||||
.buffer_id
|
||||
{
|
||||
if toggled_buffers.insert(buffer_id) {
|
||||
if self.buffer_folded(buffer_id, cx) {
|
||||
self.unfold_buffer(buffer_id, cx);
|
||||
} else {
|
||||
self.fold_buffer(buffer_id, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(buffer_id) = display_snapshot
|
||||
.display_point_to_anchor(selection.tail(), Bias::Left)
|
||||
.buffer_id
|
||||
{
|
||||
if toggled_buffers.insert(buffer_id) {
|
||||
if self.buffer_folded(buffer_id, cx) {
|
||||
self.unfold_buffer(buffer_id, cx);
|
||||
} else {
|
||||
self.fold_buffer(buffer_id, cx);
|
||||
}
|
||||
for (_, buffer_snapshot, _) in multi_buffer_snapshot.excerpts_in_ranges(
|
||||
self.selections
|
||||
.disjoint_anchors()
|
||||
.into_iter()
|
||||
.map(|selection| selection.range()),
|
||||
) {
|
||||
let buffer_id = buffer_snapshot.remote_id();
|
||||
if toggled_buffers.insert(buffer_id) {
|
||||
if self.buffer_folded(buffer_id, cx) {
|
||||
self.unfold_buffer(buffer_id, cx);
|
||||
} else {
|
||||
self.fold_buffer(buffer_id, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10426,24 +10515,17 @@ impl Editor {
|
||||
|
||||
self.fold_creases(to_fold, true, cx);
|
||||
} else {
|
||||
let (display_snapshot, selections) = self.selections.all_adjusted_display(cx);
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut folded_buffers = HashSet::default();
|
||||
for selection in selections {
|
||||
if let Some(buffer_id) = display_snapshot
|
||||
.display_point_to_anchor(selection.head(), Bias::Right)
|
||||
.buffer_id
|
||||
{
|
||||
if folded_buffers.insert(buffer_id) {
|
||||
self.fold_buffer(buffer_id, cx);
|
||||
}
|
||||
}
|
||||
if let Some(buffer_id) = display_snapshot
|
||||
.display_point_to_anchor(selection.tail(), Bias::Left)
|
||||
.buffer_id
|
||||
{
|
||||
if folded_buffers.insert(buffer_id) {
|
||||
self.fold_buffer(buffer_id, cx);
|
||||
}
|
||||
for (_, buffer_snapshot, _) in multi_buffer_snapshot.excerpts_in_ranges(
|
||||
self.selections
|
||||
.disjoint_anchors()
|
||||
.into_iter()
|
||||
.map(|selection| selection.range()),
|
||||
) {
|
||||
let buffer_id = buffer_snapshot.remote_id();
|
||||
if folded_buffers.insert(buffer_id) {
|
||||
self.fold_buffer(buffer_id, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10599,24 +10681,17 @@ impl Editor {
|
||||
|
||||
self.unfold_ranges(&ranges, true, true, cx);
|
||||
} else {
|
||||
let (display_snapshot, selections) = self.selections.all_adjusted_display(cx);
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut unfolded_buffers = HashSet::default();
|
||||
for selection in selections {
|
||||
if let Some(buffer_id) = display_snapshot
|
||||
.display_point_to_anchor(selection.head(), Bias::Right)
|
||||
.buffer_id
|
||||
{
|
||||
if unfolded_buffers.insert(buffer_id) {
|
||||
self.unfold_buffer(buffer_id, cx);
|
||||
}
|
||||
}
|
||||
if let Some(buffer_id) = display_snapshot
|
||||
.display_point_to_anchor(selection.tail(), Bias::Left)
|
||||
.buffer_id
|
||||
{
|
||||
if unfolded_buffers.insert(buffer_id) {
|
||||
self.unfold_buffer(buffer_id, cx);
|
||||
}
|
||||
for (_, buffer_snapshot, _) in multi_buffer_snapshot.excerpts_in_ranges(
|
||||
self.selections
|
||||
.disjoint_anchors()
|
||||
.into_iter()
|
||||
.map(|selection| selection.range()),
|
||||
) {
|
||||
let buffer_id = buffer_snapshot.remote_id();
|
||||
if unfolded_buffers.insert(buffer_id) {
|
||||
self.unfold_buffer(buffer_id, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10849,6 +10924,29 @@ impl Editor {
|
||||
self.display_map.read(cx).fold_placeholder.clone()
|
||||
}
|
||||
|
||||
pub fn set_expand_all_diff_hunks(&mut self, cx: &mut AppContext) {
|
||||
self.display_map.update(cx, |display_map, cx| {
|
||||
display_map.set_all_hunks_expanded(cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn expand_all_diff_hunks(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext<Self>) {
|
||||
self.display_map.update(cx, |display_map, cx| {
|
||||
display_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext<Self>) {
|
||||
let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
|
||||
self.display_map.update(cx, |display_map, cx| {
|
||||
if display_map.has_expanded_diff_hunks_in_ranges(&ranges, cx) {
|
||||
display_map.collapse_diff_hunks(ranges, cx)
|
||||
} else {
|
||||
display_map.expand_diff_hunks(ranges, cx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut ViewContext<Self>) {
|
||||
if hovered != self.gutter_hovered {
|
||||
self.gutter_hovered = hovered;
|
||||
@@ -12152,7 +12250,12 @@ impl Editor {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
if !self.diff_map.diff_bases.contains_key(&buffer_id) {
|
||||
if let Some(project) = &self.project {
|
||||
get_unstaged_changes_for_buffers(project, [buffer.clone()], cx);
|
||||
get_unstaged_changes_for_buffers(
|
||||
project,
|
||||
[buffer.clone()],
|
||||
self.display_map.clone(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
cx.emit(EditorEvent::ExcerptsAdded {
|
||||
@@ -12487,7 +12590,7 @@ impl Editor {
|
||||
|
||||
fn report_editor_event(
|
||||
&self,
|
||||
operation: &'static str,
|
||||
event_type: &'static str,
|
||||
file_extension: Option<String>,
|
||||
cx: &AppContext,
|
||||
) {
|
||||
@@ -12524,15 +12627,14 @@ impl Editor {
|
||||
.show_inline_completions;
|
||||
|
||||
let project = project.read(cx);
|
||||
let telemetry = project.client().telemetry().clone();
|
||||
telemetry.report_editor_event(
|
||||
telemetry::event!(
|
||||
event_type,
|
||||
file_extension,
|
||||
vim_mode,
|
||||
operation,
|
||||
copilot_enabled,
|
||||
copilot_enabled_for_language,
|
||||
project.is_via_ssh(),
|
||||
)
|
||||
is_via_ssh = project.is_via_ssh(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
|
||||
@@ -12857,7 +12959,8 @@ impl Editor {
|
||||
fn get_unstaged_changes_for_buffers(
|
||||
project: &Model<Project>,
|
||||
buffers: impl IntoIterator<Item = Model<Buffer>>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
display_map: Model<DisplayMap>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
let mut tasks = Vec::new();
|
||||
project.update(cx, |project, cx| {
|
||||
@@ -12865,16 +12968,17 @@ fn get_unstaged_changes_for_buffers(
|
||||
tasks.push(project.open_unstaged_changes(buffer.clone(), cx))
|
||||
}
|
||||
});
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let change_sets = futures::future::join_all(tasks).await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
for change_set in change_sets {
|
||||
if let Some(change_set) = change_set.log_err() {
|
||||
this.diff_map.add_change_set(change_set, cx);
|
||||
display_map
|
||||
.update(&mut cx, |display_map, cx| {
|
||||
for change_set in change_sets {
|
||||
if let Some(change_set) = change_set.log_err() {
|
||||
display_map.add_change_set(change_set, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -13162,56 +13266,6 @@ fn test_wrap_with_prefix() {
|
||||
);
|
||||
}
|
||||
|
||||
fn hunks_for_selections(
|
||||
snapshot: &EditorSnapshot,
|
||||
selections: &[Selection<Point>],
|
||||
) -> Vec<MultiBufferDiffHunk> {
|
||||
hunks_for_ranges(
|
||||
selections.iter().map(|selection| selection.range()),
|
||||
snapshot,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn hunks_for_ranges(
|
||||
ranges: impl Iterator<Item = Range<Point>>,
|
||||
snapshot: &EditorSnapshot,
|
||||
) -> Vec<MultiBufferDiffHunk> {
|
||||
let mut hunks = Vec::new();
|
||||
let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
|
||||
HashMap::default();
|
||||
for query_range in ranges {
|
||||
let query_rows =
|
||||
MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
|
||||
for hunk in snapshot.diff_map.diff_hunks_in_range(
|
||||
Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
|
||||
&snapshot.buffer_snapshot,
|
||||
) {
|
||||
// Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
|
||||
// when the caret is just above or just below the deleted hunk.
|
||||
let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed;
|
||||
let related_to_selection = if allow_adjacent {
|
||||
hunk.row_range.overlaps(&query_rows)
|
||||
|| hunk.row_range.start == query_rows.end
|
||||
|| hunk.row_range.end == query_rows.start
|
||||
} else {
|
||||
hunk.row_range.overlaps(&query_rows)
|
||||
};
|
||||
if related_to_selection {
|
||||
if !processed_buffer_rows
|
||||
.entry(hunk.buffer_id)
|
||||
.or_default()
|
||||
.insert(hunk.buffer_range.start..hunk.buffer_range.end)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
hunks.push(hunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hunks
|
||||
}
|
||||
|
||||
pub trait CollaborationHub {
|
||||
fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator>;
|
||||
fn user_participant_indices<'a>(
|
||||
@@ -13760,6 +13814,45 @@ impl EditorSnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn hunks_for_ranges(
|
||||
&self,
|
||||
ranges: impl Iterator<Item = Range<Point>>,
|
||||
) -> Vec<MultiBufferDiffHunk> {
|
||||
let mut hunks = Vec::new();
|
||||
let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
|
||||
HashMap::default();
|
||||
for query_range in ranges {
|
||||
let query_rows =
|
||||
MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
|
||||
for hunk in self.diff_snapshot().diff_hunks_in_range(
|
||||
Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
|
||||
) {
|
||||
// Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
|
||||
// when the caret is just above or just below the deleted hunk.
|
||||
let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed;
|
||||
let related_to_selection = if allow_adjacent {
|
||||
hunk.row_range.overlaps(&query_rows)
|
||||
|| hunk.row_range.start == query_rows.end
|
||||
|| hunk.row_range.end == query_rows.start
|
||||
} else {
|
||||
hunk.row_range.overlaps(&query_rows)
|
||||
};
|
||||
if related_to_selection {
|
||||
if !processed_buffer_rows
|
||||
.entry(hunk.buffer_id)
|
||||
.or_default()
|
||||
.insert(hunk.buffer_range.start..hunk.buffer_range.end)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
hunks.push(hunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hunks
|
||||
}
|
||||
|
||||
pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
|
||||
self.display_snapshot.buffer_snapshot.language_at(position)
|
||||
}
|
||||
@@ -14511,6 +14604,64 @@ pub fn diagnostic_block_renderer(
|
||||
})
|
||||
}
|
||||
|
||||
fn inline_completion_edit_text(
|
||||
editor_snapshot: &EditorSnapshot,
|
||||
edits: &Vec<(Range<Anchor>, String)>,
|
||||
cx: &WindowContext,
|
||||
) -> InlineCompletionText {
|
||||
let edit_start = edits
|
||||
.first()
|
||||
.unwrap()
|
||||
.0
|
||||
.start
|
||||
.to_display_point(editor_snapshot);
|
||||
|
||||
let mut text = String::new();
|
||||
let mut offset = DisplayPoint::new(edit_start.row(), 0).to_offset(editor_snapshot, Bias::Left);
|
||||
let mut highlights = Vec::new();
|
||||
for (old_range, new_text) in edits {
|
||||
let old_offset_range = old_range.to_offset(&editor_snapshot.buffer_snapshot);
|
||||
text.extend(
|
||||
editor_snapshot
|
||||
.buffer_snapshot
|
||||
.chunks(offset..old_offset_range.start, false)
|
||||
.map(|chunk| chunk.text),
|
||||
);
|
||||
offset = old_offset_range.end;
|
||||
|
||||
let start = text.len();
|
||||
text.push_str(new_text);
|
||||
let end = text.len();
|
||||
highlights.push((
|
||||
start..end,
|
||||
HighlightStyle {
|
||||
background_color: Some(cx.theme().status().created_background),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
let edit_end = edits
|
||||
.last()
|
||||
.unwrap()
|
||||
.0
|
||||
.end
|
||||
.to_display_point(editor_snapshot);
|
||||
let end_of_line = DisplayPoint::new(edit_end.row(), editor_snapshot.line_len(edit_end.row()))
|
||||
.to_offset(editor_snapshot, Bias::Right);
|
||||
text.extend(
|
||||
editor_snapshot
|
||||
.buffer_snapshot
|
||||
.chunks(offset..end_of_line, false)
|
||||
.map(|chunk| chunk.text),
|
||||
);
|
||||
|
||||
InlineCompletionText::Edit {
|
||||
text: text.into(),
|
||||
highlights,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn highlight_diagnostic_message(
|
||||
diagnostic: &Diagnostic,
|
||||
mut max_message_rows: Option<u8>,
|
||||
|
||||
@@ -18,6 +18,7 @@ pub struct EditorSettings {
|
||||
pub scroll_beyond_last_line: ScrollBeyondLastLine,
|
||||
pub vertical_scroll_margin: f32,
|
||||
pub autoscroll_on_clicks: bool,
|
||||
pub horizontal_scroll_margin: f32,
|
||||
pub scroll_sensitivity: f32,
|
||||
pub relative_line_numbers: bool,
|
||||
pub seed_search_query_from_cursor: SeedQuerySetting,
|
||||
@@ -105,6 +106,7 @@ pub struct Scrollbar {
|
||||
pub search_results: bool,
|
||||
pub diagnostics: bool,
|
||||
pub cursors: bool,
|
||||
pub axes: ScrollbarAxes,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
@@ -132,6 +134,21 @@ pub enum ShowScrollbar {
|
||||
Never,
|
||||
}
|
||||
|
||||
/// Forcefully enable or disable the scrollbar for each axis
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub struct ScrollbarAxes {
|
||||
/// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
|
||||
///
|
||||
/// Default: true
|
||||
pub horizontal: bool,
|
||||
|
||||
/// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
|
||||
///
|
||||
/// Default: true
|
||||
pub vertical: bool,
|
||||
}
|
||||
|
||||
/// The key to use for adding multiple cursors
|
||||
///
|
||||
/// Default: alt
|
||||
@@ -219,6 +236,10 @@ pub struct EditorSettingsContent {
|
||||
///
|
||||
/// Default: false
|
||||
pub autoscroll_on_clicks: Option<bool>,
|
||||
/// The number of characters to keep on either side when scrolling with the mouse.
|
||||
///
|
||||
/// Default: 5.
|
||||
pub horizontal_scroll_margin: Option<f32>,
|
||||
/// Scroll sensitivity multiplier. This multiplier is applied
|
||||
/// to both the horizontal and vertical delta values while scrolling.
|
||||
///
|
||||
@@ -328,6 +349,22 @@ pub struct ScrollbarContent {
|
||||
///
|
||||
/// Default: true
|
||||
pub cursors: Option<bool>,
|
||||
/// Forcefully enable or disable the scrollbar for each axis
|
||||
pub axes: Option<ScrollbarAxesContent>,
|
||||
}
|
||||
|
||||
/// Forcefully enable or disable the scrollbar for each axis
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub struct ScrollbarAxesContent {
|
||||
/// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
|
||||
///
|
||||
/// Default: true
|
||||
horizontal: Option<bool>,
|
||||
|
||||
/// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
|
||||
///
|
||||
/// Default: true
|
||||
vertical: Option<bool>,
|
||||
}
|
||||
|
||||
/// Gutter related settings
|
||||
|
||||
@@ -3359,8 +3359,7 @@ async fn test_custom_newlines_cause_no_false_positive_diffs(
|
||||
let snapshot = editor.snapshot(cx);
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.diff_map
|
||||
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
|
||||
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
|
||||
.collect::<Vec<_>>(),
|
||||
Vec::new(),
|
||||
"Should not have any diffs for files with custom newlines"
|
||||
@@ -8470,10 +8469,7 @@ async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
|
||||
cx.update_editor(|editor, _| {
|
||||
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||
{
|
||||
assert_eq!(
|
||||
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||
&["first", "last"]
|
||||
);
|
||||
assert_eq!(completion_menu_entries(&menu.entries), &["first", "last"]);
|
||||
} else {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
@@ -8566,7 +8562,7 @@ async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
|
||||
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||
{
|
||||
assert_eq!(
|
||||
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||
completion_menu_entries(&menu.entries),
|
||||
&["r", "ret", "Range", "return"]
|
||||
);
|
||||
} else {
|
||||
@@ -10962,11 +10958,7 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo
|
||||
match menu.as_ref().expect("should have the completions menu") {
|
||||
CodeContextMenu::Completions(completions_menu) => {
|
||||
assert_eq!(
|
||||
completions_menu
|
||||
.matches
|
||||
.iter()
|
||||
.map(|c| c.string.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
completion_menu_entries(&completions_menu.entries),
|
||||
vec!["Some(2)", "vec![2]"]
|
||||
);
|
||||
}
|
||||
@@ -11066,7 +11058,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
|
||||
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||
{
|
||||
assert_eq!(
|
||||
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||
completion_menu_entries(&menu.entries),
|
||||
&["bg-red", "bg-blue", "bg-yellow"]
|
||||
);
|
||||
} else {
|
||||
@@ -11080,7 +11072,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
|
||||
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||
{
|
||||
assert_eq!(
|
||||
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||
completion_menu_entries(&menu.entries),
|
||||
&["bg-blue", "bg-yellow"]
|
||||
);
|
||||
} else {
|
||||
@@ -11096,16 +11088,23 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
|
||||
cx.update_editor(|editor, _| {
|
||||
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||
{
|
||||
assert_eq!(
|
||||
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||
&["bg-yellow"]
|
||||
);
|
||||
assert_eq!(completion_menu_entries(&menu.entries), &["bg-yellow"]);
|
||||
} else {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn completion_menu_entries(entries: &[CompletionEntry]) -> Vec<&str> {
|
||||
entries
|
||||
.iter()
|
||||
.flat_map(|e| match e {
|
||||
CompletionEntry::Match(mat) => Some(mat.string.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |settings| {
|
||||
@@ -11655,7 +11654,9 @@ async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map.add_change_set(change_set, cx)
|
||||
});
|
||||
}
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -12073,12 +12074,34 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
for _ in 0..3 {
|
||||
for _ in 0..2 {
|
||||
editor.go_to_next_hunk(&GoToHunk, cx);
|
||||
editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
|
||||
}
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- use some::mod;
|
||||
+ ˇuse some::modified;
|
||||
|
||||
|
||||
fn main() {
|
||||
- println!("hello");
|
||||
+ println!("hello there");
|
||||
|
||||
+ println!("around the");
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.go_to_next_hunk(&GoToHunk, cx);
|
||||
editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- use some::mod;
|
||||
@@ -12164,7 +12187,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
@@ -12209,7 +12232,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
@@ -12292,7 +12315,7 @@ async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui:
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -12485,7 +12508,9 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
|
||||
cx,
|
||||
)
|
||||
});
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map.add_change_set(change_set, cx)
|
||||
});
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
@@ -12595,14 +12620,16 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext
|
||||
let buffer = buffer.read(cx).text_snapshot();
|
||||
let change_set = cx
|
||||
.new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map.add_change_set(change_set, cx)
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut cx = EditorTestContext::for_editor(editor, cx).await;
|
||||
cx.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
|
||||
cx.update_editor(|editor, cx| editor.expand_all_diff_hunks(&Default::default(), cx));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
cx.assert_state_with_diff(
|
||||
@@ -12666,7 +12693,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -12685,7 +12712,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12708,7 +12735,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12732,7 +12759,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12757,7 +12784,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12783,7 +12810,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12804,7 +12831,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
}
|
||||
@@ -12857,7 +12884,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -12876,7 +12903,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12899,7 +12926,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12922,7 +12949,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12946,7 +12973,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
}
|
||||
@@ -12999,7 +13026,7 @@ async fn test_edit_after_expanded_modification_hunk(
|
||||
cx.set_diff_base(&diff_base);
|
||||
executor.run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -13078,6 +13105,7 @@ fn assert_indent_guides(
|
||||
let indent_guides = cx.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx).display_snapshot;
|
||||
let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
|
||||
editor,
|
||||
MultiBufferRow(range.start)..MultiBufferRow(range.end),
|
||||
true,
|
||||
&snapshot,
|
||||
@@ -14362,6 +14390,175 @@ async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppCon
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_inline_completion_text(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
// Test case 1: Simple insertion
|
||||
{
|
||||
let window = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("Hello, world!", cx);
|
||||
Editor::new(EditorMode::Full, buffer, None, true, cx)
|
||||
});
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
|
||||
window
|
||||
.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
|
||||
..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
|
||||
let edits = vec![(edit_range, " beautiful".to_string())];
|
||||
|
||||
let InlineCompletionText::Edit { text, highlights } =
|
||||
inline_completion_edit_text(&snapshot, &edits, cx)
|
||||
else {
|
||||
panic!("Failed to generate inline completion text");
|
||||
};
|
||||
|
||||
assert_eq!(text, "Hello, beautiful world!");
|
||||
assert_eq!(highlights.len(), 1);
|
||||
assert_eq!(highlights[0].0, 6..16);
|
||||
assert_eq!(
|
||||
highlights[0].1.background_color,
|
||||
Some(cx.theme().status().created_background)
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Test case 2: Replacement
|
||||
{
|
||||
let window = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("This is a test.", cx);
|
||||
Editor::new(EditorMode::Full, buffer, None, true, cx)
|
||||
});
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
|
||||
window
|
||||
.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let edits = vec![(
|
||||
snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
|
||||
..snapshot.buffer_snapshot.anchor_before(Point::new(0, 4)),
|
||||
"That".to_string(),
|
||||
)];
|
||||
|
||||
let InlineCompletionText::Edit { text, highlights } =
|
||||
inline_completion_edit_text(&snapshot, &edits, cx)
|
||||
else {
|
||||
panic!("Failed to generate inline completion text");
|
||||
};
|
||||
|
||||
assert_eq!(text, "That is a test.");
|
||||
assert_eq!(highlights.len(), 1);
|
||||
assert_eq!(highlights[0].0, 0..4);
|
||||
assert_eq!(
|
||||
highlights[0].1.background_color,
|
||||
Some(cx.theme().status().created_background)
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Test case 3: Multiple edits
|
||||
{
|
||||
let window = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("Hello, world!", cx);
|
||||
Editor::new(EditorMode::Full, buffer, None, true, cx)
|
||||
});
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
|
||||
window
|
||||
.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let edits = vec![
|
||||
(
|
||||
snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
|
||||
..snapshot.buffer_snapshot.anchor_before(Point::new(0, 5)),
|
||||
"Greetings".into(),
|
||||
),
|
||||
(
|
||||
snapshot.buffer_snapshot.anchor_after(Point::new(0, 12))
|
||||
..snapshot.buffer_snapshot.anchor_before(Point::new(0, 12)),
|
||||
" and universe".into(),
|
||||
),
|
||||
];
|
||||
|
||||
let InlineCompletionText::Edit { text, highlights } =
|
||||
inline_completion_edit_text(&snapshot, &edits, cx)
|
||||
else {
|
||||
panic!("Failed to generate inline completion text");
|
||||
};
|
||||
|
||||
assert_eq!(text, "Greetings, world and universe!");
|
||||
assert_eq!(highlights.len(), 2);
|
||||
assert_eq!(highlights[0].0, 0..9);
|
||||
assert_eq!(highlights[1].0, 16..29);
|
||||
assert_eq!(
|
||||
highlights[0].1.background_color,
|
||||
Some(cx.theme().status().created_background)
|
||||
);
|
||||
assert_eq!(
|
||||
highlights[1].1.background_color,
|
||||
Some(cx.theme().status().created_background)
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Test case 4: Multiple lines with edits
|
||||
{
|
||||
let window = cx.add_window(|cx| {
|
||||
let buffer =
|
||||
MultiBuffer::build_simple("First line\nSecond line\nThird line\nFourth line", cx);
|
||||
Editor::new(EditorMode::Full, buffer, None, true, cx)
|
||||
});
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
|
||||
window
|
||||
.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let edits = vec![
|
||||
(
|
||||
snapshot.buffer_snapshot.anchor_before(Point::new(1, 7))
|
||||
..snapshot.buffer_snapshot.anchor_before(Point::new(1, 11)),
|
||||
"modified".to_string(),
|
||||
),
|
||||
(
|
||||
snapshot.buffer_snapshot.anchor_before(Point::new(2, 0))
|
||||
..snapshot.buffer_snapshot.anchor_before(Point::new(2, 10)),
|
||||
"New third line".to_string(),
|
||||
),
|
||||
(
|
||||
snapshot.buffer_snapshot.anchor_before(Point::new(3, 6))
|
||||
..snapshot.buffer_snapshot.anchor_before(Point::new(3, 6)),
|
||||
" updated".to_string(),
|
||||
),
|
||||
];
|
||||
|
||||
let InlineCompletionText::Edit { text, highlights } =
|
||||
inline_completion_edit_text(&snapshot, &edits, cx)
|
||||
else {
|
||||
panic!("Failed to generate inline completion text");
|
||||
};
|
||||
|
||||
assert_eq!(text, "Second modified\nNew third line\nFourth updated line");
|
||||
assert_eq!(highlights.len(), 3);
|
||||
assert_eq!(highlights[0].0, 7..15); // "modified"
|
||||
assert_eq!(highlights[1].0, 16..30); // "New third line"
|
||||
assert_eq!(highlights[2].0, 37..45); // " updated"
|
||||
|
||||
for highlight in &highlights {
|
||||
assert_eq!(
|
||||
highlight.1.background_color,
|
||||
Some(cx.theme().status().created_background)
|
||||
);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
||||
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
|
||||
point..point
|
||||
@@ -14529,8 +14726,7 @@ fn assert_hunk_revert(
|
||||
let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let reverted_hunk_statuses = snapshot
|
||||
.diff_map
|
||||
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
|
||||
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
|
||||
.map(|hunk| hunk_status(&hunk))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
@@ -9,12 +9,13 @@ use git::{
|
||||
use gpui::{Model, ModelContext, Subscription, Task};
|
||||
use http_client::HttpClient;
|
||||
use language::{markdown, Bias, Buffer, BufferSnapshot, Edit, LanguageRegistry, ParsedMarkdown};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use project::{Project, ProjectItem};
|
||||
use smallvec::SmallVec;
|
||||
use sum_tree::SumTree;
|
||||
use url::Url;
|
||||
|
||||
use crate::RowInfo;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct GitBlameEntry {
|
||||
pub rows: u32,
|
||||
@@ -194,15 +195,15 @@ impl GitBlame {
|
||||
|
||||
pub fn blame_for_rows<'a>(
|
||||
&'a mut self,
|
||||
rows: impl 'a + IntoIterator<Item = Option<MultiBufferRow>>,
|
||||
rows: &'a [RowInfo],
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> impl 'a + Iterator<Item = Option<BlameEntry>> {
|
||||
self.sync(cx);
|
||||
|
||||
let mut cursor = self.entries.cursor::<u32>(&());
|
||||
rows.into_iter().map(move |row| {
|
||||
let row = row?;
|
||||
cursor.seek_forward(&row.0, Bias::Right, &());
|
||||
rows.into_iter().map(move |info| {
|
||||
let row = info.buffer_row?;
|
||||
cursor.seek_forward(&row, Bias::Right, &());
|
||||
cursor.item()?.blame.clone()
|
||||
})
|
||||
}
|
||||
@@ -563,15 +564,38 @@ mod tests {
|
||||
use unindent::Unindent as _;
|
||||
use util::RandomCharIter;
|
||||
|
||||
macro_rules! assert_blame_rows {
|
||||
($blame:expr, $rows:expr, $expected:expr, $cx:expr) => {
|
||||
assert_eq!(
|
||||
$blame
|
||||
.blame_for_rows($rows.map(MultiBufferRow).map(Some), $cx)
|
||||
.collect::<Vec<_>>(),
|
||||
$expected
|
||||
);
|
||||
};
|
||||
// macro_rules! assert_blame_rows {
|
||||
// ($blame:expr, $rows:expr, $expected:expr, $cx:expr) => {
|
||||
// assert_eq!(
|
||||
// $blame
|
||||
// .blame_for_rows($rows.map(MultiBufferRow).map(Some), $cx)
|
||||
// .collect::<Vec<_>>(),
|
||||
// $expected
|
||||
// );
|
||||
// };
|
||||
// }
|
||||
|
||||
#[track_caller]
|
||||
fn assert_blame_rows(
|
||||
blame: &mut GitBlame,
|
||||
rows: Range<u32>,
|
||||
expected: Vec<Option<BlameEntry>>,
|
||||
cx: &mut ModelContext<GitBlame>,
|
||||
) {
|
||||
assert_eq!(
|
||||
blame
|
||||
.blame_for_rows(
|
||||
&rows
|
||||
.map(|row| RowInfo {
|
||||
buffer_row: Some(row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
@@ -634,7 +658,15 @@ mod tests {
|
||||
blame.update(cx, |blame, cx| {
|
||||
assert_eq!(
|
||||
blame
|
||||
.blame_for_rows((0..1).map(MultiBufferRow).map(Some), cx)
|
||||
.blame_for_rows(
|
||||
&(0..1)
|
||||
.map(|row| RowInfo {
|
||||
buffer_row: Some(row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![None]
|
||||
);
|
||||
@@ -698,7 +730,15 @@ mod tests {
|
||||
// All lines
|
||||
assert_eq!(
|
||||
blame
|
||||
.blame_for_rows((0..8).map(MultiBufferRow).map(Some), cx)
|
||||
.blame_for_rows(
|
||||
&(0..8)
|
||||
.map(|buffer_row| RowInfo {
|
||||
buffer_row: Some(buffer_row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
Some(blame_entry("1b1b1b", 0..1)),
|
||||
@@ -714,7 +754,15 @@ mod tests {
|
||||
// Subset of lines
|
||||
assert_eq!(
|
||||
blame
|
||||
.blame_for_rows((1..4).map(MultiBufferRow).map(Some), cx)
|
||||
.blame_for_rows(
|
||||
&(1..4)
|
||||
.map(|buffer_row| RowInfo {
|
||||
buffer_row: Some(buffer_row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
Some(blame_entry("0d0d0d", 1..2)),
|
||||
@@ -725,7 +773,17 @@ mod tests {
|
||||
// Subset of lines, with some not displayed
|
||||
assert_eq!(
|
||||
blame
|
||||
.blame_for_rows(vec![Some(MultiBufferRow(1)), None, None], cx)
|
||||
.blame_for_rows(
|
||||
&[
|
||||
RowInfo {
|
||||
buffer_row: Some(1),
|
||||
..Default::default()
|
||||
},
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
],
|
||||
cx
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![Some(blame_entry("0d0d0d", 1..2)), None, None]
|
||||
);
|
||||
@@ -777,16 +835,16 @@ mod tests {
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
// Sanity check before edits: make sure that we get the same blame entry for all
|
||||
// lines.
|
||||
assert_blame_rows!(
|
||||
assert_blame_rows(
|
||||
blame,
|
||||
(0..4),
|
||||
0..4,
|
||||
vec![
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
],
|
||||
cx
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -795,11 +853,11 @@ mod tests {
|
||||
buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "X")], None, cx);
|
||||
});
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows!(
|
||||
assert_blame_rows(
|
||||
blame,
|
||||
(0..2),
|
||||
0..2,
|
||||
vec![None, Some(blame_entry("1b1b1b", 0..4))],
|
||||
cx
|
||||
cx,
|
||||
);
|
||||
});
|
||||
// Modify a single line, in the middle of the line
|
||||
@@ -807,21 +865,21 @@ mod tests {
|
||||
buffer.edit([(Point::new(1, 2)..Point::new(1, 2), "X")], None, cx);
|
||||
});
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows!(
|
||||
assert_blame_rows(
|
||||
blame,
|
||||
(1..4),
|
||||
1..4,
|
||||
vec![
|
||||
None,
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
Some(blame_entry("1b1b1b", 0..4))
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
],
|
||||
cx
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
// Before we insert a newline at the end, sanity check:
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows!(blame, (3..4), vec![Some(blame_entry("1b1b1b", 0..4))], cx);
|
||||
assert_blame_rows(blame, 3..4, vec![Some(blame_entry("1b1b1b", 0..4))], cx);
|
||||
});
|
||||
// Insert a newline at the end
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
@@ -829,17 +887,17 @@ mod tests {
|
||||
});
|
||||
// Only the new line is marked as edited:
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows!(
|
||||
assert_blame_rows(
|
||||
blame,
|
||||
(3..5),
|
||||
3..5,
|
||||
vec![Some(blame_entry("1b1b1b", 0..4)), None],
|
||||
cx
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
// Before we insert a newline at the start, sanity check:
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows!(blame, (2..3), vec![Some(blame_entry("1b1b1b", 0..4)),], cx);
|
||||
assert_blame_rows(blame, 2..3, vec![Some(blame_entry("1b1b1b", 0..4))], cx);
|
||||
});
|
||||
|
||||
// Usage example
|
||||
@@ -849,11 +907,11 @@ mod tests {
|
||||
});
|
||||
// Only the new line is marked as edited:
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows!(
|
||||
assert_blame_rows(
|
||||
blame,
|
||||
(2..4),
|
||||
vec![None, Some(blame_entry("1b1b1b", 0..4)),],
|
||||
cx
|
||||
2..4,
|
||||
vec![None, Some(blame_entry("1b1b1b", 0..4))],
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ impl ProjectDiffEditor {
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut diff_display_editor =
|
||||
Editor::for_multibuffer(excerpts.clone(), Some(project.clone()), true, cx);
|
||||
diff_display_editor.set_expand_all_diff_hunks();
|
||||
diff_display_editor.set_expand_all_diff_hunks(cx);
|
||||
diff_display_editor
|
||||
});
|
||||
|
||||
@@ -311,9 +311,11 @@ impl ProjectDiffEditor {
|
||||
.update(&mut cx, |project_diff_editor, cx| {
|
||||
project_diff_editor.update_excerpts(id, new_changes, new_entry_order, cx);
|
||||
project_diff_editor.editor.update(cx, |editor, cx| {
|
||||
for change_set in change_sets {
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
}
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
for change_set in change_sets {
|
||||
display_map.add_change_set(change_set, cx)
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
@@ -1193,9 +1195,9 @@ mod tests {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
file_a_editor
|
||||
.diff_map
|
||||
.add_change_set(change_set.clone(), cx);
|
||||
file_a_editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map.add_change_set(change_set.clone(), cx)
|
||||
});
|
||||
project.update(cx, |project, cx| {
|
||||
project.buffer_store().update(cx, |buffer_store, cx| {
|
||||
buffer_store.set_change_set(
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
use collections::{HashMap, HashSet};
|
||||
use git::diff::DiffHunkStatus;
|
||||
use gpui::{
|
||||
Action, AnchorCorner, AppContext, CursorStyle, Hsla, Model, MouseButton, Subscription, Task,
|
||||
View,
|
||||
Action, AppContext, Corner, CursorStyle, Hsla, Model, MouseButton, Subscription, Task, View,
|
||||
};
|
||||
use language::{Buffer, BufferId, Point};
|
||||
use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow,
|
||||
MultiBufferSnapshot, ToOffset, ToPoint,
|
||||
MultiBufferSnapshot, ToPoint,
|
||||
};
|
||||
use project::buffer_store::BufferChangeSet;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use sum_tree::TreeMap;
|
||||
use text::OffsetRangeExt;
|
||||
use ui::{
|
||||
prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement,
|
||||
@@ -21,10 +19,10 @@ use util::RangeExt;
|
||||
use workspace::Item;
|
||||
|
||||
use crate::{
|
||||
editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, ApplyAllDiffHunks,
|
||||
ApplyDiffHunk, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight,
|
||||
DisplayRow, DisplaySnapshot, Editor, EditorElement, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk,
|
||||
RevertFile, RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
|
||||
editor_settings::CurrentLineHighlight, hunk_status, ApplyAllDiffHunks, ApplyDiffHunk,
|
||||
BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, DisplayRow,
|
||||
DisplaySnapshot, Editor, EditorElement, GoToHunk, GoToPrevHunk, RevertFile,
|
||||
RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -38,7 +36,6 @@ pub(super) struct HoveredHunk {
|
||||
pub(super) struct DiffMap {
|
||||
pub(crate) hunks: Vec<ExpandedHunk>,
|
||||
pub(crate) diff_bases: HashMap<BufferId, DiffBaseState>,
|
||||
pub(crate) snapshot: DiffMapSnapshot,
|
||||
hunk_update_tasks: HashMap<Option<BufferId>, Task<()>>,
|
||||
expand_all: bool,
|
||||
}
|
||||
@@ -52,9 +49,6 @@ pub(super) struct ExpandedHunk {
|
||||
pub folded: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct DiffMapSnapshot(TreeMap<BufferId, git::diff::BufferDiff>);
|
||||
|
||||
pub(crate) struct DiffBaseState {
|
||||
pub(crate) change_set: Model<BufferChangeSet>,
|
||||
pub(crate) last_version: Option<usize>,
|
||||
@@ -75,133 +69,7 @@ pub enum DisplayDiffHunk {
|
||||
},
|
||||
}
|
||||
|
||||
impl DiffMap {
|
||||
pub fn snapshot(&self) -> DiffMapSnapshot {
|
||||
self.snapshot.clone()
|
||||
}
|
||||
|
||||
pub fn add_change_set(
|
||||
&mut self,
|
||||
change_set: Model<BufferChangeSet>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let buffer_id = change_set.read(cx).buffer_id;
|
||||
self.snapshot
|
||||
.0
|
||||
.insert(buffer_id, change_set.read(cx).diff_to_buffer.clone());
|
||||
self.diff_bases.insert(
|
||||
buffer_id,
|
||||
DiffBaseState {
|
||||
last_version: None,
|
||||
_subscription: cx.observe(&change_set, move |editor, change_set, cx| {
|
||||
editor
|
||||
.diff_map
|
||||
.snapshot
|
||||
.0
|
||||
.insert(buffer_id, change_set.read(cx).diff_to_buffer.clone());
|
||||
Editor::sync_expanded_diff_hunks(&mut editor.diff_map, buffer_id, cx);
|
||||
}),
|
||||
change_set,
|
||||
},
|
||||
);
|
||||
Editor::sync_expanded_diff_hunks(self, buffer_id, cx);
|
||||
}
|
||||
|
||||
pub fn hunks(&self, include_folded: bool) -> impl Iterator<Item = &ExpandedHunk> {
|
||||
self.hunks
|
||||
.iter()
|
||||
.filter(move |hunk| include_folded || !hunk.folded)
|
||||
}
|
||||
}
|
||||
|
||||
impl DiffMapSnapshot {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.values().all(|diff| diff.is_empty())
|
||||
}
|
||||
|
||||
pub fn diff_hunks<'a>(
|
||||
&'a self,
|
||||
buffer_snapshot: &'a MultiBufferSnapshot,
|
||||
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
|
||||
self.diff_hunks_in_range(0..buffer_snapshot.len(), buffer_snapshot)
|
||||
}
|
||||
|
||||
pub fn diff_hunks_in_range<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
buffer_snapshot: &'a MultiBufferSnapshot,
|
||||
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
|
||||
let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot);
|
||||
buffer_snapshot
|
||||
.excerpts_for_range(range.clone())
|
||||
.filter_map(move |excerpt| {
|
||||
let buffer = excerpt.buffer();
|
||||
let buffer_id = buffer.remote_id();
|
||||
let diff = self.0.get(&buffer_id)?;
|
||||
let buffer_range = excerpt.map_range_to_buffer(range.clone());
|
||||
let buffer_range =
|
||||
buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end);
|
||||
Some(
|
||||
diff.hunks_intersecting_range(buffer_range, excerpt.buffer())
|
||||
.map(move |hunk| {
|
||||
let start =
|
||||
excerpt.map_point_from_buffer(Point::new(hunk.row_range.start, 0));
|
||||
let end =
|
||||
excerpt.map_point_from_buffer(Point::new(hunk.row_range.end, 0));
|
||||
MultiBufferDiffHunk {
|
||||
row_range: MultiBufferRow(start.row)..MultiBufferRow(end.row),
|
||||
buffer_id,
|
||||
buffer_range: hunk.buffer_range.clone(),
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn diff_hunks_in_range_rev<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
buffer_snapshot: &'a MultiBufferSnapshot,
|
||||
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
|
||||
let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot);
|
||||
buffer_snapshot
|
||||
.excerpts_for_range_rev(range.clone())
|
||||
.filter_map(move |excerpt| {
|
||||
let buffer = excerpt.buffer();
|
||||
let buffer_id = buffer.remote_id();
|
||||
let diff = self.0.get(&buffer_id)?;
|
||||
let buffer_range = excerpt.map_range_to_buffer(range.clone());
|
||||
let buffer_range =
|
||||
buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end);
|
||||
Some(
|
||||
diff.hunks_intersecting_range_rev(buffer_range, excerpt.buffer())
|
||||
.map(move |hunk| {
|
||||
let start_row = excerpt
|
||||
.map_point_from_buffer(Point::new(hunk.row_range.start, 0))
|
||||
.row;
|
||||
let end_row = excerpt
|
||||
.map_point_from_buffer(Point::new(hunk.row_range.end, 0))
|
||||
.row;
|
||||
MultiBufferDiffHunk {
|
||||
row_range: MultiBufferRow(start_row)..MultiBufferRow(end_row),
|
||||
buffer_id,
|
||||
buffer_range: hunk.buffer_range.clone(),
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
pub fn set_expand_all_diff_hunks(&mut self) {
|
||||
self.diff_map.expand_all = true;
|
||||
}
|
||||
|
||||
pub(super) fn toggle_hovered_hunk(
|
||||
&mut self,
|
||||
hovered_hunk: &HoveredHunk,
|
||||
@@ -214,47 +82,6 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext<Self>) {
|
||||
let snapshot = self.snapshot(cx);
|
||||
let selections = self.selections.all(cx);
|
||||
self.toggle_hunks_expanded(hunks_for_selections(&snapshot, &selections), cx);
|
||||
}
|
||||
|
||||
pub fn expand_all_hunk_diffs(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext<Self>) {
|
||||
let snapshot = self.snapshot(cx);
|
||||
let display_rows_with_expanded_hunks = self
|
||||
.diff_map
|
||||
.hunks(false)
|
||||
.map(|hunk| &hunk.hunk_range)
|
||||
.map(|anchor_range| {
|
||||
(
|
||||
anchor_range
|
||||
.start
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
.row(),
|
||||
anchor_range
|
||||
.end
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
.row(),
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
let hunks = self
|
||||
.diff_map
|
||||
.snapshot
|
||||
.diff_hunks(&snapshot.display_snapshot.buffer_snapshot)
|
||||
.filter(|hunk| {
|
||||
let hunk_display_row_range = Point::new(hunk.row_range.start.0, 0)
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
..Point::new(hunk.row_range.end.0, 0)
|
||||
.to_display_point(&snapshot.display_snapshot);
|
||||
let row_range_end =
|
||||
display_rows_with_expanded_hunks.get(&hunk_display_row_range.start.row());
|
||||
row_range_end.is_none() || row_range_end != Some(&hunk_display_row_range.end.row())
|
||||
});
|
||||
self.toggle_hunks_expanded(hunks.collect(), cx);
|
||||
}
|
||||
|
||||
fn toggle_hunks_expanded(
|
||||
&mut self,
|
||||
hunks_to_toggle: Vec<MultiBufferDiffHunk>,
|
||||
@@ -498,7 +325,8 @@ impl Editor {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let snapshot = self.snapshot(cx);
|
||||
let hunks = hunks_for_selections(&snapshot, &self.selections.all(cx));
|
||||
let ranges = self.selections.all(cx).into_iter().map(|s| s.range());
|
||||
let hunks = snapshot.hunks_for_ranges(ranges);
|
||||
let mut ranges_by_buffer = HashMap::default();
|
||||
self.transact(cx, |editor, cx| {
|
||||
for hunk in hunks {
|
||||
@@ -522,10 +350,8 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn has_multiple_hunks(&self, cx: &AppContext) -> bool {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut hunks = self.diff_map.snapshot.diff_hunks(&snapshot);
|
||||
hunks.nth(1).is_some()
|
||||
fn has_multiple_hunks(&self, cx: &mut WindowContext) -> bool {
|
||||
self.display_map.read(cx).has_multiple_hunks(cx)
|
||||
}
|
||||
|
||||
fn hunk_header_block(
|
||||
@@ -743,7 +569,7 @@ impl Editor {
|
||||
},
|
||||
),
|
||||
)
|
||||
.anchor(AnchorCorner::TopRight)
|
||||
.anchor(Corner::TopRight)
|
||||
.with_handle(hunk_controls_menu_handle)
|
||||
.menu(move |cx| {
|
||||
let focus = focus.clone();
|
||||
@@ -866,23 +692,15 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<'_, Editor>) -> bool {
|
||||
if self.diff_map.expand_all {
|
||||
return false;
|
||||
}
|
||||
self.diff_map.hunk_update_tasks.clear();
|
||||
self.clear_row_highlights::<DiffRowHighlight>();
|
||||
let to_remove = self
|
||||
.diff_map
|
||||
.hunks
|
||||
.drain(..)
|
||||
.flat_map(|expanded_hunk| expanded_hunk.blocks.into_iter())
|
||||
.collect::<HashSet<_>>();
|
||||
if to_remove.is_empty() {
|
||||
false
|
||||
} else {
|
||||
self.remove_blocks(to_remove, None, cx);
|
||||
true
|
||||
}
|
||||
self.display_map.update(cx, |diff_map, cx| {
|
||||
let ranges = vec![Anchor::min()..Anchor::max()];
|
||||
if diff_map.has_expanded_diff_hunks_in_ranges(&ranges, cx) {
|
||||
diff_map.collapse_diff_hunks(ranges, cx);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn sync_expanded_diff_hunks(
|
||||
@@ -910,8 +728,7 @@ impl Editor {
|
||||
.update(&mut cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let mut recalculated_hunks = snapshot
|
||||
.diff_map
|
||||
.diff_hunks(&snapshot.buffer_snapshot)
|
||||
.diff_hunks()
|
||||
.filter(|hunk| hunk.buffer_id == buffer_id)
|
||||
.fuse()
|
||||
.peekable();
|
||||
@@ -1419,7 +1236,9 @@ mod tests {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map.add_change_set(change_set, cx)
|
||||
});
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
@@ -1471,8 +1290,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.diff_map
|
||||
.diff_hunks_in_range(Point::zero()..Point::new(12, 0), &snapshot.buffer_snapshot)
|
||||
.diff_hunks_in_range(Point::zero()..Point::new(12, 0))
|
||||
.map(|hunk| (hunk_status(&hunk), hunk.row_range))
|
||||
.collect::<Vec<_>>(),
|
||||
&expected,
|
||||
@@ -1480,11 +1298,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.diff_map
|
||||
.diff_hunks_in_range_rev(
|
||||
Point::zero()..Point::new(12, 0),
|
||||
&snapshot.buffer_snapshot
|
||||
)
|
||||
.diff_hunks_in_range_rev(Point::zero()..Point::new(12, 0))
|
||||
.map(|hunk| (hunk_status(&hunk), hunk.row_range))
|
||||
.collect::<Vec<_>>(),
|
||||
expected
|
||||
|
||||
@@ -56,6 +56,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
Some(indent_guides_in_range(
|
||||
self,
|
||||
visible_buffer_range,
|
||||
self.should_show_indent_guides() == Some(true),
|
||||
snapshot,
|
||||
@@ -152,6 +153,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn indent_guides_in_range(
|
||||
editor: &Editor,
|
||||
visible_buffer_range: Range<MultiBufferRow>,
|
||||
ignore_disabled_for_language: bool,
|
||||
snapshot: &DisplaySnapshot,
|
||||
@@ -169,10 +171,20 @@ pub fn indent_guides_in_range(
|
||||
.indent_guides_in_range(start_anchor..end_anchor, ignore_disabled_for_language, cx)
|
||||
.into_iter()
|
||||
.filter(|indent_guide| {
|
||||
if editor.buffer_folded(indent_guide.buffer_id, cx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let start =
|
||||
MultiBufferRow(indent_guide.multibuffer_row_range.start.0.saturating_sub(1));
|
||||
// Filter out indent guides that are inside a fold
|
||||
!snapshot.is_line_folded(start)
|
||||
// All indent guides that are starting "offscreen" have a start value of the first visible row minus one
|
||||
// Therefore checking if a line is folded at first visible row minus one causes the other indent guides that are not related to the fold to disappear as well
|
||||
let is_folded = snapshot.is_line_folded(start);
|
||||
let line_indent = snapshot.line_indent_for_buffer_row(start);
|
||||
let contained_in_fold =
|
||||
line_indent.len(indent_guide.tab_size) <= indent_guide.indent_level();
|
||||
!(is_folded && contained_in_fold)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -317,6 +317,10 @@ impl InlineCompletionProvider for FakeInlineCompletionProvider {
|
||||
"fake-completion-provider"
|
||||
}
|
||||
|
||||
fn display_name() -> &'static str {
|
||||
"Fake Completion Provider"
|
||||
}
|
||||
|
||||
fn is_enabled(
|
||||
&self,
|
||||
_buffer: &gpui::Model<language::Buffer>,
|
||||
|
||||
@@ -733,7 +733,7 @@ impl Item for Editor {
|
||||
project: Model<Project>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.report_editor_event("save", None, cx);
|
||||
self.report_editor_event("Editor Saved", None, cx);
|
||||
let buffers = self.buffer().clone().read(cx).all_buffers();
|
||||
let buffers = buffers
|
||||
.into_iter()
|
||||
@@ -805,7 +805,7 @@ impl Item for Editor {
|
||||
.path
|
||||
.extension()
|
||||
.map(|a| a.to_string_lossy().to_string());
|
||||
self.report_editor_event("save", file_extension, cx);
|
||||
self.report_editor_event("Editor Saved", file_extension, cx);
|
||||
|
||||
project.update(cx, |project, cx| project.save_buffer_as(buffer, path, cx))
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ impl ProposedChangesEditor {
|
||||
let mut this = Self {
|
||||
editor: cx.new_view(|cx| {
|
||||
let mut editor = Editor::for_multibuffer(multibuffer.clone(), project, true, cx);
|
||||
editor.set_expand_all_diff_hunks();
|
||||
editor.set_expand_all_diff_hunks(cx);
|
||||
editor.set_completion_provider(None);
|
||||
editor.clear_code_action_providers();
|
||||
editor.set_semantics_provider(
|
||||
@@ -223,9 +223,11 @@ impl ProposedChangesEditor {
|
||||
self.buffer_entries = buffer_entries;
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |selections| selections.refresh());
|
||||
for change_set in new_change_sets {
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
}
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
for change_set in new_change_sets {
|
||||
display_map.add_change_set(change_set, cx)
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ mod actions;
|
||||
pub(crate) mod autoscroll;
|
||||
pub(crate) mod scroll_amount;
|
||||
|
||||
use crate::editor_settings::ScrollBeyondLastLine;
|
||||
use crate::editor_settings::{ScrollBeyondLastLine, ScrollbarAxes};
|
||||
use crate::{
|
||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||
hover_popover::hide_hover,
|
||||
@@ -11,7 +11,10 @@ use crate::{
|
||||
InlayHintRefreshReason, MultiBufferSnapshot, RowExt, ToPoint,
|
||||
};
|
||||
pub use autoscroll::{Autoscroll, AutoscrollStrategy};
|
||||
use gpui::{point, px, AppContext, Entity, Global, Pixels, Task, ViewContext, WindowContext};
|
||||
use core::fmt::Debug;
|
||||
use gpui::{
|
||||
point, px, Along, AppContext, Axis, Entity, Global, Pixels, Task, ViewContext, WindowContext,
|
||||
};
|
||||
use language::{Bias, Point};
|
||||
pub use scroll_amount::ScrollAmount;
|
||||
use settings::Settings;
|
||||
@@ -60,10 +63,53 @@ impl ScrollAnchor {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum Axis {
|
||||
Vertical,
|
||||
Horizontal,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AxisPair<T: Clone> {
|
||||
pub vertical: T,
|
||||
pub horizontal: T,
|
||||
}
|
||||
|
||||
pub fn axis_pair<T: Clone>(horizontal: T, vertical: T) -> AxisPair<T> {
|
||||
AxisPair {
|
||||
vertical,
|
||||
horizontal,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> AxisPair<T> {
|
||||
pub fn as_xy(&self) -> (&T, &T) {
|
||||
(&self.horizontal, &self.vertical)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Along for AxisPair<T> {
|
||||
type Unit = T;
|
||||
|
||||
fn along(&self, axis: gpui::Axis) -> Self::Unit {
|
||||
match axis {
|
||||
gpui::Axis::Horizontal => self.horizontal.clone(),
|
||||
gpui::Axis::Vertical => self.vertical.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_along(&self, axis: gpui::Axis, f: impl FnOnce(Self::Unit) -> Self::Unit) -> Self {
|
||||
match axis {
|
||||
gpui::Axis::Horizontal => Self {
|
||||
horizontal: f(self.horizontal.clone()),
|
||||
vertical: self.vertical.clone(),
|
||||
},
|
||||
gpui::Axis::Vertical => Self {
|
||||
horizontal: self.horizontal.clone(),
|
||||
vertical: f(self.vertical.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ScrollbarAxes> for AxisPair<bool> {
|
||||
fn from(value: ScrollbarAxes) -> Self {
|
||||
axis_pair(value.horizontal, value.vertical)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -136,7 +182,7 @@ pub struct ScrollManager {
|
||||
last_autoscroll: Option<(gpui::Point<f32>, f32, f32, AutoscrollStrategy)>,
|
||||
show_scrollbars: bool,
|
||||
hide_scrollbar_task: Option<Task<()>>,
|
||||
dragging_scrollbar: bool,
|
||||
dragging_scrollbar: AxisPair<bool>,
|
||||
visible_line_count: Option<f32>,
|
||||
forbid_vertical_scroll: bool,
|
||||
}
|
||||
@@ -150,7 +196,7 @@ impl ScrollManager {
|
||||
autoscroll_request: None,
|
||||
show_scrollbars: true,
|
||||
hide_scrollbar_task: None,
|
||||
dragging_scrollbar: false,
|
||||
dragging_scrollbar: axis_pair(false, false),
|
||||
last_autoscroll: None,
|
||||
visible_line_count: None,
|
||||
forbid_vertical_scroll: false,
|
||||
@@ -311,15 +357,18 @@ impl ScrollManager {
|
||||
self.autoscroll_request.map(|(autoscroll, _)| autoscroll)
|
||||
}
|
||||
|
||||
pub fn is_dragging_scrollbar(&self) -> bool {
|
||||
self.dragging_scrollbar
|
||||
pub fn is_dragging_scrollbar(&self, axis: Axis) -> bool {
|
||||
self.dragging_scrollbar.along(axis)
|
||||
}
|
||||
|
||||
pub fn set_is_dragging_scrollbar(&mut self, dragging: bool, cx: &mut ViewContext<Editor>) {
|
||||
if dragging != self.dragging_scrollbar {
|
||||
self.dragging_scrollbar = dragging;
|
||||
cx.notify();
|
||||
}
|
||||
pub fn set_is_dragging_scrollbar(
|
||||
&mut self,
|
||||
axis: Axis,
|
||||
dragging: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
self.dragging_scrollbar = self.dragging_scrollbar.apply_along(axis, |_| dragging);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DiffRowHighlight, DisplayPoint,
|
||||
Editor, MultiBuffer, RowExt,
|
||||
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
|
||||
RowExt,
|
||||
};
|
||||
use collections::BTreeMap;
|
||||
use futures::Future;
|
||||
@@ -11,11 +11,12 @@ use gpui::{
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::{Buffer, BufferSnapshot, LanguageRegistry};
|
||||
use multi_buffer::{ExcerptRange, ToPoint};
|
||||
use multi_buffer::ExcerptRange;
|
||||
use parking_lot::RwLock;
|
||||
use project::{FakeFs, Project};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
cmp,
|
||||
ops::{Deref, DerefMut, Range},
|
||||
path::Path,
|
||||
sync::{
|
||||
@@ -23,6 +24,7 @@ use std::{
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use text::Bias;
|
||||
use util::{
|
||||
assert_set_eq,
|
||||
test::{generate_marked_text, marked_text_ranges},
|
||||
@@ -332,83 +334,51 @@ impl EditorTestContext {
|
||||
///
|
||||
/// Diff hunks are indicated by lines starting with `+` and `-`.
|
||||
#[track_caller]
|
||||
pub fn assert_state_with_diff(&mut self, expected_diff: String) {
|
||||
let has_diff_markers = expected_diff
|
||||
.lines()
|
||||
.any(|line| line.starts_with("+") || line.starts_with("-"));
|
||||
let expected_diff_text = expected_diff
|
||||
.split('\n')
|
||||
.map(|line| {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.is_empty() {
|
||||
String::new()
|
||||
} else if has_diff_markers {
|
||||
line.to_string()
|
||||
} else {
|
||||
format!(" {line}")
|
||||
}
|
||||
})
|
||||
.join("\n");
|
||||
pub fn assert_state_with_diff(&mut self, expected_diff_text: String) {
|
||||
let (snapshot, selections) = self
|
||||
.editor
|
||||
.update(&mut self.cx, |editor, cx| editor.selections.all_display(cx));
|
||||
|
||||
let actual_selections = self.editor_selections();
|
||||
let actual_marked_text =
|
||||
generate_marked_text(&self.buffer_text(), &actual_selections, true);
|
||||
let diff_snapshot = snapshot.diff_snapshot();
|
||||
let diff_offsets = selections
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
let start = snapshot.display_point_to_diff_offset(s.start, Bias::Left).0;
|
||||
let end = snapshot.display_point_to_diff_offset(s.end, Bias::Left).0;
|
||||
cmp::min(start, end)..cmp::max(start, end)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let actual_marked_text = generate_marked_text(&diff_snapshot.text(), &diff_offsets, true);
|
||||
|
||||
// Read the actual diff from the editor's row highlights and block
|
||||
// decorations.
|
||||
let actual_diff = self.editor.update(&mut self.cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let insertions = editor
|
||||
.highlighted_rows::<DiffRowHighlight>()
|
||||
.map(|(range, _)| {
|
||||
let start = range.start.to_point(&snapshot.buffer_snapshot);
|
||||
let end = range.end.to_point(&snapshot.buffer_snapshot);
|
||||
start.row..end.row
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let deletions = editor
|
||||
.diff_map
|
||||
.hunks
|
||||
.iter()
|
||||
.filter_map(|hunk| {
|
||||
if hunk.blocks.is_empty() {
|
||||
return None;
|
||||
let line_infos = diff_snapshot.row_infos(0).collect::<Vec<_>>();
|
||||
let has_diff = line_infos.iter().any(|info| info.diff_status.is_some());
|
||||
|
||||
let actual_diff = actual_marked_text
|
||||
.split('\n')
|
||||
.zip(line_infos)
|
||||
.map(|(line, info)| {
|
||||
let mut marker = match info.diff_status {
|
||||
Some(DiffHunkStatus::Added) => "+ ",
|
||||
Some(DiffHunkStatus::Removed) => "- ",
|
||||
Some(DiffHunkStatus::Modified) => unreachable!(),
|
||||
None => {
|
||||
if has_diff {
|
||||
" "
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
let row = hunk
|
||||
.hunk_range
|
||||
.start
|
||||
.to_point(&snapshot.buffer_snapshot)
|
||||
.row;
|
||||
let (_, buffer, _) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.excerpt_containing(hunk.hunk_range.start, cx)
|
||||
.expect("no excerpt for expanded buffer's hunk start");
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
let change_set = &editor
|
||||
.diff_map
|
||||
.diff_bases
|
||||
.get(&buffer_id)
|
||||
.expect("should have a diff base for expanded hunk")
|
||||
.change_set;
|
||||
let deleted_text = change_set
|
||||
.read(cx)
|
||||
.base_text
|
||||
.as_ref()
|
||||
.expect("no base text for expanded hunk")
|
||||
.read(cx)
|
||||
.as_rope()
|
||||
.slice(hunk.diff_base_byte_range.clone())
|
||||
.to_string();
|
||||
if let DiffHunkStatus::Modified | DiffHunkStatus::Removed = hunk.status {
|
||||
Some((row, deleted_text))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
format_diff(actual_marked_text, deletions, insertions)
|
||||
});
|
||||
};
|
||||
if line.is_empty() {
|
||||
marker = marker.trim();
|
||||
}
|
||||
format!("{marker}{line}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
pretty_assertions::assert_eq!(actual_diff, expected_diff_text, "unexpected diff state");
|
||||
}
|
||||
@@ -503,46 +473,6 @@ impl EditorTestContext {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_diff(
|
||||
text: String,
|
||||
actual_deletions: Vec<(u32, String)>,
|
||||
actual_insertions: Vec<Range<u32>>,
|
||||
) -> String {
|
||||
let mut diff = String::new();
|
||||
for (row, line) in text.split('\n').enumerate() {
|
||||
let row = row as u32;
|
||||
if row > 0 {
|
||||
diff.push('\n');
|
||||
}
|
||||
if let Some(text) = actual_deletions
|
||||
.iter()
|
||||
.find_map(|(deletion_row, deleted_text)| {
|
||||
if *deletion_row == row {
|
||||
Some(deleted_text)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
{
|
||||
for line in text.lines() {
|
||||
diff.push('-');
|
||||
if !line.is_empty() {
|
||||
diff.push(' ');
|
||||
diff.push_str(line);
|
||||
}
|
||||
diff.push('\n');
|
||||
}
|
||||
}
|
||||
let marker = if actual_insertions.iter().any(|range| range.contains(&row)) {
|
||||
"+ "
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
diff.push_str(format!("{marker}{line}").trim_end());
|
||||
}
|
||||
diff
|
||||
}
|
||||
|
||||
impl Deref for EditorTestContext {
|
||||
type Target = gpui::VisualTestContext;
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ serde_json.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
settings.workspace = true
|
||||
task.workspace = true
|
||||
telemetry.workspace = true
|
||||
tempfile.workspace = true
|
||||
toml.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
@@ -1001,14 +1001,13 @@ impl ExtensionStore {
|
||||
extensions_to_unload.len() - reload_count
|
||||
);
|
||||
|
||||
if let Some(telemetry) = &self.telemetry {
|
||||
for extension_id in &extensions_to_load {
|
||||
if let Some(extension) = new_index.extensions.get(extension_id) {
|
||||
telemetry.report_extension_event(
|
||||
extension_id.clone(),
|
||||
extension.manifest.version.clone(),
|
||||
);
|
||||
}
|
||||
for extension_id in &extensions_to_load {
|
||||
if let Some(extension) = new_index.extensions.get(extension_id) {
|
||||
telemetry::event!(
|
||||
"Extension Loaded",
|
||||
extension_id,
|
||||
version = extension.manifest.version
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[
|
||||
("clojure", &["bb", "clj", "cljc", "cljs", "edn"]),
|
||||
("neocmake", &["CMakeLists.txt", "cmake"]),
|
||||
("csharp", &["cs"]),
|
||||
("cython", &["pyx", "pxd", "pxi"]),
|
||||
("dart", &["dart"]),
|
||||
("dockerfile", &["Dockerfile"]),
|
||||
("elisp", &["el"]),
|
||||
|
||||
@@ -449,18 +449,17 @@ impl ExtensionsPage {
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(
|
||||
div().overflow_x_hidden().text_ellipsis().child(
|
||||
Label::new(format!(
|
||||
"{}: {}",
|
||||
if extension.authors.len() > 1 {
|
||||
"Authors"
|
||||
} else {
|
||||
"Author"
|
||||
},
|
||||
extension.authors.join(", ")
|
||||
))
|
||||
.size(LabelSize::Small),
|
||||
),
|
||||
Label::new(format!(
|
||||
"{}: {}",
|
||||
if extension.authors.len() > 1 {
|
||||
"Authors"
|
||||
} else {
|
||||
"Author"
|
||||
},
|
||||
extension.authors.join(", ")
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
.text_ellipsis(),
|
||||
)
|
||||
.child(Label::new("<>").size(LabelSize::Small)),
|
||||
)
|
||||
@@ -469,11 +468,10 @@ impl ExtensionsPage {
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.children(extension.description.as_ref().map(|description| {
|
||||
div().overflow_x_hidden().text_ellipsis().child(
|
||||
Label::new(description.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Default),
|
||||
)
|
||||
Label::new(description.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Default)
|
||||
.text_ellipsis()
|
||||
}))
|
||||
.children(repository_url.map(|repository_url| {
|
||||
IconButton::new(
|
||||
@@ -550,18 +548,17 @@ impl ExtensionsPage {
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(
|
||||
div().overflow_x_hidden().text_ellipsis().child(
|
||||
Label::new(format!(
|
||||
"{}: {}",
|
||||
if extension.manifest.authors.len() > 1 {
|
||||
"Authors"
|
||||
} else {
|
||||
"Author"
|
||||
},
|
||||
extension.manifest.authors.join(", ")
|
||||
))
|
||||
.size(LabelSize::Small),
|
||||
),
|
||||
Label::new(format!(
|
||||
"{}: {}",
|
||||
if extension.manifest.authors.len() > 1 {
|
||||
"Authors"
|
||||
} else {
|
||||
"Author"
|
||||
},
|
||||
extension.manifest.authors.join(", ")
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
.text_ellipsis(),
|
||||
)
|
||||
.child(
|
||||
Label::new(format!(
|
||||
@@ -576,11 +573,10 @@ impl ExtensionsPage {
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.children(extension.manifest.description.as_ref().map(|description| {
|
||||
div().overflow_x_hidden().text_ellipsis().child(
|
||||
Label::new(description.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Default),
|
||||
)
|
||||
Label::new(description.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Default)
|
||||
.text_ellipsis()
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
|
||||
@@ -1261,8 +1261,8 @@ impl PickerDelegate for FileFinderDelegate {
|
||||
.child(
|
||||
PopoverMenu::new("menu-popover")
|
||||
.with_handle(self.popover_menu_handle.clone())
|
||||
.attach(gpui::AnchorCorner::TopRight)
|
||||
.anchor(gpui::AnchorCorner::BottomRight)
|
||||
.attach(gpui::Corner::TopRight)
|
||||
.anchor(gpui::Corner::BottomRight)
|
||||
.trigger(
|
||||
Button::new("actions-trigger", "Split Options")
|
||||
.selected_label_color(Color::Accent)
|
||||
|
||||
@@ -218,13 +218,13 @@ impl Render for GradientViewer {
|
||||
let height = square_bounds.size.height;
|
||||
let horizontal_offset = height;
|
||||
let vertical_offset = px(30.);
|
||||
let mut path = gpui::Path::new(square_bounds.lower_left());
|
||||
let mut path = gpui::Path::new(square_bounds.bottom_left());
|
||||
path.line_to(square_bounds.origin + point(horizontal_offset, vertical_offset));
|
||||
path.line_to(
|
||||
square_bounds.upper_right() + point(-horizontal_offset, vertical_offset),
|
||||
square_bounds.top_right() + point(-horizontal_offset, vertical_offset),
|
||||
);
|
||||
path.line_to(square_bounds.lower_right());
|
||||
path.line_to(square_bounds.lower_left());
|
||||
path.line_to(square_bounds.bottom_right());
|
||||
path.line_to(square_bounds.bottom_left());
|
||||
cx.paint_path(
|
||||
path,
|
||||
linear_gradient(
|
||||
|
||||
@@ -49,17 +49,17 @@ impl PaintingViewer {
|
||||
let height = square_bounds.size.height;
|
||||
let horizontal_offset = height;
|
||||
let vertical_offset = px(30.);
|
||||
let mut path = Path::new(square_bounds.lower_left());
|
||||
let mut path = Path::new(square_bounds.bottom_left());
|
||||
path.curve_to(
|
||||
square_bounds.origin + point(horizontal_offset, vertical_offset),
|
||||
square_bounds.origin + point(px(0.0), vertical_offset),
|
||||
);
|
||||
path.line_to(square_bounds.upper_right() + point(-horizontal_offset, vertical_offset));
|
||||
path.line_to(square_bounds.top_right() + point(-horizontal_offset, vertical_offset));
|
||||
path.curve_to(
|
||||
square_bounds.lower_right(),
|
||||
square_bounds.upper_right() + point(px(0.0), vertical_offset),
|
||||
square_bounds.bottom_right(),
|
||||
square_bounds.top_right() + point(px(0.0), vertical_offset),
|
||||
);
|
||||
path.line_to(square_bounds.lower_left());
|
||||
path.line_to(square_bounds.bottom_left());
|
||||
lines.push(path);
|
||||
|
||||
Self {
|
||||
|
||||
@@ -86,7 +86,7 @@ fn main() {
|
||||
.unwrap();
|
||||
|
||||
let bounds = Bounds {
|
||||
origin: screen.bounds().upper_right()
|
||||
origin: screen.bounds().top_right()
|
||||
- point(size.width + margin_offset, -margin_offset),
|
||||
size,
|
||||
};
|
||||
@@ -101,7 +101,7 @@ fn main() {
|
||||
.unwrap();
|
||||
|
||||
let bounds = Bounds {
|
||||
origin: screen.bounds().lower_left()
|
||||
origin: screen.bounds().bottom_left()
|
||||
- point(-margin_offset, size.height + margin_offset),
|
||||
size,
|
||||
};
|
||||
@@ -116,7 +116,7 @@ fn main() {
|
||||
.unwrap();
|
||||
|
||||
let bounds = Bounds {
|
||||
origin: screen.bounds().lower_right()
|
||||
origin: screen.bounds().bottom_right()
|
||||
- point(size.width + margin_offset, size.height + margin_offset),
|
||||
size,
|
||||
};
|
||||
|
||||
@@ -5,7 +5,10 @@ use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
path::{Path, PathBuf},
|
||||
rc::{Rc, Weak},
|
||||
sync::{atomic::Ordering::SeqCst, Arc},
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||
Arc,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
@@ -16,6 +19,7 @@ use futures::{
|
||||
future::{LocalBoxFuture, Shared},
|
||||
Future, FutureExt,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use slotmap::SlotMap;
|
||||
|
||||
pub use async_context::*;
|
||||
@@ -30,11 +34,12 @@ use util::ResultExt;
|
||||
use crate::{
|
||||
current_platform, hash, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle,
|
||||
Asset, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
|
||||
Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId,
|
||||
Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point,
|
||||
PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation,
|
||||
ScreenCaptureSource, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem,
|
||||
View, ViewContext, Window, WindowAppearance, WindowContext, WindowHandle, WindowId,
|
||||
Entity, EventEmitter, FocusHandle, FocusId, ForegroundExecutor, Global, KeyBinding, Keymap,
|
||||
Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
|
||||
PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render,
|
||||
RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString, SubscriberSet,
|
||||
Subscription, SvgRenderer, Task, TextSystem, View, ViewContext, Window, WindowAppearance,
|
||||
WindowContext, WindowHandle, WindowId,
|
||||
};
|
||||
|
||||
mod async_context;
|
||||
@@ -242,6 +247,7 @@ pub struct AppContext {
|
||||
pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
|
||||
pub(crate) windows: SlotMap<WindowId, Option<Window>>,
|
||||
pub(crate) window_handles: FxHashMap<WindowId, AnyWindowHandle>,
|
||||
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||
pub(crate) keymap: Rc<RefCell<Keymap>>,
|
||||
pub(crate) keyboard_layout: SharedString,
|
||||
pub(crate) global_action_listeners:
|
||||
@@ -302,8 +308,9 @@ impl AppContext {
|
||||
entities,
|
||||
new_view_observers: SubscriberSet::new(),
|
||||
new_model_observers: SubscriberSet::new(),
|
||||
window_handles: FxHashMap::default(),
|
||||
windows: SlotMap::with_key(),
|
||||
window_handles: FxHashMap::default(),
|
||||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||
keymap: Rc::new(RefCell::new(Keymap::default())),
|
||||
keyboard_layout,
|
||||
global_action_listeners: FxHashMap::default(),
|
||||
@@ -439,6 +446,7 @@ impl AppContext {
|
||||
self.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
pub(crate) fn observe_internal<W, E>(
|
||||
&mut self,
|
||||
entity: &E,
|
||||
@@ -569,6 +577,12 @@ impl AppContext {
|
||||
})
|
||||
}
|
||||
|
||||
/// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus
|
||||
/// for elements rendered within this window.
|
||||
pub fn focus_handle(&self) -> FocusHandle {
|
||||
FocusHandle::new(&self.focus_handles)
|
||||
}
|
||||
|
||||
/// Instructs the platform to activate the application by bringing it to the foreground.
|
||||
pub fn activate(&self, ignoring_other_apps: bool) {
|
||||
self.platform.activate(ignoring_other_apps);
|
||||
@@ -844,28 +858,25 @@ impl AppContext {
|
||||
|
||||
/// Repeatedly called during `flush_effects` to handle a focused handle being dropped.
|
||||
fn release_dropped_focus_handles(&mut self) {
|
||||
for window_handle in self.windows() {
|
||||
window_handle
|
||||
.update(self, |_, cx| {
|
||||
let mut blur_window = false;
|
||||
let focus = cx.window.focus;
|
||||
cx.window.focus_handles.write().retain(|handle_id, count| {
|
||||
if count.load(SeqCst) == 0 {
|
||||
if focus == Some(handle_id) {
|
||||
blur_window = true;
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
if blur_window {
|
||||
cx.blur();
|
||||
self.focus_handles
|
||||
.clone()
|
||||
.write()
|
||||
.retain(|handle_id, count| {
|
||||
if count.load(SeqCst) == 0 {
|
||||
for window_handle in self.windows() {
|
||||
window_handle
|
||||
.update(self, |_, cx| {
|
||||
if cx.window.focus == Some(handle_id) {
|
||||
cx.blur();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn apply_notify_effect(&mut self, emitter: EntityId) {
|
||||
|
||||
@@ -500,7 +500,7 @@ impl AnyElement {
|
||||
|
||||
if !focus_assigned {
|
||||
if let Some(focus_id) = cx.window.next_frame.focus {
|
||||
return FocusHandle::for_id(focus_id, &cx.window.focus_handles);
|
||||
return FocusHandle::for_id(focus_id, &cx.focus_handles);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ use smallvec::SmallVec;
|
||||
use taffy::style::{Display, Position};
|
||||
|
||||
use crate::{
|
||||
point, AnyElement, Bounds, Edges, Element, GlobalElementId, IntoElement, LayoutId,
|
||||
ParentElement, Pixels, Point, Size, Style, WindowContext,
|
||||
point, AnyElement, Axis, Bounds, Corner, Edges, Element, GlobalElementId, IntoElement,
|
||||
LayoutId, ParentElement, Pixels, Point, Size, Style, WindowContext,
|
||||
};
|
||||
|
||||
/// The state that the anchored element element uses to track its children.
|
||||
@@ -15,7 +15,7 @@ pub struct AnchoredState {
|
||||
/// will avoid overflowing the window bounds.
|
||||
pub struct Anchored {
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
anchor_corner: AnchorCorner,
|
||||
anchor_corner: Corner,
|
||||
fit_mode: AnchoredFitMode,
|
||||
anchor_position: Option<Point<Pixels>>,
|
||||
position_mode: AnchoredPositionMode,
|
||||
@@ -26,7 +26,7 @@ pub struct Anchored {
|
||||
pub fn anchored() -> Anchored {
|
||||
Anchored {
|
||||
children: SmallVec::new(),
|
||||
anchor_corner: AnchorCorner::TopLeft,
|
||||
anchor_corner: Corner::TopLeft,
|
||||
fit_mode: AnchoredFitMode::SwitchAnchor,
|
||||
anchor_position: None,
|
||||
position_mode: AnchoredPositionMode::Window,
|
||||
@@ -35,7 +35,7 @@ pub fn anchored() -> Anchored {
|
||||
|
||||
impl Anchored {
|
||||
/// Sets which corner of the anchored element should be anchored to the current position.
|
||||
pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
|
||||
pub fn anchor(mut self, anchor: Corner) -> Self {
|
||||
self.anchor_corner = anchor;
|
||||
self
|
||||
}
|
||||
@@ -120,7 +120,7 @@ impl Element for Anchored {
|
||||
for child_layout_id in &request_layout.child_layout_ids {
|
||||
let child_bounds = cx.layout_bounds(*child_layout_id);
|
||||
child_min = child_min.min(&child_bounds.origin);
|
||||
child_max = child_max.max(&child_bounds.lower_right());
|
||||
child_max = child_max.max(&child_bounds.bottom_right());
|
||||
}
|
||||
let size: Size<Pixels> = (child_max - child_min).into();
|
||||
|
||||
@@ -140,19 +140,23 @@ impl Element for Anchored {
|
||||
let mut anchor_corner = self.anchor_corner;
|
||||
|
||||
if desired.left() < limits.left() || desired.right() > limits.right() {
|
||||
let switched = anchor_corner
|
||||
.switch_axis(Axis::Horizontal)
|
||||
.get_bounds(origin, size);
|
||||
let switched = Bounds::from_corner_and_size(
|
||||
anchor_corner.other_side_corner_along(Axis::Horizontal),
|
||||
origin,
|
||||
size,
|
||||
);
|
||||
if !(switched.left() < limits.left() || switched.right() > limits.right()) {
|
||||
anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
|
||||
anchor_corner = anchor_corner.other_side_corner_along(Axis::Horizontal);
|
||||
desired = switched
|
||||
}
|
||||
}
|
||||
|
||||
if desired.top() < limits.top() || desired.bottom() > limits.bottom() {
|
||||
let switched = anchor_corner
|
||||
.switch_axis(Axis::Vertical)
|
||||
.get_bounds(origin, size);
|
||||
let switched = Bounds::from_corner_and_size(
|
||||
anchor_corner.other_side_corner_along(Axis::Vertical),
|
||||
origin,
|
||||
size,
|
||||
);
|
||||
if !(switched.top() < limits.top() || switched.bottom() > limits.bottom()) {
|
||||
desired = switched;
|
||||
}
|
||||
@@ -214,11 +218,6 @@ impl IntoElement for Anchored {
|
||||
}
|
||||
}
|
||||
|
||||
enum Axis {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
/// Which algorithm to use when fitting the anchored element to be inside the window.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum AnchoredFitMode {
|
||||
@@ -243,83 +242,25 @@ impl AnchoredPositionMode {
|
||||
fn get_position_and_bounds(
|
||||
&self,
|
||||
anchor_position: Option<Point<Pixels>>,
|
||||
anchor_corner: AnchorCorner,
|
||||
anchor_corner: Corner,
|
||||
size: Size<Pixels>,
|
||||
bounds: Bounds<Pixels>,
|
||||
) -> (Point<Pixels>, Bounds<Pixels>) {
|
||||
match self {
|
||||
AnchoredPositionMode::Window => {
|
||||
let anchor_position = anchor_position.unwrap_or(bounds.origin);
|
||||
let bounds = anchor_corner.get_bounds(anchor_position, size);
|
||||
let bounds = Bounds::from_corner_and_size(anchor_corner, anchor_position, size);
|
||||
(anchor_position, bounds)
|
||||
}
|
||||
AnchoredPositionMode::Local => {
|
||||
let anchor_position = anchor_position.unwrap_or_default();
|
||||
let bounds = anchor_corner.get_bounds(bounds.origin + anchor_position, size);
|
||||
let bounds = Bounds::from_corner_and_size(
|
||||
anchor_corner,
|
||||
bounds.origin + anchor_position,
|
||||
size,
|
||||
);
|
||||
(anchor_position, bounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Which corner of the anchored element should be considered the anchor.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AnchorCorner {
|
||||
/// The top left corner
|
||||
TopLeft,
|
||||
/// The top right corner
|
||||
TopRight,
|
||||
/// The bottom left corner
|
||||
BottomLeft,
|
||||
/// The bottom right corner
|
||||
BottomRight,
|
||||
}
|
||||
|
||||
impl AnchorCorner {
|
||||
fn get_bounds(&self, origin: Point<Pixels>, size: Size<Pixels>) -> Bounds<Pixels> {
|
||||
let origin = match self {
|
||||
Self::TopLeft => origin,
|
||||
Self::TopRight => Point {
|
||||
x: origin.x - size.width,
|
||||
y: origin.y,
|
||||
},
|
||||
Self::BottomLeft => Point {
|
||||
x: origin.x,
|
||||
y: origin.y - size.height,
|
||||
},
|
||||
Self::BottomRight => Point {
|
||||
x: origin.x - size.width,
|
||||
y: origin.y - size.height,
|
||||
},
|
||||
};
|
||||
|
||||
Bounds { origin, size }
|
||||
}
|
||||
|
||||
/// Get the point corresponding to this anchor corner in `bounds`.
|
||||
pub fn corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
|
||||
match self {
|
||||
Self::TopLeft => bounds.origin,
|
||||
Self::TopRight => bounds.upper_right(),
|
||||
Self::BottomLeft => bounds.lower_left(),
|
||||
Self::BottomRight => bounds.lower_right(),
|
||||
}
|
||||
}
|
||||
|
||||
fn switch_axis(self, axis: Axis) -> Self {
|
||||
match axis {
|
||||
Axis::Vertical => match self {
|
||||
AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
|
||||
AnchorCorner::TopRight => AnchorCorner::BottomRight,
|
||||
AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
|
||||
AnchorCorner::BottomRight => AnchorCorner::TopRight,
|
||||
},
|
||||
Axis::Horizontal => match self {
|
||||
AnchorCorner::TopLeft => AnchorCorner::TopRight,
|
||||
AnchorCorner::TopRight => AnchorCorner::TopLeft,
|
||||
AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
|
||||
AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1193,7 +1193,7 @@ impl Element for Div {
|
||||
for (ix, child_layout_id) in request_layout.child_layout_ids.iter().enumerate() {
|
||||
let child_bounds = cx.layout_bounds(*child_layout_id);
|
||||
child_min = child_min.min(&child_bounds.origin);
|
||||
child_max = child_max.max(&child_bounds.lower_right());
|
||||
child_max = child_max.max(&child_bounds.bottom_right());
|
||||
state.child_bounds.push(child_bounds);
|
||||
|
||||
if let Some(requested) = requested.as_ref() {
|
||||
@@ -1208,7 +1208,7 @@ impl Element for Div {
|
||||
for child_layout_id in &request_layout.child_layout_ids {
|
||||
let child_bounds = cx.layout_bounds(*child_layout_id);
|
||||
child_min = child_min.min(&child_bounds.origin);
|
||||
child_max = child_max.max(&child_bounds.lower_right());
|
||||
child_max = child_max.max(&child_bounds.bottom_right());
|
||||
}
|
||||
(child_max - child_min).into()
|
||||
};
|
||||
|
||||
@@ -219,7 +219,7 @@ impl Element for UniformList {
|
||||
|
||||
let padded_bounds = Bounds::from_corners(
|
||||
bounds.origin + point(border.left + padding.left, border.top + padding.top),
|
||||
bounds.lower_right()
|
||||
bounds.bottom_right()
|
||||
- point(border.right + padding.right, border.bottom + padding.bottom),
|
||||
);
|
||||
|
||||
@@ -261,7 +261,7 @@ impl Element for UniformList {
|
||||
|
||||
let padded_bounds = Bounds::from_corners(
|
||||
bounds.origin + point(border.left + padding.left, border.top),
|
||||
bounds.lower_right() - point(border.right + padding.right, border.bottom),
|
||||
bounds.bottom_right() - point(border.right + padding.right, border.bottom),
|
||||
);
|
||||
|
||||
if let Some(handle) = self.scroll_handle.as_mut() {
|
||||
|
||||
@@ -10,12 +10,12 @@ use std::{
|
||||
cmp::{self, PartialOrd},
|
||||
fmt,
|
||||
hash::Hash,
|
||||
ops::{Add, Div, Mul, MulAssign, Sub},
|
||||
ops::{Add, Div, Mul, MulAssign, Neg, Sub},
|
||||
};
|
||||
|
||||
use crate::{AppContext, DisplayId};
|
||||
|
||||
/// An axis along which a measurement can be made.
|
||||
/// Axis in a 2D cartesian space.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
||||
pub enum Axis {
|
||||
/// The y axis, or up and down
|
||||
@@ -46,7 +46,7 @@ pub trait Along {
|
||||
fn apply_along(&self, axis: Axis, f: impl FnOnce(Self::Unit) -> Self::Unit) -> Self;
|
||||
}
|
||||
|
||||
/// Describes a location in a 2D cartesian coordinate space.
|
||||
/// Describes a location in a 2D cartesian space.
|
||||
///
|
||||
/// It holds two public fields, `x` and `y`, which represent the coordinates in the space.
|
||||
/// The type `T` for the coordinates can be any type that implements `Default`, `Clone`, and `Debug`.
|
||||
@@ -54,7 +54,7 @@ pub trait Along {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Point;
|
||||
/// # use gpui::Point;
|
||||
/// let point = Point { x: 10, y: 20 };
|
||||
/// println!("{:?}", point); // Outputs: Point { x: 10, y: 20 }
|
||||
/// ```
|
||||
@@ -96,7 +96,7 @@ pub struct Point<T: Default + Clone + Debug> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Point;
|
||||
/// # use gpui::Point;
|
||||
/// let p = point(10, 20);
|
||||
/// assert_eq!(p.x, 10);
|
||||
/// assert_eq!(p.y, 20);
|
||||
@@ -137,7 +137,7 @@ impl<T: Clone + Debug + Default> Point<T> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Point;
|
||||
/// # use gpui::Point;
|
||||
/// let p = Point { x: 3, y: 4 };
|
||||
/// let p_float = p.map(|coord| coord as f32);
|
||||
/// assert_eq!(p_float, Point { x: 3.0, y: 4.0 });
|
||||
@@ -191,7 +191,7 @@ impl Point<Pixels> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Point, Pixels, ScaledPixels};
|
||||
/// # use gpui::{Point, Pixels, ScaledPixels};
|
||||
/// let p = Point { x: Pixels(10.0), y: Pixels(20.0) };
|
||||
/// let scaled_p = p.scale(1.5);
|
||||
/// assert_eq!(scaled_p, Point { x: ScaledPixels(15.0), y: ScaledPixels(30.0) });
|
||||
@@ -208,8 +208,7 @@ impl Point<Pixels> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Point;
|
||||
/// # use zed::Pixels;
|
||||
/// # use gpui::{Pixels, Point};
|
||||
/// let p = Point { x: Pixels(3.0), y: Pixels(4.0) };
|
||||
/// assert_eq!(p.magnitude(), 5.0);
|
||||
/// ```
|
||||
@@ -272,7 +271,7 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Point;
|
||||
/// # use gpui::Point;
|
||||
/// let p1 = Point { x: 3, y: 7 };
|
||||
/// let p2 = Point { x: 5, y: 2 };
|
||||
/// let max_point = p1.max(&p2);
|
||||
@@ -302,7 +301,7 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Point;
|
||||
/// # use gpui::Point;
|
||||
/// let p1 = Point { x: 3, y: 7 };
|
||||
/// let p2 = Point { x: 5, y: 2 };
|
||||
/// let min_point = p1.min(&p2);
|
||||
@@ -338,7 +337,7 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Point;
|
||||
/// # use gpui::Point;
|
||||
/// let p = Point { x: 10, y: 20 };
|
||||
/// let min = Point { x: 0, y: 5 };
|
||||
/// let max = Point { x: 15, y: 25 };
|
||||
@@ -387,7 +386,7 @@ pub struct Size<T: Clone + Default + Debug> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Size;
|
||||
/// # use gpui::Size;
|
||||
/// let my_size = size(10, 20);
|
||||
/// assert_eq!(my_size.width, 10);
|
||||
/// assert_eq!(my_size.height, 20);
|
||||
@@ -416,7 +415,7 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Size;
|
||||
/// # use gpui::Size;
|
||||
/// let my_size = Size { width: 10, height: 20 };
|
||||
/// let my_new_size = my_size.map(|dimension| dimension as f32 * 1.5);
|
||||
/// assert_eq!(my_new_size, Size { width: 15.0, height: 30.0 });
|
||||
@@ -459,7 +458,7 @@ impl Size<Pixels> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Size, Pixels, ScaledPixels};
|
||||
/// # use gpui::{Size, Pixels, ScaledPixels};
|
||||
/// let size = Size { width: Pixels(100.0), height: Pixels(50.0) };
|
||||
/// let scaled_size = size.scale(2.0);
|
||||
/// assert_eq!(scaled_size, Size { width: ScaledPixels(200.0), height: ScaledPixels(100.0) });
|
||||
@@ -513,7 +512,7 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Size;
|
||||
/// # use gpui::Size;
|
||||
/// let size1 = Size { width: 30, height: 40 };
|
||||
/// let size2 = Size { width: 50, height: 20 };
|
||||
/// let max_size = size1.max(&size2);
|
||||
@@ -533,6 +532,7 @@ where
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new `Size` with the minimum width and height from `self` and `other`.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -542,7 +542,7 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Size;
|
||||
/// # use gpui::Size;
|
||||
/// let size1 = Size { width: 30, height: 40 };
|
||||
/// let size2 = Size { width: 50, height: 20 };
|
||||
/// let min_size = size1.min(&size2);
|
||||
@@ -694,13 +694,13 @@ impl Size<Length> {
|
||||
/// Represents a rectangular area in a 2D space with an origin point and a size.
|
||||
///
|
||||
/// The `Bounds` struct is generic over a type `T` which represents the type of the coordinate system.
|
||||
/// The origin is represented as a `Point<T>` which defines the upper-left corner of the rectangle,
|
||||
/// The origin is represented as a `Point<T>` which defines the top left corner of the rectangle,
|
||||
/// and the size is represented as a `Size<T>` which defines the width and height of the rectangle.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size};
|
||||
/// # use gpui::{Bounds, Point, Size};
|
||||
/// let origin = Point { x: 0, y: 0 };
|
||||
/// let size = Size { width: 10, height: 20 };
|
||||
/// let bounds = Bounds::new(origin, size);
|
||||
@@ -731,13 +731,7 @@ impl Bounds<Pixels> {
|
||||
.or_else(|| cx.primary_display());
|
||||
|
||||
display
|
||||
.map(|display| {
|
||||
let center = display.bounds().center();
|
||||
Bounds {
|
||||
origin: point(center.x - size.width / 2., center.y - size.height / 2.),
|
||||
size,
|
||||
}
|
||||
})
|
||||
.map(|display| Bounds::centered_at(display.bounds().center(), size))
|
||||
.unwrap_or_else(|| Bounds {
|
||||
origin: point(px(0.), px(0.)),
|
||||
size,
|
||||
@@ -761,47 +755,8 @@ impl Bounds<Pixels> {
|
||||
|
||||
impl<T> Bounds<T>
|
||||
where
|
||||
T: Clone + Debug + Sub<Output = T> + Default,
|
||||
T: Clone + Debug + Default,
|
||||
{
|
||||
/// Constructs a `Bounds` from two corner points: the upper-left and lower-right corners.
|
||||
///
|
||||
/// This function calculates the origin and size of the `Bounds` based on the provided corner points.
|
||||
/// The origin is set to the upper-left corner, and the size is determined by the difference between
|
||||
/// the x and y coordinates of the lower-right and upper-left points.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `upper_left` - A `Point<T>` representing the upper-left corner of the rectangle.
|
||||
/// * `lower_right` - A `Point<T>` representing the lower-right corner of the rectangle.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a `Bounds<T>` that encompasses the area defined by the two corner points.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point};
|
||||
/// let upper_left = Point { x: 0, y: 0 };
|
||||
/// let lower_right = Point { x: 10, y: 10 };
|
||||
/// let bounds = Bounds::from_corners(upper_left, lower_right);
|
||||
///
|
||||
/// assert_eq!(bounds.origin, upper_left);
|
||||
/// assert_eq!(bounds.size.width, 10);
|
||||
/// assert_eq!(bounds.size.height, 10);
|
||||
/// ```
|
||||
pub fn from_corners(upper_left: Point<T>, lower_right: Point<T>) -> Self {
|
||||
let origin = Point {
|
||||
x: upper_left.x.clone(),
|
||||
y: upper_left.y.clone(),
|
||||
};
|
||||
let size = Size {
|
||||
width: lower_right.x - upper_left.x,
|
||||
height: lower_right.y - upper_left.y,
|
||||
};
|
||||
Bounds { origin, size }
|
||||
}
|
||||
|
||||
/// Creates a new `Bounds` with the specified origin and size.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -819,7 +774,93 @@ where
|
||||
|
||||
impl<T> Bounds<T>
|
||||
where
|
||||
T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T> + Default + Half,
|
||||
T: Clone + Debug + Sub<Output = T> + Default,
|
||||
{
|
||||
/// Constructs a `Bounds` from two corner points: the top left and bottom right corners.
|
||||
///
|
||||
/// This function calculates the origin and size of the `Bounds` based on the provided corner points.
|
||||
/// The origin is set to the top left corner, and the size is determined by the difference between
|
||||
/// the x and y coordinates of the bottom right and top left points.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `upper_left` - A `Point<T>` representing the top left corner of the rectangle.
|
||||
/// * `bottom_right` - A `Point<T>` representing the bottom right corner of the rectangle.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a `Bounds<T>` that encompasses the area defined by the two corner points.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use gpui::{Bounds, Point};
|
||||
/// let upper_left = Point { x: 0, y: 0 };
|
||||
/// let bottom_right = Point { x: 10, y: 10 };
|
||||
/// let bounds = Bounds::from_corners(upper_left, bottom_right);
|
||||
///
|
||||
/// assert_eq!(bounds.origin, upper_left);
|
||||
/// assert_eq!(bounds.size.width, 10);
|
||||
/// assert_eq!(bounds.size.height, 10);
|
||||
/// ```
|
||||
pub fn from_corners(upper_left: Point<T>, bottom_right: Point<T>) -> Self {
|
||||
let origin = Point {
|
||||
x: upper_left.x.clone(),
|
||||
y: upper_left.y.clone(),
|
||||
};
|
||||
let size = Size {
|
||||
width: bottom_right.x - upper_left.x,
|
||||
height: bottom_right.y - upper_left.y,
|
||||
};
|
||||
Bounds { origin, size }
|
||||
}
|
||||
|
||||
/// Constructs a `Bounds` from a corner point and size.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Corner, Point};
|
||||
/// todo!
|
||||
/// ```
|
||||
pub fn from_corner_and_size(corner: Corner, origin: Point<T>, size: Size<T>) -> Bounds<T> {
|
||||
let origin = match corner {
|
||||
Corner::TopLeft => origin,
|
||||
Corner::TopRight => Point {
|
||||
x: origin.x - size.width.clone(),
|
||||
y: origin.y,
|
||||
},
|
||||
Corner::BottomLeft => Point {
|
||||
x: origin.x,
|
||||
y: origin.y - size.height.clone(),
|
||||
},
|
||||
Corner::BottomRight => Point {
|
||||
x: origin.x - size.width.clone(),
|
||||
y: origin.y - size.height.clone(),
|
||||
},
|
||||
};
|
||||
|
||||
Bounds { origin, size }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Bounds<T>
|
||||
where
|
||||
T: Clone + Debug + Sub<T, Output = T> + Default + Half,
|
||||
{
|
||||
/// Creates a new bounds centered at the given point.
|
||||
pub fn centered_at(center: Point<T>, size: Size<T>) -> Self {
|
||||
let origin = Point {
|
||||
x: center.x - size.width.half(),
|
||||
y: center.y - size.height.half(),
|
||||
};
|
||||
Self::new(origin, size)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Bounds<T>
|
||||
where
|
||||
T: Clone + Debug + PartialOrd + Add<T, Output = T> + Default,
|
||||
{
|
||||
/// Checks if this `Bounds` intersects with another `Bounds`.
|
||||
///
|
||||
@@ -837,7 +878,7 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size};
|
||||
/// # use gpui::{Bounds, Point, Size};
|
||||
/// let bounds1 = Bounds {
|
||||
/// origin: Point { x: 0, y: 0 },
|
||||
/// size: Size { width: 10, height: 10 },
|
||||
@@ -855,15 +896,80 @@ where
|
||||
/// assert_eq!(bounds1.intersects(&bounds3), false); // Non-overlapping bounds
|
||||
/// ```
|
||||
pub fn intersects(&self, other: &Bounds<T>) -> bool {
|
||||
let my_lower_right = self.lower_right();
|
||||
let their_lower_right = other.lower_right();
|
||||
let my_lower_right = self.bottom_right();
|
||||
let their_lower_right = other.bottom_right();
|
||||
|
||||
self.origin.x < their_lower_right.x
|
||||
&& my_lower_right.x > other.origin.x
|
||||
&& self.origin.y < their_lower_right.y
|
||||
&& my_lower_right.y > other.origin.y
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Bounds<T>
|
||||
where
|
||||
T: Clone + Debug + Add<T, Output = T> + Default + Half,
|
||||
{
|
||||
/// Returns the center point of the bounds.
|
||||
///
|
||||
/// Calculates the center by taking the origin's x and y coordinates and adding half the width and height
|
||||
/// of the bounds, respectively. The center is represented as a `Point<T>` where `T` is the type of the
|
||||
/// coordinate system.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Point<T>` representing the center of the bounds.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use gpui::{Bounds, Point, Size};
|
||||
/// let bounds = Bounds {
|
||||
/// origin: Point { x: 0, y: 0 },
|
||||
/// size: Size { width: 10, height: 20 },
|
||||
/// };
|
||||
/// let center = bounds.center();
|
||||
/// assert_eq!(center, Point { x: 5, y: 10 });
|
||||
/// ```
|
||||
pub fn center(&self) -> Point<T> {
|
||||
Point {
|
||||
x: self.origin.x.clone() + self.size.width.clone().half(),
|
||||
y: self.origin.y.clone() + self.size.height.clone().half(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Bounds<T>
|
||||
where
|
||||
T: Clone + Debug + Add<T, Output = T> + Default,
|
||||
{
|
||||
/// Calculates the half perimeter of a rectangle defined by the bounds.
|
||||
///
|
||||
/// The half perimeter is calculated as the sum of the width and the height of the rectangle.
|
||||
/// This method is generic over the type `T` which must implement the `Sub` trait to allow
|
||||
/// calculation of the width and height from the bounds' origin and size, as well as the `Add` trait
|
||||
/// to sum the width and height for the half perimeter.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use gpui::{Bounds, Point, Size};
|
||||
/// let bounds = Bounds {
|
||||
/// origin: Point { x: 0, y: 0 },
|
||||
/// size: Size { width: 10, height: 20 },
|
||||
/// };
|
||||
/// let half_perimeter = bounds.half_perimeter();
|
||||
/// assert_eq!(half_perimeter, 30);
|
||||
/// ```
|
||||
pub fn half_perimeter(&self) -> T {
|
||||
self.size.width.clone() + self.size.height.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Bounds<T>
|
||||
where
|
||||
T: Clone + Debug + Add<T, Output = T> + Sub<Output = T> + Default,
|
||||
{
|
||||
/// Dilates the bounds by a specified amount in all directions.
|
||||
///
|
||||
/// This method expands the bounds by the given `amount`, increasing the size
|
||||
@@ -879,7 +985,7 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size};
|
||||
/// # use gpui::{Bounds, Point, Size};
|
||||
/// let mut bounds = Bounds {
|
||||
/// origin: Point { x: 10, y: 10 },
|
||||
/// size: Size { width: 10, height: 10 },
|
||||
@@ -890,79 +996,24 @@ where
|
||||
/// size: Size { width: 20, height: 20 },
|
||||
/// });
|
||||
/// ```
|
||||
pub fn dilate(&mut self, amount: T) {
|
||||
self.origin.x = self.origin.x.clone() - amount.clone();
|
||||
self.origin.y = self.origin.y.clone() - amount.clone();
|
||||
let double_amount = amount.clone() + amount;
|
||||
self.size.width = self.size.width.clone() + double_amount.clone();
|
||||
self.size.height = self.size.height.clone() + double_amount;
|
||||
}
|
||||
|
||||
/// inset the bounds by a specified amount
|
||||
/// Note that this may panic if T does not support negative values
|
||||
pub fn inset(&self, amount: T) -> Self {
|
||||
let mut result = self.clone();
|
||||
result.dilate(T::default() - amount);
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns the center point of the bounds.
|
||||
///
|
||||
/// Calculates the center by taking the origin's x and y coordinates and adding half the width and height
|
||||
/// of the bounds, respectively. The center is represented as a `Point<T>` where `T` is the type of the
|
||||
/// coordinate system.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Point<T>` representing the center of the bounds.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size};
|
||||
/// let bounds = Bounds {
|
||||
/// origin: Point { x: 0, y: 0 },
|
||||
/// size: Size { width: 10, height: 20 },
|
||||
/// };
|
||||
/// let center = bounds.center();
|
||||
/// assert_eq!(center, Point { x: 5, y: 10 });
|
||||
/// ```
|
||||
pub fn center(&self) -> Point<T> {
|
||||
Point {
|
||||
x: self.origin.x.clone() + self.size.width.clone().half(),
|
||||
y: self.origin.y.clone() + self.size.height.clone().half(),
|
||||
pub fn dilate(&self, amount: T) -> Bounds<T> {
|
||||
let double_amount = amount.clone() + amount.clone();
|
||||
Bounds {
|
||||
origin: self.origin.clone() - point(amount.clone(), amount),
|
||||
size: self.size.clone() + size(double_amount.clone(), double_amount),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the half perimeter of a rectangle defined by the bounds.
|
||||
impl<T> Bounds<T>
|
||||
where
|
||||
T: Clone + Debug + Add<T, Output = T> + Sub<T, Output = T> + Neg<Output = T> + Default,
|
||||
{
|
||||
/// Inset the bounds by a specified amount. Equivalent to `dilate` with the amount negated.
|
||||
///
|
||||
/// The half perimeter is calculated as the sum of the width and the height of the rectangle.
|
||||
/// This method is generic over the type `T` which must implement the `Sub` trait to allow
|
||||
/// calculation of the width and height from the bounds' origin and size, as well as the `Add` trait
|
||||
/// to sum the width and height for the half perimeter.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size};
|
||||
/// let bounds = Bounds {
|
||||
/// origin: Point { x: 0, y: 0 },
|
||||
/// size: Size { width: 10, height: 20 },
|
||||
/// };
|
||||
/// let half_perimeter = bounds.half_perimeter();
|
||||
/// assert_eq!(half_perimeter, 30);
|
||||
/// ```
|
||||
pub fn half_perimeter(&self) -> T {
|
||||
self.size.width.clone() + self.size.height.clone()
|
||||
}
|
||||
|
||||
/// centered_at creates a new bounds centered at the given point.
|
||||
pub fn centered_at(center: Point<T>, size: Size<T>) -> Self {
|
||||
let origin = Point {
|
||||
x: center.x - size.width.half(),
|
||||
y: center.y - size.height.half(),
|
||||
};
|
||||
Self::new(origin, size)
|
||||
/// Note that this may panic if T does not support negative values.
|
||||
pub fn inset(&self, amount: T) -> Self {
|
||||
self.dilate(-amount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -984,7 +1035,7 @@ impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output =
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size};
|
||||
/// # use gpui::{Bounds, Point, Size};
|
||||
/// let bounds1 = Bounds {
|
||||
/// origin: Point { x: 0, y: 0 },
|
||||
/// size: Size { width: 10, height: 10 },
|
||||
@@ -1002,8 +1053,8 @@ impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output =
|
||||
/// ```
|
||||
pub fn intersect(&self, other: &Self) -> Self {
|
||||
let upper_left = self.origin.max(&other.origin);
|
||||
let lower_right = self.lower_right().min(&other.lower_right());
|
||||
Self::from_corners(upper_left, lower_right)
|
||||
let bottom_right = self.bottom_right().min(&other.bottom_right());
|
||||
Self::from_corners(upper_left, bottom_right)
|
||||
}
|
||||
|
||||
/// Computes the union of two `Bounds`.
|
||||
@@ -1023,7 +1074,7 @@ impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output =
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size};
|
||||
/// # use gpui::{Bounds, Point, Size};
|
||||
/// let bounds1 = Bounds {
|
||||
/// origin: Point { x: 0, y: 0 },
|
||||
/// size: Size { width: 10, height: 10 },
|
||||
@@ -1041,7 +1092,7 @@ impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output =
|
||||
/// ```
|
||||
pub fn union(&self, other: &Self) -> Self {
|
||||
let top_left = self.origin.min(&other.origin);
|
||||
let bottom_right = self.lower_right().max(&other.lower_right());
|
||||
let bottom_right = self.bottom_right().max(&other.bottom_right());
|
||||
Bounds::from_corners(top_left, bottom_right)
|
||||
}
|
||||
}
|
||||
@@ -1089,6 +1140,34 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Add<Point<T>> for Bounds<T>
|
||||
where
|
||||
T: Add<T, Output = T> + Default + Clone + Debug,
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Point<T>) -> Self {
|
||||
Self {
|
||||
origin: self.origin + rhs,
|
||||
size: self.size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Sub<Point<T>> for Bounds<T>
|
||||
where
|
||||
T: Sub<T, Output = T> + Default + Clone + Debug,
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Point<T>) -> Self {
|
||||
Self {
|
||||
origin: self.origin - rhs,
|
||||
size: self.size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Bounds<T>
|
||||
where
|
||||
T: Add<T, Output = T> + Clone + Default + Debug,
|
||||
@@ -1129,77 +1208,103 @@ where
|
||||
self.origin.x.clone() + self.size.width.clone()
|
||||
}
|
||||
|
||||
/// Returns the upper-right corner point of the bounds.
|
||||
/// Returns the top right corner point of the bounds.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Point<T>` representing the upper-right corner of the bounds.
|
||||
/// A `Point<T>` representing the top right corner of the bounds.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size};
|
||||
/// # use gpui::{Bounds, Point, Size};
|
||||
/// let bounds = Bounds {
|
||||
/// origin: Point { x: 0, y: 0 },
|
||||
/// size: Size { width: 10, height: 20 },
|
||||
/// };
|
||||
/// let upper_right = bounds.upper_right();
|
||||
/// assert_eq!(upper_right, Point { x: 10, y: 0 });
|
||||
/// let top_right = bounds.top_right();
|
||||
/// assert_eq!(top_right, Point { x: 10, y: 0 });
|
||||
/// ```
|
||||
pub fn upper_right(&self) -> Point<T> {
|
||||
pub fn top_right(&self) -> Point<T> {
|
||||
Point {
|
||||
x: self.origin.x.clone() + self.size.width.clone(),
|
||||
y: self.origin.y.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the lower-right corner point of the bounds.
|
||||
/// Returns the bottom right corner point of the bounds.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Point<T>` representing the lower-right corner of the bounds.
|
||||
/// A `Point<T>` representing the bottom right corner of the bounds.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size};
|
||||
/// # use gpui::{Bounds, Point, Size};
|
||||
/// let bounds = Bounds {
|
||||
/// origin: Point { x: 0, y: 0 },
|
||||
/// size: Size { width: 10, height: 20 },
|
||||
/// };
|
||||
/// let lower_right = bounds.lower_right();
|
||||
/// assert_eq!(lower_right, Point { x: 10, y: 20 });
|
||||
/// let bottom_right = bounds.bottom_right();
|
||||
/// assert_eq!(bottom_right, Point { x: 10, y: 20 });
|
||||
/// ```
|
||||
pub fn lower_right(&self) -> Point<T> {
|
||||
pub fn bottom_right(&self) -> Point<T> {
|
||||
Point {
|
||||
x: self.origin.x.clone() + self.size.width.clone(),
|
||||
y: self.origin.y.clone() + self.size.height.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the lower-left corner point of the bounds.
|
||||
/// Returns the bottom left corner point of the bounds.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Point<T>` representing the lower-left corner of the bounds.
|
||||
/// A `Point<T>` representing the bottom left corner of the bounds.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size};
|
||||
/// # use gpui::{Bounds, Point, Size};
|
||||
/// let bounds = Bounds {
|
||||
/// origin: Point { x: 0, y: 0 },
|
||||
/// size: Size { width: 10, height: 20 },
|
||||
/// };
|
||||
/// let lower_left = bounds.lower_left();
|
||||
/// assert_eq!(lower_left, Point { x: 0, y: 20 });
|
||||
/// let bottom_left = bounds.bottom_left();
|
||||
/// assert_eq!(bottom_left, Point { x: 0, y: 20 });
|
||||
/// ```
|
||||
pub fn lower_left(&self) -> Point<T> {
|
||||
pub fn bottom_left(&self) -> Point<T> {
|
||||
Point {
|
||||
x: self.origin.x.clone(),
|
||||
y: self.origin.y.clone() + self.size.height.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the requested corner point of the bounds.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Point<T>` representing the corner of the bounds requested by the parameter.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Corner, Point, Size};
|
||||
/// let bounds = Bounds {
|
||||
/// origin: Point { x: 0, y: 0 },
|
||||
/// size: Size { width: 10, height: 20 },
|
||||
/// };
|
||||
/// let bottom_left = bounds.corner(Corner::BottomLeft);
|
||||
/// assert_eq!(bottom_left, Point { x: 0, y: 20 });
|
||||
/// ```
|
||||
pub fn corner(&self, corner: Corner) -> Point<T> {
|
||||
match corner {
|
||||
Corner::TopLeft => self.origin.clone(),
|
||||
Corner::TopRight => self.top_right(),
|
||||
Corner::BottomLeft => self.bottom_left(),
|
||||
Corner::BottomRight => self.bottom_right(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Bounds<T>
|
||||
@@ -1224,7 +1329,7 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Point, Bounds};
|
||||
/// # use gpui::{Point, Bounds};
|
||||
/// let bounds = Bounds {
|
||||
/// origin: Point { x: 0, y: 0 },
|
||||
/// size: Size { width: 10, height: 10 },
|
||||
@@ -1259,7 +1364,7 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size};
|
||||
/// # use gpui::{Bounds, Point, Size};
|
||||
/// let bounds = Bounds {
|
||||
/// origin: Point { x: 10.0, y: 10.0 },
|
||||
/// size: Size { width: 10.0, height: 20.0 },
|
||||
@@ -1286,7 +1391,7 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size};
|
||||
/// # use gpui::{Bounds, Point, Size};
|
||||
/// let bounds = Bounds {
|
||||
/// origin: Point { x: 10.0, y: 10.0 },
|
||||
/// size: Size { width: 10.0, height: 20.0 },
|
||||
@@ -1310,7 +1415,7 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size};
|
||||
/// # use gpui::{Bounds, Point, Size};
|
||||
/// let bounds = Bounds {
|
||||
/// origin: Point { x: 10.0, y: 10.0 },
|
||||
/// size: Size { width: 10.0, height: 20.0 },
|
||||
@@ -1385,7 +1490,7 @@ impl Bounds<Pixels> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size, Pixels};
|
||||
/// # use gpui::{Bounds, Point, Size, Pixels};
|
||||
/// let bounds = Bounds {
|
||||
/// origin: Point { x: Pixels(10.0), y: Pixels(20.0) },
|
||||
/// size: Size { width: Pixels(30.0), height: Pixels(40.0) },
|
||||
@@ -1438,7 +1543,7 @@ impl<T: Clone + Debug + Copy + Default> Copy for Bounds<T> {}
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Edges;
|
||||
/// # use gpui::Edges;
|
||||
/// let edges = Edges {
|
||||
/// top: 10.0,
|
||||
/// right: 20.0,
|
||||
@@ -1514,7 +1619,7 @@ impl<T: Clone + Default + Debug> Edges<T> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Edges;
|
||||
/// # use gpui::Edges;
|
||||
/// let uniform_edges = Edges::all(10.0);
|
||||
/// assert_eq!(uniform_edges.top, 10.0);
|
||||
/// assert_eq!(uniform_edges.right, 10.0);
|
||||
@@ -1547,7 +1652,7 @@ impl<T: Clone + Default + Debug> Edges<T> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Edges;
|
||||
/// # use gpui::Edges;
|
||||
/// let edges = Edges { top: 10, right: 20, bottom: 30, left: 40 };
|
||||
/// let edges_float = edges.map(|&value| value as f32 * 1.1);
|
||||
/// assert_eq!(edges_float, Edges { top: 11.0, right: 22.0, bottom: 33.0, left: 44.0 });
|
||||
@@ -1579,7 +1684,7 @@ impl<T: Clone + Default + Debug> Edges<T> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Edges;
|
||||
/// # use gpui::Edges;
|
||||
/// let edges = Edges {
|
||||
/// top: 10,
|
||||
/// right: 0,
|
||||
@@ -1611,7 +1716,7 @@ impl Edges<Length> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Edges;
|
||||
/// # use gpui::Edges;
|
||||
/// let auto_edges = Edges::auto();
|
||||
/// assert_eq!(auto_edges.top, Length::Auto);
|
||||
/// assert_eq!(auto_edges.right, Length::Auto);
|
||||
@@ -1639,7 +1744,7 @@ impl Edges<Length> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Edges;
|
||||
/// # use gpui::Edges;
|
||||
/// let no_edges = Edges::zero();
|
||||
/// assert_eq!(no_edges.top, Length::Definite(DefiniteLength::from(Pixels(0.))));
|
||||
/// assert_eq!(no_edges.right, Length::Definite(DefiniteLength::from(Pixels(0.))));
|
||||
@@ -1669,12 +1774,12 @@ impl Edges<DefiniteLength> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Edges;
|
||||
/// # use gpui::{px, Edges};
|
||||
/// let no_edges = Edges::zero();
|
||||
/// assert_eq!(no_edges.top, DefiniteLength::from(zed::px(0.)));
|
||||
/// assert_eq!(no_edges.right, DefiniteLength::from(zed::px(0.)));
|
||||
/// assert_eq!(no_edges.bottom, DefiniteLength::from(zed::px(0.)));
|
||||
/// assert_eq!(no_edges.left, DefiniteLength::from(zed::px(0.)));
|
||||
/// assert_eq!(no_edges.top, DefiniteLength::from(px(0.)));
|
||||
/// assert_eq!(no_edges.right, DefiniteLength::from(px(0.)));
|
||||
/// assert_eq!(no_edges.bottom, DefiniteLength::from(px(0.)));
|
||||
/// assert_eq!(no_edges.left, DefiniteLength::from(px(0.)));
|
||||
/// ```
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
@@ -1702,7 +1807,7 @@ impl Edges<DefiniteLength> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Edges, DefiniteLength, px, AbsoluteLength, Size};
|
||||
/// # use gpui::{Edges, DefiniteLength, px, AbsoluteLength, Size};
|
||||
/// let edges = Edges {
|
||||
/// top: DefiniteLength::Absolute(AbsoluteLength::Pixels(px(10.0))),
|
||||
/// right: DefiniteLength::Fraction(0.5),
|
||||
@@ -1744,7 +1849,7 @@ impl Edges<AbsoluteLength> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Edges;
|
||||
/// # use gpui::Edges;
|
||||
/// let no_edges = Edges::zero();
|
||||
/// assert_eq!(no_edges.top, AbsoluteLength::Pixels(Pixels(0.0)));
|
||||
/// assert_eq!(no_edges.right, AbsoluteLength::Pixels(Pixels(0.0)));
|
||||
@@ -1776,7 +1881,7 @@ impl Edges<AbsoluteLength> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Edges, AbsoluteLength, Pixels, px};
|
||||
/// # use gpui::{Edges, AbsoluteLength, Pixels, px};
|
||||
/// let edges = Edges {
|
||||
/// top: AbsoluteLength::Pixels(px(10.0)),
|
||||
/// right: AbsoluteLength::Rems(rems(1.0)),
|
||||
@@ -1817,7 +1922,7 @@ impl Edges<Pixels> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Edges, Pixels};
|
||||
/// # use gpui::{Edges, Pixels};
|
||||
/// let edges = Edges {
|
||||
/// top: Pixels(10.0),
|
||||
/// right: Pixels(20.0),
|
||||
@@ -1867,6 +1972,64 @@ impl From<Pixels> for Edges<Pixels> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifies a corner of a 2d box.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Corner {
|
||||
/// The top left corner
|
||||
TopLeft,
|
||||
/// The top right corner
|
||||
TopRight,
|
||||
/// The bottom left corner
|
||||
BottomLeft,
|
||||
/// The bottom right corner
|
||||
BottomRight,
|
||||
}
|
||||
|
||||
impl Corner {
|
||||
/// Returns the directly opposite corner.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Corner;
|
||||
/// assert_eq!(Corner::TopLeft.opposite_corner(), Corner::BottomRight);
|
||||
/// ```
|
||||
pub fn opposite_corner(self) -> Self {
|
||||
match self {
|
||||
Corner::TopLeft => Corner::BottomRight,
|
||||
Corner::TopRight => Corner::BottomLeft,
|
||||
Corner::BottomLeft => Corner::TopRight,
|
||||
Corner::BottomRight => Corner::TopLeft,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the corner across from this corner, moving along the specified axis.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Corner;
|
||||
/// let result = Corner::TopLeft.other_side_corner_along(Axis::Horizontal);
|
||||
/// assert_eq!(result, Corner::TopRight);
|
||||
/// ```
|
||||
pub fn other_side_corner_along(self, axis: Axis) -> Self {
|
||||
match axis {
|
||||
Axis::Vertical => match self {
|
||||
Corner::TopLeft => Corner::BottomLeft,
|
||||
Corner::TopRight => Corner::BottomRight,
|
||||
Corner::BottomLeft => Corner::TopLeft,
|
||||
Corner::BottomRight => Corner::TopRight,
|
||||
},
|
||||
Axis::Horizontal => match self {
|
||||
Corner::TopLeft => Corner::TopRight,
|
||||
Corner::TopRight => Corner::TopLeft,
|
||||
Corner::BottomLeft => Corner::BottomRight,
|
||||
Corner::BottomRight => Corner::BottomLeft,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the corners of a box in a 2D space, such as border radius.
|
||||
///
|
||||
/// Each field represents the size of the corner on one side of the box: `top_left`, `top_right`, `bottom_right`, and `bottom_left`.
|
||||
@@ -1905,7 +2068,7 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::Corners;
|
||||
/// # use gpui::Corners;
|
||||
/// let uniform_corners = Corners::all(5.0);
|
||||
/// assert_eq!(uniform_corners.top_left, 5.0);
|
||||
/// assert_eq!(uniform_corners.top_right, 5.0);
|
||||
@@ -1920,6 +2083,33 @@ where
|
||||
bottom_left: value,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the requested corner.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Point<T>` representing the corner requested by the parameter.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Corner, Corners};
|
||||
/// let corners = Corners {
|
||||
/// top_left: 1,
|
||||
/// top_right: 2,
|
||||
/// bottom_left: 3,
|
||||
/// bottom_right: 4
|
||||
/// };
|
||||
/// assert_eq!(corners.corner(Corner::BottomLeft), 3);
|
||||
/// ```
|
||||
pub fn corner(&self, corner: Corner) -> T {
|
||||
match corner {
|
||||
Corner::TopLeft => self.top_left.clone(),
|
||||
Corner::TopRight => self.top_right.clone(),
|
||||
Corner::BottomLeft => self.bottom_left.clone(),
|
||||
Corner::BottomRight => self.bottom_right.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Corners<AbsoluteLength> {
|
||||
@@ -1942,7 +2132,7 @@ impl Corners<AbsoluteLength> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Corners, AbsoluteLength, Pixels, Size};
|
||||
/// # use gpui::{Corners, AbsoluteLength, Pixels, Size};
|
||||
/// let corners = Corners {
|
||||
/// top_left: AbsoluteLength::Pixels(Pixels(15.0)),
|
||||
/// top_right: AbsoluteLength::Rems(Rems(1.0)),
|
||||
@@ -1986,7 +2176,7 @@ impl Corners<Pixels> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Corners, Pixels};
|
||||
/// # use gpui::{Corners, Pixels};
|
||||
/// let corners = Corners {
|
||||
/// top_left: Pixels(10.0),
|
||||
/// top_right: Pixels(20.0),
|
||||
@@ -2039,7 +2229,7 @@ impl<T: Clone + Default + Debug> Corners<T> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Corners, Pixels};
|
||||
/// # use gpui::{Corners, Pixels};
|
||||
/// let corners = Corners {
|
||||
/// top_left: Pixels(10.0),
|
||||
/// top_right: Pixels(20.0),
|
||||
@@ -2193,7 +2383,7 @@ impl From<Percentage> for Radians {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use zed::Pixels;
|
||||
/// use gpui::Pixels;
|
||||
///
|
||||
/// // Define a length of 10 pixels
|
||||
/// let length = Pixels(10.0);
|
||||
@@ -2505,7 +2695,7 @@ impl DevicePixels {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::DevicePixels;
|
||||
/// # use gpui::DevicePixels;
|
||||
/// let pixels = DevicePixels(10); // 10 device pixels
|
||||
/// let bytes_per_pixel = 4; // Assume each pixel is represented by 4 bytes (e.g., RGBA)
|
||||
/// let total_bytes = pixels.to_bytes(bytes_per_pixel);
|
||||
@@ -2717,7 +2907,7 @@ impl AbsoluteLength {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{AbsoluteLength, Pixels};
|
||||
/// # use gpui::{AbsoluteLength, Pixels};
|
||||
/// let length_in_pixels = AbsoluteLength::Pixels(Pixels(42.0));
|
||||
/// let length_in_rems = AbsoluteLength::Rems(Rems(2.0));
|
||||
/// let rem_size = Pixels(16.0);
|
||||
@@ -2770,7 +2960,7 @@ impl DefiniteLength {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{DefiniteLength, AbsoluteLength, Pixels, px, rems};
|
||||
/// # use gpui::{DefiniteLength, AbsoluteLength, Pixels, px, rems};
|
||||
/// let length_in_pixels = DefiniteLength::Absolute(AbsoluteLength::Pixels(px(42.0)));
|
||||
/// let length_in_rems = DefiniteLength::Absolute(AbsoluteLength::Rems(rems(2.0)));
|
||||
/// let length_as_fraction = DefiniteLength::Fraction(0.5);
|
||||
|
||||
@@ -509,7 +509,7 @@ impl Style {
|
||||
} => None,
|
||||
_ => {
|
||||
let mut min = bounds.origin;
|
||||
let mut max = bounds.lower_right();
|
||||
let mut max = bounds.bottom_right();
|
||||
|
||||
if self
|
||||
.border_color
|
||||
@@ -530,12 +530,12 @@ impl Style {
|
||||
// x visible, y hidden
|
||||
(true, false) => Bounds::from_corners(
|
||||
point(min.x, bounds.origin.y),
|
||||
point(max.x, bounds.lower_right().y),
|
||||
point(max.x, bounds.bottom_right().y),
|
||||
),
|
||||
// x hidden, y visible
|
||||
(false, true) => Bounds::from_corners(
|
||||
point(bounds.origin.x, min.y),
|
||||
point(bounds.lower_right().x, max.y),
|
||||
point(bounds.bottom_right().x, max.y),
|
||||
),
|
||||
// both hidden
|
||||
(false, false) => Bounds::from_corners(min, max),
|
||||
@@ -604,19 +604,19 @@ impl Style {
|
||||
|
||||
let top_bounds = Bounds::from_corners(
|
||||
bounds.origin,
|
||||
bounds.upper_right() + point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
|
||||
bounds.top_right() + point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
|
||||
);
|
||||
let bottom_bounds = Bounds::from_corners(
|
||||
bounds.lower_left() - point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
|
||||
bounds.lower_right(),
|
||||
bounds.bottom_left() - point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
|
||||
bounds.bottom_right(),
|
||||
);
|
||||
let left_bounds = Bounds::from_corners(
|
||||
top_bounds.lower_left(),
|
||||
top_bounds.bottom_left(),
|
||||
bottom_bounds.origin + point(max_border_width, Pixels::ZERO),
|
||||
);
|
||||
let right_bounds = Bounds::from_corners(
|
||||
top_bounds.lower_right() - point(max_border_width, Pixels::ZERO),
|
||||
bottom_bounds.upper_right(),
|
||||
top_bounds.bottom_right() - point(max_border_width, Pixels::ZERO),
|
||||
bottom_bounds.top_right(),
|
||||
);
|
||||
|
||||
let mut background = self.border_color.unwrap_or_default();
|
||||
|
||||
@@ -531,7 +531,6 @@ pub struct Window {
|
||||
pub(crate) tooltip_bounds: Option<TooltipBounds>,
|
||||
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
|
||||
pub(crate) dirty_views: FxHashSet<EntityId>,
|
||||
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||
focus_listeners: SubscriberSet<(), AnyWindowFocusListener>,
|
||||
focus_lost_listeners: SubscriberSet<(), AnyObserver>,
|
||||
default_prevented: bool,
|
||||
@@ -809,7 +808,6 @@ impl Window {
|
||||
next_tooltip_id: TooltipId::default(),
|
||||
tooltip_bounds: None,
|
||||
dirty_views: FxHashSet::default(),
|
||||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||
focus_listeners: SubscriberSet::new(),
|
||||
focus_lost_listeners: SubscriberSet::new(),
|
||||
default_prevented: true,
|
||||
@@ -931,17 +929,11 @@ impl<'a> WindowContext<'a> {
|
||||
self.window.removed = true;
|
||||
}
|
||||
|
||||
/// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus
|
||||
/// for elements rendered within this window.
|
||||
pub fn focus_handle(&self) -> FocusHandle {
|
||||
FocusHandle::new(&self.window.focus_handles)
|
||||
}
|
||||
|
||||
/// Obtain the currently focused [`FocusHandle`]. If no elements are focused, returns `None`.
|
||||
pub fn focused(&self) -> Option<FocusHandle> {
|
||||
self.window
|
||||
.focus
|
||||
.and_then(|id| FocusHandle::for_id(id, &self.window.focus_handles))
|
||||
.and_then(|id| FocusHandle::for_id(id, &self.app.focus_handles))
|
||||
}
|
||||
|
||||
/// Move focus to the element associated with the given [`FocusHandle`].
|
||||
@@ -2281,9 +2273,7 @@ impl<'a> WindowContext<'a> {
|
||||
let content_mask = self.content_mask();
|
||||
let opacity = self.element_opacity();
|
||||
for shadow in shadows {
|
||||
let mut shadow_bounds = bounds;
|
||||
shadow_bounds.origin += shadow.offset;
|
||||
shadow_bounds.dilate(shadow.spread_radius);
|
||||
let shadow_bounds = (bounds + shadow.offset).dilate(shadow.spread_radius);
|
||||
self.window.next_frame.scene.insert_primitive(Shadow {
|
||||
order: 0,
|
||||
blur_radius: shadow.blur_radius.scale(scale_factor),
|
||||
@@ -3023,7 +3013,7 @@ impl<'a> WindowContext<'a> {
|
||||
let event = FocusOutEvent {
|
||||
blurred: WeakFocusHandle {
|
||||
id: blurred_id,
|
||||
handles: Arc::downgrade(&cx.window.focus_handles),
|
||||
handles: Arc::downgrade(&cx.app.focus_handles),
|
||||
},
|
||||
};
|
||||
listener(event, cx)
|
||||
@@ -4441,7 +4431,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
let event = FocusOutEvent {
|
||||
blurred: WeakFocusHandle {
|
||||
id: blurred_id,
|
||||
handles: Arc::downgrade(&cx.window.focus_handles),
|
||||
handles: Arc::downgrade(&cx.app.focus_handles),
|
||||
},
|
||||
};
|
||||
listener(view, event, cx)
|
||||
|
||||
@@ -19,6 +19,7 @@ pub struct InlineCompletion {
|
||||
|
||||
pub trait InlineCompletionProvider: 'static + Sized {
|
||||
fn name() -> &'static str;
|
||||
fn display_name() -> &'static str;
|
||||
fn is_enabled(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
@@ -51,6 +52,7 @@ pub trait InlineCompletionProvider: 'static + Sized {
|
||||
|
||||
pub trait InlineCompletionProviderHandle {
|
||||
fn name(&self) -> &'static str;
|
||||
fn display_name(&self) -> &'static str;
|
||||
fn is_enabled(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
@@ -89,6 +91,10 @@ where
|
||||
T::name()
|
||||
}
|
||||
|
||||
fn display_name(&self) -> &'static str {
|
||||
T::display_name()
|
||||
}
|
||||
|
||||
fn is_enabled(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
|
||||
@@ -4,7 +4,7 @@ use editor::{scroll::Autoscroll, Editor};
|
||||
use feature_flags::{FeatureFlagAppExt, ZetaFeatureFlag};
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
actions, div, Action, AnchorCorner, AppContext, AsyncWindowContext, Entity, IntoElement,
|
||||
actions, div, Action, AppContext, AsyncWindowContext, Corner, Entity, IntoElement,
|
||||
ParentElement, Render, Subscription, View, ViewContext, WeakView, WindowContext,
|
||||
};
|
||||
use language::{
|
||||
@@ -123,7 +123,7 @@ impl Render for InlineCompletionButton {
|
||||
_ => this.update(cx, |this, cx| this.build_copilot_start_menu(cx)),
|
||||
})
|
||||
})
|
||||
.anchor(AnchorCorner::BottomRight)
|
||||
.anchor(Corner::BottomRight)
|
||||
.trigger(
|
||||
IconButton::new("copilot-icon", icon)
|
||||
.tooltip(|cx| Tooltip::text("GitHub Copilot", cx)),
|
||||
@@ -191,7 +191,7 @@ impl Render for InlineCompletionButton {
|
||||
),
|
||||
_ => None,
|
||||
})
|
||||
.anchor(AnchorCorner::BottomRight)
|
||||
.anchor(Corner::BottomRight)
|
||||
.trigger(
|
||||
IconButton::new("supermaven-icon", icon)
|
||||
.tooltip(move |cx| Tooltip::text(tooltip_text.clone(), cx)),
|
||||
|
||||
@@ -107,7 +107,7 @@ impl<T: PopoverTrigger> RenderOnce for LanguageModelSelectorPopoverMenu<T> {
|
||||
PopoverMenu::new("model-switcher")
|
||||
.menu(move |_cx| Some(language_model_selector.clone()))
|
||||
.trigger(self.trigger)
|
||||
.attach(gpui::AnchorCorner::BottomLeft)
|
||||
.attach(gpui::Corner::BottomLeft)
|
||||
.when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use copilot::Copilot;
|
||||
use editor::{actions::MoveToEnd, Editor, EditorEvent};
|
||||
use futures::{channel::mpsc, StreamExt};
|
||||
use gpui::{
|
||||
actions, div, AnchorCorner, AppContext, Context, EventEmitter, FocusHandle, FocusableView,
|
||||
actions, div, AppContext, Context, Corner, EventEmitter, FocusHandle, FocusableView,
|
||||
IntoElement, Model, ModelContext, ParentElement, Render, Styled, Subscription, View,
|
||||
ViewContext, VisualContext, WeakModel, WindowContext,
|
||||
};
|
||||
@@ -1158,7 +1158,7 @@ impl Render for LspLogToolbarItemView {
|
||||
.collect();
|
||||
let log_toolbar_view = cx.view().clone();
|
||||
let lsp_menu = PopoverMenu::new("LspLogView")
|
||||
.anchor(AnchorCorner::TopLeft)
|
||||
.anchor(Corner::TopLeft)
|
||||
.trigger(Button::new(
|
||||
"language_server_menu_header",
|
||||
current_server
|
||||
@@ -1214,7 +1214,7 @@ impl Render for LspLogToolbarItemView {
|
||||
let rpc_trace_enabled = server.rpc_trace_enabled;
|
||||
let log_view = log_view.clone();
|
||||
PopoverMenu::new("LspViewSelector")
|
||||
.anchor(AnchorCorner::TopLeft)
|
||||
.anchor(Corner::TopLeft)
|
||||
.trigger(Button::new(
|
||||
"language_server_menu_header",
|
||||
server.selected_entry.label(),
|
||||
@@ -1301,7 +1301,7 @@ impl Render for LspLogToolbarItemView {
|
||||
let log_view = log_view.clone();
|
||||
div().child(
|
||||
PopoverMenu::new("lsp-trace-level-menu")
|
||||
.anchor(AnchorCorner::TopLeft)
|
||||
.anchor(Corner::TopLeft)
|
||||
.trigger(Button::new(
|
||||
"language_server_trace_level_selector",
|
||||
"Trace level",
|
||||
@@ -1359,7 +1359,7 @@ impl Render for LspLogToolbarItemView {
|
||||
let log_view = log_view.clone();
|
||||
div().child(
|
||||
PopoverMenu::new("lsp-log-level-menu")
|
||||
.anchor(AnchorCorner::TopLeft)
|
||||
.anchor(Corner::TopLeft)
|
||||
.trigger(Button::new(
|
||||
"language_server_log_level_selector",
|
||||
"Log level",
|
||||
|
||||
@@ -18,10 +18,10 @@ use std::{
|
||||
};
|
||||
use theme::{ActiveTheme, SyntaxTheme, ThemeSettings};
|
||||
use ui::{
|
||||
h_flex, relative, tooltip_container, v_flex, Checkbox, Clickable, Color, FluentBuilder,
|
||||
IconButton, IconName, IconSize, InteractiveElement, Label, LabelCommon, LabelSize, LinkPreview,
|
||||
StatefulInteractiveElement, StyledExt, StyledImage, ToggleState, ViewContext, VisibleOnHover,
|
||||
VisualContext as _,
|
||||
h_flex, relative, tooltip_container, v_flex, ButtonCommon, Checkbox, Clickable, Color,
|
||||
FluentBuilder, IconButton, IconName, IconSize, InteractiveElement, Label, LabelCommon,
|
||||
LabelSize, LinkPreview, StatefulInteractiveElement, StyledExt, StyledImage, ToggleState,
|
||||
Tooltip, ViewContext, VisibleOnHover, VisualContext as _,
|
||||
};
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -385,6 +385,7 @@ fn render_markdown_code_block(
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(contents.to_string()));
|
||||
}
|
||||
})
|
||||
.tooltip(|cx| Tooltip::text("Copy code block", cx))
|
||||
.visible_on_hover("markdown-block");
|
||||
|
||||
cx.with_common_p(div())
|
||||
|
||||
@@ -27,11 +27,13 @@ collections.workspace = true
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
futures.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
project.workspace = true
|
||||
rand.workspace = true
|
||||
settings.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
2563
crates/multi_buffer/src/diff_map.rs
Normal file
@@ -1,9 +1,11 @@
|
||||
mod anchor;
|
||||
mod diff_map;
|
||||
|
||||
pub use anchor::{Anchor, AnchorRangeExt, Offset};
|
||||
use anyhow::{anyhow, Result};
|
||||
use clock::ReplicaId;
|
||||
use collections::{BTreeMap, Bound, HashMap, HashSet};
|
||||
pub use diff_map::*;
|
||||
use futures::{channel::mpsc, SinkExt};
|
||||
use gpui::{AppContext, EntityId, EventEmitter, Model, ModelContext, Task};
|
||||
use itertools::Itertools;
|
||||
@@ -69,6 +71,7 @@ pub struct MultiBuffer {
|
||||
history: History,
|
||||
title: Option<String>,
|
||||
capability: Capability,
|
||||
diff_map: Model<DiffMap>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -179,6 +182,7 @@ struct BufferState {
|
||||
pub struct MultiBufferSnapshot {
|
||||
singleton: bool,
|
||||
excerpts: SumTree<Excerpt>,
|
||||
// diff_map_snapshot: DiffMapSnapshot,
|
||||
excerpt_ids: SumTree<ExcerptIdMapping>,
|
||||
trailing_excerpt_update_count: usize,
|
||||
non_text_state_update_count: usize,
|
||||
@@ -1547,6 +1551,36 @@ impl MultiBuffer {
|
||||
excerpts
|
||||
}
|
||||
|
||||
pub fn ranges_for_buffer(
|
||||
&self,
|
||||
buffer_id: BufferId,
|
||||
cx: &AppContext,
|
||||
) -> Vec<(ExcerptId, Range<Point>, Range<text::Anchor>)> {
|
||||
let mut ranges = Vec::new();
|
||||
let snapshot = self.read(cx);
|
||||
for locator in self
|
||||
.buffers
|
||||
.borrow()
|
||||
.get(&buffer_id)
|
||||
.map(|state| &state.excerpts)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, Point)>(&());
|
||||
cursor.seek_forward(&Some(locator), Bias::Left, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
if excerpt.locator == *locator {
|
||||
ranges.push((
|
||||
excerpt.id,
|
||||
cursor.start().1..cursor.end(&()).1,
|
||||
excerpt.range.context.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
ranges
|
||||
}
|
||||
|
||||
pub fn excerpt_ranges_for_buffer(
|
||||
&self,
|
||||
buffer_id: BufferId,
|
||||
@@ -2529,6 +2563,39 @@ impl MultiBufferSnapshot {
|
||||
.eq(needle.bytes())
|
||||
}
|
||||
|
||||
pub fn range_to_buffer_ranges<T: ToOffset>(
|
||||
&self,
|
||||
range: Range<T>,
|
||||
) -> Vec<(BufferSnapshot, Range<usize>, ExcerptId)> {
|
||||
let start = range.start.to_offset(&self);
|
||||
let end = range.end.to_offset(&self);
|
||||
|
||||
let mut result = Vec::new();
|
||||
let mut cursor = self.excerpts.cursor::<usize>(&());
|
||||
cursor.seek(&start, Bias::Right, &());
|
||||
if cursor.item().is_none() {
|
||||
cursor.prev(&());
|
||||
}
|
||||
|
||||
while let Some(excerpt) = cursor.item() {
|
||||
if *cursor.start() > end {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut end_before_newline = cursor.end(&());
|
||||
if excerpt.has_trailing_newline {
|
||||
end_before_newline -= 1;
|
||||
}
|
||||
let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
|
||||
let start = excerpt_start + (cmp::max(start, *cursor.start()) - *cursor.start());
|
||||
let end = excerpt_start + (cmp::min(end, end_before_newline) - *cursor.start());
|
||||
result.push((excerpt.buffer.clone(), start..end, excerpt.id));
|
||||
cursor.next(&());
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn surrounding_word<T: ToOffset>(
|
||||
&self,
|
||||
start: T,
|
||||
@@ -4080,6 +4147,17 @@ impl MultiBufferSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buffer_range_for_excerpt(&self, excerpt_id: ExcerptId) -> Option<Range<text::Anchor>> {
|
||||
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&());
|
||||
let locator = self.excerpt_locator_for_id(excerpt_id);
|
||||
if cursor.seek(&Some(locator), Bias::Left, &()) {
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
return Some(excerpt.range.context.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn excerpt(&self, excerpt_id: ExcerptId) -> Option<&Excerpt> {
|
||||
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&());
|
||||
let locator = self.excerpt_locator_for_id(excerpt_id);
|
||||
@@ -4675,6 +4753,10 @@ impl<'a> MultiBufferExcerpt<'a> {
|
||||
self.excerpt_position
|
||||
}
|
||||
|
||||
pub fn end_point(&self) -> Point {
|
||||
self.excerpt_position + self.excerpt.text_summary.lines
|
||||
}
|
||||
|
||||
/// Maps an offset within the [`MultiBuffer`] to an offset within the [`Buffer`]
|
||||
pub fn map_offset_to_buffer(&self, offset: usize) -> usize {
|
||||
self.excerpt.buffer_start_offset()
|
||||
|
||||
@@ -4257,7 +4257,7 @@ impl OutlinePanel {
|
||||
deferred(
|
||||
anchored()
|
||||
.position(*position)
|
||||
.anchor(gpui::AnchorCorner::TopLeft)
|
||||
.anchor(gpui::Corner::TopLeft)
|
||||
.child(menu.clone()),
|
||||
)
|
||||
.with_priority(1)
|
||||
|
||||
@@ -4102,7 +4102,7 @@ impl Render for ProjectPanel {
|
||||
deferred(
|
||||
anchored()
|
||||
.position(*position)
|
||||
.anchor(gpui::AnchorCorner::TopLeft)
|
||||
.anchor(gpui::Corner::TopLeft)
|
||||
.child(menu.clone()),
|
||||
)
|
||||
.with_priority(1)
|
||||
|
||||
@@ -43,6 +43,7 @@ serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
telemetry.workspace = true
|
||||
terminal.workspace = true
|
||||
terminal_view.workspace = true
|
||||
theme.workspace = true
|
||||
|
||||
@@ -253,7 +253,7 @@ impl<T: PopoverTrigger> RenderOnce for KernelSelector<T> {
|
||||
PopoverMenu::new("kernel-switcher")
|
||||
.menu(move |_cx| Some(picker_view.clone()))
|
||||
.trigger(self.trigger)
|
||||
.attach(gpui::AnchorCorner::BottomLeft)
|
||||
.attach(gpui::Corner::BottomLeft)
|
||||
.when_some(self.handle, |menu, handle| menu.with_handle(handle))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,16 +24,15 @@ pub use crate::repl_sessions_ui::{
|
||||
};
|
||||
use crate::repl_store::ReplStore;
|
||||
pub use crate::session::Session;
|
||||
use client::telemetry::Telemetry;
|
||||
|
||||
pub const KERNEL_DOCS_URL: &str = "https://zed.dev/docs/repl#changing-kernels";
|
||||
|
||||
pub fn init(fs: Arc<dyn Fs>, telemetry: Arc<Telemetry>, cx: &mut AppContext) {
|
||||
pub fn init(fs: Arc<dyn Fs>, cx: &mut AppContext) {
|
||||
set_dispatcher(zed_dispatcher(cx));
|
||||
JupyterSettings::register(cx);
|
||||
::editor::init_settings(cx);
|
||||
repl_sessions_ui::init(cx);
|
||||
ReplStore::init(fs, telemetry, cx);
|
||||
ReplStore::init(fs, cx);
|
||||
}
|
||||
|
||||
fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {
|
||||
|
||||
@@ -33,7 +33,6 @@ pub fn assign_kernelspec(
|
||||
});
|
||||
|
||||
let fs = store.read(cx).fs().clone();
|
||||
let telemetry = store.read(cx).telemetry().clone();
|
||||
|
||||
if let Some(session) = store.read(cx).get_session(weak_editor.entity_id()).cloned() {
|
||||
// Drop previous session, start new one
|
||||
@@ -44,8 +43,7 @@ pub fn assign_kernelspec(
|
||||
});
|
||||
}
|
||||
|
||||
let session = cx
|
||||
.new_view(|cx| Session::new(weak_editor.clone(), fs, telemetry, kernel_specification, cx));
|
||||
let session = cx.new_view(|cx| Session::new(weak_editor.clone(), fs, kernel_specification, cx));
|
||||
|
||||
weak_editor
|
||||
.update(cx, |_editor, cx| {
|
||||
@@ -105,15 +103,13 @@ pub fn run(editor: WeakView<Editor>, move_down: bool, cx: &mut WindowContext) ->
|
||||
.ok_or_else(|| anyhow::anyhow!("No kernel found for language: {}", language.name()))?;
|
||||
|
||||
let fs = store.read(cx).fs().clone();
|
||||
let telemetry = store.read(cx).telemetry().clone();
|
||||
|
||||
let session = if let Some(session) = store.read(cx).get_session(editor.entity_id()).cloned()
|
||||
{
|
||||
session
|
||||
} else {
|
||||
let weak_editor = editor.downgrade();
|
||||
let session = cx
|
||||
.new_view(|cx| Session::new(weak_editor, fs, telemetry, kernel_specification, cx));
|
||||
let session = cx.new_view(|cx| Session::new(weak_editor, fs, kernel_specification, cx));
|
||||
|
||||
editor.update(cx, |_editor, cx| {
|
||||
cx.notify();
|
||||
|
||||