Merge main into branch

This commit is contained in:
Mikayla Maki 2022-08-02 09:25:52 -07:00
commit 7111576986
100 changed files with 2312 additions and 1155 deletions

3
Cargo.lock generated
View file

@ -2761,6 +2761,7 @@ dependencies = [
"rpc",
"serde",
"serde_json",
"settings",
"similar",
"smallvec",
"smol",
@ -6993,7 +6994,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.48.1"
version = "0.49.1"
dependencies = [
"activity_indicator",
"anyhow",

View file

@ -1,3 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 6L10.5 6V7.5L6 12L1.5 7.5V6H4.5V0L7.5 3.57746e-08V6Z" fill="white"/>
<path d="M10.9007 7.45347L6.60649 11.7477C6.44009 11.9168 6.22001 12 5.99993 12C5.77985 12 5.56031 11.9161 5.39283 11.7484L1.09859 7.45414C0.763098 7.11865 0.763098 6.57516 1.09859 6.23967C1.43407 5.90419 1.97756 5.90419 2.31305 6.23967L5.14108 9.06904V0.834694C5.14108 0.359912 5.52568 0 5.97577 0C6.42587 0 6.85878 0.359912 6.85878 0.834694V9.06891L9.68761 6.24008C10.0231 5.90459 10.5666 5.90459 10.9021 6.24008C11.2376 6.57556 11.2362 7.11771 10.9007 7.4532V7.45347Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 186 B

After

Width:  |  Height:  |  Size: 590 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.75 8L13.25 8V9.75L8 15L2.75 9.75V8H6.25V1L9.75 1V8Z" fill="white"/>
<path d="M13.0538 9.45347L8.75952 13.7477C8.59312 13.9168 8.37304 14 8.15296 14C7.93288 14 7.71334 13.9161 7.54586 13.7484L3.25162 9.45414C2.91613 9.11865 2.91613 8.57516 3.25162 8.23967C3.5871 7.90419 4.13059 7.90419 4.46608 8.23967L7.29411 11.069V2.83469C7.29411 2.35991 7.67871 2 8.1288 2C8.5789 2 9.01181 2.35991 9.01181 2.83469V11.0689L11.8406 8.24008C12.1761 7.90459 12.7196 7.90459 13.0551 8.24008C13.3906 8.57556 13.3893 9.11771 13.0538 9.4532V9.45347Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 183 B

After

Width:  |  Height:  |  Size: 580 B

View file

@ -1,3 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 4L7 4V5L4 8L1 5V4H3V0L5 2.38498e-08V4Z" fill="white"/>
<path d="M7.26716 4.96898L4.40433 7.83181C4.2934 7.94453 4.14668 8 3.99996 8C3.85324 8 3.70688 7.94409 3.59523 7.83226L0.732395 4.96943C0.508737 4.74577 0.508737 4.38344 0.732395 4.15978C0.956054 3.93612 1.31838 3.93612 1.54204 4.15978L3.42739 6.04603V0.556463C3.42739 0.239941 3.68379 0 3.98385 0C4.28392 0 4.57252 0.239941 4.57252 0.556463V6.04594L6.45841 4.16005C6.68207 3.93639 7.0444 3.93639 7.26806 4.16005C7.49172 4.38371 7.49082 4.74514 7.26716 4.9688V4.96898Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 166 B

After

Width:  |  Height:  |  Size: 584 B

View file

@ -1,3 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 7.5L6 10.5H4.5L0 6L4.5 1.5L6 1.5V4.5L12 4.5V7.5L6 7.5Z" fill="white"/>
<path d="M12 6.00001C12 6.47507 11.6403 6.85889 11.1653 6.85889H2.93347L5.7624 9.68781C6.0979 10.0233 6.0979 10.5668 5.7624 10.9023C5.59331 11.0701 5.37322 11.1533 5.15313 11.1533C4.93305 11.1533 4.71349 11.0694 4.54601 10.9017L0.251624 6.60726C-0.0838748 6.27176 -0.0838748 5.72825 0.251624 5.39275L4.54601 1.09837C4.88151 0.762866 5.42502 0.762866 5.76052 1.09837C6.09602 1.43386 6.09602 1.97737 5.76052 2.31287L2.93347 5.14113H11.1653C11.6403 5.14113 12 5.52494 12 6.00001Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 186 B

After

Width:  |  Height:  |  Size: 596 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 9.75L8 13.25H6.25L1 8L6.25 2.75L8 2.75V6.25L15 6.25V9.75L8 9.75Z" fill="white"/>
<path d="M14 8.15327C14 8.62833 13.6403 9.01214 13.1653 9.01214H4.93347L7.7624 11.8411C8.0979 12.1766 8.0979 12.7201 7.7624 13.0556C7.59331 13.2233 7.37322 13.3065 7.15313 13.3065C6.93305 13.3065 6.71349 13.2227 6.54601 13.0549L2.25162 8.76052C1.91613 8.42502 1.91613 7.88151 2.25162 7.54601L6.54601 3.25162C6.88151 2.91613 7.42502 2.91613 7.76052 3.25162C8.09602 3.58712 8.09602 4.13063 7.76052 4.46613L4.93347 7.29439H13.1653C13.6403 7.29439 14 7.6782 14 8.15327Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 196 B

After

Width:  |  Height:  |  Size: 585 B

View file

@ -1,3 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 5L4 7H3L0 4L3 1L4 1V3L8 3V5L4 5Z" fill="white"/>
<path d="M8 4.00003C8 4.31674 7.76023 4.57261 7.44352 4.57261H1.95565L3.8416 6.45856C4.06527 6.68223 4.06527 7.04457 3.8416 7.26823C3.72887 7.38007 3.58215 7.43554 3.43542 7.43554C3.2887 7.43554 3.14233 7.37962 3.03068 7.26779L0.16775 4.40486C-0.0559165 4.1812 -0.0559165 3.81886 0.16775 3.59519L3.03068 0.732264C3.25434 0.508598 3.61668 0.508598 3.84035 0.732264C4.06401 0.95593 4.06401 1.31827 3.84035 1.54194L1.95565 3.42744H7.44352C7.76023 3.42744 8 3.68331 8 4.00003Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 160 B

After

Width:  |  Height:  |  Size: 588 B

View file

@ -1,3 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 4.5L6 1.5L7.5 1.5L12 6L7.5 10.5H6V7.5H9.53595e-08L0 4.5L6 4.5Z" fill="white"/>
<path d="M11.749 6.60576L7.46298 10.8917C7.2969 11.0605 7.07724 11.1436 6.85758 11.1436C6.63793 11.1436 6.41881 11.0598 6.25165 10.8924C5.91681 10.5576 5.91681 10.0151 6.25165 9.68029L9.07558 6.85756H0.857198C0.383864 6.85756 0 6.4745 0 6.00036C0 5.52623 0.383596 5.14317 0.85693 5.14317H9.07531L6.25192 2.31977C5.91708 1.98493 5.91708 1.44248 6.25192 1.10764C6.58676 0.772796 7.12921 0.772796 7.46405 1.10764L11.75 5.39363C12.0835 5.72981 12.0835 6.27092 11.7487 6.60576H11.749Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 194 B

After

Width:  |  Height:  |  Size: 599 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 6.25L8 2.75L9.75 2.75L15 8L9.75 13.25H8V9.75H1L1 6.25L8 6.25Z" fill="white"/>
<path d="M13.749 8.74925L9.46298 13.0352C9.2969 13.204 9.07724 13.287 8.85758 13.287C8.63793 13.287 8.41881 13.2033 8.25165 13.0359C7.91681 12.7011 7.91681 12.1586 8.25165 11.8238L11.0756 9.00106H2.8572C2.38386 9.00106 2 8.61799 2 8.14386C2 7.66972 2.3836 7.28666 2.85693 7.28666H11.0753L8.25192 4.46326C7.91708 4.12842 7.91708 3.58598 8.25192 3.25113C8.58676 2.91629 9.12921 2.91629 9.46405 3.25113L13.75 7.53712C14.0835 7.8733 14.0835 8.41441 13.7487 8.74925H13.749Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 193 B

After

Width:  |  Height:  |  Size: 588 B

View file

@ -1,3 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 3L4 1L5 1L8 4L5 7H4V5H6.3573e-08L0 3L4 3Z" fill="white"/>
<path d="M7.83265 4.40382L4.97532 7.26115C4.8646 7.37365 4.71816 7.42901 4.57172 7.42901C4.42528 7.42901 4.2792 7.37321 4.16777 7.26159C3.94454 7.03836 3.94454 6.67673 4.16777 6.4535L6.05039 4.57169H0.571465C0.255909 4.57169 0 4.31631 0 4.00022C0 3.68413 0.255731 3.42876 0.571287 3.42876H6.05021L4.16795 1.54649C3.94472 1.32326 3.94472 0.961634 4.16795 0.738405C4.39117 0.515177 4.75281 0.515177 4.97603 0.738405L7.83336 3.59573C8.0557 3.81985 8.0557 4.18059 7.83247 4.40382H7.83265Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 169 B

After

Width:  |  Height:  |  Size: 600 B

View file

@ -1,3 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.5 6L1.5 6L1.5 4.5L6 0L10.5 4.5V6L7.5 6V12H4.5L4.5 6Z" fill="white"/>
<path d="M10.9009 5.7604C10.7345 5.92948 10.5144 6.01268 10.2943 6.01268C10.0742 6.01268 9.85471 5.92881 9.68724 5.76107L6.85903 2.93408V11.1653C6.85903 11.6401 6.47444 12 6.02437 12C5.57429 12 5.14139 11.6404 5.14139 11.1653V2.93408L2.31346 5.76013C1.97798 6.09561 1.43451 6.09561 1.09903 5.76013C0.763558 5.42466 0.763558 4.88119 1.09903 4.54571L5.39314 0.251607C5.72861 -0.0838692 6.27209 -0.0838692 6.60756 0.251607L10.9017 4.54571C11.2363 4.88253 11.2363 5.42466 10.9009 5.76013V5.7604Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 184 B

After

Width:  |  Height:  |  Size: 611 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.25 8L2.75 8L2.75 6.25L8 1L13.25 6.25V8L9.75 8V15H6.25L6.25 8Z" fill="white"/>
<path d="M13.0534 7.7604C12.887 7.92948 12.667 8.01268 12.4469 8.01268C12.2268 8.01268 12.0073 7.92881 11.8398 7.76107L9.01161 4.93408V13.1653C9.01161 13.6401 8.62702 14 8.17694 14C7.72687 14 7.29397 13.6404 7.29397 13.1653V4.93408L4.46603 7.76013C4.13056 8.09561 3.58708 8.09561 3.25161 7.76013C2.91613 7.42466 2.91613 6.88119 3.25161 6.54571L7.54571 2.25161C7.88119 1.91613 8.42466 1.91613 8.76013 2.25161L13.0542 6.54571C13.3889 6.88253 13.3889 7.42466 13.0534 7.76013V7.7604Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 193 B

After

Width:  |  Height:  |  Size: 599 B

View file

@ -1,3 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 4L1 4L1 3L4 0L7 3V4L5 4V8H3L3 4Z" fill="white"/>
<path d="M7.26724 3.84027C7.15631 3.95299 7.0096 4.00845 6.86288 4.00845C6.71617 4.00845 6.56981 3.95254 6.45816 3.84072L4.57269 1.95605V7.44356C4.57269 7.76007 4.3163 8 4.01625 8C3.7162 8 3.4276 7.76025 3.4276 7.44356V1.95605L1.54231 3.84009C1.31866 4.06374 0.956346 4.06374 0.732695 3.84009C0.509044 3.61644 0.509044 3.25412 0.732695 3.03047L3.59543 0.167738C3.81908 -0.0559128 4.1814 -0.0559128 4.40505 0.167738L7.26778 3.03047C7.49089 3.25502 7.49089 3.61644 7.26724 3.84009V3.84027Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 160 B

After

Width:  |  Height:  |  Size: 603 B

View file

@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 8.66661C12 9.40202 11.4021 9.99992 10.6667 9.99992H4.27722C3.92306 9.99992 3.58348 9.86034 3.33348 9.61034L0.195413 6.47082C0.0702071 6.34582 0 6.17707 0 5.99999C0 5.82291 0.0702071 5.65416 0.195205 5.52917L3.33328 2.39068C3.58327 2.14048 3.92285 2.00006 4.27701 2.00006H10.6665C11.4019 2.00006 11.9998 2.59693 11.9998 3.33337V8.66661H12ZM5.64594 5.00209L6.62718 5.99999L5.64594 6.97914C5.4522 7.17497 5.4522 7.49163 5.64594 7.66871C5.84177 7.88121 6.15843 7.88121 6.33552 7.66871L7.33341 6.70623L8.31256 7.66871C8.50839 7.88121 8.82506 7.88121 9.00214 7.66871C9.21463 7.49163 9.21463 7.17497 9.00214 6.97914L8.03965 5.99999L9.00214 5.00209C9.21463 4.82501 9.21463 4.50835 9.00214 4.31252C8.82506 4.11877 8.50839 4.11877 8.31256 4.31252L7.33341 5.29375L6.33552 4.31252C6.15843 4.11877 5.84177 4.11877 5.64594 4.31252C5.4522 4.50835 5.4522 4.82501 5.64594 5.00209Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 996 B

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 11.1111C15 11.969 14.3024 12.6666 13.4445 12.6666H5.99009C5.5769 12.6666 5.18073 12.5038 4.88906 12.2121L1.22798 8.54932C1.08191 8.40349 1 8.20661 1 8.00002C1 7.79343 1.08191 7.59656 1.22774 7.45072L4.88882 3.78916C5.18048 3.49725 5.57666 3.33344 5.98984 3.33344H13.4442C14.3022 3.33344 14.9998 4.02978 14.9998 4.88896V11.1111H15ZM7.58693 6.8358L8.73171 8.00002L7.58693 9.14236C7.3609 9.37083 7.3609 9.74027 7.58693 9.94686C7.8154 10.1948 8.18484 10.1948 8.39143 9.94686L9.55565 8.82396L10.698 9.94686C10.9265 10.1948 11.2959 10.1948 11.5025 9.94686C11.7504 9.74027 11.7504 9.37083 11.5025 9.14236L10.3796 8.00002L11.5025 6.8358C11.7504 6.62921 11.7504 6.25977 11.5025 6.0313C11.2959 5.80527 10.9265 5.80527 10.698 6.0313L9.55565 7.17608L8.39143 6.0313C8.18484 5.80527 7.8154 5.80527 7.58693 6.0313C7.3609 6.25977 7.3609 6.62921 7.58693 6.8358Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 977 B

View file

@ -0,0 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 5.77774C8 6.26801 7.6014 6.66661 7.11113 6.66661H2.85148C2.61537 6.66661 2.38899 6.57356 2.22232 6.4069L0.130276 4.31388C0.0468047 4.23055 0 4.11805 0 3.99999C0 3.88194 0.0468047 3.76944 0.130137 3.68611L2.22218 1.59379C2.38885 1.42698 2.61523 1.33337 2.85134 1.33337H7.11099C7.60126 1.33337 7.99986 1.73128 7.99986 2.22225V5.77774H8ZM3.76396 3.33473L4.41812 3.99999L3.76396 4.65276C3.6348 4.78331 3.6348 4.99442 3.76396 5.11247C3.89452 5.25414 4.10562 5.25414 4.22368 5.11247L4.88894 4.47082L5.54171 5.11247C5.67226 5.25414 5.88337 5.25414 6.00142 5.11247C6.14309 4.99442 6.14309 4.78331 6.00142 4.65276L5.35977 3.99999L6.00142 3.33473C6.14309 3.21667 6.14309 3.00557 6.00142 2.87501C5.88337 2.74585 5.67226 2.74585 5.54171 2.87501L4.88894 3.52917L4.22368 2.87501C4.10562 2.74585 3.89452 2.74585 3.76396 2.87501C3.6348 3.00557 3.6348 3.21667 3.76396 3.33473Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 987 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.33035 7.12505H12.3791C12.7427 7.12505 13.0708 7.352 13.1747 7.69105C13.3251 8.03283 13.2294 8.41836 12.956 8.65898L5.95626 14.7837C5.64728 15.0517 5.19531 15.0736 4.86501 14.833C4.53443 14.5923 4.41413 14.1549 4.57517 13.7803L6.6781 8.87499H3.60478C3.26491 8.87499 2.93844 8.64804 2.8102 8.30899C2.68199 7.96721 2.77876 7.58168 3.05317 7.34106L10.0521 1.21657C10.3611 0.947515 10.8122 0.927008 11.1431 1.16708C11.4739 1.40731 11.5942 1.84381 11.4329 2.21966L9.33024 7.12467L9.33035 7.12505Z" fill="white"/>
<path d="M9.33035 7.12505H12.3791C12.7427 7.12505 13.0708 7.352 13.1747 7.69105C13.3251 8.03283 13.2294 8.41836 12.956 8.65898L5.95625 14.7837C5.64728 15.0517 5.19531 15.0736 4.86501 14.833C4.53443 14.5923 4.41413 14.1549 4.57517 13.7803L6.6781 8.87499H3.60478C3.26491 8.87499 2.93844 8.64804 2.8102 8.30899C2.68199 7.96721 2.77876 7.58168 3.05317 7.34106L10.0521 1.21657C10.3611 0.947515 10.8122 0.927008 11.1431 1.16708C11.4739 1.40731 11.5942 1.84381 11.4329 2.21966L9.33024 7.12467L9.33035 7.12505Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 622 B

After

Width:  |  Height:  |  Size: 622 B

View file

@ -1,3 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 3.33519L4.38149 10.9537L0 6.57221L2.28892 4.28329L4.38149 6.37586L9.71108 1.04626L12 3.33519Z" fill="white"/>
<path d="M11.7489 1.96607C12.0837 2.3009 12.0837 2.842 11.7489 3.17684L4.89143 10.0343C4.55659 10.3691 4.01549 10.3691 3.68066 10.0343L0.251131 6.60556C-0.0837057 6.27072 -0.0837057 5.72963 0.251104 5.39479C0.585887 5.05995 1.12859 5.05995 1.46343 5.39479L4.26185 8.21545L10.538 1.96607C10.8729 1.6307 11.414 1.6307 11.7488 1.96607H11.7489Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 266 B

After

Width:  |  Height:  |  Size: 460 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 4.89105L6.11174 13.7793L1 8.66758L3.67041 5.99717L6.11174 8.4385L12.3296 2.22064L15 4.89105Z" fill="white"/>
<path d="M13.7489 4.25153C14.0837 4.58637 14.0837 5.12746 13.7489 5.4623L6.89143 12.3197C6.55659 12.6546 6.01549 12.6546 5.68066 12.3197L2.25113 8.89102C1.91629 8.55618 1.91629 8.01509 2.2511 7.68025C2.58589 7.34542 3.12859 7.34542 3.46343 7.68025L6.26185 10.5009L12.538 4.25153C12.8729 3.91616 13.414 3.91616 13.7488 4.25153H13.7489Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 265 B

After

Width:  |  Height:  |  Size: 454 B

View file

@ -1,3 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 2.22346L2.92099 7.30247L0 4.38148L1.52595 2.85553L2.92099 4.25057L6.47405 0.69751L8 2.22346Z" fill="white"/>
<path d="M7.83258 1.31075C8.05581 1.53398 8.05581 1.89471 7.83258 2.11793L3.26095 6.68956C3.03773 6.91279 2.677 6.91279 2.45377 6.68956L0.16742 4.40375C-0.0558038 4.18052 -0.0558038 3.81979 0.167403 3.59657C0.390591 3.37334 0.752393 3.37334 0.975617 3.59657L2.84124 5.47701L7.02535 1.31075C7.24857 1.08717 7.6093 1.08717 7.83253 1.31075H7.83258Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 260 B

After

Width:  |  Height:  |  Size: 461 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.99982 15C4.74388 15 4.48794 14.9023 4.29294 14.707C3.90231 14.3164 3.90231 13.6836 4.29294 13.293L9.58738 7.99992L4.29294 2.70705C3.90231 2.31642 3.90231 1.6836 4.29294 1.29297C4.68357 0.902343 5.31639 0.902343 5.70702 1.29297L11.7071 7.29304C12.0977 7.68367 12.0977 8.31649 11.7071 8.70712L5.70702 14.7072C5.51233 14.9031 5.25608 15 4.99982 15Z" fill="white"/>
<path d="M4.99982 15C4.74388 15 4.48794 14.9023 4.29294 14.707C3.90231 14.3164 3.90231 13.6836 4.29294 13.2929L9.58738 7.99992L4.29294 2.70705C3.90231 2.31642 3.90231 1.6836 4.29294 1.29297C4.68357 0.902343 5.31639 0.902343 5.70702 1.29297L11.7071 7.29304C12.0977 7.68367 12.0977 8.31649 11.7071 8.70712L5.70702 14.7072C5.51233 14.9031 5.25608 15 4.99982 15Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 477 B

After

Width:  |  Height:  |  Size: 478 B

View file

@ -1,3 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.28561 8C2.13936 8 1.99311 7.9442 1.88168 7.83259C1.65846 7.60937 1.65846 7.24776 1.88168 7.02454L4.90707 3.99996L1.88168 0.975457C1.65846 0.752241 1.65846 0.390629 1.88168 0.167413C2.10489 -0.0558042 2.4665 -0.0558042 2.68972 0.167413L6.11833 3.59602C6.34155 3.81924 6.34155 4.18085 6.11833 4.40407L2.68972 7.83268C2.57847 7.94464 2.43204 8 2.28561 8Z" fill="white"/>
<path d="M2.28561 8C2.13936 8 1.99311 7.9442 1.88168 7.83259C1.65846 7.60937 1.65846 7.24776 1.88168 7.02454L4.90707 3.99996L1.88168 0.975457C1.65846 0.75224 1.65846 0.390629 1.88168 0.167413C2.10489 -0.0558042 2.4665 -0.0558042 2.68972 0.167413L6.11833 3.59602C6.34155 3.81924 6.34155 4.18085 6.11833 4.40407L2.68972 7.83268C2.57847 7.94464 2.43204 8 2.28561 8Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 479 B

After

Width:  |  Height:  |  Size: 478 B

View file

@ -1,3 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.59892 2.76222C3.14641 2.17067 3.93013 1.80018 4.80011 1.80018C5.91195 1.80018 6.8813 2.40485 7.40066 3.30389C7.68565 3.09577 8.03064 3.00015 8.4 3.00015C9.39372 3.00015 10.1999 3.7895 10.1999 4.80009C10.1999 5.02884 10.1568 5.24633 10.08 5.44882C11.1749 5.67007 11.9999 6.63941 11.9999 7.80001C11.9999 8.48623 11.7112 9.10497 11.233 9.52683L11.8274 9.99557C12.0224 10.1493 12.058 10.4324 11.9043 10.6274C11.7505 10.8224 11.4674 10.858 11.2724 10.7043L0.172556 2.00436C-0.0231882 1.85099 -0.0574997 1.56825 0.0958708 1.37251C0.249241 1.17676 0.531795 1.14245 0.727671 1.29582L2.59868 2.76184L2.59892 2.76222ZM1.82213 4.43635L9.13873 10.1999H2.70017C1.20903 10.1999 0.000248596 8.99059 0.000248596 7.50001C0.000248596 6.32255 0.753414 5.32133 1.80395 4.95196C1.80151 4.90134 1.8002 4.85072 1.8002 4.80009C1.8002 4.67635 1.8077 4.55448 1.82213 4.43635Z" fill="white"/>
<path d="M2.59892 2.76222C3.14641 2.17067 3.93013 1.80018 4.80011 1.80018C5.91195 1.80018 6.8813 2.40485 7.40066 3.30389C7.68565 3.09577 8.03064 3.00015 8.4 3.00015C9.39372 3.00015 10.1999 3.7895 10.1999 4.80009C10.1999 5.02884 10.1568 5.24633 10.08 5.44882C11.1749 5.67007 11.9999 6.63941 11.9999 7.8C11.9999 8.48623 11.7112 9.10497 11.233 9.52683L11.8274 9.99557C12.0224 10.1493 12.058 10.4324 11.9043 10.6274C11.7505 10.8224 11.4674 10.858 11.2724 10.7043L0.172556 2.00436C-0.0231882 1.85099 -0.0574997 1.56825 0.0958708 1.37251C0.249241 1.17676 0.531796 1.14245 0.727671 1.29582L2.59868 2.76184L2.59892 2.76222ZM1.82213 4.43635L9.13873 10.1999H2.70017C1.20903 10.1999 0.000248596 8.99059 0.000248596 7.50001C0.000248596 6.32255 0.753414 5.32133 1.80395 4.95196C1.80151 4.90134 1.8002 4.85072 1.8002 4.80009C1.8002 4.67635 1.8077 4.55448 1.82213 4.43635Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 981 B

After

Width:  |  Height:  |  Size: 977 B

View file

@ -1,3 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.73261 1.8415C2.0976 1.44713 2.62009 1.20014 3.20007 1.20014C3.9413 1.20014 4.58753 1.60325 4.93377 2.20261C5.12376 2.06387 5.35376 2.00012 5.6 2.00012C6.26248 2.00012 6.79997 2.52635 6.79997 3.20008C6.79997 3.35258 6.77122 3.49757 6.71997 3.63257C7.44995 3.78007 7.99993 4.4263 7.99993 5.20002C7.99993 5.65751 7.80744 6.07 7.48869 6.35124L7.88493 6.66373C8.01493 6.76623 8.03868 6.95497 7.93618 7.08497C7.83368 7.21496 7.64494 7.23871 7.51494 7.13622L0.115037 1.33626C-0.0154588 1.23402 -0.0383331 1.04552 0.0639139 0.915025C0.166161 0.784529 0.35453 0.761655 0.485114 0.863902L1.73245 1.84125L1.73261 1.8415ZM1.21475 2.95759L6.09249 6.79998H1.80011C0.806017 6.79998 0.000165731 5.99375 0.000165731 5.00003C0.000165731 4.21505 0.502276 3.54757 1.20263 3.30133C1.20101 3.26758 1.20013 3.23383 1.20013 3.20008C1.20013 3.11759 1.20513 3.03634 1.21475 2.95759Z" fill="white"/>
<path d="M1.73261 1.8415C2.0976 1.44713 2.62009 1.20014 3.20007 1.20014C3.9413 1.20014 4.58753 1.60325 4.93377 2.20261C5.12376 2.06387 5.35376 2.00012 5.6 2.00012C6.26248 2.00012 6.79996 2.52635 6.79996 3.20008C6.79996 3.35258 6.77122 3.49757 6.71997 3.63257C7.44995 3.78007 7.99993 4.4263 7.99993 5.20002C7.99993 5.65751 7.80744 6.07 7.48869 6.35124L7.88493 6.66373C8.01493 6.76623 8.03868 6.95497 7.93618 7.08497C7.83368 7.21496 7.64494 7.23871 7.51494 7.13622L0.115037 1.33626C-0.0154588 1.23402 -0.0383331 1.04552 0.0639139 0.915025C0.166161 0.784529 0.35453 0.761655 0.485114 0.863902L1.73245 1.84125L1.73261 1.8415ZM1.21475 2.95759L6.09249 6.79998H1.80011C0.806017 6.79998 0.000165731 5.99375 0.000165731 5.00003C0.000165731 4.21505 0.502276 3.54757 1.20263 3.30133C1.20101 3.26758 1.20013 3.23383 1.20013 3.20008C1.20013 3.11759 1.20513 3.03634 1.21475 2.95759Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 984 B

After

Width:  |  Height:  |  Size: 984 B

View file

@ -1,3 +1,10 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.5 4.5L7 4.5V0H5V4.5H2.5V5.625L6 9L9.5 5.625V4.5ZM10 10H2V12H10V10Z" fill="white"/>
<g clip-path="url(#clip0_430_1270)">
<path d="M4.30957 0.857736V4.28922L2.35703 4.28788C2.10067 4.28788 1.86816 4.44057 1.76636 4.67656C1.66511 4.91229 1.71332 5.18633 1.88959 5.37304L5.53269 9.23312C5.77565 9.49028 6.22488 9.49028 6.46784 9.23312L10.1123 5.37277C10.2875 5.18794 10.3354 4.9147 10.2342 4.6763C10.1337 4.44057 9.90066 4.28788 9.66761 4.28788H7.73891L7.73891 0.857736C7.73891 0.383865 7.35504 2.35669e-07 6.88171 2.14979e-07L5.16731 1.4004e-07C4.66906 -0.000267757 4.30957 0.383865 4.30957 0.857736ZM11.1433 11.1187C11.1434 10.6687 10.7595 10.2856 10.2861 10.2856H1.71413C1.23972 10.2856 0.856659 10.6687 0.856659 11.1187C0.856659 11.6169 1.23972 12 1.71386 12H10.2861C10.7595 12 11.1433 11.6169 11.1433 11.1187Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_430_1270">
<rect width="12" height="12" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 238 B

After

Width:  |  Height:  |  Size: 954 B

View file

@ -1,3 +1,10 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.33333 3L4.66666 3V0H3.33333V3H1.66666V3.75L3.99999 6L6.33333 3.75V3ZM6.66666 6.66667H1.33333V8H6.66666V6.66667Z" fill="white"/>
<g clip-path="url(#clip0_430_1353)">
<path d="M2.87305 0.571824L2.87305 2.85948L1.57136 2.85858C1.40045 2.85858 1.24544 2.96038 1.17758 3.11771C1.11008 3.27486 1.14222 3.45755 1.25973 3.58203L3.68846 6.15541C3.85044 6.32685 4.14992 6.32685 4.3119 6.15541L6.74153 3.58185C6.85832 3.45862 6.89029 3.27647 6.82278 3.11753C6.75581 2.96038 6.60045 2.85858 6.44508 2.85858L5.15928 2.85858L5.15928 0.571824C5.15928 0.25591 4.90337 -1.70497e-08 4.58781 -3.08431e-08L3.44488 -8.08023e-08C3.11271 -0.000178679 2.87341 0.25591 2.87341 0.571824L2.87305 0.571824ZM7.42889 7.41246C7.42891 7.11244 7.17298 6.85707 6.85743 6.85707L1.14276 6.85707C0.826487 6.85707 0.571113 7.11244 0.571113 7.41246C0.571113 7.74463 0.826487 8 1.14258 8L6.85743 8C7.17298 8 7.42889 7.74463 7.42889 7.41246V7.41246Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_430_1353">
<rect width="8" height="8" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 279 B

After

Width:  |  Height:  |  Size: 1,001 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.2222 2.55555H11.5L10.7222 1.77777H8.77778C8.34951 1.77777 8 2.12728 8 2.55555V6.44444C8 6.8727 8.34951 7.22222 8.77778 7.22222H14.2222C14.6505 7.22222 15 6.8727 15 6.44444V3.33333C15 2.90506 14.65 2.55555 14.2222 2.55555ZM14.2222 9.55555H11.5L10.7222 8.77777H8.77778C8.34951 8.77777 8 9.12728 8 9.55555V13.4444C8 13.8727 8.34951 14.2222 8.77778 14.2222H14.2222C14.6505 14.2222 15 13.8727 15 13.4444V10.3333C15 9.90555 14.65 9.55555 14.2222 9.55555ZM2.55556 2.16666C2.55556 1.95095 2.3825 1.77777 2.16667 1.77777H1.38889C1.17318 1.77777 1 1.95083 1 2.16666V11.8889C1 12.3171 1.34951 12.6667 1.77778 12.6667H7.22222V11.1111H2.55556V5.66666H7.22222V4.1111H2.55556V2.16666Z" fill="white"/>
<path d="M14.2222 2.55555H11.5L10.7222 1.77777H8.77778C8.34951 1.77777 8 2.12728 8 2.55555V6.44444C8 6.8727 8.34951 7.22222 8.77778 7.22222H14.2222C14.6505 7.22222 15 6.8727 15 6.44444V3.33333C15 2.90506 14.65 2.55555 14.2222 2.55555ZM14.2222 9.55555H11.5L10.7222 8.77777H8.77778C8.34951 8.77777 8 9.12729 8 9.55555V13.4444C8 13.8727 8.34951 14.2222 8.77778 14.2222H14.2222C14.6505 14.2222 15 13.8727 15 13.4444V10.3333C15 9.90555 14.65 9.55555 14.2222 9.55555ZM2.55556 2.16666C2.55556 1.95095 2.3825 1.77777 2.16667 1.77777H1.38889C1.17318 1.77777 1 1.95083 1 2.16666V11.8889C1 12.3171 1.34951 12.6667 1.77778 12.6667H7.22222V11.1111H2.55556V5.66666H7.22222V4.1111H2.55556V2.16666Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 802 B

After

Width:  |  Height:  |  Size: 802 B

View file

@ -1,3 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.8167 11.0391L8.67607 7.89844C9.35576 7.06641 9.73076 6.01875 9.73076 4.875C9.73076 2.18203 7.54802 0 4.85576 0C2.16349 0 0.00161743 2.18273 0.00161743 4.875C0.00161743 7.56727 2.18412 9.75 4.85552 9.75C5.99904 9.75 7.0481 9.35367 7.87896 8.69437L11.0196 11.835C11.1508 11.9461 11.2961 12 11.4391 12C11.5821 12 11.7269 11.9449 11.8369 11.835C12.0555 11.6154 12.0555 11.2591 11.8165 11.0388L11.8167 11.0391ZM1.12685 4.875C1.12685 2.80734 2.8092 1.125 4.87685 1.125C6.94451 1.125 8.62685 2.80734 8.62685 4.875C8.62685 6.94266 6.94451 8.625 4.87685 8.625C2.8092 8.625 1.12685 6.94219 1.12685 4.875Z" fill="white"/>
<path d="M11.8167 11.0391L8.67607 7.89844C9.35576 7.06641 9.73076 6.01875 9.73076 4.875C9.73076 2.18203 7.54802 0 4.85576 0C2.16349 0 0.00161743 2.18273 0.00161743 4.875C0.00161743 7.56727 2.18412 9.75 4.85552 9.75C5.99904 9.75 7.0481 9.35367 7.87896 8.69438L11.0196 11.835C11.1508 11.9461 11.2961 12 11.4391 12C11.5821 12 11.7269 11.9449 11.8369 11.835C12.0555 11.6154 12.0555 11.2591 11.8165 11.0388L11.8167 11.0391ZM1.12685 4.875C1.12685 2.80734 2.8092 1.125 4.87685 1.125C6.94451 1.125 8.62685 2.80734 8.62685 4.875C8.62685 6.94266 6.94451 8.625 4.87685 8.625C2.8092 8.625 1.12685 6.94219 1.12685 4.875Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 727 B

After

Width:  |  Height:  |  Size: 727 B

View file

@ -1,3 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.87779 7.35938L5.78404 5.26562C6.23716 4.71094 6.48716 4.0125 6.48716 3.25C6.48716 1.45469 5.03201 0 3.23716 0C1.44232 0 0.00106812 1.45516 0.00106812 3.25C0.00106812 5.04484 1.45607 6.5 3.23701 6.5C3.99935 6.5 4.69873 6.23578 5.25263 5.79625L7.34638 7.89C7.43388 7.96406 7.53076 8 7.62607 8C7.72138 8 7.81794 7.96328 7.89123 7.89C8.03701 7.74359 8.03701 7.50609 7.87763 7.35922L7.87779 7.35938ZM0.751224 3.25C0.751224 1.87156 1.87279 0.75 3.25122 0.75C4.62966 0.75 5.75123 1.87156 5.75123 3.25C5.75123 4.62844 4.62966 5.75 3.25122 5.75C1.87279 5.75 0.751224 4.62812 0.751224 3.25Z" fill="white"/>
<path d="M7.87779 7.35938L5.78404 5.26562C6.23716 4.71094 6.48716 4.0125 6.48716 3.25C6.48716 1.45469 5.03201 0 3.23716 0C1.44232 0 0.00106812 1.45516 0.00106812 3.25C0.00106812 5.04484 1.45607 6.5 3.23701 6.5C3.99935 6.5 4.69873 6.23578 5.25263 5.79625L7.34638 7.89C7.43388 7.96406 7.53076 8 7.62607 8C7.72138 8 7.81794 7.96328 7.89123 7.89C8.03701 7.74359 8.03701 7.50609 7.87763 7.35922L7.87779 7.35938ZM0.751224 3.25C0.751224 1.87156 1.87279 0.75 3.25122 0.75C4.62966 0.75 5.75122 1.87156 5.75122 3.25C5.75122 4.62844 4.62966 5.75 3.25122 5.75C1.87279 5.75 0.751224 4.62813 0.751224 3.25Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 708 B

After

Width:  |  Height:  |  Size: 708 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 3.625C1 2.65839 1.78339 1.875 2.75 1.875H13.25C14.2152 1.875 15 2.65839 15 3.625V12.375C15 13.3402 14.2152 14.125 13.25 14.125H2.75C1.78339 14.125 1 13.3402 1 12.375V3.625ZM3.79727 5.15078C3.55254 5.41875 3.57059 5.83165 3.83828 6.07773L5.93555 8L3.83828 9.92227C3.57059 10.1684 3.55254 10.5812 3.79727 10.8492C4.04335 11.0926 4.45625 11.1336 4.72422 10.8902L7.34922 8.48398C7.48593 8.3582 7.5625 8.1832 7.5625 7.97539C7.5625 7.8168 7.48593 7.6418 7.34922 7.51602L4.72422 5.10977C4.45625 4.8664 4.04335 4.88282 3.79727 5.15078ZM7.78125 10.1875C7.41758 10.1875 7.125 10.4801 7.125 10.8437C7.125 11.2074 7.41758 11.5 7.78125 11.5H11.7187C12.0824 11.5 12.375 11.2074 12.375 10.8437C12.375 10.4801 12.0824 10.1875 11.7187 10.1875H7.78125Z" fill="white"/>
<path d="M1 3.625C1 2.65839 1.78339 1.875 2.75 1.875H13.25C14.2152 1.875 15 2.65839 15 3.625V12.375C15 13.3402 14.2152 14.125 13.25 14.125H2.75C1.78339 14.125 1 13.3402 1 12.375V3.625ZM3.79727 5.15078C3.55254 5.41875 3.57059 5.83165 3.83828 6.07773L5.93555 8L3.83828 9.92227C3.57059 10.1684 3.55254 10.5813 3.79727 10.8492C4.04335 11.0926 4.45625 11.1336 4.72422 10.8902L7.34922 8.48398C7.48593 8.3582 7.5625 8.1832 7.5625 7.97539C7.5625 7.8168 7.48593 7.6418 7.34922 7.51602L4.72422 5.10977C4.45625 4.8664 4.04335 4.88282 3.79727 5.15078ZM7.78125 10.1875C7.41758 10.1875 7.125 10.4801 7.125 10.8438C7.125 11.2074 7.41758 11.5 7.78125 11.5H11.7188C12.0824 11.5 12.375 11.2074 12.375 10.8438C12.375 10.4801 12.0824 10.1875 11.7188 10.1875H7.78125Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 866 B

After

Width:  |  Height:  |  Size: 866 B

View file

@ -1,3 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 0C1.79063 0 0 1.79063 0 4C0 6.20937 1.79063 8 4 8C6.20937 8 8 6.20937 8 4C8 1.79063 6.20937 0 4 0ZM4 2C4.62141 2 5.125 2.50375 5.125 3.125C5.125 3.74625 4.62187 4.25 4 4.25C3.37875 4.25 2.875 3.74625 2.875 3.125C2.875 2.50375 3.37813 2 4 2ZM4 7C3.17297 7 2.42344 6.66359 1.87969 6.12047C2.13281 5.46719 2.75781 5 3.5 5H4.5C5.24281 5 5.86781 5.46687 6.12031 6.12047C5.57656 6.66406 4.82656 7 4 7Z" fill="white"/>
<path d="M4 0C1.79063 0 0 1.79063 0 4C0 6.20937 1.79063 8 4 8C6.20937 8 8 6.20937 8 4C8 1.79063 6.20937 0 4 0ZM4 2C4.62141 2 5.125 2.50375 5.125 3.125C5.125 3.74625 4.62187 4.25 4 4.25C3.37875 4.25 2.875 3.74625 2.875 3.125C2.875 2.50375 3.37812 2 4 2ZM4 7C3.17297 7 2.42344 6.66359 1.87969 6.12047C2.13281 5.46719 2.75781 5 3.5 5H4.5C5.24281 5 5.86781 5.46688 6.12031 6.12047C5.57656 6.66406 4.82656 7 4 7Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 523 B

After

Width:  |  Height:  |  Size: 523 B

View file

@ -1,3 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.2 6.00001C5.52563 6.00001 6.6 4.92545 6.6 3.60001C6.6 2.27457 5.52563 1.20001 4.2 1.20001C2.87438 1.20001 1.8 2.27457 1.8 3.60001C1.8 4.92545 2.87438 6.00001 4.2 6.00001ZM5.15063 6.90001H3.24938C1.45444 6.90001 0 8.35501 0 10.1494C0 10.5094 0.291 10.8 0.649875 10.8H7.74938C8.10938 10.8 8.4 10.5094 8.4 10.1494C8.4 8.35501 6.945 6.90001 5.15063 6.90001ZM8.98313 7.20001H7.59844C8.46 7.90689 9 8.96439 9 10.1494C9 10.3894 8.92875 10.6106 8.8125 10.8H11.4C11.7319 10.8 12 10.53 12 10.1831C12 8.54251 10.6575 7.20001 8.98313 7.20001ZM8.1 6.00001C9.26063 6.00001 10.2 5.06064 10.2 3.90001C10.2 2.73939 9.26063 1.80001 8.1 1.80001C7.62919 1.80001 7.19925 1.96042 6.849 2.22207C7.065 2.63682 7.2 3.10126 7.2 3.60001C7.2 4.26601 6.97631 4.87764 6.60769 5.37582C6.98813 5.76001 7.515 6.00001 8.1 6.00001Z" fill="white"/>
<path d="M4.2 6.00001C5.52563 6.00001 6.6 4.92545 6.6 3.60001C6.6 2.27457 5.52563 1.20001 4.2 1.20001C2.87438 1.20001 1.8 2.27457 1.8 3.60001C1.8 4.92545 2.87438 6.00001 4.2 6.00001ZM5.15063 6.90001H3.24938C1.45444 6.90001 0 8.35501 0 10.1494C0 10.5094 0.291 10.8 0.649875 10.8H7.74937C8.10938 10.8 8.4 10.5094 8.4 10.1494C8.4 8.35501 6.945 6.90001 5.15063 6.90001ZM8.98313 7.20001H7.59844C8.46 7.90689 9 8.96439 9 10.1494C9 10.3894 8.92875 10.6106 8.8125 10.8H11.4C11.7319 10.8 12 10.53 12 10.1831C12 8.54251 10.6575 7.20001 8.98313 7.20001ZM8.1 6.00001C9.26063 6.00001 10.2 5.06064 10.2 3.90001C10.2 2.73939 9.26063 1.80001 8.1 1.80001C7.62919 1.80001 7.19925 1.96042 6.849 2.22207C7.065 2.63682 7.2 3.10126 7.2 3.60001C7.2 4.26601 6.97631 4.87764 6.60769 5.37582C6.98813 5.76001 7.515 6.00001 8.1 6.00001Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 928 B

After

Width:  |  Height:  |  Size: 928 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.9 8.00002C7.44656 8.00002 8.7 6.74637 8.7 5.20002C8.7 3.65368 7.44656 2.40002 5.9 2.40002C4.35344 2.40002 3.1 3.65368 3.1 5.20002C3.1 6.74637 4.35344 8.00002 5.9 8.00002ZM7.00906 9.05002H4.79094C2.69684 9.05002 1 10.7475 1 12.841C1 13.261 1.3395 13.6 1.75819 13.6H10.0409C10.4609 13.6 10.8 13.261 10.8 12.841C10.8 10.7475 9.1025 9.05002 7.00906 9.05002ZM11.4803 9.40002H9.86484C10.87 10.2247 11.5 11.4585 11.5 12.841C11.5 13.121 11.4169 13.3791 11.2812 13.6H14.3C14.6872 13.6 15 13.285 15 12.8803C15 10.9663 13.4337 9.40002 11.4803 9.40002ZM10.45 8.00002C11.8041 8.00002 12.9 6.90409 12.9 5.55002C12.9 4.19596 11.8041 3.10002 10.45 3.10002C9.90072 3.10002 9.39912 3.28716 8.9905 3.59243C9.2425 4.07631 9.4 4.61815 9.4 5.20002C9.4 5.97702 9.13903 6.69059 8.70897 7.27181C9.15281 7.72002 9.7675 8.00002 10.45 8.00002Z" fill="white"/>
<path d="M5.9 8.00002C7.44656 8.00002 8.7 6.74637 8.7 5.20002C8.7 3.65368 7.44656 2.40002 5.9 2.40002C4.35344 2.40002 3.1 3.65368 3.1 5.20002C3.1 6.74637 4.35344 8.00002 5.9 8.00002ZM7.00906 9.05002H4.79094C2.69684 9.05002 1 10.7475 1 12.841C1 13.261 1.3395 13.6 1.75819 13.6H10.0409C10.4609 13.6 10.8 13.261 10.8 12.841C10.8 10.7475 9.1025 9.05002 7.00906 9.05002ZM11.4803 9.40002H9.86484C10.87 10.2247 11.5 11.4585 11.5 12.841C11.5 13.121 11.4169 13.3791 11.2812 13.6H14.3C14.6872 13.6 15 13.285 15 12.8803C15 10.9663 13.4338 9.40002 11.4803 9.40002ZM10.45 8.00002C11.8041 8.00002 12.9 6.90409 12.9 5.55002C12.9 4.19596 11.8041 3.10002 10.45 3.10002C9.90072 3.10002 9.39913 3.28716 8.9905 3.59243C9.2425 4.07631 9.4 4.61815 9.4 5.20002C9.4 5.97702 9.13903 6.69059 8.70897 7.27181C9.15281 7.72002 9.7675 8.00002 10.45 8.00002Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 947 B

After

Width:  |  Height:  |  Size: 947 B

View file

@ -1,3 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.8 3.99999C3.68375 3.99999 4.4 3.28361 4.4 2.39999C4.4 1.51636 3.68375 0.799988 2.8 0.799988C1.91625 0.799988 1.2 1.51636 1.2 2.39999C1.2 3.28361 1.91625 3.99999 2.8 3.99999ZM3.43375 4.59999H2.16625C0.969625 4.59999 0 5.56999 0 6.76624C0 7.00624 0.194 7.19999 0.43325 7.19999H5.16625C5.40625 7.19999 5.6 7.00624 5.6 6.76624C5.6 5.56999 4.63 4.59999 3.43375 4.59999ZM5.98875 4.79999H5.06563C5.64 5.27124 6 5.97624 6 6.76624C6 6.92624 5.9525 7.07374 5.875 7.19999H7.6C7.82125 7.19999 8 7.01999 8 6.78874C8 5.69499 7.105 4.79999 5.98875 4.79999ZM5.4 3.99999C6.17375 3.99999 6.8 3.37374 6.8 2.59999C6.8 1.82624 6.17375 1.19999 5.4 1.19999C5.08613 1.19999 4.7995 1.30693 4.566 1.48136C4.71 1.75786 4.8 2.06749 4.8 2.39999C4.8 2.84399 4.65088 3.25174 4.40513 3.58386C4.65875 3.83999 5.01 3.99999 5.4 3.99999Z" fill="white"/>
<path d="M2.8 3.99999C3.68375 3.99999 4.4 3.28361 4.4 2.39999C4.4 1.51636 3.68375 0.799988 2.8 0.799988C1.91625 0.799988 1.2 1.51636 1.2 2.39999C1.2 3.28361 1.91625 3.99999 2.8 3.99999ZM3.43375 4.59999H2.16625C0.969625 4.59999 0 5.56999 0 6.76624C0 7.00624 0.194 7.19999 0.43325 7.19999H5.16625C5.40625 7.19999 5.6 7.00624 5.6 6.76624C5.6 5.56999 4.63 4.59999 3.43375 4.59999ZM5.98875 4.79999H5.06563C5.64 5.27124 6 5.97624 6 6.76624C6 6.92624 5.9525 7.07374 5.875 7.19999H7.6C7.82125 7.19999 8 7.01999 8 6.78874C8 5.69499 7.105 4.79999 5.98875 4.79999ZM5.4 3.99999C6.17375 3.99999 6.8 3.37374 6.8 2.59999C6.8 1.82624 6.17375 1.19999 5.4 1.19999C5.08612 1.19999 4.7995 1.30693 4.566 1.48136C4.71 1.75786 4.8 2.06749 4.8 2.39999C4.8 2.84399 4.65088 3.25174 4.40513 3.58386C4.65875 3.83999 5.01 3.99999 5.4 3.99999Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 929 B

After

Width:  |  Height:  |  Size: 929 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.9 8.00002C7.44656 8.00002 8.7 6.74637 8.7 5.20002C8.7 3.65368 7.44656 2.40002 5.9 2.40002C4.35344 2.40002 3.1 3.65368 3.1 5.20002C3.1 6.74637 4.35344 8.00002 5.9 8.00002ZM7.00906 9.05002H4.79094C2.69772 9.05002 1 10.7475 1 12.841C1 13.261 1.3395 13.6 1.75819 13.6H10.0422C10.4609 13.6 10.8 13.261 10.8 12.841C10.8 10.7475 9.1025 9.05002 7.00906 9.05002ZM14.475 6.77502H13.425V5.72502C13.425 5.43627 13.1909 5.20002 12.9 5.20002C12.6091 5.20002 12.375 5.43518 12.375 5.72502V6.77502H11.325C11.0362 6.77502 10.8 7.01127 10.8 7.30002C10.8 7.58877 11.0352 7.82502 11.325 7.82502H12.375V8.87502C12.375 9.16596 12.6112 9.40002 12.9 9.40002C13.1887 9.40002 13.425 9.16487 13.425 8.87502V7.82502H14.475C14.7659 7.82502 15 7.59096 15 7.30002C15 7.00909 14.7659 6.77502 14.475 6.77502Z" fill="white"/>
<path d="M5.9 8.00002C7.44656 8.00002 8.7 6.74637 8.7 5.20002C8.7 3.65368 7.44656 2.40002 5.9 2.40002C4.35344 2.40002 3.1 3.65368 3.1 5.20002C3.1 6.74637 4.35344 8.00002 5.9 8.00002ZM7.00906 9.05002H4.79094C2.69772 9.05002 1 10.7475 1 12.841C1 13.261 1.3395 13.6 1.75819 13.6H10.0422C10.4609 13.6 10.8 13.261 10.8 12.841C10.8 10.7475 9.1025 9.05002 7.00906 9.05002ZM14.475 6.77502H13.425V5.72502C13.425 5.43627 13.1909 5.20002 12.9 5.20002C12.6091 5.20002 12.375 5.43518 12.375 5.72502V6.77502H11.325C11.0363 6.77502 10.8 7.01127 10.8 7.30002C10.8 7.58877 11.0352 7.82502 11.325 7.82502H12.375V8.87502C12.375 9.16596 12.6112 9.40002 12.9 9.40002C13.1887 9.40002 13.425 9.16487 13.425 8.87502V7.82502H14.475C14.7659 7.82502 15 7.59096 15 7.30002C15 7.00909 14.7659 6.77502 14.475 6.77502Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 907 B

After

Width:  |  Height:  |  Size: 907 B

View file

@ -192,6 +192,7 @@
"shift-f8": "editor::GoToPrevDiagnostic",
"f2": "editor::Rename",
"f12": "editor::GoToDefinition",
"cmd-f12": "editor::GoToTypeDefinition",
"alt-shift-f12": "editor::FindAllReferences",
"ctrl-m": "editor::MoveToEnclosingBracket",
"alt-cmd-[": "editor::Fold",

View file

@ -14,30 +14,30 @@
"k": "vim::Up",
"l": "vim::Right",
"0": "vim::StartOfLine",
"shift-$": "vim::EndOfLine",
"shift-G": "vim::EndOfDocument",
"$": "vim::EndOfLine",
"shift-g": "vim::EndOfDocument",
"w": "vim::NextWordStart",
"shift-W": [
"shift-w": [
"vim::NextWordStart",
{
"ignorePunctuation": true
}
],
"e": "vim::NextWordEnd",
"shift-E": [
"shift-e": [
"vim::NextWordEnd",
{
"ignorePunctuation": true
}
],
"b": "vim::PreviousWordStart",
"shift-B": [
"shift-b": [
"vim::PreviousWordStart",
{
"ignorePunctuation": true
}
],
"shift-%": "vim::Matching",
"%": "vim::Matching",
"escape": "editor::Cancel"
}
},
@ -48,12 +48,12 @@
"vim::PushOperator",
"Change"
],
"shift-C": "vim::ChangeToEndOfLine",
"shift-c": "vim::ChangeToEndOfLine",
"d": [
"vim::PushOperator",
"Delete"
],
"shift-D": "vim::DeleteToEndOfLine",
"shift-d": "vim::DeleteToEndOfLine",
"y": [
"vim::PushOperator",
"Yank"
@ -62,14 +62,14 @@
"vim::SwitchMode",
"Insert"
],
"shift-I": "vim::InsertFirstNonWhitespace",
"shift-i": "vim::InsertFirstNonWhitespace",
"a": "vim::InsertAfter",
"shift-A": "vim::InsertEndOfLine",
"shift-a": "vim::InsertEndOfLine",
"x": "vim::DeleteRight",
"shift-X": "vim::DeleteLeft",
"shift-^": "vim::FirstNonWhitespace",
"shift-x": "vim::DeleteLeft",
"^": "vim::FirstNonWhitespace",
"o": "vim::InsertLineBelow",
"shift-O": "vim::InsertLineAbove",
"shift-o": "vim::InsertLineAbove",
"v": [
"vim::SwitchMode",
{
@ -78,7 +78,7 @@
}
}
],
"shift-V": [
"shift-v": [
"vim::SwitchMode",
{
"Visual": {
@ -113,7 +113,7 @@
"context": "Editor && vim_operator == c",
"bindings": {
"w": "vim::ChangeWord",
"shift-W": [
"shift-w": [
"vim::ChangeWord",
{
"ignorePunctuation": true

View file

@ -84,7 +84,8 @@
"shell": "system",
// What working directory to use when launching the terminal.
// May take 4 values:
// 1. Use the current file's project directory.
// 1. Use the current file's project directory. Will Fallback to the
// first project directory strategy if unsuccessful
// "working_directory": "current_project_directory"
// 2. Use the first project in this workspace's directory
// "working_directory": "first_project_directory"

View file

@ -82,6 +82,7 @@ impl ActivityIndicator {
buffer.update(cx, |buffer, cx| {
buffer.edit(
[(0..0, format!("Language server error: {}\n\n", lsp_name))],
None,
cx,
);
});

View file

@ -569,14 +569,14 @@ impl Client {
) -> anyhow::Result<()> {
let was_disconnected = match *self.status().borrow() {
Status::SignedOut => true,
Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
false
Status::ConnectionError
| Status::ConnectionLost
| Status::Authenticating { .. }
| Status::Reauthenticating { .. }
| Status::ReconnectionError { .. } => false,
Status::Connected { .. } | Status::Connecting { .. } | Status::Reconnecting { .. } => {
return Ok(())
}
Status::Connected { .. }
| Status::Connecting { .. }
| Status::Reconnecting { .. }
| Status::Authenticating
| Status::Reauthenticating => return Ok(()),
Status::UpgradeRequired => return Err(EstablishConnectionError::UpgradeRequired)?,
};
@ -593,13 +593,22 @@ impl Client {
read_from_keychain = credentials.is_some();
}
if credentials.is_none() {
credentials = Some(match self.authenticate(&cx).await {
Ok(credentials) => credentials,
Err(err) => {
self.set_status(Status::ConnectionError, cx);
return Err(err);
let mut status_rx = self.status();
let _ = status_rx.next().await;
futures::select_biased! {
authenticate = self.authenticate(&cx).fuse() => {
match authenticate {
Ok(creds) => credentials = Some(creds),
Err(err) => {
self.set_status(Status::ConnectionError, cx);
return Err(err);
}
}
}
});
_ = status_rx.next().fuse() => {
return Err(anyhow!("authentication canceled"));
}
}
}
let credentials = credentials.unwrap();
@ -899,40 +908,42 @@ impl Client {
// custom URL scheme instead of this local HTTP server.
let (user_id, access_token) = executor
.spawn(async move {
if let Some(req) = server.recv_timeout(Duration::from_secs(10 * 60))? {
let path = req.url();
let mut user_id = None;
let mut access_token = None;
let url = Url::parse(&format!("http://example.com{}", path))
.context("failed to parse login notification url")?;
for (key, value) in url.query_pairs() {
if key == "access_token" {
access_token = Some(value.to_string());
} else if key == "user_id" {
user_id = Some(value.to_string());
for _ in 0..100 {
if let Some(req) = server.recv_timeout(Duration::from_secs(1))? {
let path = req.url();
let mut user_id = None;
let mut access_token = None;
let url = Url::parse(&format!("http://example.com{}", path))
.context("failed to parse login notification url")?;
for (key, value) in url.query_pairs() {
if key == "access_token" {
access_token = Some(value.to_string());
} else if key == "user_id" {
user_id = Some(value.to_string());
}
}
}
let post_auth_url =
format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL);
req.respond(
tiny_http::Response::empty(302).with_header(
tiny_http::Header::from_bytes(
&b"Location"[..],
post_auth_url.as_bytes(),
)
.unwrap(),
),
)
.context("failed to respond to login http request")?;
Ok((
user_id.ok_or_else(|| anyhow!("missing user_id parameter"))?,
access_token
.ok_or_else(|| anyhow!("missing access_token parameter"))?,
))
} else {
Err(anyhow!("didn't receive login redirect"))
let post_auth_url =
format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL);
req.respond(
tiny_http::Response::empty(302).with_header(
tiny_http::Header::from_bytes(
&b"Location"[..],
post_auth_url.as_bytes(),
)
.unwrap(),
),
)
.context("failed to respond to login http request")?;
return Ok((
user_id.ok_or_else(|| anyhow!("missing user_id parameter"))?,
access_token
.ok_or_else(|| anyhow!("missing access_token parameter"))?,
));
}
}
Err(anyhow!("didn't receive login redirect"))
})
.await?;
@ -1061,7 +1072,9 @@ pub fn decode_worktree_url(url: &str) -> Option<(u64, String)> {
mod tests {
use super::*;
use crate::test::{FakeHttpClient, FakeServer};
use gpui::TestAppContext;
use gpui::{executor::Deterministic, TestAppContext};
use parking_lot::Mutex;
use std::future;
#[gpui::test(iterations = 10)]
async fn test_reconnection(cx: &mut TestAppContext) {
@ -1098,6 +1111,48 @@ mod tests {
assert_eq!(server.auth_count(), 2); // Client re-authenticated due to an invalid token
}
#[gpui::test(iterations = 10)]
async fn test_authenticating_more_than_once(
cx: &mut TestAppContext,
deterministic: Arc<Deterministic>,
) {
cx.foreground().forbid_parking();
let auth_count = Arc::new(Mutex::new(0));
let dropped_auth_count = Arc::new(Mutex::new(0));
let client = Client::new(FakeHttpClient::with_404_response());
client.override_authenticate({
let auth_count = auth_count.clone();
let dropped_auth_count = dropped_auth_count.clone();
move |cx| {
let auth_count = auth_count.clone();
let dropped_auth_count = dropped_auth_count.clone();
cx.foreground().spawn(async move {
*auth_count.lock() += 1;
let _drop = util::defer(move || *dropped_auth_count.lock() += 1);
future::pending::<()>().await;
unreachable!()
})
}
});
let _authenticate = cx.spawn(|cx| {
let client = client.clone();
async move { client.authenticate_and_connect(false, &cx).await }
});
deterministic.run_until_parked();
assert_eq!(*auth_count.lock(), 1);
assert_eq!(*dropped_auth_count.lock(), 0);
let _authenticate = cx.spawn(|cx| {
let client = client.clone();
async move { client.authenticate_and_connect(false, &cx).await }
});
deterministic.run_until_parked();
assert_eq!(*auth_count.lock(), 2);
assert_eq!(*dropped_auth_count.lock(), 1);
}
#[test]
fn test_encode_and_decode_worktree_url() {
let url = encode_worktree_url(5, "deadbeef");

View file

@ -6,7 +6,6 @@ use async_trait::async_trait;
use axum::http::StatusCode;
use collections::HashMap;
use futures::StreamExt;
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
pub use sqlx::postgres::PgPoolOptions as DbOptions;
use sqlx::{types::Uuid, FromRow, QueryBuilder, Row};
@ -218,7 +217,7 @@ impl Db for PostgresDb {
.push_bind(github_login)
.push_bind(email_address)
.push_bind(false)
.push_bind(nanoid!(16))
.push_bind(random_invite_code())
.push_bind(invite_count as i32);
},
);
@ -346,7 +345,7 @@ impl Db for PostgresDb {
WHERE id = $2 AND invite_code IS NULL
",
)
.bind(nanoid!(16))
.bind(random_invite_code())
.bind(id)
.execute(&mut tx)
.await?;
@ -451,15 +450,17 @@ impl Db for PostgresDb {
let invitee_id = sqlx::query_scalar(
"
INSERT INTO users
(github_login, email_address, admin, inviter_id)
(github_login, email_address, admin, inviter_id, invite_code, invite_count)
VALUES
($1, $2, 'f', $3)
($1, $2, 'f', $3, $4, $5)
RETURNING id
",
)
.bind(login)
.bind(email_address)
.bind(inviter_id)
.bind(random_invite_code())
.bind(5)
.fetch_one(&mut tx)
.await
.map(UserId)?;
@ -1458,6 +1459,10 @@ fn fuzzy_like_string(string: &str) -> String {
result
}
fn random_invite_code() -> String {
nanoid::nanoid!(16)
}
#[cfg(test)]
pub mod tests {
use super::*;
@ -2381,6 +2386,20 @@ pub mod tests {
.unwrap_err();
let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
assert_eq!(invite_count, 1);
// Ensure invited users get invite codes too.
assert_eq!(
db.get_invite_code_for_user(user2).await.unwrap().unwrap().1,
5
);
assert_eq!(
db.get_invite_code_for_user(user3).await.unwrap().unwrap().1,
5
);
assert_eq!(
db.get_invite_code_for_user(user4).await.unwrap().unwrap().1,
5
);
}
pub struct TestDb {

View file

@ -842,8 +842,8 @@ async fn test_propagate_saves_and_fs_changes(
.update(cx_c, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
.await
.unwrap();
buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], cx));
buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], cx));
buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], None, cx));
buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], None, cx));
// Open and edit that buffer as the host.
let buffer_a = project_a
@ -855,7 +855,7 @@ async fn test_propagate_saves_and_fs_changes(
.condition(cx_a, |buf, _| buf.text() == "i-am-c, i-am-b, ")
.await;
buffer_a.update(cx_a, |buf, cx| {
buf.edit([(buf.len()..buf.len(), "i-am-a")], cx)
buf.edit([(buf.len()..buf.len(), "i-am-a")], None, cx)
});
// Wait for edits to propagate
@ -871,7 +871,7 @@ async fn test_propagate_saves_and_fs_changes(
// Edit the buffer as the host and concurrently save as guest B.
let save_b = buffer_b.update(cx_b, |buf, cx| buf.save(cx));
buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "hi-a, ")], cx));
buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "hi-a, ")], None, cx));
save_b.await.unwrap();
assert_eq!(
client_a.fs.load("/a/file1".as_ref()).await.unwrap(),
@ -1237,7 +1237,7 @@ async fn test_buffer_conflict_after_save(cx_a: &mut TestAppContext, cx_b: &mut T
.await
.unwrap();
buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "world ")], cx));
buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "world ")], None, cx));
buffer_b.read_with(cx_b, |buf, _| {
assert!(buf.is_dirty());
assert!(!buf.has_conflict());
@ -1251,7 +1251,7 @@ async fn test_buffer_conflict_after_save(cx_a: &mut TestAppContext, cx_b: &mut T
assert!(!buf.has_conflict());
});
buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "hello ")], cx));
buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "hello ")], None, cx));
buffer_b.read_with(cx_b, |buf, _| {
assert!(buf.is_dirty());
assert!(!buf.has_conflict());
@ -1342,9 +1342,9 @@ async fn test_editing_while_guest_opens_buffer(
// Edit the buffer as client A while client B is still opening it.
cx_b.background().simulate_random_delay().await;
buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "X")], cx));
buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "X")], None, cx));
cx_b.background().simulate_random_delay().await;
buffer_a.update(cx_a, |buf, cx| buf.edit([(1..1, "Y")], cx));
buffer_a.update(cx_a, |buf, cx| buf.edit([(1..1, "Y")], None, cx));
let text = buffer_a.read_with(cx_a, |buf, _| buf.text());
let buffer_b = buffer_b.await.unwrap();
@ -1882,8 +1882,8 @@ async fn test_reloading_buffer_manually(cx_a: &mut TestAppContext, cx_b: &mut Te
.await
.unwrap();
buffer_b.update(cx_b, |buffer, cx| {
buffer.edit([(4..7, "six")], cx);
buffer.edit([(10..11, "6")], cx);
buffer.edit([(4..7, "six")], None, cx);
buffer.edit([(10..11, "6")], None, cx);
assert_eq!(buffer.text(), "let six = 6;");
assert!(buffer.is_dirty());
assert!(!buffer.has_conflict());
@ -2589,7 +2589,7 @@ async fn test_project_symbols(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
// Attempt to craft a symbol and violate host's privacy by opening an arbitrary file.
let mut fake_symbol = symbols[0].clone();
fake_symbol.path = Path::new("/code/secrets").into();
fake_symbol.path.path = Path::new("/code/secrets").into();
let error = project_b
.update(cx_b, |project, cx| {
project.open_buffer_for_symbol(&fake_symbol, cx)
@ -2964,7 +2964,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
);
rename.editor.update(cx, |rename_editor, cx| {
rename_editor.buffer().update(cx, |rename_buffer, cx| {
rename_buffer.edit([(0..3, "THREE")], cx);
rename_buffer.edit([(0..3, "THREE")], None, cx);
});
});
});

View file

@ -1270,13 +1270,6 @@ mod tests {
.detach();
});
let request = server.receive::<proto::RegisterProject>().await.unwrap();
server
.respond(
request.receipt(),
proto::RegisterProjectResponse { project_id: 200 },
)
.await;
let get_users_request = server.receive::<proto::GetUsers>().await.unwrap();
server
.respond(
@ -1307,6 +1300,14 @@ mod tests {
)
.await;
let request = server.receive::<proto::RegisterProject>().await.unwrap();
server
.respond(
request.receipt(),
proto::RegisterProjectResponse { project_id: 200 },
)
.await;
server.send(proto::UpdateContacts {
incoming_requests: vec![proto::IncomingContactRequest {
requester_id: 1,

View file

@ -897,7 +897,7 @@ pub mod tests {
let ix = snapshot.buffer_snapshot.text().find("seven").unwrap();
buffer.update(cx, |buffer, cx| {
buffer.edit([(ix..ix, "and ")], cx);
buffer.edit([(ix..ix, "and ")], None, cx);
});
let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
@ -936,6 +936,7 @@ pub mod tests {
(Point::new(1, 1)..Point::new(1, 1), "\t"),
(Point::new(2, 1)..Point::new(2, 1), "\t"),
],
None,
cx,
)
});

View file

@ -1164,7 +1164,7 @@ mod tests {
// Insert a line break, separating two block decorations into separate lines.
let buffer_snapshot = buffer.update(cx, |buffer, cx| {
buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], cx);
buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
buffer.snapshot(cx)
});

View file

@ -1240,6 +1240,7 @@ mod tests {
(Point::new(0, 0)..Point::new(0, 1), "123"),
(Point::new(2, 3)..Point::new(2, 3), "123"),
],
None,
cx,
);
buffer.snapshot(cx)
@ -1262,7 +1263,7 @@ mod tests {
);
let buffer_snapshot = buffer.update(cx, |buffer, cx| {
buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], cx);
buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], None, cx);
buffer.snapshot(cx)
});
let (snapshot4, _) = map.read(buffer_snapshot.clone(), subscription.consume().into_inner());
@ -1318,7 +1319,7 @@ mod tests {
// Edit within one of the folds.
let buffer_snapshot = buffer.update(cx, |buffer, cx| {
buffer.edit([(0..1, "12345")], cx);
buffer.edit([(0..1, "12345")], None, cx);
buffer.snapshot(cx)
});
let (snapshot, _) =
@ -1360,7 +1361,7 @@ mod tests {
assert_eq!(snapshot.text(), "aa…cccc\nd…eeeee");
let buffer_snapshot = buffer.update(cx, |buffer, cx| {
buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], cx);
buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx);
buffer.snapshot(cx)
});
let (snapshot, _) = map.read(buffer_snapshot.clone(), subscription.consume().into_inner());

View file

@ -38,9 +38,9 @@ use hover_popover::{hide_hover, HoverState};
pub use items::MAX_TAB_TITLE_LEN;
pub use language::{char_kind, CharKind};
use language::{
BracketPair, Buffer, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticSeverity,
IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal,
TransactionId,
AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, Diagnostic,
DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Point,
Selection, SelectionGoal, TransactionId,
};
use link_go_to_definition::LinkGoToDefinitionState;
pub use multi_buffer::{
@ -187,6 +187,7 @@ actions!(
SelectLargerSyntaxNode,
SelectSmallerSyntaxNode,
GoToDefinition,
GoToTypeDefinition,
MoveToEnclosingBracket,
UndoSelection,
RedoSelection,
@ -297,6 +298,7 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Editor::go_to_diagnostic);
cx.add_action(Editor::go_to_prev_diagnostic);
cx.add_action(Editor::go_to_definition);
cx.add_action(Editor::go_to_type_definition);
cx.add_action(Editor::page_up);
cx.add_action(Editor::page_down);
cx.add_action(Editor::fold);
@ -755,9 +757,11 @@ impl CompletionsMenu {
.collect()
};
matches.sort_unstable_by_key(|mat| {
let completion = &self.completions[mat.candidate_id];
(
completion.lsp_completion.sort_text.as_ref(),
Reverse(OrderedFloat(mat.score)),
self.completions[mat.candidate_id].sort_key(),
completion.sort_key(),
)
});
@ -877,6 +881,7 @@ struct ActiveDiagnosticGroup {
pub struct ClipboardSelection {
pub len: usize,
pub is_entire_line: bool,
pub first_line_indent: u32,
}
#[derive(Debug)]
@ -892,6 +897,11 @@ pub struct NavigationData {
pub struct EditorCreated(pub ViewHandle<Editor>);
enum GotoDefinitionKind {
Symbol,
Type,
}
impl Editor {
pub fn single_line(
field_editor_style: Option<GetFieldEditorTheme>,
@ -1462,7 +1472,8 @@ impl Editor {
S: ToOffset,
T: Into<Arc<str>>,
{
self.buffer.update(cx, |buffer, cx| buffer.edit(edits, cx));
self.buffer
.update(cx, |buffer, cx| buffer.edit(edits, None, cx));
}
pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
@ -1471,8 +1482,9 @@ impl Editor {
S: ToOffset,
T: Into<Arc<str>>,
{
self.buffer
.update(cx, |buffer, cx| buffer.edit_with_autoindent(edits, cx));
self.buffer.update(cx, |buffer, cx| {
buffer.edit(edits, Some(AutoindentMode::EachLine), cx)
});
}
fn select(&mut self, Select(phase): &Select, cx: &mut ViewContext<Self>) {
@ -1885,9 +1897,7 @@ impl Editor {
.unzip()
};
this.buffer.update(cx, |buffer, cx| {
buffer.edit_with_autoindent(edits, cx);
});
this.edit_with_autoindent(edits, cx);
let buffer = this.buffer.read(cx).snapshot(cx);
let new_selections = selection_fixup_info
.into_iter()
@ -1920,10 +1930,11 @@ impl Editor {
})
.collect::<Vec<_>>()
};
buffer.edit_with_autoindent(
buffer.edit(
old_selections
.iter()
.map(|s| (s.start..s.end, text.clone())),
Some(AutoindentMode::EachLine),
cx,
);
anchors
@ -1984,6 +1995,7 @@ impl Editor {
(s.end.clone()..s.end.clone(), pair_end.clone()),
]
}),
None,
cx,
);
});
@ -2059,6 +2071,7 @@ impl Editor {
selection_ranges
.iter()
.map(|range| (range.clone(), pair_end.clone())),
None,
cx,
);
snapshot = buffer.snapshot(cx);
@ -2361,8 +2374,11 @@ impl Editor {
this.insert_snippet(&ranges, snippet, cx).log_err();
} else {
this.buffer.update(cx, |buffer, cx| {
buffer
.edit_with_autoindent(ranges.iter().map(|range| (range.clone(), text)), cx);
buffer.edit(
ranges.iter().map(|range| (range.clone(), text)),
Some(AutoindentMode::EachLine),
cx,
);
});
}
});
@ -2723,11 +2739,12 @@ impl Editor {
) -> Result<()> {
let tabstops = self.buffer.update(cx, |buffer, cx| {
let snippet_text: Arc<str> = snippet.text.clone().into();
buffer.edit_with_autoindent(
buffer.edit(
insertion_ranges
.iter()
.cloned()
.map(|range| (range, snippet_text.clone())),
Some(AutoindentMode::EachLine),
cx,
);
@ -2931,7 +2948,11 @@ impl Editor {
let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
IndentSize::spaces(chars_to_next_tab_stop)
};
buffer.edit([(cursor..cursor, tab_size.chars().collect::<String>())], cx);
buffer.edit(
[(cursor..cursor, tab_size.chars().collect::<String>())],
None,
cx,
);
cursor.column += tab_size.len;
selection.start = cursor;
selection.end = cursor;
@ -3004,6 +3025,7 @@ impl Editor {
row_start..row_start,
indent_delta.chars().collect::<String>(),
)],
None,
cx,
);
@ -3078,6 +3100,7 @@ impl Editor {
deletion_ranges
.into_iter()
.map(|range| (range, empty_str.clone())),
None,
cx,
);
});
@ -3143,6 +3166,7 @@ impl Editor {
edit_ranges
.into_iter()
.map(|range| (range, empty_str.clone())),
None,
cx,
);
buffer.snapshot(cx)
@ -3200,7 +3224,7 @@ impl Editor {
self.transact(cx, |this, cx| {
this.buffer.update(cx, |buffer, cx| {
buffer.edit(edits, cx);
buffer.edit(edits, None, cx);
});
this.request_autoscroll(Autoscroll::Fit, cx);
@ -3309,7 +3333,7 @@ impl Editor {
this.unfold_ranges(unfold_ranges, true, cx);
this.buffer.update(cx, |buffer, cx| {
for (range, text) in edits {
buffer.edit([(range, text)], cx);
buffer.edit([(range, text)], None, cx);
}
});
this.fold_ranges(refold_ranges, cx);
@ -3414,7 +3438,7 @@ impl Editor {
this.unfold_ranges(unfold_ranges, true, cx);
this.buffer.update(cx, |buffer, cx| {
for (range, text) in edits {
buffer.edit([(range, text)], cx);
buffer.edit([(range, text)], None, cx);
}
});
this.fold_ranges(refold_ranges, cx);
@ -3465,7 +3489,8 @@ impl Editor {
});
edits
});
this.buffer.update(cx, |buffer, cx| buffer.edit(edits, cx));
this.buffer
.update(cx, |buffer, cx| buffer.edit(edits, None, cx));
let selections = this.selections.all::<usize>(cx);
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.select(selections);
@ -3495,6 +3520,7 @@ impl Editor {
clipboard_selections.push(ClipboardSelection {
len,
is_entire_line,
first_line_indent: buffer.indent_size_for_line(selection.start.row).len,
});
}
}
@ -3532,6 +3558,7 @@ impl Editor {
clipboard_selections.push(ClipboardSelection {
len,
is_entire_line,
first_line_indent: buffer.indent_size_for_line(start.row).len,
});
}
}
@ -3566,18 +3593,22 @@ impl Editor {
let snapshot = buffer.read(cx);
let mut start_offset = 0;
let mut edits = Vec::new();
let mut start_columns = Vec::new();
let line_mode = this.selections.line_mode;
for (ix, selection) in old_selections.iter().enumerate() {
let to_insert;
let entire_line;
let start_column;
if let Some(clipboard_selection) = clipboard_selections.get(ix) {
let end_offset = start_offset + clipboard_selection.len;
to_insert = &clipboard_text[start_offset..end_offset];
entire_line = clipboard_selection.is_entire_line;
start_offset = end_offset;
start_column = clipboard_selection.first_line_indent;
} else {
to_insert = clipboard_text.as_str();
entire_line = all_selections_were_entire_line;
start_column = 0;
}
// If the corresponding selection was empty when this slice of the
@ -3593,9 +3624,16 @@ impl Editor {
};
edits.push((range, to_insert));
start_columns.push(start_column);
}
drop(snapshot);
buffer.edit_with_autoindent(edits, cx);
buffer.edit(
edits,
Some(AutoindentMode::Block {
original_indent_columns: start_columns,
}),
cx,
);
});
let selections = this.selections.all::<usize>(cx);
@ -4430,6 +4468,7 @@ impl Editor {
.iter()
.cloned()
.map(|range| (range, empty_str.clone())),
None,
cx,
);
} else {
@ -4439,7 +4478,7 @@ impl Editor {
let position = Point::new(range.start.row, min_column);
(position..position, full_comment_prefix.clone())
});
buffer.edit(edits, cx);
buffer.edit(edits, None, cx);
}
}
}
@ -4661,6 +4700,22 @@ impl Editor {
workspace: &mut Workspace,
_: &GoToDefinition,
cx: &mut ViewContext<Workspace>,
) {
Self::go_to_definition_of_kind(GotoDefinitionKind::Symbol, workspace, cx);
}
pub fn go_to_type_definition(
workspace: &mut Workspace,
_: &GoToTypeDefinition,
cx: &mut ViewContext<Workspace>,
) {
Self::go_to_definition_of_kind(GotoDefinitionKind::Type, workspace, cx);
}
fn go_to_definition_of_kind(
kind: GotoDefinitionKind,
workspace: &mut Workspace,
cx: &mut ViewContext<Workspace>,
) {
let active_item = workspace.active_item(cx);
let editor_handle = if let Some(editor) = active_item
@ -4682,7 +4737,11 @@ impl Editor {
};
let project = workspace.project().clone();
let definitions = project.update(cx, |project, cx| project.definition(&buffer, head, cx));
let definitions = project.update(cx, |project, cx| match kind {
GotoDefinitionKind::Symbol => project.definition(&buffer, head, cx),
GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx),
});
cx.spawn(|workspace, mut cx| async move {
let definitions = definitions.await?;
workspace.update(&mut cx, |workspace, cx| {
@ -4873,9 +4932,9 @@ impl Editor {
editor.override_text_style =
Some(Box::new(move |style| old_highlight_id.style(&style.syntax)));
}
editor
.buffer
.update(cx, |buffer, cx| buffer.edit([(0..0, old_name.clone())], cx));
editor.buffer.update(cx, |buffer, cx| {
buffer.edit([(0..0, old_name.clone())], None, cx)
});
editor.select_all(&SelectAll, cx);
editor
});
@ -6656,8 +6715,8 @@ mod tests {
// Simulate an edit in another editor
buffer.update(cx, |buffer, cx| {
buffer.start_transaction_at(now, cx);
buffer.edit([(0..1, "a")], cx);
buffer.edit([(1..1, "b")], cx);
buffer.edit([(0..1, "a")], None, cx);
buffer.edit([(1..1, "b")], None, cx);
buffer.end_transaction_at(now, cx);
});
@ -7198,6 +7257,7 @@ mod tests {
(Point::new(1, 0)..Point::new(1, 0), "\t"),
(Point::new(1, 1)..Point::new(1, 1), "\t"),
],
None,
cx,
);
});
@ -7834,6 +7894,7 @@ mod tests {
(Point::new(1, 2)..Point::new(3, 0), ""),
(Point::new(4, 2)..Point::new(6, 0), ""),
],
None,
cx,
);
assert_eq!(
@ -7892,7 +7953,7 @@ mod tests {
// Edit the buffer directly, deleting ranges surrounding the editor's selections
buffer.update(cx, |buffer, cx| {
buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], cx);
buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
});
@ -8629,6 +8690,118 @@ mod tests {
t|he lazy dog"});
}
#[gpui::test]
async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
let language = Arc::new(Language::new(
LanguageConfig::default(),
Some(tree_sitter_rust::language()),
));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// Cut an indented block, without the leading whitespace.
cx.set_state(indoc! {"
const a = (
b(),
[c(
d,
e
)}
);
"});
cx.update_editor(|e, cx| e.cut(&Cut, cx));
cx.assert_editor_state(indoc! {"
const a = (
b(),
|
);
"});
// Paste it at the same position.
cx.update_editor(|e, cx| e.paste(&Paste, cx));
cx.assert_editor_state(indoc! {"
const a = (
b(),
c(
d,
e
)|
);
"});
// Paste it at a line with a lower indent level.
cx.update_editor(|e, cx| e.paste(&Paste, cx));
cx.set_state(indoc! {"
|
const a = (
b(),
);
"});
cx.update_editor(|e, cx| e.paste(&Paste, cx));
cx.assert_editor_state(indoc! {"
c(
d,
e
)|
const a = (
b(),
);
"});
// Cut an indented block, with the leading whitespace.
cx.set_state(indoc! {"
const a = (
b(),
[ c(
d,
e
)
});
"});
cx.update_editor(|e, cx| e.cut(&Cut, cx));
cx.assert_editor_state(indoc! {"
const a = (
b(),
|);
"});
// Paste it at the same position.
cx.update_editor(|e, cx| e.paste(&Paste, cx));
cx.assert_editor_state(indoc! {"
const a = (
b(),
c(
d,
e
)
|);
"});
// Paste it at a line with a higher indent level.
cx.set_state(indoc! {"
const a = (
b(),
c(
d,
e|
)
);
"});
cx.update_editor(|e, cx| e.paste(&Paste, cx));
cx.set_state(indoc! {"
const a = (
b(),
c(
d,
ec(
d,
e
)|
)
);
"});
}
#[gpui::test]
fn test_select_all(cx: &mut gpui::MutableAppContext) {
cx.set_global(Settings::test(cx));

View file

@ -6,7 +6,9 @@ use super::{
use crate::{
display_map::{BlockStyle, DisplaySnapshot, TransformBlock},
hover_popover::HoverAt,
link_go_to_definition::{CmdChanged, GoToFetchedDefinition, UpdateGoToDefinitionLink},
link_go_to_definition::{
CmdShiftChanged, GoToFetchedDefinition, GoToFetchedTypeDefinition, UpdateGoToDefinitionLink,
},
mouse_context_menu::DeployMouseContextMenu,
EditorStyle,
};
@ -122,7 +124,12 @@ impl EditorElement {
if cmd && paint.text_bounds.contains_point(position) {
let (point, overshoot) = paint.point_for_position(&self.snapshot(cx), layout, position);
if overshoot.is_zero() {
cx.dispatch_action(GoToFetchedDefinition { point });
if shift {
cx.dispatch_action(GoToFetchedTypeDefinition { point });
} else {
cx.dispatch_action(GoToFetchedDefinition { point });
}
return true;
}
}
@ -238,8 +245,12 @@ impl EditorElement {
fn mouse_moved(
&self,
position: Vector2F,
cmd: bool,
MouseMovedEvent {
cmd,
shift,
position,
..
}: MouseMovedEvent,
layout: &LayoutState,
paint: &PaintState,
cx: &mut EventContext,
@ -260,6 +271,7 @@ impl EditorElement {
cx.dispatch_action(UpdateGoToDefinitionLink {
point,
cmd_held: cmd,
shift_held: shift,
});
if paint
@ -283,8 +295,11 @@ impl EditorElement {
true
}
fn modifiers_changed(&self, cmd: bool, cx: &mut EventContext) -> bool {
cx.dispatch_action(CmdChanged { cmd_down: cmd });
fn modifiers_changed(&self, event: ModifiersChangedEvent, cx: &mut EventContext) -> bool {
cx.dispatch_action(CmdShiftChanged {
cmd_down: event.cmd,
shift_down: event.shift,
});
false
}
@ -1534,32 +1549,34 @@ impl Element for EditorElement {
paint,
cx,
),
Event::MouseDown(MouseButtonEvent {
button: MouseButton::Right,
position,
..
}) => self.mouse_right_down(*position, layout, paint, cx),
Event::MouseUp(MouseButtonEvent {
button: MouseButton::Left,
position,
..
}) => self.mouse_up(*position, cx),
Event::MouseMoved(MouseMovedEvent {
pressed_button: Some(MouseButton::Left),
position,
..
}) => self.mouse_dragged(*position, layout, paint, cx),
Event::ScrollWheel(ScrollWheelEvent {
position,
delta,
precise,
}) => self.scroll(*position, *delta, *precise, layout, paint, cx),
Event::ModifiersChanged(ModifiersChangedEvent { cmd, .. }) => {
self.modifiers_changed(*cmd, cx)
}
Event::MouseMoved(MouseMovedEvent { position, cmd, .. }) => {
self.mouse_moved(*position, *cmd, layout, paint, cx)
}
&Event::ModifiersChanged(event) => self.modifiers_changed(event, cx),
&Event::MouseMoved(event) => self.mouse_moved(event, layout, paint, cx),
_ => false,
}

View file

@ -8,18 +8,21 @@ use util::TryFutureExt;
use workspace::Workspace;
use crate::{
Anchor, DisplayPoint, Editor, EditorSnapshot, Event, GoToDefinition, Select, SelectPhase,
Anchor, DisplayPoint, Editor, EditorSnapshot, Event, GoToDefinition, GoToTypeDefinition,
Select, SelectPhase,
};
#[derive(Clone, PartialEq)]
pub struct UpdateGoToDefinitionLink {
pub point: Option<DisplayPoint>,
pub cmd_held: bool,
pub shift_held: bool,
}
#[derive(Clone, PartialEq)]
pub struct CmdChanged {
pub struct CmdShiftChanged {
pub cmd_down: bool,
pub shift_down: bool,
}
#[derive(Clone, PartialEq)]
@ -27,28 +30,44 @@ pub struct GoToFetchedDefinition {
pub point: DisplayPoint,
}
#[derive(Clone, PartialEq)]
pub struct GoToFetchedTypeDefinition {
pub point: DisplayPoint,
}
impl_internal_actions!(
editor,
[UpdateGoToDefinitionLink, CmdChanged, GoToFetchedDefinition]
[
UpdateGoToDefinitionLink,
CmdShiftChanged,
GoToFetchedDefinition,
GoToFetchedTypeDefinition
]
);
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(update_go_to_definition_link);
cx.add_action(cmd_changed);
cx.add_action(cmd_shift_changed);
cx.add_action(go_to_fetched_definition);
cx.add_action(go_to_fetched_type_definition);
}
#[derive(Default)]
pub struct LinkGoToDefinitionState {
pub last_mouse_location: Option<Anchor>,
pub symbol_range: Option<Range<Anchor>>,
pub kind: Option<LinkDefinitionKind>,
pub definitions: Vec<LocationLink>,
pub task: Option<Task<Option<()>>>,
}
pub fn update_go_to_definition_link(
editor: &mut Editor,
&UpdateGoToDefinitionLink { point, cmd_held }: &UpdateGoToDefinitionLink,
&UpdateGoToDefinitionLink {
point,
cmd_held,
shift_held,
}: &UpdateGoToDefinitionLink,
cx: &mut ViewContext<Editor>,
) {
// Store new mouse point as an anchor
@ -72,7 +91,13 @@ pub fn update_go_to_definition_link(
editor.link_go_to_definition_state.last_mouse_location = point.clone();
if cmd_held {
if let Some(point) = point {
show_link_definition(editor, point, snapshot, cx);
let kind = if shift_held {
LinkDefinitionKind::Type
} else {
LinkDefinitionKind::Symbol
};
show_link_definition(kind, editor, point, snapshot, cx);
return;
}
}
@ -80,9 +105,12 @@ pub fn update_go_to_definition_link(
hide_link_definition(editor, cx);
}
pub fn cmd_changed(
pub fn cmd_shift_changed(
editor: &mut Editor,
&CmdChanged { cmd_down }: &CmdChanged,
&CmdShiftChanged {
cmd_down,
shift_down,
}: &CmdShiftChanged,
cx: &mut ViewContext<Editor>,
) {
if let Some(point) = editor
@ -92,19 +120,37 @@ pub fn cmd_changed(
{
if cmd_down {
let snapshot = editor.snapshot(cx);
show_link_definition(editor, point.clone(), snapshot, cx);
let kind = if shift_down {
LinkDefinitionKind::Type
} else {
LinkDefinitionKind::Symbol
};
show_link_definition(kind, editor, point.clone(), snapshot, cx);
} else {
hide_link_definition(editor, cx)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LinkDefinitionKind {
Symbol,
Type,
}
pub fn show_link_definition(
definition_kind: LinkDefinitionKind,
editor: &mut Editor,
trigger_point: Anchor,
snapshot: EditorSnapshot,
cx: &mut ViewContext<Editor>,
) {
let same_kind = editor.link_go_to_definition_state.kind == Some(definition_kind);
if !same_kind {
hide_link_definition(editor, cx);
}
if editor.pending_rename.is_some() {
return;
}
@ -135,17 +181,20 @@ pub fn show_link_definition(
return;
};
// Don't request again if the location is within the symbol region of a previous request
// Don't request again if the location is within the symbol region of a previous request with the same kind
if let Some(symbol_range) = &editor.link_go_to_definition_state.symbol_range {
if symbol_range
let point_after_start = symbol_range
.start
.cmp(&trigger_point, &snapshot.buffer_snapshot)
.is_le()
&& symbol_range
.end
.cmp(&trigger_point, &snapshot.buffer_snapshot)
.is_ge()
{
.is_le();
let point_before_end = symbol_range
.end
.cmp(&trigger_point, &snapshot.buffer_snapshot)
.is_ge();
let point_within_range = point_after_start && point_before_end;
if point_within_range && same_kind {
return;
}
}
@ -154,8 +203,14 @@ pub fn show_link_definition(
async move {
// query the LSP for definition info
let definition_request = cx.update(|cx| {
project.update(cx, |project, cx| {
project.definition(&buffer, buffer_position.clone(), cx)
project.update(cx, |project, cx| match definition_kind {
LinkDefinitionKind::Symbol => {
project.definition(&buffer, buffer_position.clone(), cx)
}
LinkDefinitionKind::Type => {
project.type_definition(&buffer, buffer_position.clone(), cx)
}
})
});
@ -181,6 +236,7 @@ pub fn show_link_definition(
this.update(&mut cx, |this, cx| {
// Clear any existing highlights
this.clear_text_highlights::<LinkGoToDefinitionState>(cx);
this.link_go_to_definition_state.kind = Some(definition_kind);
this.link_go_to_definition_state.symbol_range = result
.as_ref()
.and_then(|(symbol_range, _)| symbol_range.clone());
@ -258,7 +314,24 @@ pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
pub fn go_to_fetched_definition(
workspace: &mut Workspace,
GoToFetchedDefinition { point }: &GoToFetchedDefinition,
&GoToFetchedDefinition { point }: &GoToFetchedDefinition,
cx: &mut ViewContext<Workspace>,
) {
go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, workspace, point, cx);
}
pub fn go_to_fetched_type_definition(
workspace: &mut Workspace,
&GoToFetchedTypeDefinition { point }: &GoToFetchedTypeDefinition,
cx: &mut ViewContext<Workspace>,
) {
go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, workspace, point, cx);
}
fn go_to_fetched_definition_of_kind(
kind: LinkDefinitionKind,
workspace: &mut Workspace,
point: DisplayPoint,
cx: &mut ViewContext<Workspace>,
) {
let active_item = workspace.active_item(cx);
@ -271,13 +344,14 @@ pub fn go_to_fetched_definition(
return;
};
let definitions = editor_handle.update(cx, |editor, cx| {
let (cached_definitions, cached_definitions_kind) = editor_handle.update(cx, |editor, cx| {
let definitions = editor.link_go_to_definition_state.definitions.clone();
hide_link_definition(editor, cx);
definitions
(definitions, editor.link_go_to_definition_state.kind)
});
if !definitions.is_empty() {
let is_correct_kind = cached_definitions_kind == Some(kind);
if !cached_definitions.is_empty() && is_correct_kind {
editor_handle.update(cx, |editor, cx| {
if !editor.focused {
cx.focus_self();
@ -285,7 +359,7 @@ pub fn go_to_fetched_definition(
}
});
Editor::navigate_to_definitions(workspace, editor_handle, definitions, cx);
Editor::navigate_to_definitions(workspace, editor_handle, cached_definitions, cx);
} else {
editor_handle.update(cx, |editor, cx| {
editor.select(
@ -298,7 +372,13 @@ pub fn go_to_fetched_definition(
);
});
Editor::go_to_definition(workspace, &GoToDefinition, cx);
match kind {
LinkDefinitionKind::Symbol => Editor::go_to_definition(workspace, &GoToDefinition, cx),
LinkDefinitionKind::Type => {
Editor::go_to_type_definition(workspace, &GoToTypeDefinition, cx)
}
}
}
}
@ -306,11 +386,128 @@ pub fn go_to_fetched_definition(
mod tests {
use futures::StreamExt;
use indoc::indoc;
use lsp::request::{GotoDefinition, GotoTypeDefinition};
use crate::test::EditorLspTestContext;
use super::*;
#[gpui::test]
async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) {
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
..Default::default()
},
cx,
)
.await;
cx.set_state(indoc! {"
struct A;
let v|ariable = A;
"});
// Basic hold cmd+shift, expect highlight in region if response contains type definition
let hover_point = cx.display_point(indoc! {"
struct A;
let v|ariable = A;
"});
let symbol_range = cx.lsp_range(indoc! {"
struct A;
let [variable] = A;
"});
let target_range = cx.lsp_range(indoc! {"
struct [A];
let variable = A;
"});
let mut requests =
cx.handle_request::<GotoTypeDefinition, _, _>(move |url, _, _| async move {
Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
lsp::LocationLink {
origin_selection_range: Some(symbol_range),
target_uri: url.clone(),
target_range,
target_selection_range: target_range,
},
])))
});
// Press cmd+shift to trigger highlight
cx.update_editor(|editor, cx| {
update_go_to_definition_link(
editor,
&UpdateGoToDefinitionLink {
point: Some(hover_point),
cmd_held: true,
shift_held: true,
},
cx,
);
});
requests.next().await;
cx.foreground().run_until_parked();
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
struct A;
let [variable] = A;
"});
// Unpress shift causes highlight to go away (normal goto-definition is not valid here)
cx.update_editor(|editor, cx| {
cmd_shift_changed(
editor,
&CmdShiftChanged {
cmd_down: true,
shift_down: false,
},
cx,
);
});
// Assert no link highlights
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
struct A;
let variable = A;
"});
// Cmd+shift click without existing definition requests and jumps
let hover_point = cx.display_point(indoc! {"
struct A;
let v|ariable = A;
"});
let target_range = cx.lsp_range(indoc! {"
struct [A];
let variable = A;
"});
let mut requests =
cx.handle_request::<GotoTypeDefinition, _, _>(move |url, _, _| async move {
Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
lsp::LocationLink {
origin_selection_range: None,
target_uri: url,
target_range,
target_selection_range: target_range,
},
])))
});
cx.update_workspace(|workspace, cx| {
go_to_fetched_type_definition(
workspace,
&GoToFetchedTypeDefinition { point: hover_point },
cx,
);
});
requests.next().await;
cx.foreground().run_until_parked();
cx.assert_editor_state(indoc! {"
struct [A};
let variable = A;
"});
}
#[gpui::test]
async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) {
let mut cx = EditorLspTestContext::new_rust(
@ -327,7 +524,8 @@ mod tests {
do_work();
fn do_work()
test();"});
test();
"});
// Basic hold cmd, expect highlight in region if response contains definition
let hover_point = cx.display_point(indoc! {"
@ -335,38 +533,41 @@ mod tests {
do_w|ork();
fn do_work()
test();"});
test();
"});
let symbol_range = cx.lsp_range(indoc! {"
fn test()
[do_work]();
fn do_work()
test();"});
test();
"});
let target_range = cx.lsp_range(indoc! {"
fn test()
do_work();
fn [do_work]()
test();"});
test();
"});
let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
lsp::LocationLink {
origin_selection_range: Some(symbol_range),
target_uri: url.clone(),
target_range,
target_selection_range: target_range,
},
])))
});
let mut requests =
cx.handle_request::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
lsp::LocationLink {
origin_selection_range: Some(symbol_range),
target_uri: url.clone(),
target_range,
target_selection_range: target_range,
},
])))
});
cx.update_editor(|editor, cx| {
update_go_to_definition_link(
editor,
&UpdateGoToDefinitionLink {
point: Some(hover_point),
cmd_held: true,
shift_held: false,
},
cx,
);
@ -378,11 +579,19 @@ mod tests {
[do_work]();
fn do_work()
test();"});
test();
"});
// Unpress cmd causes highlight to go away
cx.update_editor(|editor, cx| {
cmd_changed(editor, &CmdChanged { cmd_down: false }, cx);
cmd_shift_changed(
editor,
&CmdShiftChanged {
cmd_down: false,
shift_down: false,
},
cx,
);
});
// Assert no link highlights
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
@ -390,28 +599,29 @@ mod tests {
do_work();
fn do_work()
test();"});
test();
"});
// Response without source range still highlights word
cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_mouse_location = None);
let mut requests =
cx.handle_request::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
lsp::LocationLink {
// No origin range
origin_selection_range: None,
target_uri: url.clone(),
target_range,
target_selection_range: target_range,
},
])))
});
let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
lsp::LocationLink {
// No origin range
origin_selection_range: None,
target_uri: url.clone(),
target_range,
target_selection_range: target_range,
},
])))
});
cx.update_editor(|editor, cx| {
update_go_to_definition_link(
editor,
&UpdateGoToDefinitionLink {
point: Some(hover_point),
cmd_held: true,
shift_held: false,
},
cx,
);
@ -424,7 +634,8 @@ mod tests {
[do_work]();
fn do_work()
test();"});
test();
"});
// Moving mouse to location with no response dismisses highlight
let hover_point = cx.display_point(indoc! {"
@ -432,19 +643,21 @@ mod tests {
do_work();
fn do_work()
test();"});
let mut requests =
cx.lsp
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
// No definitions returned
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
});
test();
"});
let mut requests = cx
.lsp
.handle_request::<GotoDefinition, _, _>(move |_, _| async move {
// No definitions returned
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
});
cx.update_editor(|editor, cx| {
update_go_to_definition_link(
editor,
&UpdateGoToDefinitionLink {
point: Some(hover_point),
cmd_held: true,
shift_held: false,
},
cx,
);
@ -458,7 +671,8 @@ mod tests {
do_work();
fn do_work()
test();"});
test();
"});
// Move mouse without cmd and then pressing cmd triggers highlight
let hover_point = cx.display_point(indoc! {"
@ -466,13 +680,15 @@ mod tests {
do_work();
fn do_work()
te|st();"});
te|st();
"});
cx.update_editor(|editor, cx| {
update_go_to_definition_link(
editor,
&UpdateGoToDefinitionLink {
point: Some(hover_point),
cmd_held: false,
shift_held: false,
},
cx,
);
@ -485,34 +701,43 @@ mod tests {
do_work();
fn do_work()
test();"});
test();
"});
let symbol_range = cx.lsp_range(indoc! {"
fn test()
do_work();
fn do_work()
[test]();"});
[test]();
"});
let target_range = cx.lsp_range(indoc! {"
fn [test]()
do_work();
fn do_work()
test();"});
test();
"});
let mut requests =
cx.handle_request::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
lsp::LocationLink {
origin_selection_range: Some(symbol_range),
target_uri: url,
target_range,
target_selection_range: target_range,
},
])))
});
let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
lsp::LocationLink {
origin_selection_range: Some(symbol_range),
target_uri: url,
target_range,
target_selection_range: target_range,
},
])))
});
cx.update_editor(|editor, cx| {
cmd_changed(editor, &CmdChanged { cmd_down: true }, cx);
cmd_shift_changed(
editor,
&CmdShiftChanged {
cmd_down: true,
shift_down: false,
},
cx,
);
});
requests.next().await;
cx.foreground().run_until_parked();
@ -522,7 +747,8 @@ mod tests {
do_work();
fn do_work()
[test]();"});
[test]();
"});
// Moving within symbol range doesn't re-request
let hover_point = cx.display_point(indoc! {"
@ -530,13 +756,15 @@ mod tests {
do_work();
fn do_work()
tes|t();"});
tes|t();
"});
cx.update_editor(|editor, cx| {
update_go_to_definition_link(
editor,
&UpdateGoToDefinitionLink {
point: Some(hover_point),
cmd_held: true,
shift_held: false,
},
cx,
);
@ -547,7 +775,8 @@ mod tests {
do_work();
fn do_work()
[test]();"});
[test]();
"});
// Cmd click with existing definition doesn't re-request and dismisses highlight
cx.update_workspace(|workspace, cx| {
@ -555,7 +784,7 @@ mod tests {
});
// Assert selection moved to to definition
cx.lsp
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
.handle_request::<GotoDefinition, _, _>(move |_, _| async move {
// Empty definition response to make sure we aren't hitting the lsp and using
// the cached location instead
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
@ -565,14 +794,16 @@ mod tests {
do_work();
fn do_work()
test();"});
test();
"});
// Assert no link highlights after jump
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
fn test()
do_work();
fn do_work()
test();"});
test();
"});
// Cmd click without existing definition requests and jumps
let hover_point = cx.display_point(indoc! {"
@ -580,25 +811,26 @@ mod tests {
do_w|ork();
fn do_work()
test();"});
test();
"});
let target_range = cx.lsp_range(indoc! {"
fn test()
do_work();
fn [do_work]()
test();"});
test();
"});
let mut requests =
cx.handle_request::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
lsp::LocationLink {
origin_selection_range: None,
target_uri: url,
target_range,
target_selection_range: target_range,
},
])))
});
let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
lsp::LocationLink {
origin_selection_range: None,
target_uri: url,
target_range,
target_selection_range: target_range,
},
])))
});
cx.update_workspace(|workspace, cx| {
go_to_fetched_definition(workspace, &GoToFetchedDefinition { point: hover_point }, cx);
});
@ -610,6 +842,7 @@ mod tests {
do_work();
fn [do_work}()
test();"});
test();
"});
}
}

View file

@ -2,8 +2,8 @@ use context_menu::ContextMenuItem;
use gpui::{geometry::vector::Vector2F, impl_internal_actions, MutableAppContext, ViewContext};
use crate::{
DisplayPoint, Editor, EditorMode, Event, FindAllReferences, GoToDefinition, Rename, SelectMode,
ToggleCodeActions,
DisplayPoint, Editor, EditorMode, Event, FindAllReferences, GoToDefinition, GoToTypeDefinition,
Rename, SelectMode, ToggleCodeActions,
};
#[derive(Clone, PartialEq)]
@ -50,6 +50,7 @@ pub fn deploy_context_menu(
vec![
ContextMenuItem::item("Rename Symbol", Rename),
ContextMenuItem::item("Go To Definition", GoToDefinition),
ContextMenuItem::item("Go To Type Definition", GoToTypeDefinition),
ContextMenuItem::item("Find All References", FindAllReferences),
ContextMenuItem::item(
"Code Actions",

View file

@ -7,11 +7,10 @@ use collections::{Bound, HashMap, HashSet};
use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
pub use language::Completion;
use language::{
char_kind, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, DiagnosticEntry, Event, File,
IndentSize, Language, OffsetRangeExt, Outline, OutlineItem, Selection, ToOffset as _,
ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId,
char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk,
DiagnosticEntry, Event, File, IndentSize, Language, OffsetRangeExt, Outline, OutlineItem,
Selection, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId,
};
use settings::Settings;
use smallvec::SmallVec;
use std::{
borrow::Cow,
@ -303,28 +302,10 @@ impl MultiBuffer {
self.read(cx).symbols_containing(offset, theme)
}
pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut ModelContext<Self>)
where
I: IntoIterator<Item = (Range<S>, T)>,
S: ToOffset,
T: Into<Arc<str>>,
{
self.edit_internal(edits, false, cx)
}
pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut ModelContext<Self>)
where
I: IntoIterator<Item = (Range<S>, T)>,
S: ToOffset,
T: Into<Arc<str>>,
{
self.edit_internal(edits, true, cx)
}
pub fn edit_internal<I, S, T>(
pub fn edit<I, S, T>(
&mut self,
edits: I,
autoindent: bool,
mut autoindent_mode: Option<AutoindentMode>,
cx: &mut ModelContext<Self>,
) where
I: IntoIterator<Item = (Range<S>, T)>,
@ -346,26 +327,23 @@ impl MultiBuffer {
if let Some(buffer) = self.as_singleton() {
return buffer.update(cx, |buffer, cx| {
if autoindent {
let language_name = buffer.language().map(|language| language.name());
let settings = cx.global::<Settings>();
let indent_size = if settings.hard_tabs(language_name.as_deref()) {
IndentSize::tab()
} else {
IndentSize::spaces(settings.tab_size(language_name.as_deref()).get())
};
buffer.edit_with_autoindent(edits, indent_size, cx);
} else {
buffer.edit(edits, cx);
}
buffer.edit(edits, autoindent_mode, cx);
});
}
let mut buffer_edits: HashMap<usize, Vec<(Range<usize>, Arc<str>, bool)>> =
let original_indent_columns = match &mut autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns,
}) => mem::take(original_indent_columns),
_ => Default::default(),
};
let mut buffer_edits: HashMap<usize, Vec<(Range<usize>, Arc<str>, bool, u32)>> =
Default::default();
let mut cursor = snapshot.excerpts.cursor::<usize>();
for (range, new_text) in edits {
for (ix, (range, new_text)) in edits.enumerate() {
let new_text: Arc<str> = new_text.into();
let original_indent_column = original_indent_columns.get(ix).copied().unwrap_or(0);
cursor.seek(&range.start, Bias::Right, &());
if cursor.item().is_none() && range.start == *cursor.start() {
cursor.prev(&());
@ -396,7 +374,12 @@ impl MultiBuffer {
buffer_edits
.entry(start_excerpt.buffer_id)
.or_insert(Vec::new())
.push((buffer_start..buffer_end, new_text, true));
.push((
buffer_start..buffer_end,
new_text,
true,
original_indent_column,
));
} else {
let start_excerpt_range = buffer_start
..start_excerpt
@ -413,11 +396,21 @@ impl MultiBuffer {
buffer_edits
.entry(start_excerpt.buffer_id)
.or_insert(Vec::new())
.push((start_excerpt_range, new_text.clone(), true));
.push((
start_excerpt_range,
new_text.clone(),
true,
original_indent_column,
));
buffer_edits
.entry(end_excerpt.buffer_id)
.or_insert(Vec::new())
.push((end_excerpt_range, new_text.clone(), false));
.push((
end_excerpt_range,
new_text.clone(),
false,
original_indent_column,
));
cursor.seek(&range.start, Bias::Right, &());
cursor.next(&());
@ -432,6 +425,7 @@ impl MultiBuffer {
excerpt.range.context.to_offset(&excerpt.buffer),
new_text.clone(),
false,
original_indent_column,
));
cursor.next(&());
}
@ -439,19 +433,25 @@ impl MultiBuffer {
}
for (buffer_id, mut edits) in buffer_edits {
edits.sort_unstable_by_key(|(range, _, _)| range.start);
edits.sort_unstable_by_key(|(range, _, _, _)| range.start);
self.buffers.borrow()[&buffer_id]
.buffer
.update(cx, |buffer, cx| {
let mut edits = edits.into_iter().peekable();
let mut insertions = Vec::new();
let mut original_indent_columns = Vec::new();
let mut deletions = Vec::new();
let empty_str: Arc<str> = "".into();
while let Some((mut range, new_text, mut is_insertion)) = edits.next() {
while let Some((next_range, _, next_is_insertion)) = edits.peek() {
while let Some((
mut range,
new_text,
mut is_insertion,
original_indent_column,
)) = edits.next()
{
while let Some((next_range, _, next_is_insertion, _)) = edits.peek() {
if range.end >= next_range.start {
range.end = cmp::max(next_range.end, range.end);
is_insertion |= *next_is_insertion;
edits.next();
} else {
@ -460,6 +460,7 @@ impl MultiBuffer {
}
if is_insertion {
original_indent_columns.push(original_indent_column);
insertions.push((
buffer.anchor_before(range.start)..buffer.anchor_before(range.end),
new_text.clone(),
@ -471,22 +472,26 @@ impl MultiBuffer {
));
}
}
let language_name = buffer.language().map(|l| l.name());
if autoindent {
let settings = cx.global::<Settings>();
let indent_size = if settings.hard_tabs(language_name.as_deref()) {
IndentSize::tab()
let deletion_autoindent_mode =
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns: Default::default(),
})
} else {
IndentSize::spaces(settings.tab_size(language_name.as_deref()).get())
None
};
let insertion_autoindent_mode =
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns,
})
} else {
None
};
buffer.edit_with_autoindent(deletions, indent_size, cx);
buffer.edit_with_autoindent(insertions, indent_size, cx);
} else {
buffer.edit(deletions, cx);
buffer.edit(insertions, cx);
}
buffer.edit(deletions, deletion_autoindent_mode, cx);
buffer.edit(insertions, insertion_autoindent_mode, cx);
})
}
}
@ -1402,7 +1407,7 @@ impl MultiBuffer {
log::info!("mutating multi-buffer with {:?}", edits);
drop(snapshot);
self.edit(edits, cx);
self.edit(edits, None, cx);
}
pub fn randomly_edit_excerpts(
@ -3220,6 +3225,7 @@ mod tests {
use gpui::MutableAppContext;
use language::{Buffer, Rope};
use rand::prelude::*;
use settings::Settings;
use std::{env, rc::Rc};
use text::{Point, RandomCharIter};
use util::test::sample_text;
@ -3239,7 +3245,7 @@ mod tests {
.collect::<Vec<_>>()
);
buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], cx));
buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], None, cx));
let snapshot = multibuffer.read(cx).snapshot(cx);
assert_eq!(snapshot.text(), buffer.read(cx).text());
@ -3262,11 +3268,11 @@ mod tests {
let snapshot = multibuffer.read(cx).snapshot(cx);
assert_eq!(snapshot.text(), "a");
guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], cx));
guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], None, cx));
let snapshot = multibuffer.read(cx).snapshot(cx);
assert_eq!(snapshot.text(), "ab");
guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], cx));
guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], None, cx));
let snapshot = multibuffer.read(cx).snapshot(cx);
assert_eq!(snapshot.text(), "abc");
}
@ -3407,6 +3413,7 @@ mod tests {
(Point::new(0, 0)..Point::new(0, 0), text),
(Point::new(2, 1)..Point::new(2, 3), text),
],
None,
cx,
);
});
@ -3544,8 +3551,8 @@ mod tests {
let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
let old_snapshot = multibuffer.read(cx).snapshot(cx);
buffer.update(cx, |buffer, cx| {
buffer.edit([(0..0, "X")], cx);
buffer.edit([(5..5, "Y")], cx);
buffer.edit([(0..0, "X")], None, cx);
buffer.edit([(5..5, "Y")], None, cx);
});
let new_snapshot = multibuffer.read(cx).snapshot(cx);
@ -3592,12 +3599,12 @@ mod tests {
assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
buffer_1.update(cx, |buffer, cx| {
buffer.edit([(0..0, "W")], cx);
buffer.edit([(5..5, "X")], cx);
buffer.edit([(0..0, "W")], None, cx);
buffer.edit([(5..5, "X")], None, cx);
});
buffer_2.update(cx, |buffer, cx| {
buffer.edit([(0..0, "Y")], cx);
buffer.edit([(6..6, "Z")], cx);
buffer.edit([(0..0, "Y")], None, cx);
buffer.edit([(6..6, "Z")], None, cx);
});
let new_snapshot = multibuffer.read(cx).snapshot(cx);
@ -3626,7 +3633,7 @@ mod tests {
// Create an insertion id in buffer 1 that doesn't exist in buffer 2.
// Add an excerpt from buffer 1 that spans this new insertion.
buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], cx));
buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], None, cx));
let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| {
multibuffer
.push_excerpts(
@ -4199,6 +4206,7 @@ mod tests {
(Point::new(0, 0)..Point::new(0, 0), "A"),
(Point::new(1, 0)..Point::new(1, 0), "A"),
],
None,
cx,
);
multibuffer.edit(
@ -4206,6 +4214,7 @@ mod tests {
(Point::new(0, 1)..Point::new(0, 1), "B"),
(Point::new(1, 1)..Point::new(1, 1), "B"),
],
None,
cx,
);
multibuffer.end_transaction_at(now, cx);
@ -4214,19 +4223,19 @@ mod tests {
// Edit buffer 1 through the multibuffer
now += 2 * group_interval;
multibuffer.start_transaction_at(now, cx);
multibuffer.edit([(2..2, "C")], cx);
multibuffer.edit([(2..2, "C")], None, cx);
multibuffer.end_transaction_at(now, cx);
assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
// Edit buffer 1 independently
buffer_1.update(cx, |buffer_1, cx| {
buffer_1.start_transaction_at(now);
buffer_1.edit([(3..3, "D")], cx);
buffer_1.edit([(3..3, "D")], None, cx);
buffer_1.end_transaction_at(now, cx);
now += 2 * group_interval;
buffer_1.start_transaction_at(now);
buffer_1.edit([(4..4, "E")], cx);
buffer_1.edit([(4..4, "E")], None, cx);
buffer_1.end_transaction_at(now, cx);
});
assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
@ -4267,7 +4276,7 @@ mod tests {
// Redo stack gets cleared after an edit.
now += 2 * group_interval;
multibuffer.start_transaction_at(now, cx);
multibuffer.edit([(0..0, "X")], cx);
multibuffer.edit([(0..0, "X")], None, cx);
multibuffer.end_transaction_at(now, cx);
assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
multibuffer.redo(cx);

View file

@ -181,7 +181,7 @@ pub async fn match_strings(
cancel_flag: &AtomicBool,
background: Arc<executor::Background>,
) -> Vec<StringMatch> {
if candidates.is_empty() {
if candidates.is_empty() || max_results == 0 {
return Default::default();
}

View file

@ -41,6 +41,7 @@ pub struct Keystroke {
pub alt: bool,
pub shift: bool,
pub cmd: bool,
pub function: bool,
pub key: String,
}
@ -277,6 +278,7 @@ impl Keystroke {
let mut alt = false;
let mut shift = false;
let mut cmd = false;
let mut function = false;
let mut key = None;
let mut components = source.split("-").peekable();
@ -286,6 +288,7 @@ impl Keystroke {
"alt" => alt = true,
"shift" => shift = true,
"cmd" => cmd = true,
"fn" => function = true,
_ => {
if let Some(component) = components.peek() {
if component.is_empty() && source.ends_with('-') {
@ -306,6 +309,7 @@ impl Keystroke {
alt,
shift,
cmd,
function,
key: key.unwrap(),
})
}
@ -464,6 +468,7 @@ mod tests {
alt: false,
shift: false,
cmd: false,
function: false,
}
);
@ -475,6 +480,7 @@ mod tests {
alt: true,
shift: true,
cmd: false,
function: false,
}
);
@ -486,6 +492,7 @@ mod tests {
alt: false,
shift: true,
cmd: true,
function: false,
}
);

View file

@ -11,7 +11,7 @@ pub struct KeyUpEvent {
pub keystroke: Keystroke,
}
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug)]
pub struct ModifiersChangedEvent {
pub ctrl: bool,
pub alt: bool,
@ -19,7 +19,7 @@ pub struct ModifiersChangedEvent {
pub cmd: bool,
}
#[derive(Clone, Debug, Default)]
#[derive(Clone, Copy, Debug, Default)]
pub struct ScrollWheelEvent {
pub position: Vector2F,
pub delta: Vector2F,

View file

@ -210,19 +210,24 @@ impl Event {
unsafe fn parse_keystroke(native_event: id) -> Keystroke {
use cocoa::appkit::*;
let modifiers = native_event.modifierFlags();
let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
let mut shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
let mut chars_ignoring_modifiers =
CStr::from_ptr(native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char)
.to_str()
.unwrap();
let first_char = chars_ignoring_modifiers.chars().next().map(|ch| ch as u16);
let modifiers = native_event.modifierFlags();
let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
let mut shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask)
&& first_char.map_or(true, |ch| {
ch < NSUpArrowFunctionKey || ch > NSModeSwitchFunctionKey
});
#[allow(non_upper_case_globals)]
let key = match chars_ignoring_modifiers.chars().next().map(|ch| ch as u16) {
let key = match first_char {
Some(SPACE_KEY) => "space",
Some(BACKSPACE_KEY) => "backspace",
Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => "enter",
@ -282,6 +287,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
alt,
shift,
cmd,
function,
key: key.into(),
}
}

View file

@ -184,6 +184,7 @@ impl MacForegroundPlatform {
(keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
(keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
(keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
(keystroke.shift, NSEventModifierFlags::NSShiftKeyMask),
] {
if *modifier {
mask |= *flag;

View file

@ -154,6 +154,10 @@ unsafe fn build_classes() {
sel!(performKeyEquivalent:),
handle_key_equivalent as extern "C" fn(&Object, Sel, id) -> BOOL,
);
decl.add_method(
sel!(keyDown:),
handle_key_down as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(mouseDown:),
handle_view_event as extern "C" fn(&Object, Sel, id),
@ -275,7 +279,8 @@ struct WindowState {
should_close_callback: Option<Box<dyn FnMut() -> bool>>,
close_callback: Option<Box<dyn FnOnce()>>,
input_handler: Option<Box<dyn InputHandler>>,
pending_key_down_event: Option<KeyDownEvent>,
pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
performed_key_equivalent: bool,
synthetic_drag_counter: usize,
executor: Rc<executor::Foreground>,
scene_to_render: Option<Scene>,
@ -287,6 +292,11 @@ struct WindowState {
previous_modifiers_changed_event: Option<Event>,
}
struct InsertText {
replacement_range: Option<Range<usize>>,
text: String,
}
impl Window {
pub fn open(
id: usize,
@ -359,7 +369,8 @@ impl Window {
close_callback: None,
activate_callback: None,
input_handler: None,
pending_key_down_event: None,
pending_key_down: None,
performed_key_equivalent: false,
synthetic_drag_counter: 0,
executor,
scene_to_render: Default::default(),
@ -689,13 +700,28 @@ extern "C" fn dealloc_view(this: &Object, _: Sel) {
}
extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
handle_key_event(this, native_event, true)
}
extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
handle_key_event(this, native_event, false);
}
extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL {
let window_state = unsafe { get_window_state(this) };
let mut window_state_borrow = window_state.as_ref().borrow_mut();
let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) };
if let Some(event) = event {
window_state_borrow.pending_key_down_event = match event {
if key_equivalent {
window_state_borrow.performed_key_equivalent = true;
} else if window_state_borrow.performed_key_equivalent {
return NO;
}
let function_is_held;
window_state_borrow.pending_key_down = match event {
Event::KeyDown(event) => {
let keydown = event.keystroke.clone();
// Ignore events from held-down keys after some of the initially-pressed keys
@ -708,19 +734,23 @@ extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) ->
window_state_borrow.last_fresh_keydown = Some(keydown);
}
Some(event)
function_is_held = event.keystroke.function;
Some((event, None))
}
_ => return NO,
};
drop(window_state_borrow);
unsafe {
let input_context: id = msg_send![this, inputContext];
let _: BOOL = msg_send![input_context, handleEvent: native_event];
if !function_is_held {
unsafe {
let input_context: id = msg_send![this, inputContext];
let _: BOOL = msg_send![input_context, handleEvent: native_event];
}
}
let mut handled = false;
let mut window_state_borrow = window_state.borrow_mut();
if let Some(event) = window_state_borrow.pending_key_down_event.take() {
if let Some((event, insert_text)) = window_state_borrow.pending_key_down.take() {
if let Some(mut callback) = window_state_borrow.event_callback.take() {
drop(window_state_borrow);
@ -729,14 +759,26 @@ extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) ->
.flatten()
.is_some();
if !is_composing {
callback(Event::KeyDown(event));
handled = callback(Event::KeyDown(event));
}
if !handled {
if let Some(insert) = insert_text {
handled = true;
with_input_handler(this, |input_handler| {
input_handler
.replace_text_in_range(insert.replacement_range, &insert.text)
});
}
}
window_state.borrow_mut().event_callback = Some(callback);
}
} else {
handled = true;
}
YES
handled as BOOL
} else {
NO
}
@ -819,6 +861,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
ctrl: false,
alt: false,
shift: false,
function: false,
key: ".".into(),
};
let event = Event::KeyDown(KeyDownEvent {
@ -837,6 +880,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
extern "C" fn send_event(this: &Object, _: Sel, native_event: id) {
unsafe {
let () = msg_send![super(this, class!(NSWindow)), sendEvent: native_event];
get_window_state(this).borrow_mut().performed_key_equivalent = false;
}
}
@ -1042,7 +1086,7 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS
unsafe {
let window_state = get_window_state(this);
let mut window_state_borrow = window_state.borrow_mut();
let pending_key_down_event = window_state_borrow.pending_key_down_event.take();
let pending_key_down = window_state_borrow.pending_key_down.take();
drop(window_state_borrow);
let is_attributed_string: BOOL =
@ -1062,24 +1106,17 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS
.flatten()
.is_some();
if is_composing || text.chars().count() > 1 || pending_key_down_event.is_none() {
if is_composing || text.chars().count() > 1 || pending_key_down.is_none() {
with_input_handler(this, |input_handler| {
input_handler.replace_text_in_range(replacement_range, text)
});
} else {
let mut handled = false;
let event_callback = window_state.borrow_mut().event_callback.take();
if let Some(mut event_callback) = event_callback {
handled = event_callback(Event::KeyDown(pending_key_down_event.unwrap()));
window_state.borrow_mut().event_callback = Some(event_callback);
}
if !handled {
with_input_handler(this, |input_handler| {
input_handler.replace_text_in_range(replacement_range, text)
});
}
let mut pending_key_down = pending_key_down.unwrap();
pending_key_down.1 = Some(InsertText {
replacement_range,
text: text.to_string(),
});
window_state.borrow_mut().pending_key_down = Some(pending_key_down);
}
}
}
@ -1092,10 +1129,7 @@ extern "C" fn set_marked_text(
replacement_range: NSRange,
) {
unsafe {
get_window_state(this)
.borrow_mut()
.pending_key_down_event
.take();
get_window_state(this).borrow_mut().pending_key_down.take();
let is_attributed_string: BOOL =
msg_send![text, isKindOfClass: [class!(NSAttributedString)]];

View file

@ -16,6 +16,7 @@ test-support = [
"text/test-support",
"tree-sitter-rust",
"tree-sitter-typescript",
"settings/test-support",
"util/test-support",
]
@ -27,6 +28,7 @@ fuzzy = { path = "../fuzzy" }
gpui = { path = "../gpui" }
lsp = { path = "../lsp" }
rpc = { path = "../rpc" }
settings = { path = "../settings" }
sum_tree = { path = "../sum_tree" }
text = { path = "../text" }
theme = { path = "../theme" }
@ -56,6 +58,7 @@ collections = { path = "../collections", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
text = { path = "../text", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
ctor = "0.1"
env_logger = "0.9"

View file

@ -14,12 +14,13 @@ use futures::FutureExt as _;
use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, MutableAppContext, Task};
use lazy_static::lazy_static;
use parking_lot::Mutex;
use settings::Settings;
use similar::{ChangeTag, TextDiff};
use smol::future::yield_now;
use std::{
any::Any,
cmp::{self, Ordering},
collections::{BTreeMap, HashMap},
collections::BTreeMap,
ffi::OsStr,
future::Future,
iter::{self, Iterator, Peekable},
@ -228,12 +229,37 @@ struct SyntaxTree {
version: clock::Global,
}
#[derive(Clone, Debug)]
pub enum AutoindentMode {
/// Indent each line of inserted text.
EachLine,
/// Apply the same indentation adjustment to all of the lines
/// in a given insertion.
Block {
/// The original indentation level of the first line of each
/// insertion, if it has been copied.
original_indent_columns: Vec<u32>,
},
}
#[derive(Clone)]
struct AutoindentRequest {
before_edit: BufferSnapshot,
edited: Vec<Anchor>,
inserted: Option<Vec<Range<Anchor>>>,
entries: Vec<AutoindentRequestEntry>,
indent_size: IndentSize,
is_block_mode: bool,
}
#[derive(Clone)]
struct AutoindentRequestEntry {
/// A range of the buffer whose indentation should be adjusted.
range: Range<Anchor>,
/// Whether or not these lines should be considered brand new, for the
/// purpose of auto-indent. When text is not new, its indentation will
/// only be adjusted if the suggested indentation level has *changed*
/// since the edit was made.
first_line_is_new: bool,
original_indent_column: Option<u32>,
}
#[derive(Debug)]
@ -796,19 +822,25 @@ impl Buffer {
Some(async move {
let mut indent_sizes = BTreeMap::new();
for request in autoindent_requests {
let old_to_new_rows = request
.edited
.iter()
.map(|anchor| anchor.summary::<Point>(&request.before_edit).row)
.zip(
request
.edited
.iter()
.map(|anchor| anchor.summary::<Point>(&snapshot).row),
)
.collect::<BTreeMap<u32, u32>>();
// Resolve each edited range to its row in the current buffer and in the
// buffer before this batch of edits.
let mut row_ranges = Vec::new();
let mut old_to_new_rows = BTreeMap::new();
for entry in &request.entries {
let position = entry.range.start;
let new_row = position.to_point(&snapshot).row;
let new_end_row = entry.range.end.to_point(&snapshot).row + 1;
if !entry.first_line_is_new {
let old_row = position.to_point(&request.before_edit).row;
old_to_new_rows.insert(old_row, new_row);
}
row_ranges.push((new_row..new_end_row, entry.original_indent_column));
}
let mut old_suggestions = HashMap::<u32, IndentSize>::default();
// Build a map containing the suggested indentation for each of the edited lines
// with respect to the state of the buffer before these edits. This map is keyed
// by the rows for these lines in the current state of the buffer.
let mut old_suggestions = BTreeMap::<u32, IndentSize>::default();
let old_edited_ranges =
contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields);
for old_edited_range in old_edited_ranges {
@ -819,19 +851,15 @@ impl Buffer {
.flatten();
for (old_row, suggestion) in old_edited_range.zip(suggestions) {
if let Some(suggestion) = suggestion {
let mut suggested_indent = old_to_new_rows
let suggested_indent = old_to_new_rows
.get(&suggestion.basis_row)
.and_then(|from_row| old_suggestions.get(from_row).copied())
.unwrap_or_else(|| {
request
.before_edit
.indent_size_for_line(suggestion.basis_row)
});
if suggestion.delta.is_gt() {
suggested_indent += request.indent_size;
} else if suggestion.delta.is_lt() {
suggested_indent -= request.indent_size;
}
})
.with_delta(suggestion.delta, request.indent_size);
old_suggestions
.insert(*old_to_new_rows.get(&old_row).unwrap(), suggested_indent);
}
@ -839,10 +867,21 @@ impl Buffer {
yield_now().await;
}
// At this point, old_suggestions contains the suggested indentation for all edited lines with respect to the state of the
// buffer before the edit, but keyed by the row for these lines after the edits were applied.
let new_edited_row_ranges =
contiguous_ranges(old_to_new_rows.values().copied(), max_rows_between_yields);
// In block mode, only compute indentation suggestions for the first line
// of each insertion. Otherwise, compute suggestions for every inserted line.
let new_edited_row_ranges = contiguous_ranges(
row_ranges.iter().flat_map(|(range, _)| {
if request.is_block_mode {
range.start..range.start + 1
} else {
range.clone()
}
}),
max_rows_between_yields,
);
// Compute new suggestions for each line, but only include them in the result
// if they differ from the old suggestion for that line.
for new_edited_row_range in new_edited_row_ranges {
let suggestions = snapshot
.suggest_autoindents(new_edited_row_range.clone())
@ -850,17 +889,13 @@ impl Buffer {
.flatten();
for (new_row, suggestion) in new_edited_row_range.zip(suggestions) {
if let Some(suggestion) = suggestion {
let mut suggested_indent = indent_sizes
let suggested_indent = indent_sizes
.get(&suggestion.basis_row)
.copied()
.unwrap_or_else(|| {
snapshot.indent_size_for_line(suggestion.basis_row)
});
if suggestion.delta.is_gt() {
suggested_indent += request.indent_size;
} else if suggestion.delta.is_lt() {
suggested_indent -= request.indent_size;
}
})
.with_delta(suggestion.delta, request.indent_size);
if old_suggestions
.get(&new_row)
.map_or(true, |old_indentation| {
@ -874,36 +909,40 @@ impl Buffer {
yield_now().await;
}
if let Some(inserted) = request.inserted.as_ref() {
let inserted_row_ranges = contiguous_ranges(
inserted
.iter()
.map(|range| range.to_point(&snapshot))
.flat_map(|range| range.start.row..range.end.row + 1),
max_rows_between_yields,
);
for inserted_row_range in inserted_row_ranges {
let suggestions = snapshot
.suggest_autoindents(inserted_row_range.clone())
// For each block of inserted text, adjust the indentation of the remaining
// lines of the block by the same amount as the first line was adjusted.
if request.is_block_mode {
for (row_range, original_indent_column) in
row_ranges
.into_iter()
.flatten();
for (row, suggestion) in inserted_row_range.zip(suggestions) {
if let Some(suggestion) = suggestion {
let mut suggested_indent = indent_sizes
.get(&suggestion.basis_row)
.copied()
.unwrap_or_else(|| {
snapshot.indent_size_for_line(suggestion.basis_row)
});
if suggestion.delta.is_gt() {
suggested_indent += request.indent_size;
} else if suggestion.delta.is_lt() {
suggested_indent -= request.indent_size;
.filter_map(|(range, original_indent_column)| {
if range.len() > 1 {
Some((range, original_indent_column?))
} else {
None
}
indent_sizes.insert(row, suggested_indent);
})
{
let new_indent = indent_sizes
.get(&row_range.start)
.copied()
.unwrap_or_else(|| snapshot.indent_size_for_line(row_range.start));
let delta = new_indent.len as i64 - original_indent_column as i64;
if delta != 0 {
for row in row_range.skip(1) {
indent_sizes.entry(row).or_insert_with(|| {
let mut size = snapshot.indent_size_for_line(row);
if size.kind == new_indent.kind {
if delta > 0 {
size.len = size.len + delta as u32;
} else if delta < 0 {
size.len = size.len.saturating_sub(-delta as u32);
}
}
size
});
}
}
yield_now().await;
}
}
}
@ -945,6 +984,7 @@ impl Buffer {
.take((size.len - current_size.len) as usize)
.collect::<String>(),
)],
None,
cx,
);
} else if size.len < current_size.len {
@ -953,6 +993,7 @@ impl Buffer {
Point::new(row, 0)..Point::new(row, current_size.len - size.len),
"",
)],
None,
cx,
);
}
@ -990,7 +1031,7 @@ impl Buffer {
match tag {
ChangeTag::Equal => offset += len,
ChangeTag::Delete => {
self.edit([(range, "")], cx);
self.edit([(range, "")], None, cx);
}
ChangeTag::Insert => {
self.edit(
@ -999,6 +1040,7 @@ impl Buffer {
&diff.new_text[range.start - diff.start_offset
..range.end - diff.start_offset],
)],
None,
cx,
);
offset += len;
@ -1135,40 +1177,13 @@ impl Buffer {
where
T: Into<Arc<str>>,
{
self.edit_internal([(0..self.len(), text)], None, cx)
self.edit([(0..self.len(), text)], None, cx)
}
pub fn edit<I, S, T>(
&mut self,
edits_iter: I,
cx: &mut ModelContext<Self>,
) -> Option<clock::Local>
where
I: IntoIterator<Item = (Range<S>, T)>,
S: ToOffset,
T: Into<Arc<str>>,
{
self.edit_internal(edits_iter, None, cx)
}
pub fn edit_with_autoindent<I, S, T>(
&mut self,
edits_iter: I,
indent_size: IndentSize,
cx: &mut ModelContext<Self>,
) -> Option<clock::Local>
where
I: IntoIterator<Item = (Range<S>, T)>,
S: ToOffset,
T: Into<Arc<str>>,
{
self.edit_internal(edits_iter, Some(indent_size), cx)
}
pub fn edit_internal<I, S, T>(
&mut self,
edits_iter: I,
autoindent_size: Option<IndentSize>,
autoindent_mode: Option<AutoindentMode>,
cx: &mut ModelContext<Self>,
) -> Option<clock::Local>
where
@ -1203,58 +1218,79 @@ impl Buffer {
self.start_transaction();
self.pending_autoindent.take();
let autoindent_request =
self.language
.as_ref()
.and_then(|_| autoindent_size)
.map(|autoindent_size| {
let before_edit = self.snapshot();
let edited = edits
.iter()
.filter_map(|(range, new_text)| {
let start = range.start.to_point(self);
if new_text.starts_with('\n')
&& start.column == self.line_len(start.row)
{
None
} else {
Some(self.anchor_before(range.start))
}
})
.collect();
(before_edit, edited, autoindent_size)
});
let autoindent_request = autoindent_mode
.and_then(|mode| self.language.as_ref().map(|_| (self.snapshot(), mode)));
let edit_operation = self.text.edit(edits.iter().cloned());
let edit_id = edit_operation.local_timestamp();
if let Some((before_edit, edited, size)) = autoindent_request {
let mut delta = 0isize;
let inserted_ranges = edits
.into_iter()
.zip(&edit_operation.as_edit().unwrap().new_text)
.filter_map(|((range, _), new_text)| {
let first_newline_ix = new_text.find('\n')?;
let new_text_len = new_text.len();
let start = (delta + range.start as isize) as usize + first_newline_ix + 1;
let end = (delta + range.start as isize) as usize + new_text_len;
delta += new_text_len as isize - (range.end as isize - range.start as isize);
Some(self.anchor_before(start)..self.anchor_after(end))
})
.collect::<Vec<Range<Anchor>>>();
let inserted = if inserted_ranges.is_empty() {
None
if let Some((before_edit, mode)) = autoindent_request {
let language_name = self.language().map(|language| language.name());
let settings = cx.global::<Settings>();
let indent_size = if settings.hard_tabs(language_name.as_deref()) {
IndentSize::tab()
} else {
Some(inserted_ranges)
IndentSize::spaces(settings.tab_size(language_name.as_deref()).get())
};
let (start_columns, is_block_mode) = match mode {
AutoindentMode::Block {
original_indent_columns: start_columns,
} => (start_columns, true),
AutoindentMode::EachLine => (Default::default(), false),
};
let mut delta = 0isize;
let entries = edits
.into_iter()
.enumerate()
.zip(&edit_operation.as_edit().unwrap().new_text)
.map(|((ix, (range, _)), new_text)| {
let new_text_len = new_text.len();
let old_start = range.start.to_point(&before_edit);
let new_start = (delta + range.start as isize) as usize;
delta += new_text_len as isize - (range.end as isize - range.start as isize);
let mut range_of_insertion_to_indent = 0..new_text_len;
let mut first_line_is_new = false;
let mut start_column = None;
// When inserting an entire line at the beginning of an existing line,
// treat the insertion as new.
if new_text.contains('\n')
&& old_start.column <= before_edit.indent_size_for_line(old_start.row).len
{
first_line_is_new = true;
}
// When inserting text starting with a newline, avoid auto-indenting the
// previous line.
if new_text[range_of_insertion_to_indent.clone()].starts_with('\n') {
range_of_insertion_to_indent.start += 1;
first_line_is_new = true;
}
// Avoid auto-indenting before the insertion.
if is_block_mode {
start_column = start_columns.get(ix).copied();
if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') {
range_of_insertion_to_indent.end -= 1;
}
}
AutoindentRequestEntry {
first_line_is_new,
original_indent_column: start_column,
range: self.anchor_before(new_start + range_of_insertion_to_indent.start)
..self.anchor_after(new_start + range_of_insertion_to_indent.end),
}
})
.collect();
self.autoindent_requests.push(Arc::new(AutoindentRequest {
before_edit,
edited,
inserted,
indent_size: size,
entries,
indent_size,
is_block_mode,
}));
}
@ -1541,7 +1577,7 @@ impl Buffer {
edits.push((range, new_text));
}
log::info!("mutating buffer {} with {:?}", self.replica_id(), edits);
self.edit(edits, cx);
self.edit(edits, None, cx);
}
pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng, cx: &mut ModelContext<Self>) {
@ -2139,8 +2175,12 @@ impl BufferSnapshot {
}
pub fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize {
indent_size_for_text(text.chars_at(Point::new(row, 0)))
}
pub fn indent_size_for_text(text: impl Iterator<Item = char>) -> IndentSize {
let mut result = IndentSize::spaces(0);
for c in text.chars_at(Point::new(row, 0)) {
for c in text {
let kind = match c {
' ' => IndentKind::Space,
'\t' => IndentKind::Tab,
@ -2503,23 +2543,24 @@ impl IndentSize {
IndentKind::Tab => '\t',
}
}
}
impl std::ops::AddAssign for IndentSize {
fn add_assign(&mut self, other: IndentSize) {
if self.len == 0 {
*self = other;
} else if self.kind == other.kind {
self.len += other.len;
}
}
}
impl std::ops::SubAssign for IndentSize {
fn sub_assign(&mut self, other: IndentSize) {
if self.kind == other.kind && self.len >= other.len {
self.len -= other.len;
pub fn with_delta(mut self, direction: Ordering, size: IndentSize) -> Self {
match direction {
Ordering::Less => {
if self.kind == size.kind && self.len >= size.len {
self.len -= size.len;
}
}
Ordering::Equal => {}
Ordering::Greater => {
if self.len == 0 {
self = size;
} else if self.kind == size.kind {
self.len += size.len;
}
}
}
self
}
}

View file

@ -3,6 +3,7 @@ use clock::ReplicaId;
use collections::BTreeMap;
use gpui::{ModelHandle, MutableAppContext};
use rand::prelude::*;
use settings::Settings;
use std::{
cell::RefCell,
env,
@ -24,6 +25,7 @@ fn init_logger() {
#[gpui::test]
fn test_line_endings(cx: &mut gpui::MutableAppContext) {
cx.set_global(Settings::test(cx));
cx.add_model(|cx| {
let mut buffer =
Buffer::new(0, "one\r\ntwo\rthree", cx).with_language(Arc::new(rust_lang()), cx);
@ -31,12 +33,12 @@ fn test_line_endings(cx: &mut gpui::MutableAppContext) {
assert_eq!(buffer.line_ending(), LineEnding::Windows);
buffer.check_invariants();
buffer.edit_with_autoindent(
buffer.edit(
[(buffer.len()..buffer.len(), "\r\nfour")],
IndentSize::spaces(2),
Some(AutoindentMode::EachLine),
cx,
);
buffer.edit([(0..0, "zero\r\n")], cx);
buffer.edit([(0..0, "zero\r\n")], None, cx);
assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour");
assert_eq!(buffer.line_ending(), LineEnding::Windows);
buffer.check_invariants();
@ -116,7 +118,7 @@ fn test_edit_events(cx: &mut gpui::MutableAppContext) {
// An edit emits an edited event, followed by a dirty changed event,
// since the buffer was previously in a clean state.
buffer.edit([(2..4, "XYZ")], cx);
buffer.edit([(2..4, "XYZ")], None, cx);
// An empty transaction does not emit any events.
buffer.start_transaction();
@ -125,8 +127,8 @@ fn test_edit_events(cx: &mut gpui::MutableAppContext) {
// A transaction containing two edits emits one edited event.
now += Duration::from_secs(1);
buffer.start_transaction_at(now);
buffer.edit([(5..5, "u")], cx);
buffer.edit([(6..6, "w")], cx);
buffer.edit([(5..5, "u")], None, cx);
buffer.edit([(6..6, "w")], None, cx);
buffer.end_transaction_at(now, cx);
// Undoing a transaction emits one edited event.
@ -226,11 +228,11 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
buf.start_transaction();
let offset = buf.text().find(")").unwrap();
buf.edit([(offset..offset, "b: C")], cx);
buf.edit([(offset..offset, "b: C")], None, cx);
assert!(!buf.is_parsing());
let offset = buf.text().find("}").unwrap();
buf.edit([(offset..offset, " d; ")], cx);
buf.edit([(offset..offset, " d; ")], None, cx);
assert!(!buf.is_parsing());
buf.end_transaction(cx);
@ -255,19 +257,19 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
// * add a turbofish to the method call
buffer.update(cx, |buf, cx| {
let offset = buf.text().find(";").unwrap();
buf.edit([(offset..offset, ".e")], cx);
buf.edit([(offset..offset, ".e")], None, cx);
assert_eq!(buf.text(), "fn a(b: C) { d.e; }");
assert!(buf.is_parsing());
});
buffer.update(cx, |buf, cx| {
let offset = buf.text().find(";").unwrap();
buf.edit([(offset..offset, "(f)")], cx);
buf.edit([(offset..offset, "(f)")], None, cx);
assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }");
assert!(buf.is_parsing());
});
buffer.update(cx, |buf, cx| {
let offset = buf.text().find("(f)").unwrap();
buf.edit([(offset..offset, "::<G>")], cx);
buf.edit([(offset..offset, "::<G>")], None, cx);
assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
assert!(buf.is_parsing());
});
@ -545,6 +547,7 @@ async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
#[gpui::test]
fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
cx.set_global(Settings::test(cx));
let buffer = cx.add_model(|cx| {
let text = "
mod x {
@ -620,34 +623,37 @@ fn test_range_for_syntax_ancestor(cx: &mut MutableAppContext) {
#[gpui::test]
fn test_autoindent_with_soft_tabs(cx: &mut MutableAppContext) {
let settings = Settings::test(cx);
cx.set_global(settings);
cx.add_model(|cx| {
let text = "fn a() {}";
let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
buffer.edit_with_autoindent([(8..8, "\n\n")], IndentSize::spaces(4), cx);
buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "fn a() {\n \n}");
buffer.edit_with_autoindent(
buffer.edit(
[(Point::new(1, 4)..Point::new(1, 4), "b()\n")],
IndentSize::spaces(4),
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(buffer.text(), "fn a() {\n b()\n \n}");
// Create a field expression on a new line, causing that line
// to be indented.
buffer.edit_with_autoindent(
buffer.edit(
[(Point::new(2, 4)..Point::new(2, 4), ".c")],
IndentSize::spaces(4),
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}");
// Remove the dot so that the line is no longer a field expression,
// causing the line to be outdented.
buffer.edit_with_autoindent(
buffer.edit(
[(Point::new(2, 8)..Point::new(2, 9), "")],
IndentSize::spaces(4),
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(buffer.text(), "fn a() {\n b()\n c\n}");
@ -658,34 +664,38 @@ fn test_autoindent_with_soft_tabs(cx: &mut MutableAppContext) {
#[gpui::test]
fn test_autoindent_with_hard_tabs(cx: &mut MutableAppContext) {
let mut settings = Settings::test(cx);
settings.editor_overrides.hard_tabs = Some(true);
cx.set_global(settings);
cx.add_model(|cx| {
let text = "fn a() {}";
let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
buffer.edit_with_autoindent([(8..8, "\n\n")], IndentSize::tab(), cx);
buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "fn a() {\n\t\n}");
buffer.edit_with_autoindent(
buffer.edit(
[(Point::new(1, 1)..Point::new(1, 1), "b()\n")],
IndentSize::tab(),
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\n}");
// Create a field expression on a new line, causing that line
// to be indented.
buffer.edit_with_autoindent(
buffer.edit(
[(Point::new(2, 1)..Point::new(2, 1), ".c")],
IndentSize::tab(),
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\t.c\n}");
// Remove the dot so that the line is no longer a field expression,
// causing the line to be outdented.
buffer.edit_with_autoindent(
buffer.edit(
[(Point::new(2, 2)..Point::new(2, 3), "")],
IndentSize::tab(),
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(buffer.text(), "fn a() {\n\tb()\n\tc\n}");
@ -696,6 +706,9 @@ fn test_autoindent_with_hard_tabs(cx: &mut MutableAppContext) {
#[gpui::test]
fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) {
let settings = Settings::test(cx);
cx.set_global(settings);
cx.add_model(|cx| {
let text = "
fn a() {
@ -709,12 +722,12 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta
// Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
// their indentation is not adjusted.
buffer.edit_with_autoindent(
buffer.edit(
[
(empty(Point::new(1, 1)), "()"),
(empty(Point::new(2, 1)), "()"),
],
IndentSize::spaces(4),
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(
@ -730,12 +743,12 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta
// When appending new content after these lines, the indentation is based on the
// preceding lines' actual indentation.
buffer.edit_with_autoindent(
buffer.edit(
[
(empty(Point::new(1, 1)), "\n.f\n.g"),
(empty(Point::new(2, 1)), "\n.f\n.g"),
],
IndentSize::spaces(4),
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(
@ -756,26 +769,54 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta
});
cx.add_model(|cx| {
let text = "fn a() {\n {\n b()?\n }\n\n Ok(())\n}";
let text = "
fn a() {
{
b()?
}
Ok(())
}
"
.unindent();
let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
buffer.edit_with_autoindent(
// Delete a closing curly brace changes the suggested indent for the line.
buffer.edit(
[(Point::new(3, 4)..Point::new(3, 5), "")],
IndentSize::spaces(4),
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(
buffer.text(),
"fn a() {\n {\n b()?\n \n\n Ok(())\n}"
"
fn a() {
{
b()?
|
Ok(())
}
"
.replace("|", "") // included in the string to preserve trailing whites
.unindent()
);
buffer.edit_with_autoindent(
// Manually editing the leading whitespace
buffer.edit(
[(Point::new(3, 0)..Point::new(3, 12), "")],
IndentSize::spaces(4),
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(
buffer.text(),
"fn a() {\n {\n b()?\n\n\n Ok(())\n}"
"
fn a() {
{
b()?
Ok(())
}
"
.unindent()
);
buffer
});
@ -783,6 +824,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta
#[gpui::test]
fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) {
cx.set_global(Settings::test(cx));
cx.add_model(|cx| {
let text = "
fn a() {}
@ -791,7 +833,7 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte
let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
buffer.edit_with_autoindent([(5..5, "\nb")], IndentSize::spaces(4), cx);
buffer.edit([(5..5, "\nb")], Some(AutoindentMode::EachLine), cx);
assert_eq!(
buffer.text(),
"
@ -803,9 +845,9 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte
// The indentation suggestion changed because `@end` node (a close paren)
// is now at the beginning of the line.
buffer.edit_with_autoindent(
buffer.edit(
[(Point::new(1, 4)..Point::new(1, 5), "")],
IndentSize::spaces(4),
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(
@ -823,17 +865,137 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte
#[gpui::test]
fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut MutableAppContext) {
cx.set_global(Settings::test(cx));
cx.add_model(|cx| {
let text = "a\nb";
let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
buffer.edit_with_autoindent([(0..1, "\n"), (2..3, "\n")], IndentSize::spaces(4), cx);
buffer.edit(
[(0..1, "\n"), (2..3, "\n")],
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(buffer.text(), "\n\n\n");
buffer
});
}
#[gpui::test]
fn test_autoindent_disabled(cx: &mut MutableAppContext) {
fn test_autoindent_multi_line_insertion(cx: &mut MutableAppContext) {
cx.set_global(Settings::test(cx));
cx.add_model(|cx| {
let text = "
const a: usize = 1;
fn b() {
if c {
let d = 2;
}
}
"
.unindent();
let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
buffer.edit(
[(Point::new(3, 0)..Point::new(3, 0), "e(\n f()\n);\n")],
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(
buffer.text(),
"
const a: usize = 1;
fn b() {
if c {
e(
f()
);
let d = 2;
}
}
"
.unindent()
);
buffer
});
}
#[gpui::test]
fn test_autoindent_block_mode(cx: &mut MutableAppContext) {
cx.set_global(Settings::test(cx));
cx.add_model(|cx| {
let text = r#"
fn a() {
b();
}
"#
.unindent();
let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
let inserted_text = r#"
"
c
d
e
"
"#
.unindent();
// Insert the block at column zero. The entire block is indented
// so that the first line matches the previous line's indentation.
buffer.edit(
[(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())],
Some(AutoindentMode::Block {
original_indent_columns: vec![0],
}),
cx,
);
assert_eq!(
buffer.text(),
r#"
fn a() {
b();
"
c
d
e
"
}
"#
.unindent()
);
// Insert the block at a deeper indent level. The entire block is outdented.
buffer.undo(cx);
buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx);
buffer.edit(
[(Point::new(2, 8)..Point::new(2, 8), inserted_text.clone())],
Some(AutoindentMode::Block {
original_indent_columns: vec![0],
}),
cx,
);
assert_eq!(
buffer.text(),
r#"
fn a() {
b();
"
c
d
e
"
}
"#
.unindent()
);
buffer
});
}
#[gpui::test]
fn test_autoindent_language_without_indents_query(cx: &mut MutableAppContext) {
cx.set_global(Settings::test(cx));
cx.add_model(|cx| {
let text = "
* one
@ -853,9 +1015,9 @@ fn test_autoindent_disabled(cx: &mut MutableAppContext) {
)),
cx,
);
buffer.edit_with_autoindent(
buffer.edit(
[(Point::new(3, 0)..Point::new(3, 0), "\n")],
IndentSize::spaces(4),
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(
@ -879,18 +1041,18 @@ fn test_serialization(cx: &mut gpui::MutableAppContext) {
let buffer1 = cx.add_model(|cx| {
let mut buffer = Buffer::new(0, "abc", cx);
buffer.edit([(3..3, "D")], cx);
buffer.edit([(3..3, "D")], None, cx);
now += Duration::from_secs(1);
buffer.start_transaction_at(now);
buffer.edit([(4..4, "E")], cx);
buffer.edit([(4..4, "E")], None, cx);
buffer.end_transaction_at(now, cx);
assert_eq!(buffer.text(), "abcDE");
buffer.undo(cx);
assert_eq!(buffer.text(), "abcD");
buffer.edit([(4..4, "F")], cx);
buffer.edit([(4..4, "F")], None, cx);
assert_eq!(buffer.text(), "abcDF");
buffer
});

View file

@ -1,3 +1,4 @@
pub use lsp_types::request::*;
pub use lsp_types::*;
use anyhow::{anyhow, Context, Result};

View file

@ -1,5 +1,5 @@
use anyhow::Result;
use std::path::PathBuf;
use std::path::Path;
use std::sync::Arc;
pub struct Db(DbStore);
@ -16,8 +16,8 @@ enum DbStore {
impl Db {
/// Open or create a database at the given file path.
pub fn open(path: PathBuf) -> Result<Arc<Self>> {
let db = rocksdb::DB::open_default(&path)?;
pub fn open(path: &Path) -> Result<Arc<Self>> {
let db = rocksdb::DB::open_default(path)?;
Ok(Arc::new(Self(DbStore::Real(db))))
}
@ -125,7 +125,7 @@ mod tests {
fn test_db() {
let dir = TempDir::new("db-test").unwrap();
let fake_db = Db::open_fake();
let real_db = Db::open(dir.path().join("test.db")).unwrap();
let real_db = Db::open(&dir.path().join("test.db")).unwrap();
for db in [&real_db, &fake_db] {
assert_eq!(
@ -152,7 +152,7 @@ mod tests {
drop(real_db);
let real_db = Db::open(dir.path().join("test.db")).unwrap();
let real_db = Db::open(&dir.path().join("test.db")).unwrap();
assert_eq!(
real_db.read(["key-1", "key-2", "key-3"]).unwrap(),
&[Some("one".as_bytes().to_vec()), None, None,]

View file

@ -8,11 +8,11 @@ use gpui::{AppContext, AsyncAppContext, ModelHandle};
use language::{
point_from_lsp, point_to_lsp,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
range_from_lsp, Anchor, Bias, Buffer, PointUtf16, ToPointUtf16,
range_from_lsp, Anchor, Bias, Buffer, CachedLspAdapter, PointUtf16, ToPointUtf16,
};
use lsp::{DocumentHighlightKind, ServerCapabilities};
use lsp::{DocumentHighlightKind, LanguageServer, ServerCapabilities};
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
use std::{cmp::Reverse, ops::Range, path::Path};
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
#[async_trait(?Send)]
pub(crate) trait LspCommand: 'static + Sized {
@ -75,6 +75,10 @@ pub(crate) struct GetDefinition {
pub position: PointUtf16,
}
pub(crate) struct GetTypeDefinition {
pub position: PointUtf16,
}
pub(crate) struct GetReferences {
pub position: PointUtf16,
}
@ -238,13 +242,7 @@ impl LspCommand for PerformRename {
mut cx: AsyncAppContext,
) -> Result<ProjectTransaction> {
if let Some(edit) = message {
let (lsp_adapter, lsp_server) = project
.read_with(&cx, |project, cx| {
project
.language_server_for_buffer(buffer.read(cx), cx)
.map(|(adapter, server)| (adapter.clone(), server.clone()))
})
.ok_or_else(|| anyhow!("no language server found for buffer"))?;
let (lsp_adapter, lsp_server) = language_server_for_buffer(&project, &buffer, &mut cx)?;
Project::deserialize_workspace_edit(
project,
edit,
@ -352,83 +350,9 @@ impl LspCommand for GetDefinition {
message: Option<lsp::GotoDefinitionResponse>,
project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext,
cx: AsyncAppContext,
) -> Result<Vec<LocationLink>> {
let mut definitions = Vec::new();
let (lsp_adapter, language_server) = project
.read_with(&cx, |project, cx| {
project
.language_server_for_buffer(buffer.read(cx), cx)
.map(|(adapter, server)| (adapter.clone(), server.clone()))
})
.ok_or_else(|| anyhow!("no language server found for buffer"))?;
if let Some(message) = message {
let mut unresolved_links = Vec::new();
match message {
lsp::GotoDefinitionResponse::Scalar(loc) => {
unresolved_links.push((None, loc.uri, loc.range));
}
lsp::GotoDefinitionResponse::Array(locs) => {
unresolved_links.extend(locs.into_iter().map(|l| (None, l.uri, l.range)));
}
lsp::GotoDefinitionResponse::Link(links) => {
unresolved_links.extend(links.into_iter().map(|l| {
(
l.origin_selection_range,
l.target_uri,
l.target_selection_range,
)
}));
}
}
for (origin_range, target_uri, target_range) in unresolved_links {
let target_buffer_handle = project
.update(&mut cx, |this, cx| {
this.open_local_buffer_via_lsp(
target_uri,
language_server.server_id(),
lsp_adapter.name.clone(),
cx,
)
})
.await?;
cx.read(|cx| {
let origin_location = origin_range.map(|origin_range| {
let origin_buffer = buffer.read(cx);
let origin_start = origin_buffer
.clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left);
let origin_end = origin_buffer
.clip_point_utf16(point_from_lsp(origin_range.end), Bias::Left);
Location {
buffer: buffer.clone(),
range: origin_buffer.anchor_after(origin_start)
..origin_buffer.anchor_before(origin_end),
}
});
let target_buffer = target_buffer_handle.read(cx);
let target_start = target_buffer
.clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
let target_end = target_buffer
.clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
let target_location = Location {
buffer: target_buffer_handle,
range: target_buffer.anchor_after(target_start)
..target_buffer.anchor_before(target_end),
};
definitions.push(LocationLink {
origin: origin_location,
target: target_location,
})
});
}
}
Ok(definitions)
location_links_from_lsp(message, project, buffer, cx).await
}
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition {
@ -469,32 +393,7 @@ impl LspCommand for GetDefinition {
_: &clock::Global,
cx: &AppContext,
) -> proto::GetDefinitionResponse {
let links = response
.into_iter()
.map(|definition| {
let origin = definition.origin.map(|origin| {
let buffer = project.serialize_buffer_for_peer(&origin.buffer, peer_id, cx);
proto::Location {
start: Some(serialize_anchor(&origin.range.start)),
end: Some(serialize_anchor(&origin.range.end)),
buffer: Some(buffer),
}
});
let buffer =
project.serialize_buffer_for_peer(&definition.target.buffer, peer_id, cx);
let target = proto::Location {
start: Some(serialize_anchor(&definition.target.range.start)),
end: Some(serialize_anchor(&definition.target.range.end)),
buffer: Some(buffer),
};
proto::LocationLink {
origin,
target: Some(target),
}
})
.collect();
let links = location_links_to_proto(response, project, peer_id, cx);
proto::GetDefinitionResponse { links }
}
@ -503,61 +402,9 @@ impl LspCommand for GetDefinition {
message: proto::GetDefinitionResponse,
project: ModelHandle<Project>,
_: ModelHandle<Buffer>,
mut cx: AsyncAppContext,
cx: AsyncAppContext,
) -> Result<Vec<LocationLink>> {
let mut links = Vec::new();
for link in message.links {
let origin = match link.origin {
Some(origin) => {
let buffer = origin
.buffer
.ok_or_else(|| anyhow!("missing origin buffer"))?;
let buffer = project
.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
.await?;
let start = origin
.start
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("missing origin start"))?;
let end = origin
.end
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("missing origin end"))?;
buffer
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
.await;
Some(Location {
buffer,
range: start..end,
})
}
None => None,
};
let target = link.target.ok_or_else(|| anyhow!("missing target"))?;
let buffer = target.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
let buffer = project
.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
.await?;
let start = target
.start
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("missing target start"))?;
let end = target
.end
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("missing target end"))?;
buffer
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
.await;
let target = Location {
buffer,
range: start..end,
};
links.push(LocationLink { origin, target })
}
Ok(links)
location_links_from_proto(message.links, project, cx).await
}
fn buffer_id_from_proto(message: &proto::GetDefinition) -> u64 {
@ -565,6 +412,281 @@ impl LspCommand for GetDefinition {
}
}
#[async_trait(?Send)]
impl LspCommand for GetTypeDefinition {
type Response = Vec<LocationLink>;
type LspRequest = lsp::request::GotoTypeDefinition;
type ProtoRequest = proto::GetTypeDefinition;
fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::GotoTypeDefinitionParams {
lsp::GotoTypeDefinitionParams {
text_document_position_params: lsp::TextDocumentPositionParams {
text_document: lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(path).unwrap(),
},
position: point_to_lsp(self.position),
},
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
}
}
async fn response_from_lsp(
self,
message: Option<lsp::GotoTypeDefinitionResponse>,
project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
cx: AsyncAppContext,
) -> Result<Vec<LocationLink>> {
location_links_from_lsp(message, project, buffer, cx).await
}
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetTypeDefinition {
proto::GetTypeDefinition {
project_id,
buffer_id: buffer.remote_id(),
position: Some(language::proto::serialize_anchor(
&buffer.anchor_before(self.position),
)),
version: serialize_version(&buffer.version()),
}
}
async fn from_proto(
message: proto::GetTypeDefinition,
_: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Self> {
let position = message
.position
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("invalid position"))?;
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(message.version))
})
.await;
Ok(Self {
position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
})
}
fn response_to_proto(
response: Vec<LocationLink>,
project: &mut Project,
peer_id: PeerId,
_: &clock::Global,
cx: &AppContext,
) -> proto::GetTypeDefinitionResponse {
let links = location_links_to_proto(response, project, peer_id, cx);
proto::GetTypeDefinitionResponse { links }
}
async fn response_from_proto(
self,
message: proto::GetTypeDefinitionResponse,
project: ModelHandle<Project>,
_: ModelHandle<Buffer>,
cx: AsyncAppContext,
) -> Result<Vec<LocationLink>> {
location_links_from_proto(message.links, project, cx).await
}
fn buffer_id_from_proto(message: &proto::GetTypeDefinition) -> u64 {
message.buffer_id
}
}
fn language_server_for_buffer(
project: &ModelHandle<Project>,
buffer: &ModelHandle<Buffer>,
cx: &mut AsyncAppContext,
) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
project
.read_with(cx, |project, cx| {
project
.language_server_for_buffer(buffer.read(cx), cx)
.map(|(adapter, server)| (adapter.clone(), server.clone()))
})
.ok_or_else(|| anyhow!("no language server found for buffer"))
}
async fn location_links_from_proto(
proto_links: Vec<proto::LocationLink>,
project: ModelHandle<Project>,
mut cx: AsyncAppContext,
) -> Result<Vec<LocationLink>> {
let mut links = Vec::new();
for link in proto_links {
let origin = match link.origin {
Some(origin) => {
let buffer = origin
.buffer
.ok_or_else(|| anyhow!("missing origin buffer"))?;
let buffer = project
.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
.await?;
let start = origin
.start
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("missing origin start"))?;
let end = origin
.end
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("missing origin end"))?;
buffer
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
.await;
Some(Location {
buffer,
range: start..end,
})
}
None => None,
};
let target = link.target.ok_or_else(|| anyhow!("missing target"))?;
let buffer = target.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
let buffer = project
.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
.await?;
let start = target
.start
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("missing target start"))?;
let end = target
.end
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("missing target end"))?;
buffer
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
.await;
let target = Location {
buffer,
range: start..end,
};
links.push(LocationLink { origin, target })
}
Ok(links)
}
async fn location_links_from_lsp(
message: Option<lsp::GotoDefinitionResponse>,
project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Vec<LocationLink>> {
let message = match message {
Some(message) => message,
None => return Ok(Vec::new()),
};
let mut unresolved_links = Vec::new();
match message {
lsp::GotoDefinitionResponse::Scalar(loc) => {
unresolved_links.push((None, loc.uri, loc.range));
}
lsp::GotoDefinitionResponse::Array(locs) => {
unresolved_links.extend(locs.into_iter().map(|l| (None, l.uri, l.range)));
}
lsp::GotoDefinitionResponse::Link(links) => {
unresolved_links.extend(links.into_iter().map(|l| {
(
l.origin_selection_range,
l.target_uri,
l.target_selection_range,
)
}));
}
}
let (lsp_adapter, language_server) = language_server_for_buffer(&project, &buffer, &mut cx)?;
let mut definitions = Vec::new();
for (origin_range, target_uri, target_range) in unresolved_links {
let target_buffer_handle = project
.update(&mut cx, |this, cx| {
this.open_local_buffer_via_lsp(
target_uri,
language_server.server_id(),
lsp_adapter.name.clone(),
cx,
)
})
.await?;
cx.read(|cx| {
let origin_location = origin_range.map(|origin_range| {
let origin_buffer = buffer.read(cx);
let origin_start =
origin_buffer.clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left);
let origin_end =
origin_buffer.clip_point_utf16(point_from_lsp(origin_range.end), Bias::Left);
Location {
buffer: buffer.clone(),
range: origin_buffer.anchor_after(origin_start)
..origin_buffer.anchor_before(origin_end),
}
});
let target_buffer = target_buffer_handle.read(cx);
let target_start =
target_buffer.clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
let target_end =
target_buffer.clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
let target_location = Location {
buffer: target_buffer_handle,
range: target_buffer.anchor_after(target_start)
..target_buffer.anchor_before(target_end),
};
definitions.push(LocationLink {
origin: origin_location,
target: target_location,
})
});
}
Ok(definitions)
}
fn location_links_to_proto(
links: Vec<LocationLink>,
project: &mut Project,
peer_id: PeerId,
cx: &AppContext,
) -> Vec<proto::LocationLink> {
links
.into_iter()
.map(|definition| {
let origin = definition.origin.map(|origin| {
let buffer = project.serialize_buffer_for_peer(&origin.buffer, peer_id, cx);
proto::Location {
start: Some(serialize_anchor(&origin.range.start)),
end: Some(serialize_anchor(&origin.range.end)),
buffer: Some(buffer),
}
});
let buffer = project.serialize_buffer_for_peer(&definition.target.buffer, peer_id, cx);
let target = proto::Location {
start: Some(serialize_anchor(&definition.target.range.start)),
end: Some(serialize_anchor(&definition.target.range.end)),
buffer: Some(buffer),
};
proto::LocationLink {
origin,
target: Some(target),
}
})
.collect()
}
#[async_trait(?Send)]
impl LspCommand for GetReferences {
type Response = Vec<Location>;
@ -595,13 +717,8 @@ impl LspCommand for GetReferences {
mut cx: AsyncAppContext,
) -> Result<Vec<Location>> {
let mut references = Vec::new();
let (lsp_adapter, language_server) = project
.read_with(&cx, |project, cx| {
project
.language_server_for_buffer(buffer.read(cx), cx)
.map(|(adapter, server)| (adapter.clone(), server.clone()))
})
.ok_or_else(|| anyhow!("no language server found for buffer"))?;
let (lsp_adapter, language_server) =
language_server_for_buffer(&project, &buffer, &mut cx)?;
if let Some(locations) = locations {
for lsp_location in locations {

View file

@ -254,10 +254,9 @@ pub struct DocumentHighlight {
#[derive(Clone, Debug)]
pub struct Symbol {
pub source_worktree_id: WorktreeId,
pub worktree_id: WorktreeId,
pub language_server_name: LanguageServerName,
pub path: PathBuf,
pub source_worktree_id: WorktreeId,
pub path: ProjectPath,
pub label: CodeLabel,
pub name: String,
pub kind: lsp::SymbolKind,
@ -3169,7 +3168,7 @@ impl Project {
buffer.finalize_last_transaction();
buffer.start_transaction();
for (range, text) in edits {
buffer.edit([(range, text)], cx);
buffer.edit([(range, text)], None, cx);
}
if buffer.end_transaction(cx).is_some() {
let transaction = buffer.finalize_last_transaction().unwrap().clone();
@ -3251,6 +3250,16 @@ impl Project {
self.request_lsp(buffer.clone(), GetDefinition { position }, cx)
}
pub fn type_definition<T: ToPointUtf16>(
&self,
buffer: &ModelHandle<Buffer>,
position: T,
cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<LocationLink>>> {
let position = position.to_point_utf16(buffer.read(cx));
self.request_lsp(buffer.clone(), GetTypeDefinition { position }, cx)
}
pub fn references<T: ToPointUtf16>(
&self,
buffer: &ModelHandle<Buffer>,
@ -3324,16 +3333,19 @@ impl Project {
if let Some((worktree, rel_path)) =
this.find_local_worktree(&abs_path, cx)
{
worktree_id = worktree.read(cx).id();
worktree_id = (&worktree.read(cx)).id();
path = rel_path;
} else {
path = relativize_path(&worktree_abs_path, &abs_path);
}
let signature = this.symbol_signature(worktree_id, &path);
let language = this.languages.select_language(&path);
let project_path = ProjectPath {
worktree_id,
path: path.into(),
};
let signature = this.symbol_signature(&project_path);
let language = this.languages.select_language(&project_path.path);
let language_server_name = adapter.name.clone();
Some(async move {
let label = if let Some(language) = language {
language
@ -3344,15 +3356,14 @@ impl Project {
};
Symbol {
source_worktree_id,
worktree_id,
language_server_name,
source_worktree_id,
path: project_path,
label: label.unwrap_or_else(|| {
CodeLabel::plain(lsp_symbol.name.clone(), None)
}),
kind: lsp_symbol.kind,
name: lsp_symbol.name,
path,
range: range_from_lsp(lsp_symbol.location.range),
signature,
}
@ -3410,7 +3421,7 @@ impl Project {
};
let worktree_abs_path = if let Some(worktree_abs_path) = self
.worktree_for_id(symbol.worktree_id, cx)
.worktree_for_id(symbol.path.worktree_id, cx)
.and_then(|worktree| worktree.read(cx).as_local())
.map(|local_worktree| local_worktree.abs_path())
{
@ -3418,7 +3429,7 @@ impl Project {
} else {
return Task::ready(Err(anyhow!("worktree not found for symbol")));
};
let symbol_abs_path = worktree_abs_path.join(&symbol.path);
let symbol_abs_path = worktree_abs_path.join(&symbol.path.path);
let symbol_uri = if let Ok(uri) = lsp::Url::from_file_path(symbol_abs_path) {
uri
} else {
@ -3662,7 +3673,7 @@ impl Project {
buffer.finalize_last_transaction();
buffer.start_transaction();
for (range, text) in edits {
buffer.edit([(range, text)], cx);
buffer.edit([(range, text)], None, cx);
}
let transaction = if buffer.end_transaction(cx).is_some() {
let transaction = buffer.finalize_last_transaction().unwrap().clone();
@ -4022,7 +4033,7 @@ impl Project {
buffer.finalize_last_transaction();
buffer.start_transaction();
for (range, text) in edits {
buffer.edit([(range, text)], cx);
buffer.edit([(range, text)], None, cx);
}
let transaction = if buffer.end_transaction(cx).is_some() {
let transaction = buffer.finalize_last_transaction().unwrap().clone();
@ -4622,11 +4633,11 @@ impl Project {
self.active_entry
}
pub fn entry_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option<ProjectEntryId> {
pub fn entry_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option<Entry> {
self.worktree_for_id(path.worktree_id, cx)?
.read(cx)
.entry_for_path(&path.path)
.map(|entry| entry.id)
.cloned()
}
pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &AppContext) -> Option<ProjectPath> {
@ -5436,7 +5447,7 @@ impl Project {
.read_with(&cx, |this, _| this.deserialize_symbol(symbol))
.await?;
let symbol = this.read_with(&cx, |this, _| {
let signature = this.symbol_signature(symbol.worktree_id, &symbol.path);
let signature = this.symbol_signature(&symbol.path);
if signature == symbol.signature {
Ok(symbol)
} else {
@ -5454,10 +5465,10 @@ impl Project {
})
}
fn symbol_signature(&self, worktree_id: WorktreeId, path: &Path) -> [u8; 32] {
fn symbol_signature(&self, project_path: &ProjectPath) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(worktree_id.to_proto().to_be_bytes());
hasher.update(path.to_string_lossy().as_bytes());
hasher.update(project_path.worktree_id.to_proto().to_be_bytes());
hasher.update(project_path.path.to_string_lossy().as_bytes());
hasher.update(self.nonce.to_be_bytes());
hasher.finalize().as_slice().try_into().unwrap()
}
@ -5655,14 +5666,17 @@ impl Project {
.end
.ok_or_else(|| anyhow!("invalid end"))?;
let kind = unsafe { mem::transmute(serialized_symbol.kind) };
let path = PathBuf::from(serialized_symbol.path);
let language = languages.select_language(&path);
Ok(Symbol {
source_worktree_id,
let path = ProjectPath {
worktree_id,
path: PathBuf::from(serialized_symbol.path).into(),
};
let language = languages.select_language(&path.path);
Ok(Symbol {
language_server_name: LanguageServerName(
serialized_symbol.language_server_name.into(),
),
source_worktree_id,
path,
label: {
match language {
Some(language) => {
@ -5676,7 +5690,6 @@ impl Project {
},
name: serialized_symbol.name,
path,
range: PointUtf16::new(start.row, start.column)
..PointUtf16::new(end.row, end.column),
kind,
@ -5764,6 +5777,10 @@ impl Project {
let mut lsp_edits = lsp_edits.into_iter().peekable();
let mut edits = Vec::new();
while let Some((mut range, mut new_text)) = lsp_edits.next() {
// Clip invalid ranges provided by the language server.
range.start = snapshot.clip_point_utf16(range.start, Bias::Left);
range.end = snapshot.clip_point_utf16(range.end, Bias::Left);
// Combine any LSP edits that are adjacent.
//
// Also, combine LSP edits that are separated from each other by only
@ -5791,12 +5808,6 @@ impl Project {
lsp_edits.next();
}
if snapshot.clip_point_utf16(range.start, Bias::Left) != range.start
|| snapshot.clip_point_utf16(range.end, Bias::Left) != range.end
{
return Err(anyhow!("invalid edits received from language server"));
}
// For multiline edits, perform a diff of the old and new text so that
// we can identify the changes more precisely, preserving the locations
// of any anchors positioned in the unchanged regions.
@ -6144,12 +6155,12 @@ impl From<lsp::DeleteFileOptions> for fs::RemoveOptions {
fn serialize_symbol(symbol: &Symbol) -> proto::Symbol {
proto::Symbol {
source_worktree_id: symbol.source_worktree_id.to_proto(),
worktree_id: symbol.worktree_id.to_proto(),
language_server_name: symbol.language_server_name.0.to_string(),
source_worktree_id: symbol.source_worktree_id.to_proto(),
worktree_id: symbol.path.worktree_id.to_proto(),
path: symbol.path.path.to_string_lossy().to_string(),
name: symbol.name.clone(),
kind: unsafe { mem::transmute(symbol.kind) },
path: symbol.path.to_string_lossy().to_string(),
start: Some(proto::Point {
row: symbol.range.start.row,
column: symbol.range.start.column,

View file

@ -169,7 +169,7 @@ async fn test_managing_language_servers(
});
// Edit a buffer. The changes are reported to the language server.
rust_buffer.update(cx, |buffer, cx| buffer.edit([(16..16, "2")], cx));
rust_buffer.update(cx, |buffer, cx| buffer.edit([(16..16, "2")], None, cx));
assert_eq!(
fake_rust_server
.receive_notification::<lsp::notification::DidChangeTextDocument>()
@ -226,8 +226,10 @@ async fn test_managing_language_servers(
});
// Changes are reported only to servers matching the buffer's language.
toml_buffer.update(cx, |buffer, cx| buffer.edit([(5..5, "23")], cx));
rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "let x = 1;")], cx));
toml_buffer.update(cx, |buffer, cx| buffer.edit([(5..5, "23")], None, cx));
rust_buffer2.update(cx, |buffer, cx| {
buffer.edit([(0..0, "let x = 1;")], None, cx)
});
assert_eq!(
fake_rust_server
.receive_notification::<lsp::notification::DidChangeTextDocument>()
@ -348,7 +350,7 @@ async fn test_managing_language_servers(
});
// The renamed file's version resets after changing language server.
rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "// ")], cx));
rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "// ")], None, cx));
assert_eq!(
fake_json_server
.receive_notification::<lsp::notification::DidChangeTextDocument>()
@ -972,7 +974,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
.await;
// Edit the buffer, moving the content down
buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "\n\n")], cx));
buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "\n\n")], None, cx));
let change_notification_1 = fake_server
.receive_notification::<lsp::notification::DidChangeTextDocument>()
.await;
@ -1137,9 +1139,13 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
// Keep editing the buffer and ensure disk-based diagnostics get translated according to the
// changes since the last save.
buffer.update(cx, |buffer, cx| {
buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], cx);
buffer.edit([(Point::new(2, 8)..Point::new(2, 10), "(x: usize)")], cx);
buffer.edit([(Point::new(3, 10)..Point::new(3, 10), "xxx")], cx);
buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx);
buffer.edit(
[(Point::new(2, 8)..Point::new(2, 10), "(x: usize)")],
None,
cx,
);
buffer.edit([(Point::new(3, 10)..Point::new(3, 10), "xxx")], None, cx);
});
let change_notification_2 = fake_server
.receive_notification::<lsp::notification::DidChangeTextDocument>()
@ -1330,6 +1336,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
Point::new(0, 0)..Point::new(0, 0),
"// above first function\n",
)],
None,
cx,
);
buffer.edit(
@ -1337,6 +1344,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
Point::new(2, 0)..Point::new(2, 0),
" // inside first function\n",
)],
None,
cx,
);
buffer.edit(
@ -1344,6 +1352,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
Point::new(6, 4)..Point::new(6, 4),
"// inside second function ",
)],
None,
cx,
);
@ -1405,7 +1414,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
buffer.update(cx, |buffer, cx| {
for (range, new_text) in edits {
buffer.edit([(range, new_text)], cx);
buffer.edit([(range, new_text)], None, cx);
}
assert_eq!(
buffer.text(),
@ -1517,7 +1526,7 @@ async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestApp
);
for (range, new_text) in edits {
buffer.edit([(range, new_text)], cx);
buffer.edit([(range, new_text)], None, cx);
}
assert_eq!(
buffer.text(),
@ -1565,7 +1574,7 @@ async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
.unwrap();
// Simulate the language server sending us edits in a non-ordered fashion,
// with ranges sometimes being inverted.
// with ranges sometimes being inverted or pointing to invalid locations.
let edits = project
.update(cx, |project, cx| {
project.edits_from_lsp(
@ -1580,7 +1589,7 @@ async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
new_text: "a::{b, c}".into(),
},
lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(7, 0)),
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(99, 0)),
new_text: "".into(),
},
lsp::TextEdit {
@ -1620,7 +1629,7 @@ async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
);
for (range, new_text) in edits {
buffer.edit([(range, new_text)], cx);
buffer.edit([(range, new_text)], None, cx);
}
assert_eq!(
buffer.text(),
@ -2025,7 +2034,7 @@ async fn test_save_file(cx: &mut gpui::TestAppContext) {
buffer
.update(cx, |buffer, cx| {
assert_eq!(buffer.text(), "the old contents");
buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], cx);
buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx);
buffer.save(cx)
})
.await
@ -2053,7 +2062,7 @@ async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
.unwrap();
buffer
.update(cx, |buffer, cx| {
buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], cx);
buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx);
buffer.save(cx)
})
.await
@ -2073,7 +2082,7 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
project.create_buffer("", None, cx).unwrap()
});
buffer.update(cx, |buffer, cx| {
buffer.edit([(0..0, "abc")], cx);
buffer.edit([(0..0, "abc")], None, cx);
assert!(buffer.is_dirty());
assert!(!buffer.has_conflict());
});
@ -2329,7 +2338,7 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
assert!(!buffer.is_dirty());
assert!(events.borrow().is_empty());
buffer.edit([(1..2, "")], cx);
buffer.edit([(1..2, "")], None, cx);
});
// after the first edit, the buffer is dirty, and emits a dirtied event.
@ -2356,8 +2365,8 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
assert_eq!(*events.borrow(), &[language::Event::Saved]);
events.borrow_mut().clear();
buffer.edit([(1..1, "B")], cx);
buffer.edit([(2..2, "D")], cx);
buffer.edit([(1..1, "B")], None, cx);
buffer.edit([(2..2, "D")], None, cx);
});
// after editing again, the buffer is dirty, and emits another dirty event.
@ -2376,7 +2385,7 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
// After restoring the buffer to its previously-saved state,
// the buffer is not considered dirty anymore.
buffer.edit([(1..3, "")], cx);
buffer.edit([(1..3, "")], None, cx);
assert!(buffer.text() == "ac");
assert!(!buffer.is_dirty());
});
@ -2427,7 +2436,7 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
});
buffer3.update(cx, |buffer, cx| {
buffer.edit([(0..0, "x")], cx);
buffer.edit([(0..0, "x")], None, cx);
});
events.borrow_mut().clear();
fs.remove_file("/dir/file3".as_ref(), Default::default())
@ -2495,7 +2504,7 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
// Modify the buffer
buffer.update(cx, |buffer, cx| {
buffer.edit([(0..0, " ")], cx);
buffer.edit([(0..0, " ")], None, cx);
assert!(buffer.is_dirty());
assert!(!buffer.has_conflict());
});
@ -2986,7 +2995,7 @@ async fn test_search(cx: &mut gpui::TestAppContext) {
.unwrap();
buffer_4.update(cx, |buffer, cx| {
let text = "two::TWO";
buffer.edit([(20..28, text), (31..43, text)], cx);
buffer.edit([(20..28, text), (31..43, text)], None, cx);
});
assert_eq!(

View file

@ -1028,11 +1028,11 @@ impl ProjectPanel {
.with_child(
ConstrainedBox::new(if kind == EntryKind::Dir {
if details.is_expanded {
Svg::new("icons/chevron_right_8.svg")
Svg::new("icons/chevron_down_8.svg")
.with_color(style.icon_color)
.boxed()
} else {
Svg::new("icons/chevron_down_8.svg")
Svg::new("icons/chevron_right_8.svg")
.with_color(style.icon_color)
.boxed()
}

View file

@ -26,7 +26,8 @@ pub struct ProjectSymbolsView {
project: ModelHandle<Project>,
selected_match_index: usize,
symbols: Vec<Symbol>,
match_candidates: Vec<StringMatchCandidate>,
visible_match_candidates: Vec<StringMatchCandidate>,
external_match_candidates: Vec<StringMatchCandidate>,
show_worktree_root_name: bool,
pending_update: Task<()>,
matches: Vec<StringMatch>,
@ -63,7 +64,8 @@ impl ProjectSymbolsView {
picker: cx.add_view(|cx| Picker::new(handle, cx)),
selected_match_index: 0,
symbols: Default::default(),
match_candidates: Default::default(),
visible_match_candidates: Default::default(),
external_match_candidates: Default::default(),
matches: Default::default(),
show_worktree_root_name: false,
pending_update: Task::ready(()),
@ -80,38 +82,39 @@ impl ProjectSymbolsView {
}
fn filter(&mut self, query: &str, cx: &mut ViewContext<Self>) {
let mut matches = if query.is_empty() {
self.match_candidates
.iter()
.enumerate()
.map(|(candidate_id, candidate)| StringMatch {
candidate_id,
score: Default::default(),
positions: Default::default(),
string: candidate.string.clone(),
})
.collect()
} else {
cx.background_executor().block(fuzzy::match_strings(
&self.match_candidates,
query,
false,
100,
&Default::default(),
cx.background().clone(),
))
};
matches.sort_unstable_by_key(|mat| {
let label = &self.symbols[mat.candidate_id].label;
const MAX_MATCHES: usize = 100;
let mut visible_matches = cx.background_executor().block(fuzzy::match_strings(
&self.visible_match_candidates,
query,
false,
MAX_MATCHES,
&Default::default(),
cx.background().clone(),
));
let mut external_matches = cx.background_executor().block(fuzzy::match_strings(
&self.external_match_candidates,
query,
false,
MAX_MATCHES - visible_matches.len(),
&Default::default(),
cx.background().clone(),
));
let sort_key_for_match = |mat: &StringMatch| {
let symbol = &self.symbols[mat.candidate_id];
(
Reverse(OrderedFloat(mat.score)),
&label.text[label.filter_range.clone()],
&symbol.label.text[symbol.label.filter_range.clone()],
)
});
};
visible_matches.sort_unstable_by_key(sort_key_for_match);
external_matches.sort_unstable_by_key(sort_key_for_match);
let mut matches = visible_matches;
matches.append(&mut external_matches);
for mat in &mut matches {
let filter_start = self.symbols[mat.candidate_id].label.filter_range.start;
let symbol = &self.symbols[mat.candidate_id];
let filter_start = symbol.label.filter_range.start;
for position in &mut mat.positions {
*position += filter_start;
}
@ -198,7 +201,8 @@ impl PickerDelegate for ProjectSymbolsView {
if let Some(this) = this.upgrade(&cx) {
if let Some(symbols) = symbols {
this.update(&mut cx, |this, cx| {
this.match_candidates = symbols
let project = this.project.read(cx);
let (visible_match_candidates, external_match_candidates) = symbols
.iter()
.enumerate()
.map(|(id, symbol)| {
@ -208,7 +212,14 @@ impl PickerDelegate for ProjectSymbolsView {
.to_string(),
)
})
.collect();
.partition(|candidate| {
project
.entry_for_path(&symbols[candidate.id].path, cx)
.map_or(false, |e| !e.is_ignored)
});
this.visible_match_candidates = visible_match_candidates;
this.external_match_candidates = external_match_candidates;
this.symbols = symbols;
this.filter(&query, cx);
});
@ -232,10 +243,10 @@ impl PickerDelegate for ProjectSymbolsView {
let symbol = &self.symbols[string_match.candidate_id];
let syntax_runs = styled_runs_for_code_label(&symbol.label, &settings.theme.editor.syntax);
let mut path = symbol.path.to_string_lossy();
let mut path = symbol.path.path.to_string_lossy();
if self.show_worktree_root_name {
let project = self.project.read(cx);
if let Some(worktree) = project.worktree_for_id(symbol.worktree_id, cx) {
if let Some(worktree) = project.worktree_for_id(symbol.path.worktree_id, cx) {
path = Cow::Owned(format!(
"{}{}{}",
worktree.read(cx).root_name(),
@ -275,7 +286,7 @@ mod tests {
use gpui::{serde_json::json, TestAppContext};
use language::{FakeLspAdapter, Language, LanguageConfig};
use project::FakeFs;
use std::sync::Arc;
use std::{path::Path, sync::Arc};
#[gpui::test]
async fn test_project_symbols(cx: &mut TestAppContext) {
@ -309,15 +320,21 @@ mod tests {
// Set up fake langauge server to return fuzzy matches against
// a fixed set of symbol names.
let fake_symbol_names = ["one", "ton", "uno"];
let fake_symbols = [
symbol("one", "/external"),
symbol("ton", "/dir/test.rs"),
symbol("uno", "/dir/test.rs"),
];
let fake_server = fake_servers.next().await.unwrap();
fake_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(
move |params: lsp::WorkspaceSymbolParams, cx| {
let executor = cx.background();
let fake_symbols = fake_symbols.clone();
async move {
let candidates = fake_symbol_names
.into_iter()
.map(|name| StringMatchCandidate::new(0, name.into()))
let candidates = fake_symbols
.iter()
.enumerate()
.map(|(id, symbol)| StringMatchCandidate::new(id, symbol.name.clone()))
.collect::<Vec<_>>();
let matches = if params.query.is_empty() {
Vec::new()
@ -334,7 +351,10 @@ mod tests {
};
Ok(Some(
matches.into_iter().map(|mat| symbol(&mat.string)).collect(),
matches
.into_iter()
.map(|mat| fake_symbols[mat.candidate_id].clone())
.collect(),
))
}
},
@ -367,8 +387,8 @@ mod tests {
cx.foreground().run_until_parked();
symbols_view.read_with(cx, |symbols_view, _| {
assert_eq!(symbols_view.matches.len(), 2);
assert_eq!(symbols_view.matches[0].string, "one");
assert_eq!(symbols_view.matches[1].string, "ton");
assert_eq!(symbols_view.matches[0].string, "ton");
assert_eq!(symbols_view.matches[1].string, "one");
});
// Spawn more updates such that in the end, there are again no matches.
@ -383,7 +403,7 @@ mod tests {
});
}
fn symbol(name: &str) -> lsp::SymbolInformation {
fn symbol(name: &str, path: impl AsRef<Path>) -> lsp::SymbolInformation {
#[allow(deprecated)]
lsp::SymbolInformation {
name: name.to_string(),
@ -392,7 +412,7 @@ mod tests {
deprecated: None,
container_name: None,
location: lsp::Location::new(
lsp::Url::from_file_path("/a/b").unwrap(),
lsp::Url::from_file_path(path.as_ref()).unwrap(),
lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
),
}

View file

@ -26,85 +26,87 @@ message Envelope {
GetDefinition get_definition = 20;
GetDefinitionResponse get_definition_response = 21;
GetReferences get_references = 22;
GetReferencesResponse get_references_response = 23;
GetDocumentHighlights get_document_highlights = 24;
GetDocumentHighlightsResponse get_document_highlights_response = 25;
GetProjectSymbols get_project_symbols = 26;
GetProjectSymbolsResponse get_project_symbols_response = 27;
OpenBufferForSymbol open_buffer_for_symbol = 28;
OpenBufferForSymbolResponse open_buffer_for_symbol_response = 29;
GetTypeDefinition get_type_definition = 22;
GetTypeDefinitionResponse get_type_definition_response = 23;
GetReferences get_references = 24;
GetReferencesResponse get_references_response = 25;
GetDocumentHighlights get_document_highlights = 26;
GetDocumentHighlightsResponse get_document_highlights_response = 27;
GetProjectSymbols get_project_symbols = 28;
GetProjectSymbolsResponse get_project_symbols_response = 29;
OpenBufferForSymbol open_buffer_for_symbol = 30;
OpenBufferForSymbolResponse open_buffer_for_symbol_response = 31;
UpdateProject update_project = 30;
RegisterProjectActivity register_project_activity = 31;
UpdateWorktree update_worktree = 32;
UpdateWorktreeExtensions update_worktree_extensions = 33;
UpdateProject update_project = 32;
RegisterProjectActivity register_project_activity = 33;
UpdateWorktree update_worktree = 34;
UpdateWorktreeExtensions update_worktree_extensions = 35;
CreateProjectEntry create_project_entry = 34;
RenameProjectEntry rename_project_entry = 35;
CopyProjectEntry copy_project_entry = 36;
DeleteProjectEntry delete_project_entry = 37;
ProjectEntryResponse project_entry_response = 38;
CreateProjectEntry create_project_entry = 36;
RenameProjectEntry rename_project_entry = 37;
CopyProjectEntry copy_project_entry = 38;
DeleteProjectEntry delete_project_entry = 39;
ProjectEntryResponse project_entry_response = 40;
UpdateDiagnosticSummary update_diagnostic_summary = 39;
StartLanguageServer start_language_server = 40;
UpdateLanguageServer update_language_server = 41;
UpdateDiagnosticSummary update_diagnostic_summary = 41;
StartLanguageServer start_language_server = 42;
UpdateLanguageServer update_language_server = 43;
OpenBufferById open_buffer_by_id = 42;
OpenBufferByPath open_buffer_by_path = 43;
OpenBufferResponse open_buffer_response = 44;
UpdateBuffer update_buffer = 45;
UpdateBufferFile update_buffer_file = 46;
SaveBuffer save_buffer = 47;
BufferSaved buffer_saved = 48;
BufferReloaded buffer_reloaded = 49;
ReloadBuffers reload_buffers = 50;
ReloadBuffersResponse reload_buffers_response = 51;
FormatBuffers format_buffers = 52;
FormatBuffersResponse format_buffers_response = 53;
GetCompletions get_completions = 54;
GetCompletionsResponse get_completions_response = 55;
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 56;
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 57;
GetCodeActions get_code_actions = 58;
GetCodeActionsResponse get_code_actions_response = 59;
GetHover get_hover = 60;
GetHoverResponse get_hover_response = 61;
ApplyCodeAction apply_code_action = 62;
ApplyCodeActionResponse apply_code_action_response = 63;
PrepareRename prepare_rename = 64;
PrepareRenameResponse prepare_rename_response = 65;
PerformRename perform_rename = 66;
PerformRenameResponse perform_rename_response = 67;
SearchProject search_project = 68;
SearchProjectResponse search_project_response = 69;
OpenBufferById open_buffer_by_id = 44;
OpenBufferByPath open_buffer_by_path = 45;
OpenBufferResponse open_buffer_response = 46;
UpdateBuffer update_buffer = 47;
UpdateBufferFile update_buffer_file = 48;
SaveBuffer save_buffer = 49;
BufferSaved buffer_saved = 50;
BufferReloaded buffer_reloaded = 51;
ReloadBuffers reload_buffers = 52;
ReloadBuffersResponse reload_buffers_response = 53;
FormatBuffers format_buffers = 54;
FormatBuffersResponse format_buffers_response = 55;
GetCompletions get_completions = 56;
GetCompletionsResponse get_completions_response = 57;
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 58;
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 59;
GetCodeActions get_code_actions = 60;
GetCodeActionsResponse get_code_actions_response = 61;
GetHover get_hover = 62;
GetHoverResponse get_hover_response = 63;
ApplyCodeAction apply_code_action = 64;
ApplyCodeActionResponse apply_code_action_response = 65;
PrepareRename prepare_rename = 66;
PrepareRenameResponse prepare_rename_response = 67;
PerformRename perform_rename = 68;
PerformRenameResponse perform_rename_response = 69;
SearchProject search_project = 70;
SearchProjectResponse search_project_response = 71;
GetChannels get_channels = 70;
GetChannelsResponse get_channels_response = 71;
JoinChannel join_channel = 72;
JoinChannelResponse join_channel_response = 73;
LeaveChannel leave_channel = 74;
SendChannelMessage send_channel_message = 75;
SendChannelMessageResponse send_channel_message_response = 76;
ChannelMessageSent channel_message_sent = 77;
GetChannelMessages get_channel_messages = 78;
GetChannelMessagesResponse get_channel_messages_response = 79;
GetChannels get_channels = 72;
GetChannelsResponse get_channels_response = 73;
JoinChannel join_channel = 74;
JoinChannelResponse join_channel_response = 75;
LeaveChannel leave_channel = 76;
SendChannelMessage send_channel_message = 77;
SendChannelMessageResponse send_channel_message_response = 78;
ChannelMessageSent channel_message_sent = 79;
GetChannelMessages get_channel_messages = 80;
GetChannelMessagesResponse get_channel_messages_response = 81;
UpdateContacts update_contacts = 80;
UpdateInviteInfo update_invite_info = 81;
ShowContacts show_contacts = 82;
UpdateContacts update_contacts = 82;
UpdateInviteInfo update_invite_info = 83;
ShowContacts show_contacts = 84;
GetUsers get_users = 83;
FuzzySearchUsers fuzzy_search_users = 84;
UsersResponse users_response = 85;
RequestContact request_contact = 86;
RespondToContactRequest respond_to_contact_request = 87;
RemoveContact remove_contact = 88;
GetUsers get_users = 85;
FuzzySearchUsers fuzzy_search_users = 86;
UsersResponse users_response = 87;
RequestContact request_contact = 88;
RespondToContactRequest respond_to_contact_request = 89;
RemoveContact remove_contact = 90;
Follow follow = 89;
FollowResponse follow_response = 90;
UpdateFollowers update_followers = 91;
Unfollow unfollow = 92;
Follow follow = 91;
FollowResponse follow_response = 92;
UpdateFollowers update_followers = 93;
Unfollow unfollow = 94;
}
}
@ -263,6 +265,17 @@ message GetDefinitionResponse {
repeated LocationLink links = 1;
}
message GetTypeDefinition {
uint64 project_id = 1;
uint64 buffer_id = 2;
Anchor position = 3;
repeated VectorClockEntry version = 4;
}
message GetTypeDefinitionResponse {
repeated LocationLink links = 1;
}
message GetReferences {
uint64 project_id = 1;
uint64 buffer_id = 2;

View file

@ -106,6 +106,8 @@ messages!(
(GetCompletionsResponse, Background),
(GetDefinition, Background),
(GetDefinitionResponse, Background),
(GetTypeDefinition, Background),
(GetTypeDefinitionResponse, Background),
(GetDocumentHighlights, Background),
(GetDocumentHighlightsResponse, Background),
(GetReferences, Background),
@ -183,6 +185,7 @@ request_messages!(
(GetHover, GetHoverResponse),
(GetCompletions, GetCompletionsResponse),
(GetDefinition, GetDefinitionResponse),
(GetTypeDefinition, GetTypeDefinitionResponse),
(GetDocumentHighlights, GetDocumentHighlightsResponse),
(GetReferences, GetReferencesResponse),
(GetProjectSymbols, GetProjectSymbolsResponse),
@ -226,6 +229,7 @@ entity_messages!(
GetCodeActions,
GetCompletions,
GetDefinition,
GetTypeDefinition,
GetDocumentHighlights,
GetHover,
GetReferences,

View file

@ -6,4 +6,4 @@ pub use conn::Connection;
pub use peer::*;
mod macros;
pub const PROTOCOL_VERSION: u32 = 28;
pub const PROTOCOL_VERSION: u32 = 29;

View file

@ -260,7 +260,7 @@ impl BufferSearchBar {
self.query_editor.update(cx, |query_editor, cx| {
query_editor.buffer().update(cx, |query_buffer, cx| {
let len = query_buffer.len(cx);
query_buffer.edit([(0..len, query)], cx);
query_buffer.edit([(0..len, query)], None, cx);
});
});
}

View file

@ -177,4 +177,12 @@ impl View for ConnectedView {
self.terminal
.update(cx, |terminal, _| terminal.write_to_pty(text.into()));
}
fn keymap_context(&self, _: &gpui::AppContext) -> gpui::keymap::Context {
let mut context = Self::default_keymap_context();
if self.modal {
context.set.insert("ModalTerminal".into());
}
context
}
}

View file

@ -323,6 +323,7 @@ mod test {
alt: false,
shift: false,
cmd: false,
function: false,
key: "🖖🏻".to_string(), //2 char string
};
assert_eq!(to_esc_str(&ks, &TermMode::NONE), None);

View file

@ -1,4 +1,5 @@
use gpui::{ModelHandle, ViewContext};
use settings::{Settings, WorkingDirectory};
use workspace::Workspace;
use crate::{
@ -28,7 +29,14 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon
// No connection was stored, create a new terminal
if let Some(closed_terminal_handle) = workspace.toggle_modal(cx, |workspace, cx| {
// No terminal modal visible, construct a new one.
let working_directory = get_working_directory(workspace, cx);
let wd_strategy = cx
.global::<Settings>()
.terminal_overrides
.working_directory
.clone()
.unwrap_or(WorkingDirectory::CurrentProjectDirectory);
let working_directory = get_working_directory(workspace, cx, wd_strategy);
let this = cx.add_view(|cx| TerminalView::new(working_directory, true, cx));

View file

@ -742,4 +742,28 @@ mod alacritty_unix {
pub fn default_shell(pw: &Passwd<'_>) -> Program {
Program::Just(env::var("SHELL").unwrap_or_else(|_| pw.shell.to_owned()))
}
//Active entry with a work tree, worktree is a file, integration test with the strategy interface
#[gpui::test]
async fn active_entry_worktree_is_file_int(cx: &mut TestAppContext) {
//Setup variables
let mut cx = TerminalTestContext::new(cx, true);
let (project, workspace) = cx.blank_workspace().await;
let (_wt, _entry) = cx.create_folder_wt(project.clone(), "/root1/").await;
let (wt2, entry2) = cx.create_file_wt(project.clone(), "/root2.txt").await;
cx.insert_active_entry_for(wt2, entry2, project.clone());
//Test
cx.cx.update(|cx| {
let workspace = workspace.read(cx);
let active_entry = project.read(cx).active_entry();
assert!(active_entry.is_some());
let res =
get_working_directory(workspace, cx, WorkingDirectory::CurrentProjectDirectory);
let first = first_project_directory(workspace, cx);
assert_eq!(res, first);
});
}
}

View file

@ -38,11 +38,7 @@ pub struct Theme {
pub struct Workspace {
pub background: Color,
pub titlebar: Titlebar,
pub active_pane_active_tab: Tab,
pub active_pane_inactive_tab: Tab,
pub inactive_pane_active_tab: Tab,
pub inactive_pane_inactive_tab: Tab,
pub pane_button: Interactive<IconButton>,
pub tab_bar: TabBar,
pub pane_divider: Border,
pub leader_border_opacity: f32,
pub leader_border_width: f32,
@ -72,6 +68,22 @@ pub struct Titlebar {
pub outdated_warning: ContainedText,
}
#[derive(Clone, Deserialize, Default)]
pub struct TabBar {
#[serde(flatten)]
pub container: ContainerStyle,
pub pane_button: Interactive<IconButton>,
pub active_pane: TabStyles,
pub inactive_pane: TabStyles,
pub height: f32,
}
#[derive(Clone, Deserialize, Default)]
pub struct TabStyles {
pub active_tab: Tab,
pub inactive_tab: Tab,
}
#[derive(Clone, Deserialize, Default)]
pub struct AvatarRibbon {
#[serde(flatten)]

View file

@ -13,7 +13,7 @@ use change::init as change_init;
use collections::HashSet;
use editor::{Autoscroll, Bias, ClipboardSelection, DisplayPoint};
use gpui::{actions, MutableAppContext, ViewContext};
use language::{Point, SelectionGoal};
use language::{AutoindentMode, Point, SelectionGoal};
use workspace::Workspace;
use self::{change::change_over, delete::delete_over, yank::yank_over};
@ -278,7 +278,7 @@ fn paste(_: &mut Workspace, _: &Paste, cx: &mut ViewContext<Workspace>) {
}
}
drop(snapshot);
buffer.edit_with_autoindent(edits, cx);
buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
});
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
@ -427,7 +427,7 @@ mod test {
#[gpui::test]
async fn test_jump_to_line_boundaries(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["shift-$"]);
let mut cx = cx.binding(["$"]);
cx.assert("T|est test", "Test tes|t");
cx.assert("Test tes|t", "Test tes|t");
cx.assert(
@ -471,7 +471,7 @@ mod test {
#[gpui::test]
async fn test_jump_to_end(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["shift-G"]);
let mut cx = cx.binding(["shift-g"]);
cx.assert(
indoc! {"
@ -561,7 +561,7 @@ mod test {
);
for cursor_offset in cursor_offsets {
cx.simulate_keystroke("shift-W");
cx.simulate_keystroke("shift-w");
cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
}
}
@ -607,7 +607,7 @@ mod test {
Mode::Normal,
);
for cursor_offset in cursor_offsets {
cx.simulate_keystroke("shift-E");
cx.simulate_keystroke("shift-e");
cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
}
}
@ -653,7 +653,7 @@ mod test {
Mode::Normal,
);
for cursor_offset in cursor_offsets.into_iter().rev() {
cx.simulate_keystroke("shift-B");
cx.simulate_keystroke("shift-b");
cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
}
}
@ -740,7 +740,7 @@ mod test {
#[gpui::test]
async fn test_insert_end_of_line(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["shift-A"]).mode_after(Mode::Insert);
let mut cx = cx.binding(["shift-a"]).mode_after(Mode::Insert);
cx.assert("The q|uick", "The quick|");
cx.assert("The q|uick ", "The quick |");
cx.assert("|", "|");
@ -765,7 +765,7 @@ mod test {
#[gpui::test]
async fn test_jump_to_first_non_whitespace(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["shift-^"]);
let mut cx = cx.binding(["^"]);
cx.assert("The q|uick", "|The quick");
cx.assert(" The q|uick", " |The quick");
cx.assert("|", "|");
@ -792,7 +792,7 @@ mod test {
#[gpui::test]
async fn test_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["shift-I"]).mode_after(Mode::Insert);
let mut cx = cx.binding(["shift-i"]).mode_after(Mode::Insert);
cx.assert("The q|uick", "|The quick");
cx.assert(" The q|uick", " |The quick");
cx.assert("|", "|");
@ -817,7 +817,7 @@ mod test {
#[gpui::test]
async fn test_delete_to_end_of_line(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["shift-D"]);
let mut cx = cx.binding(["shift-d"]);
cx.assert(
indoc! {"
The q|uick
@ -858,7 +858,7 @@ mod test {
#[gpui::test]
async fn test_delete_left(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["shift-X"]);
let mut cx = cx.binding(["shift-x"]);
cx.assert("Te|st", "T|st");
cx.assert("T|est", "|est");
cx.assert("|Test", "|Test");
@ -956,7 +956,7 @@ mod test {
#[gpui::test]
async fn test_insert_line_above(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["shift-O"]).mode_after(Mode::Insert);
let mut cx = cx.binding(["shift-o"]).mode_after(Mode::Insert);
cx.assert(
"|",

View file

@ -139,7 +139,7 @@ mod test {
test"},
);
let mut cx = cx.binding(["c", "shift-W"]);
let mut cx = cx.binding(["c", "shift-w"]);
cx.assert("Test te|st-test test", "Test te| test");
}
@ -174,7 +174,7 @@ mod test {
test"},
);
let mut cx = cx.binding(["c", "shift-E"]);
let mut cx = cx.binding(["c", "shift-e"]);
cx.assert("Test te|st-test test", "Test te| test");
}
@ -204,14 +204,14 @@ mod test {
test"},
);
let mut cx = cx.binding(["c", "shift-B"]);
let mut cx = cx.binding(["c", "shift-b"]);
cx.assert("Test test-test |test", "Test |test");
}
#[gpui::test]
async fn test_change_end_of_line(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["c", "shift-$"]).mode_after(Mode::Insert);
let mut cx = cx.binding(["c", "$"]).mode_after(Mode::Insert);
cx.assert(
indoc! {"
The q|uick
@ -347,7 +347,7 @@ mod test {
#[gpui::test]
async fn test_change_end_of_document(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["c", "shift-G"]).mode_after(Mode::Insert);
let mut cx = cx.binding(["c", "shift-g"]).mode_after(Mode::Insert);
cx.assert(
indoc! {"
The quick

View file

@ -109,7 +109,7 @@ mod test {
test"},
);
let mut cx = cx.binding(["d", "shift-W"]);
let mut cx = cx.binding(["d", "shift-w"]);
cx.assert("Test te|st-test test", "Test te|test");
}
@ -144,7 +144,7 @@ mod test {
test"},
);
let mut cx = cx.binding(["d", "shift-E"]);
let mut cx = cx.binding(["d", "shift-e"]);
cx.assert("Test te|st-test test", "Test te| test");
}
@ -176,14 +176,14 @@ mod test {
test"},
);
let mut cx = cx.binding(["d", "shift-B"]);
let mut cx = cx.binding(["d", "shift-b"]);
cx.assert("Test test-test |test", "Test |test");
}
#[gpui::test]
async fn test_delete_end_of_line(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["d", "shift-$"]);
let mut cx = cx.binding(["d", "$"]);
cx.assert(
indoc! {"
The q|uick
@ -304,7 +304,7 @@ mod test {
#[gpui::test]
async fn test_delete_end_of_document(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["d", "shift-G"]);
let mut cx = cx.binding(["d", "shift-g"]);
cx.assert(
indoc! {"
The quick

View file

@ -17,6 +17,7 @@ pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut Mut
clipboard_selections.push(ClipboardSelection {
len: text.len() - initial_len,
is_entire_line: linewise,
first_line_indent: buffer.indent_size_for_line(start.row).len,
});
}
}

View file

@ -3,7 +3,7 @@ use std::borrow::Cow;
use collections::HashMap;
use editor::{display_map::ToDisplayPoint, Autoscroll, Bias, ClipboardSelection};
use gpui::{actions, MutableAppContext, ViewContext};
use language::SelectionGoal;
use language::{AutoindentMode, SelectionGoal};
use workspace::Workspace;
use crate::{motion::Motion, state::Mode, utils::copy_selections_content, Vim};
@ -254,7 +254,7 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext<Workspace>
}
}
drop(snapshot);
buffer.edit_with_autoindent(edits, cx);
buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
});
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
@ -422,7 +422,7 @@ mod test {
#[gpui::test]
async fn test_visual_line_delete(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["shift-V", "x"]);
let mut cx = cx.binding(["shift-v", "x"]);
cx.assert(
indoc! {"
The qu|ick brown
@ -457,7 +457,7 @@ mod test {
The quick brown
fox ju|mps over"},
);
let mut cx = cx.binding(["shift-V", "j", "x"]);
let mut cx = cx.binding(["shift-v", "j", "x"]);
cx.assert(
indoc! {"
The qu|ick brown
@ -558,7 +558,7 @@ mod test {
#[gpui::test]
async fn test_visual_line_change(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["shift-V", "c"]).mode_after(Mode::Insert);
let mut cx = cx.binding(["shift-v", "c"]).mode_after(Mode::Insert);
cx.assert(
indoc! {"
The qu|ick brown
@ -597,7 +597,7 @@ mod test {
fox jumps over
|"},
);
let mut cx = cx.binding(["shift-V", "j", "c"]).mode_after(Mode::Insert);
let mut cx = cx.binding(["shift-v", "j", "c"]).mode_after(Mode::Insert);
cx.assert(
indoc! {"
The qu|ick brown

View file

@ -873,6 +873,13 @@ impl Pane {
};
let is_pane_active = self.is_active;
let tab_styles = match is_pane_active {
true => theme.workspace.tab_bar.active_pane.clone(),
false => theme.workspace.tab_bar.inactive_pane.clone(),
};
let filler_style = tab_styles.inactive_tab.clone();
let mut row = Flex::row().scrollable::<Tabs, _>(1, autoscroll, cx);
for (ix, (item, detail)) in self.items.iter().zip(self.tab_details(cx)).enumerate() {
let item_id = item.id();
@ -890,12 +897,11 @@ impl Pane {
};
row.add_child({
let mut tab_style = match (is_pane_active, is_tab_active) {
(true, true) => theme.workspace.active_pane_active_tab.clone(),
(true, false) => theme.workspace.active_pane_inactive_tab.clone(),
(false, true) => theme.workspace.inactive_pane_active_tab.clone(),
(false, false) => theme.workspace.inactive_pane_inactive_tab.clone(),
let mut tab_style = match is_tab_active {
true => tab_styles.active_tab.clone(),
false => tab_styles.inactive_tab.clone(),
};
let title = item.tab_content(detail, &tab_style, cx);
if ix == 0 {
@ -1003,17 +1009,11 @@ impl Pane {
})
}
let filler_style = if is_pane_active {
&theme.workspace.active_pane_inactive_tab
} else {
&theme.workspace.inactive_pane_inactive_tab
};
row.add_child(
Empty::new()
.contained()
.with_style(filler_style.container)
.with_border(theme.workspace.active_pane_active_tab.container.border)
.with_border(filler_style.container.border)
.flex(0., true)
.named("filler"),
);
@ -1088,7 +1088,8 @@ impl View for Pane {
0,
cx,
|mouse_state, cx| {
let theme = &cx.global::<Settings>().theme.workspace;
let theme =
&cx.global::<Settings>().theme.workspace.tab_bar;
let style =
theme.pane_button.style_for(mouse_state, false);
Svg::new("icons/split_12.svg")
@ -1118,13 +1119,7 @@ impl View for Pane {
tab_row
.constrained()
.with_height(
cx.global::<Settings>()
.theme
.workspace
.active_pane_active_tab
.height,
)
.with_height(cx.global::<Settings>().theme.workspace.tab_bar.height)
.boxed()
})
.with_child(ChildView::new(&self.toolbar).boxed())

View file

@ -949,11 +949,11 @@ impl Workspace {
&mut self,
cx: &mut ViewContext<Self>,
app_state: Arc<AppState>,
mut callback: F,
callback: F,
) -> T
where
T: 'static,
F: FnMut(&mut Workspace, &mut ViewContext<Workspace>) -> T,
F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
{
if self.project.read(cx).is_local() {
callback(self, cx)
@ -1811,7 +1811,7 @@ impl Workspace {
match &*self.client.status().borrow() {
client::Status::ConnectionError
| client::Status::ConnectionLost
| client::Status::Reauthenticating
| client::Status::Reauthenticating { .. }
| client::Status::Reconnecting { .. }
| client::Status::ReconnectionError { .. } => Some(
Container::new(
@ -2821,7 +2821,9 @@ mod tests {
project.read_with(cx, |project, cx| {
assert_eq!(
project.active_entry(),
project.entry_for_path(&(worktree_id, "one.txt").into(), cx)
project
.entry_for_path(&(worktree_id, "one.txt").into(), cx)
.map(|e| e.id)
);
});
assert_eq!(
@ -2838,7 +2840,9 @@ mod tests {
project.read_with(cx, |project, cx| {
assert_eq!(
project.active_entry(),
project.entry_for_path(&(worktree_id, "two.txt").into(), cx)
project
.entry_for_path(&(worktree_id, "two.txt").into(), cx)
.map(|e| e.id)
);
});
@ -2856,7 +2860,9 @@ mod tests {
project.read_with(cx, |project, cx| {
assert_eq!(
project.active_entry(),
project.entry_for_path(&(worktree_id, "one.txt").into(), cx)
project
.entry_for_path(&(worktree_id, "one.txt").into(), cx)
.map(|e| e.id)
);
});

View file

@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.48.1"
version = "0.49.1"
[lib]
name = "zed"

View file

@ -249,34 +249,37 @@ impl super::LspAdapter for CLspAdapter {
#[cfg(test)]
mod tests {
use gpui::MutableAppContext;
use language::{Buffer, IndentSize};
use language::{AutoindentMode, Buffer};
use settings::Settings;
use std::sync::Arc;
#[gpui::test]
fn test_c_autoindent(cx: &mut MutableAppContext) {
cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
let mut settings = Settings::test(cx);
settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
cx.set_global(settings);
let language = crate::languages::language("c", tree_sitter_c::language(), None);
cx.add_model(|cx| {
let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(language), cx);
let size = IndentSize::spaces(2);
// empty function
buffer.edit_with_autoindent([(0..0, "int main() {}")], size, cx);
buffer.edit([(0..0, "int main() {}")], None, cx);
// indent inside braces
let ix = buffer.len() - 1;
buffer.edit_with_autoindent([(ix..ix, "\n\n")], size, cx);
buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "int main() {\n \n}");
// indent body of single-statement if statement
let ix = buffer.len() - 2;
buffer.edit_with_autoindent([(ix..ix, "if (a)\nb;")], size, cx);
buffer.edit([(ix..ix, "if (a)\nb;")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "int main() {\n if (a)\n b;\n}");
// indent inside field expression
let ix = buffer.len() - 3;
buffer.edit_with_autoindent([(ix..ix, "\n.c")], size, cx);
buffer.edit([(ix..ix, "\n.c")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "int main() {\n if (a)\n b\n .c;\n}");
buffer

View file

@ -147,20 +147,23 @@ impl LspAdapter for PythonLspAdapter {
#[cfg(test)]
mod tests {
use gpui::{ModelContext, MutableAppContext};
use language::{Buffer, IndentSize};
use language::{AutoindentMode, Buffer};
use settings::Settings;
use std::sync::Arc;
#[gpui::test]
fn test_python_autoindent(cx: &mut MutableAppContext) {
cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
let language = crate::languages::language("python", tree_sitter_python::language(), None);
let mut settings = Settings::test(cx);
settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
cx.set_global(settings);
cx.add_model(|cx| {
let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(language), cx);
let size = IndentSize::spaces(2);
let append = |buffer: &mut Buffer, text: &str, cx: &mut ModelContext<Buffer>| {
let ix = buffer.len();
buffer.edit_with_autoindent([(ix..ix, text)], size, cx);
buffer.edit([(ix..ix, text)], Some(AutoindentMode::EachLine), cx);
};
// indent after "def():"
@ -204,7 +207,11 @@ mod tests {
// dedent the closing paren if it is shifted to the beginning of the line
let argument_ix = buffer.text().find("1").unwrap();
buffer.edit_with_autoindent([(argument_ix..argument_ix + 1, "")], size, cx);
buffer.edit(
[(argument_ix..argument_ix + 1, "")],
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(
buffer.text(),
"def a():\n \n if a:\n b()\n else:\n foo(\n )"
@ -219,7 +226,11 @@ mod tests {
// manually outdent the last line
let end_whitespace_ix = buffer.len() - 4;
buffer.edit_with_autoindent([(end_whitespace_ix..buffer.len(), "")], size, cx);
buffer.edit(
[(end_whitespace_ix..buffer.len(), "")],
Some(AutoindentMode::EachLine),
cx,
);
assert_eq!(
buffer.text(),
"def a():\n \n if a:\n b()\n else:\n foo(\n )\n"
@ -233,7 +244,7 @@ mod tests {
);
// reset to a simple if statement
buffer.edit([(0..buffer.len(), "if a:\n b(\n )")], cx);
buffer.edit([(0..buffer.len(), "if a:\n b(\n )")], None, cx);
// dedent "else" on the line after a closing paren
append(&mut buffer, "\n else:\n", cx);

View file

@ -257,6 +257,7 @@ mod tests {
use super::*;
use crate::languages::{language, CachedLspAdapter};
use gpui::{color::Color, MutableAppContext};
use settings::Settings;
use theme::SyntaxTheme;
#[gpui::test]
@ -433,37 +434,39 @@ mod tests {
fn test_rust_autoindent(cx: &mut MutableAppContext) {
cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
let language = crate::languages::language("rust", tree_sitter_rust::language(), None);
let mut settings = Settings::test(cx);
settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
cx.set_global(settings);
cx.add_model(|cx| {
let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(language), cx);
let size = IndentSize::spaces(2);
// indent between braces
buffer.set_text("fn a() {}", cx);
let ix = buffer.len() - 1;
buffer.edit_with_autoindent([(ix..ix, "\n\n")], size, cx);
buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "fn a() {\n \n}");
// indent between braces, even after empty lines
buffer.set_text("fn a() {\n\n\n}", cx);
let ix = buffer.len() - 2;
buffer.edit_with_autoindent([(ix..ix, "\n")], size, cx);
buffer.edit([(ix..ix, "\n")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "fn a() {\n\n\n \n}");
// indent a line that continues a field expression
buffer.set_text("fn a() {\n \n}", cx);
let ix = buffer.len() - 2;
buffer.edit_with_autoindent([(ix..ix, "b\n.c")], size, cx);
buffer.edit([(ix..ix, "b\n.c")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "fn a() {\n b\n .c\n}");
// indent further lines that continue the field expression, even after empty lines
let ix = buffer.len() - 2;
buffer.edit_with_autoindent([(ix..ix, "\n\n.d")], size, cx);
buffer.edit([(ix..ix, "\n\n.d")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "fn a() {\n b\n .c\n \n .d\n}");
// dedent the line after the field expression
let ix = buffer.len() - 2;
buffer.edit_with_autoindent([(ix..ix, ";\ne")], size, cx);
buffer.edit([(ix..ix, ";\ne")], Some(AutoindentMode::EachLine), cx);
assert_eq!(
buffer.text(),
"fn a() {\n b\n .c\n \n .d;\n e\n}"
@ -472,17 +475,17 @@ mod tests {
// indent inside a struct within a call
buffer.set_text("const a: B = c(D {});", cx);
let ix = buffer.len() - 3;
buffer.edit_with_autoindent([(ix..ix, "\n\n")], size, cx);
buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "const a: B = c(D {\n \n});");
// indent further inside a nested call
let ix = buffer.len() - 4;
buffer.edit_with_autoindent([(ix..ix, "e: f(\n\n)")], size, cx);
buffer.edit([(ix..ix, "e: f(\n\n)")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "const a: B = c(D {\n e: f(\n \n )\n});");
// keep that indent after an empty line
let ix = buffer.len() - 8;
buffer.edit_with_autoindent([(ix..ix, "\n")], size, cx);
buffer.edit([(ix..ix, "\n")], Some(AutoindentMode::EachLine), cx);
assert_eq!(
buffer.text(),
"const a: B = c(D {\n e: f(\n \n \n )\n});"

View file

@ -28,15 +28,7 @@ use project::{Fs, ProjectStore};
use serde_json::json;
use settings::{self, KeymapFileContent, Settings, SettingsFileContent};
use smol::process::Command;
use std::{
env,
ffi::OsStr,
fs, panic,
path::{Path, PathBuf},
sync::Arc,
thread,
time::Duration,
};
use std::{env, ffi::OsStr, fs, panic, path::PathBuf, sync::Arc, thread, time::Duration};
use terminal;
use theme::ThemeRegistry;
use util::{ResultExt, TryFutureExt};
@ -50,20 +42,17 @@ use zed::{
fn main() {
let http = http::client();
let home_dir = dirs::home_dir().expect("could not find home dir");
let db_dir_path = home_dir.join("Library/Application Support/Zed");
let logs_dir_path = home_dir.join("Library/Logs/Zed");
fs::create_dir_all(&db_dir_path).expect("could not create database path");
fs::create_dir_all(&logs_dir_path).expect("could not create logs path");
init_logger(&logs_dir_path);
init_paths();
init_logger();
log::info!("========== starting zed ==========");
let mut app = gpui::App::new(Assets).unwrap();
let app_version = ZED_APP_VERSION
.or_else(|| app.platform().app_version().ok())
.map_or("dev".to_string(), |v| v.to_string());
init_panic_hook(logs_dir_path, app_version, http.clone(), app.background());
init_panic_hook(app_version, http.clone(), app.background());
let db = app.background().spawn(async move {
project::Db::open(db_dir_path.join("zed.db"))
project::Db::open(&*zed::paths::DB)
.log_err()
.unwrap_or(project::Db::null())
});
@ -99,7 +88,7 @@ fn main() {
app.run(move |cx| {
let client = client::Client::new(http.clone());
let mut languages = LanguageRegistry::new(login_shell_env_loaded);
languages.set_language_server_download_dir(zed::ROOT_PATH.clone());
languages.set_language_server_download_dir(zed::paths::LANGUAGES_DIR.clone());
let languages = Arc::new(languages);
let init_languages = cx
.background()
@ -204,35 +193,57 @@ fn main() {
});
}
fn init_logger(logs_dir_path: &Path) {
fn init_paths() {
fs::create_dir_all(&*zed::paths::CONFIG_DIR).expect("could not create config path");
fs::create_dir_all(&*zed::paths::LANGUAGES_DIR).expect("could not create languages path");
fs::create_dir_all(&*zed::paths::DB_DIR).expect("could not create database path");
fs::create_dir_all(&*zed::paths::LOGS_DIR).expect("could not create logs path");
// Copy setting files from legacy locations. TODO: remove this after a few releases.
thread::spawn(|| {
if fs::metadata(&*zed::paths::legacy::SETTINGS).is_ok()
&& fs::metadata(&*zed::paths::SETTINGS).is_err()
{
fs::copy(&*zed::paths::legacy::SETTINGS, &*zed::paths::SETTINGS).log_err();
}
if fs::metadata(&*zed::paths::legacy::KEYMAP).is_ok()
&& fs::metadata(&*zed::paths::KEYMAP).is_err()
{
fs::copy(&*zed::paths::legacy::KEYMAP, &*zed::paths::KEYMAP).log_err();
}
});
}
fn init_logger() {
if stdout_is_a_pty() {
env_logger::init();
} else {
let level = LevelFilter::Info;
let log_file_path = logs_dir_path.join("Zed.log");
// Prevent log file from becoming too large.
const MAX_LOG_BYTES: u64 = 1 * 1024 * 1024;
if fs::metadata(&*zed::paths::LOG).map_or(false, |metadata| metadata.len() > MAX_LOG_BYTES)
{
let _ = fs::rename(&*zed::paths::LOG, &*zed::paths::OLD_LOG);
}
let log_file = OpenOptions::new()
.create(true)
.append(true)
.open(log_file_path)
.open(&*zed::paths::LOG)
.expect("could not open logfile");
simplelog::WriteLogger::init(level, simplelog::Config::default(), log_file)
.expect("could not initialize logger");
}
}
fn init_panic_hook(
logs_dir_path: PathBuf,
app_version: String,
http: Arc<dyn HttpClient>,
background: Arc<Background>,
) {
fn init_panic_hook(app_version: String, http: Arc<dyn HttpClient>, background: Arc<Background>) {
background
.spawn({
let logs_dir_path = logs_dir_path.clone();
async move {
let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL);
let mut children = smol::fs::read_dir(&logs_dir_path).await?;
let mut children = smol::fs::read_dir(&*zed::paths::LOGS_DIR).await?;
while let Some(child) = children.next().await {
let child = child?;
let child_path = child.path();
@ -322,7 +333,7 @@ fn init_panic_hook(
let panic_filename = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
fs::write(
logs_dir_path.join(format!("zed-{}-{}.panic", app_version, panic_filename)),
zed::paths::LOGS_DIR.join(format!("zed-{}-{}.panic", app_version, panic_filename)),
&message,
)
.context("error writing panic to disk")
@ -456,8 +467,8 @@ fn load_config_files(
.clone()
.spawn(async move {
let settings_file =
WatchedJsonFile::new(fs.clone(), &executor, zed::SETTINGS_PATH.clone()).await;
let keymap_file = WatchedJsonFile::new(fs, &executor, zed::KEYMAP_PATH.clone()).await;
WatchedJsonFile::new(fs.clone(), &executor, zed::paths::SETTINGS.clone()).await;
let keymap_file = WatchedJsonFile::new(fs, &executor, zed::paths::KEYMAP.clone()).await;
tx.send((settings_file, keymap_file)).ok()
})
.detach();

View file

@ -274,6 +274,10 @@ pub fn menus() -> Vec<Menu<'static>> {
name: "Go to Definition",
action: Box::new(editor::GoToDefinition),
},
MenuItem::Action {
name: "Go to Type Definition",
action: Box::new(editor::GoToTypeDefinition),
},
MenuItem::Action {
name: "Go to References",
action: Box::new(editor::FindAllReferences),

24
crates/zed/src/paths.rs Normal file
View file

@ -0,0 +1,24 @@
use std::path::PathBuf;
lazy_static::lazy_static! {
static ref HOME: PathBuf = dirs::home_dir().expect("failed to determine home directory");
pub static ref CONFIG_DIR: PathBuf = HOME.join(".config").join("zed");
pub static ref LOGS_DIR: PathBuf = HOME.join("Library/Logs/Zed");
pub static ref LANGUAGES_DIR: PathBuf = HOME.join("Library/Application Support/Zed/languages");
pub static ref DB_DIR: PathBuf = HOME.join("Library/Application Support/Zed/db");
pub static ref DB: PathBuf = DB_DIR.join("zed.db");
pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json");
pub static ref KEYMAP: PathBuf = CONFIG_DIR.join("keymap.json");
pub static ref LOG: PathBuf = LOGS_DIR.join("Zed.log");
pub static ref OLD_LOG: PathBuf = LOGS_DIR.join("Zed.log.old");
}
pub mod legacy {
use std::path::PathBuf;
lazy_static::lazy_static! {
static ref CONFIG_DIR: PathBuf = super::HOME.join(".zed");
pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json");
pub static ref KEYMAP: PathBuf = CONFIG_DIR.join("keymap.json");
}
}

View file

@ -1,6 +1,7 @@
mod feedback;
pub mod languages;
pub mod menus;
pub mod paths;
pub mod settings_file;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
@ -9,6 +10,7 @@ use anyhow::{anyhow, Context, Result};
use assets::Assets;
use breadcrumbs::Breadcrumbs;
pub use client;
use collections::VecDeque;
pub use contacts_panel;
use contacts_panel::ContactsPanel;
pub use editor;
@ -21,7 +23,6 @@ use gpui::{
AssetSource, AsyncAppContext, ViewContext,
};
use language::Rope;
use lazy_static::lazy_static;
pub use lsp;
pub use project::{self, fs};
use project_panel::ProjectPanel;
@ -29,11 +30,7 @@ use search::{BufferSearchBar, ProjectSearchBar};
use serde::Deserialize;
use serde_json::to_string_pretty;
use settings::{keymap_file_json_schema, settings_file_json_schema, Settings};
use std::{
path::{Path, PathBuf},
str,
sync::Arc,
};
use std::{env, path::Path, str, sync::Arc};
use util::ResultExt;
pub use workspace;
use workspace::{sidebar::Side, AppState, Workspace};
@ -52,6 +49,7 @@ actions!(
Quit,
DebugElements,
OpenSettings,
OpenLog,
OpenKeymap,
OpenDefaultSettings,
OpenDefaultKeymap,
@ -64,14 +62,6 @@ actions!(
const MIN_FONT_SIZE: f32 = 6.0;
lazy_static! {
pub static ref ROOT_PATH: PathBuf = dirs::home_dir()
.expect("failed to determine home directory")
.join(".zed");
pub static ref SETTINGS_PATH: PathBuf = ROOT_PATH.join("settings.json");
pub static ref KEYMAP_PATH: PathBuf = ROOT_PATH.join("keymap.json");
}
pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
cx.add_action(about);
cx.add_global_action(quit);
@ -108,7 +98,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
cx.add_action({
let app_state = app_state.clone();
move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
open_config_file(&SETTINGS_PATH, app_state.clone(), cx, || {
open_config_file(&paths::SETTINGS, app_state.clone(), cx, || {
str::from_utf8(
Assets
.load("settings/initial_user_settings.json")
@ -120,10 +110,16 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
});
}
});
cx.add_action({
let app_state = app_state.clone();
move |workspace: &mut Workspace, _: &OpenLog, cx: &mut ViewContext<Workspace>| {
open_log_file(workspace, app_state.clone(), cx);
}
});
cx.add_action({
let app_state = app_state.clone();
move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| {
open_config_file(&KEYMAP_PATH, app_state.clone(), cx, || Default::default());
open_config_file(&paths::KEYMAP, app_state.clone(), cx, || Default::default());
}
});
cx.add_action({
@ -227,11 +223,11 @@ pub fn initialize_workspace(
},
"schemas": [
{
"fileMatch": [".zed/settings.json"],
"fileMatch": [schema_file_match(&*paths::SETTINGS)],
"schema": settings_file_json_schema(theme_names, language_names),
},
{
"fileMatch": [".zed/keymap.json"],
"fileMatch": [schema_file_match(&*paths::KEYMAP)],
"schema": keymap_file_json_schema(&action_names),
}
]
@ -389,7 +385,6 @@ fn open_config_file(
cx.spawn(|workspace, mut cx| async move {
let fs = &app_state.fs;
if !fs.is_file(path).await {
fs.create_dir(&ROOT_PATH).await?;
fs.create_file(path, Default::default()).await?;
fs.save(path, &default_content(), Default::default())
.await?;
@ -407,6 +402,60 @@ fn open_config_file(
.detach_and_log_err(cx)
}
fn open_log_file(
workspace: &mut Workspace,
app_state: Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) {
const MAX_LINES: usize = 1000;
workspace.with_local_workspace(cx, app_state.clone(), |_, cx| {
cx.spawn_weak(|workspace, mut cx| async move {
let (old_log, new_log) = futures::join!(
app_state.fs.load(&paths::OLD_LOG),
app_state.fs.load(&paths::LOG)
);
if let Some(workspace) = workspace.upgrade(&cx) {
let mut lines = VecDeque::with_capacity(MAX_LINES);
for line in old_log
.iter()
.flat_map(|log| log.lines())
.chain(new_log.iter().flat_map(|log| log.lines()))
{
if lines.len() == MAX_LINES {
lines.pop_front();
}
lines.push_back(line);
}
let log = lines
.into_iter()
.flat_map(|line| [line, "\n"])
.collect::<String>();
workspace.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
let buffer = project
.update(cx, |project, cx| project.create_buffer("", None, cx))
.expect("creating buffers on a local workspace always succeeds");
buffer.update(cx, |buffer, cx| buffer.edit([(0..0, log)], None, cx));
let buffer = cx.add_model(|cx| {
MultiBuffer::singleton(buffer, cx).with_title("Log".into())
});
workspace.add_item(
Box::new(
cx.add_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx)),
),
cx,
);
});
}
})
.detach();
});
}
fn open_bundled_config_file(
workspace: &mut Workspace,
app_state: Arc<AppState>,
@ -431,6 +480,11 @@ fn open_bundled_config_file(
});
}
fn schema_file_match(path: &Path) -> &Path {
path.strip_prefix(path.parent().unwrap().parent().unwrap())
.unwrap()
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -18,7 +18,7 @@ export default function contextMenu(theme: Theme) {
item: {
padding: { left: 4, right: 4, top: 2, bottom: 2 },
cornerRadius: 6,
label: text(theme, "sans", "secondary", { size: "sm" }),
label: text(theme, "sans", "primary", { size: "sm" }),
keystroke: {
...text(theme, "sans", "muted", { size: "sm", weight: "bold" }),
padding: { left: 3, right: 3 },
@ -29,7 +29,7 @@ export default function contextMenu(theme: Theme) {
},
active: {
background: backgroundColor(theme, 300, "active"),
text: text(theme, "sans", "primary", { size: "sm" }),
text: text(theme, "sans", "active", { size: "sm" }),
},
activeHover: {
background: backgroundColor(theme, 300, "hovered"),

View file

@ -55,7 +55,7 @@ export default function editor(theme: Theme) {
textColor: theme.syntax.primary.color,
background: backgroundColor(theme, 500),
activeLineBackground: theme.editor.line.active,
codeActionsIndicator: iconColor(theme, "muted"),
codeActionsIndicator: iconColor(theme, "secondary"),
diffBackgroundDeleted: backgroundColor(theme, "error"),
diffBackgroundInserted: backgroundColor(theme, "ok"),
documentHighlightReadBackground: theme.editor.highlight.occurrence,
@ -107,7 +107,7 @@ export default function editor(theme: Theme) {
top: true,
}),
code: {
...text(theme, "mono", "muted", { size: "sm" }),
...text(theme, "mono", "secondary", { size: "sm" }),
margin: {
left: 10,
},
@ -135,17 +135,17 @@ export default function editor(theme: Theme) {
warningDiagnostic: diagnostic(theme, "warning"),
informationDiagnostic: diagnostic(theme, "info"),
hintDiagnostic: diagnostic(theme, "info"),
invalidErrorDiagnostic: diagnostic(theme, "muted"),
invalidHintDiagnostic: diagnostic(theme, "muted"),
invalidInformationDiagnostic: diagnostic(theme, "muted"),
invalidWarningDiagnostic: diagnostic(theme, "muted"),
invalidErrorDiagnostic: diagnostic(theme, "secondary"),
invalidHintDiagnostic: diagnostic(theme, "secondary"),
invalidInformationDiagnostic: diagnostic(theme, "secondary"),
invalidWarningDiagnostic: diagnostic(theme, "secondary"),
hoverPopover: hoverPopover(theme),
linkDefinition: {
color: theme.syntax.linkUri.color,
underline: theme.syntax.linkUri.underline,
},
jumpIcon: {
color: iconColor(theme, "muted"),
color: iconColor(theme, "secondary"),
iconWidth: 20,
buttonWidth: 20,
cornerRadius: 6,
@ -157,7 +157,7 @@ export default function editor(theme: Theme) {
},
hover: {
color: iconColor(theme, "active"),
background: backgroundColor(theme, "on500", "base"),
background: backgroundColor(theme, "on500"),
},
},
compositionMark: {

View file

@ -24,7 +24,7 @@ export default function picker(theme: Theme) {
highlightText: text(theme, "sans", "feature", { weight: "bold" }),
active: {
background: backgroundColor(theme, 300, "active"),
text: text(theme, "sans", "primary"),
text: text(theme, "sans", "active"),
},
hover: {
background: backgroundColor(theme, 300, "hovered"),
@ -32,7 +32,7 @@ export default function picker(theme: Theme) {
},
border: border(theme, "primary"),
empty: {
text: text(theme, "sans", "placeholder"),
text: text(theme, "sans", "muted"),
padding: {
bottom: 4,
left: 12,

View file

@ -12,25 +12,24 @@ export default function projectPanel(theme: Theme) {
iconColor: iconColor(theme, "muted"),
iconSize: 8,
iconSpacing: 8,
text: text(theme, "mono", "muted", { size: "sm" }),
text: text(theme, "mono", "secondary", { size: "sm" }),
hover: {
background: backgroundColor(theme, 300, "hovered"),
text: text(theme, "mono", "primary", { size: "sm" }),
},
active: {
background: backgroundColor(theme, 300, "active"),
text: text(theme, "mono", "primary", { size: "sm" }),
text: text(theme, "mono", "active", { size: "sm" }),
},
activeHover: {
background: backgroundColor(theme, 300, "hovered"),
background: backgroundColor(theme, 300, "active"),
text: text(theme, "mono", "active", { size: "sm" }),
},
},
cutEntryFade: 0.4,
ignoredEntryFade: 0.6,
filenameEditor: {
background: backgroundColor(theme, 500, "active"),
text: text(theme, "mono", "primary", { size: "sm" }),
background: backgroundColor(theme, "on300"),
text: text(theme, "mono", "active", { size: "sm" }),
selection: player(theme, 1).selection,
},
};

View file

@ -23,28 +23,28 @@ export default function statusBar(theme: Theme) {
right: 6,
},
border: border(theme, "primary", { top: true, overlay: true }),
cursorPosition: text(theme, "sans", "muted"),
autoUpdateProgressMessage: text(theme, "sans", "muted"),
autoUpdateDoneMessage: text(theme, "sans", "muted"),
cursorPosition: text(theme, "sans", "secondary"),
autoUpdateProgressMessage: text(theme, "sans", "secondary"),
autoUpdateDoneMessage: text(theme, "sans", "secondary"),
lspStatus: {
...diagnosticStatusContainer,
iconSpacing: 4,
iconWidth: 14,
height: 18,
message: text(theme, "sans", "muted"),
message: text(theme, "sans", "secondary"),
iconColor: iconColor(theme, "muted"),
hover: {
message: text(theme, "sans", "primary"),
iconColor: iconColor(theme, "active"),
iconColor: iconColor(theme, "primary"),
background: backgroundColor(theme, 300, "hovered"),
},
},
diagnosticMessage: {
...text(theme, "sans", "muted"),
hover: text(theme, "sans", "secondary"),
...text(theme, "sans", "secondary"),
hover: text(theme, "sans", "active"),
},
feedback: {
...text(theme, "sans", "muted"),
...text(theme, "sans", "secondary"),
hover: text(theme, "sans", "active"),
},
diagnosticSummary: {
@ -53,7 +53,7 @@ export default function statusBar(theme: Theme) {
iconSpacing: 2,
summarySpacing: 6,
text: text(theme, "sans", "primary", { size: "sm" }),
iconColorOk: iconColor(theme, "secondary"),
iconColorOk: iconColor(theme, "muted"),
iconColorWarning: iconColor(theme, "warning"),
iconColorError: iconColor(theme, "error"),
containerOk: {
@ -95,7 +95,7 @@ export default function statusBar(theme: Theme) {
item: {
...statusContainer,
iconSize: 16,
iconColor: iconColor(theme, "secondary"),
iconColor: iconColor(theme, "muted"),
hover: {
iconColor: iconColor(theme, "active"),
background: backgroundColor(theme, 300, "hovered"),

View file

@ -0,0 +1,87 @@
import Theme from "../themes/common/theme";
import { iconColor, text, border, backgroundColor } from "./components";
export default function tabBar(theme: Theme) {
const height = 32;
const tab = {
height,
background: backgroundColor(theme, 300),
border: border(theme, "primary", {
left: true,
bottom: true,
overlay: true,
}),
iconClose: iconColor(theme, "muted"),
iconCloseActive: iconColor(theme, "active"),
iconConflict: iconColor(theme, "warning"),
iconDirty: iconColor(theme, "info"),
iconWidth: 8,
spacing: 8,
text: text(theme, "sans", "secondary", { size: "sm" }),
padding: {
left: 8,
right: 8,
},
description: {
margin: { left: 6, top: 1 },
...text(theme, "sans", "muted", { size: "2xs" })
}
};
const activePaneActiveTab = {
...tab,
background: backgroundColor(theme, 500),
text: text(theme, "sans", "active", { size: "sm" }),
border: {
...tab.border,
bottom: false
},
};
const inactivePaneInactiveTab = {
...tab,
background: backgroundColor(theme, 300),
text: text(theme, "sans", "muted", { size: "sm" }),
};
const inactivePaneActiveTab = {
...tab,
background: backgroundColor(theme, 500),
text: text(theme, "sans", "secondary", { size: "sm" }),
border: {
...tab.border,
bottom: false
},
}
return {
height,
background: backgroundColor(theme, 300),
border: border(theme, "primary", {
left: true,
bottom: true,
overlay: true,
}),
activePane: {
activeTab: activePaneActiveTab,
inactiveTab: tab,
},
inactivePane: {
activeTab: inactivePaneActiveTab,
inactiveTab: inactivePaneInactiveTab,
},
paneButton: {
color: iconColor(theme, "secondary"),
border: {
...tab.border,
},
iconWidth: 12,
buttonWidth: activePaneActiveTab.height,
hover: {
color: iconColor(theme, "active"),
background: backgroundColor(theme, 300),
},
},
}
}

View file

@ -9,13 +9,13 @@ export default function tooltip(theme: Theme) {
margin: { top: 6, left: 6 },
shadow: popoverShadow(theme),
cornerRadius: 6,
text: text(theme, "sans", "secondary", { size: "xs", weight: "bold" }),
text: text(theme, "sans", "primary", { size: "xs" }),
keystroke: {
background: backgroundColor(theme, "on500"),
cornerRadius: 4,
margin: { left: 6 },
padding: { left: 4, right: 4 },
...text(theme, "mono", "muted", { size: "xs", weight: "bold" }),
...text(theme, "mono", "secondary", { size: "xs", weight: "bold" }),
},
maxTextWidth: 200,
};

View file

@ -8,58 +8,13 @@ import {
text,
} from "./components";
import statusBar from "./statusBar";
import tabBar from "./tabBar";
export function workspaceBackground(theme: Theme) {
return backgroundColor(theme, 300);
}
export default function workspace(theme: Theme) {
const activePaneInactiveTab = {
height: 32,
background: workspaceBackground(theme),
iconClose: iconColor(theme, "muted"),
iconCloseActive: iconColor(theme, "active"),
iconConflict: iconColor(theme, "warning"),
iconDirty: iconColor(theme, "info"),
iconWidth: 8,
spacing: 8,
text: text(theme, "sans", "secondary", { size: "sm" }),
border: border(theme, "primary", {
left: true,
bottom: true,
overlay: true,
}),
padding: {
left: 8,
right: 8,
},
description: {
margin: { left: 6, top: 1 },
...text(theme, "sans", "muted", { size: "2xs" })
}
};
const activePaneActiveTab = {
...activePaneInactiveTab,
background: backgroundColor(theme, 500),
text: text(theme, "sans", "active", { size: "sm" }),
border: {
...activePaneInactiveTab.border,
bottom: false,
},
};
const inactivePaneInactiveTab = {
...activePaneInactiveTab,
background: backgroundColor(theme, 100),
text: text(theme, "sans", "placeholder", { size: "sm" }),
};
const inactivePaneActiveTab = {
...activePaneInactiveTab,
text: text(theme, "sans", "placeholder", { size: "sm" }),
}
const titlebarPadding = 6;
return {
@ -74,22 +29,7 @@ export default function workspace(theme: Theme) {
},
leaderBorderOpacity: 0.7,
leaderBorderWidth: 2.0,
activePaneActiveTab,
activePaneInactiveTab,
inactivePaneActiveTab,
inactivePaneInactiveTab,
paneButton: {
color: iconColor(theme, "secondary"),
border: {
...activePaneActiveTab.border,
},
iconWidth: 12,
buttonWidth: activePaneActiveTab.height,
hover: {
color: iconColor(theme, "active"),
background: backgroundColor(theme, 300),
},
},
tabBar: tabBar(theme),
modal: {
margin: {
bottom: 52,
@ -188,7 +128,7 @@ export default function workspace(theme: Theme) {
cornerRadius: 6,
hover: {
color: iconColor(theme, "active"),
background: backgroundColor(theme, 300),
background: backgroundColor(theme, "on500", "hovered"),
},
disabled: {
color: withOpacity(iconColor(theme, "muted"), 0.6),

View file

@ -130,8 +130,8 @@ export function createTheme(
const textColor = {
primary: sample(ramps.neutral, 6),
secondary: sample(ramps.neutral, 5),
muted: sample(ramps.neutral, 5),
placeholder: sample(ramps.neutral, 4),
muted: sample(ramps.neutral, 4),
placeholder: sample(ramps.neutral, 3),
active: sample(ramps.neutral, 7),
feature: sample(ramps.blue, 0.5),
ok: sample(ramps.green, 0.5),