Compare commits
34 Commits
dynamic-ru
...
v0.125.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eae06aaca9 | ||
|
|
8ebee1f31d | ||
|
|
bc4230a6b1 | ||
|
|
430d197e91 | ||
|
|
a9696c59cc | ||
|
|
3afc9d8607 | ||
|
|
1c9ba85146 | ||
|
|
80ebf5aff9 | ||
|
|
acd21f5c15 | ||
|
|
608ad1636d | ||
|
|
c05f8154be | ||
|
|
de71d7c0a0 | ||
|
|
da671d9771 | ||
|
|
993c001fc4 | ||
|
|
7e501f6c1b | ||
|
|
3a0f842f3d | ||
|
|
38564c5ab2 | ||
|
|
023498ef53 | ||
|
|
1f61804ae0 | ||
|
|
d229b39621 | ||
|
|
ae2326c781 | ||
|
|
c4d1855824 | ||
|
|
57f5f128f3 | ||
|
|
7efa8d079d | ||
|
|
d0ffd51bb1 | ||
|
|
7aba9eb4b7 | ||
|
|
517ea734ee | ||
|
|
a52177fd39 | ||
|
|
893e55ff96 | ||
|
|
f8959834c4 | ||
|
|
2e516261fe | ||
|
|
ca092fb694 | ||
|
|
96d9df073e | ||
|
|
9f7e625d37 |
9
Cargo.lock
generated
@@ -6374,6 +6374,7 @@ dependencies = [
|
||||
name = "picker"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ctor",
|
||||
"editor",
|
||||
"env_logger",
|
||||
@@ -7143,11 +7144,16 @@ dependencies = [
|
||||
name = "recent_projects"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"editor",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"language",
|
||||
"menu",
|
||||
"ordered-float 2.10.0",
|
||||
"picker",
|
||||
"project",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smol",
|
||||
"ui",
|
||||
"util",
|
||||
@@ -10487,6 +10493,7 @@ dependencies = [
|
||||
"take-until",
|
||||
"tempfile",
|
||||
"tendril",
|
||||
"unicase",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -11602,7 +11609,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.125.0"
|
||||
version = "0.125.4"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
|
||||
@@ -294,6 +294,7 @@ tree-sitter-vue = { git = "https://github.com/zed-industries/tree-sitter-vue", r
|
||||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930" }
|
||||
tree-sitter-zig = { git = "https://github.com/maxxnino/tree-sitter-zig", rev = "0d08703e4c3f426ec61695d7617415fff97029bd" }
|
||||
unindent = "0.1.7"
|
||||
unicase = "2.6"
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
wasmtime = "18.0"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 22.596c6.628 0 12-4.338 12-9.688 0-3.318-2.057-6.248-5.219-7.986-1.286-.715-2.297-1.357-3.139-1.89C14.058 2.025 13.08 1.404 12 1.404c-1.097 0-2.334.785-3.966 1.821a49.92 49.92 0 0 1-2.816 1.697C2.057 6.66 0 9.59 0 12.908c0 5.35 5.372 9.687 12 9.687v.001ZM10.599 4.715c.334-.759.503-1.58.498-2.409 0-.145.202-.187.23-.029.658 2.783-.902 4.162-2.057 4.624-.124.048-.199-.121-.103-.209a5.763 5.763 0 0 0 1.432-1.977Zm2.058-.102a5.82 5.82 0 0 0-.782-2.306v-.016c-.069-.123.086-.263.185-.172 1.962 2.111 1.307 4.067.556 5.051-.082.103-.23-.003-.189-.126a5.85 5.85 0 0 0 .23-2.431Zm1.776-.561a5.727 5.727 0 0 0-1.612-1.806v-.014c-.112-.085-.024-.274.114-.218 2.595 1.087 2.774 3.18 2.459 4.407a.116.116 0 0 1-.049.071.11.11 0 0 1-.153-.026.122.122 0 0 1-.022-.083 5.891 5.891 0 0 0-.737-2.331Zm-5.087.561c-.617.546-1.282.76-2.063 1-.117 0-.195-.078-.156-.181 1.752-.909 2.376-1.649 2.999-2.778 0 0 .155-.118.188.085 0 .304-.349 1.329-.968 1.874Zm4.945 11.237a2.957 2.957 0 0 1-.937 1.553c-.346.346-.8.565-1.286.62a2.178 2.178 0 0 1-1.327-.62 2.955 2.955 0 0 1-.925-1.553.244.244 0 0 1 .064-.198.234.234 0 0 1 .193-.069h3.965a.226.226 0 0 1 .19.07c.05.053.073.125.063.197Zm-5.458-2.176a1.862 1.862 0 0 1-2.384-.245 1.98 1.98 0 0 1-.233-2.447c.207-.319.503-.566.848-.713a1.84 1.84 0 0 1 1.092-.11c.366.075.703.261.967.531a1.98 1.98 0 0 1 .408 2.114 1.931 1.931 0 0 1-.698.869v.001Zm8.495.005a1.86 1.86 0 0 1-2.381-.253 1.964 1.964 0 0 1-.547-1.366c0-.384.11-.76.32-1.079.207-.319.503-.567.849-.713a1.844 1.844 0 0 1 1.093-.108c.367.076.704.262.968.534a1.98 1.98 0 0 1 .4 2.117 1.932 1.932 0 0 1-.702.868Z"/>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 7 13.179688 C 10.867188 13.179688 14 10.652344 14 7.53125 C 14 5.59375 12.800781 3.886719 10.957031 2.871094 C 10.207031 2.453125 9.617188 2.078125 9.125 1.769531 C 8.199219 1.179688 7.628906 0.820312 7 0.820312 C 6.359375 0.820312 5.636719 1.277344 4.6875 1.882812 C 4.148438 2.230469 3.601562 2.558594 3.042969 2.871094 C 1.199219 3.886719 0 5.59375 0 7.53125 C 0 10.652344 3.132812 13.179688 7 13.179688 Z M 6.183594 2.75 C 6.378906 2.308594 6.476562 1.828125 6.472656 1.34375 C 6.472656 1.261719 6.589844 1.234375 6.605469 1.328125 C 6.992188 2.953125 6.082031 3.757812 5.40625 4.027344 C 5.335938 4.054688 5.292969 3.953125 5.347656 3.902344 C 5.703125 3.582031 5.988281 3.191406 6.183594 2.75 Z M 7.382812 2.691406 C 7.328125 2.214844 7.171875 1.757812 6.925781 1.347656 L 6.925781 1.335938 C 6.886719 1.265625 6.976562 1.183594 7.035156 1.234375 C 8.179688 2.46875 7.796875 3.609375 7.359375 4.183594 C 7.3125 4.242188 7.226562 4.179688 7.25 4.109375 C 7.394531 3.652344 7.4375 3.167969 7.382812 2.691406 Z M 8.417969 2.363281 C 8.183594 1.949219 7.863281 1.589844 7.480469 1.308594 L 7.480469 1.300781 C 7.414062 1.253906 7.464844 1.140625 7.546875 1.175781 C 9.058594 1.808594 9.164062 3.03125 8.980469 3.746094 C 8.976562 3.761719 8.964844 3.777344 8.953125 3.785156 C 8.921875 3.808594 8.882812 3.800781 8.863281 3.773438 C 8.851562 3.757812 8.847656 3.742188 8.847656 3.722656 C 8.800781 3.246094 8.65625 2.78125 8.417969 2.363281 Z M 5.453125 2.691406 C 5.09375 3.007812 4.703125 3.132812 4.25 3.273438 C 4.179688 3.273438 4.132812 3.230469 4.15625 3.167969 C 5.179688 2.636719 5.542969 2.207031 5.90625 1.546875 C 5.90625 1.546875 5.996094 1.480469 6.015625 1.597656 C 6.015625 1.773438 5.8125 2.371094 5.453125 2.691406 Z M 8.335938 9.246094 C 8.253906 9.597656 8.0625 9.914062 7.789062 10.152344 C 7.589844 10.355469 7.324219 10.480469 7.039062 10.511719 C 6.746094 10.484375 6.472656 10.359375 6.265625 10.152344 C 5.996094 9.914062 5.808594 9.597656 5.726562 9.246094 C 5.71875 9.203125 5.734375 9.160156 5.761719 9.128906 C 5.792969 9.101562 5.835938 9.085938 5.875 9.089844 L 8.1875 9.089844 C 8.230469 9.085938 8.269531 9.101562 8.300781 9.132812 C 8.328125 9.160156 8.34375 9.203125 8.335938 9.246094 Z M 5.152344 7.976562 C 4.714844 8.273438 4.128906 8.210938 3.761719 7.832031 C 3.390625 7.445312 3.335938 6.855469 3.625 6.40625 C 3.746094 6.21875 3.917969 6.074219 4.121094 5.992188 C 4.320312 5.90625 4.542969 5.882812 4.757812 5.925781 C 4.972656 5.96875 5.167969 6.078125 5.320312 6.234375 C 5.636719 6.5625 5.730469 7.046875 5.558594 7.46875 C 5.476562 7.675781 5.335938 7.851562 5.152344 7.976562 Z M 10.109375 7.980469 C 9.671875 8.273438 9.085938 8.210938 8.71875 7.832031 C 8.511719 7.617188 8.398438 7.332031 8.398438 7.035156 C 8.398438 6.8125 8.464844 6.589844 8.585938 6.40625 C 8.707031 6.21875 8.878906 6.074219 9.082031 5.988281 C 9.28125 5.90625 9.503906 5.882812 9.71875 5.925781 C 9.933594 5.972656 10.128906 6.078125 10.285156 6.238281 C 10.597656 6.566406 10.691406 7.050781 10.515625 7.472656 C 10.433594 7.679688 10.292969 7.855469 10.109375 7.980469 Z M 10.109375 7.980469 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.3 KiB |
6
assets/icons/file_icons/coffeescript.svg
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
@@ -1,4 +1,6 @@
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24.235 6.519l-16.47-0.004 0.266 3.277 12.653 0.002-0.319 3.394h-8.298l0.3 3.215h7.725l-0.457 4.403-3.636 1.005-3.694-1.012-0.235-2.637h-3.262l0.362 4.817 6.829 2.128 6.714-1.912 1.521-16.675zM2.879 1.004h26.242l-2.387 26.946-10.763 3.045-10.703-3.047z"></path>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 10.601562 2.851562 L 3.398438 2.851562 L 3.511719 4.285156 L 9.050781 4.285156 L 8.910156 5.769531 L 5.28125 5.769531 L 5.410156 7.175781 L 8.789062 7.175781 L 8.589844 9.101562 L 7 9.542969 L 5.382812 9.097656 L 5.28125 7.945312 L 3.851562 7.945312 L 4.011719 10.054688 L 7 10.984375 L 9.9375 10.148438 Z M 1.257812 0.4375 L 12.742188 0.4375 L 11.695312 12.226562 L 6.988281 13.558594 L 2.304688 12.226562 Z M 1.257812 0.4375 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 482 B After Width: | Height: | Size: 730 B |
@@ -1 +1,12 @@
|
||||
<svg height="64" viewBox="0 0 128 128" width="64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="0" x2="128" y1="128" y2="0"><stop offset="0" stop-color="#333"/><stop offset="1" stop-color="#5d5d5d"/></linearGradient><path d="m12.239265 30.664279h14.960911c-5.59432 5.460938-7.654216 10.692785-10.342106 18.023379-3.200764 8.729348-.549141 29.987457 3.815534 37.55289 2.943384 5.101853 6.282685 8.994876 8.233522 11.095173h-16.667861zm89.614855 0h13.90661v66.671442h-13.55518c1.31391-1.750328 3.43934-4.534454 5.12085-6.426163 2.32782-2.618784 4.97023-6.978412 4.97023-6.978412l-16.015202-8.133112s-5.48977 11.600331-15.964999 15.964998c-10.475214 4.364666-19.784679-.838179-25.604243-7.530659-5.819578-6.692502-5.82371-22.14014-5.82371-22.14014h60.797524c1.16391-14.839892-2.63216-21.249816-4.66901-25.90547-.91799-2.098266-1.89261-3.810819-3.16287-5.522484zm-38.356164 1.757154c.35429-.01632.731685-.0092 1.104497 0 11.930114.290977 13.053143 12.802122 13.053143 12.802122h-27.311192s2.170772-12.298638 13.153552-12.802122z" fill="url(#a)"/></svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<defs>
|
||||
<linearGradient id="linear0" gradientUnits="userSpaceOnUse" x1="0" y1="128" x2="128" y2="0" gradientTransform="matrix(0.109375,0,0,0.109375,0,0)">
|
||||
<stop offset="0" style="stop-color:rgb(20%,20%,20%);stop-opacity:1;"/>
|
||||
<stop offset="1" style="stop-color:rgb(36.470588%,36.470588%,36.470588%);stop-opacity:1;"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear0);" d="M 1.339844 3.355469 L 2.976562 3.355469 C 2.363281 3.953125 2.136719 4.523438 1.84375 5.324219 C 1.492188 6.28125 1.785156 8.605469 2.261719 9.433594 C 2.582031 9.992188 2.949219 10.417969 3.160156 10.644531 L 1.339844 10.644531 Z M 11.140625 3.355469 L 12.660156 3.355469 L 12.660156 10.644531 L 11.179688 10.644531 C 11.324219 10.453125 11.554688 10.148438 11.738281 9.941406 C 11.992188 9.65625 12.28125 9.179688 12.28125 9.179688 L 10.53125 8.289062 C 10.53125 8.289062 9.929688 9.558594 8.785156 10.035156 C 7.640625 10.515625 6.621094 9.945312 5.984375 9.214844 C 5.347656 8.480469 5.347656 6.792969 5.347656 6.792969 L 11.996094 6.792969 C 12.125 5.167969 11.710938 4.46875 11.484375 3.957031 C 11.386719 3.726562 11.277344 3.542969 11.140625 3.355469 Z M 6.945312 3.546875 C 6.984375 3.542969 7.023438 3.546875 7.066406 3.546875 C 8.371094 3.578125 8.492188 4.945312 8.492188 4.945312 L 5.507812 4.945312 C 5.507812 4.945312 5.742188 3.601562 6.945312 3.546875 Z M 6.945312 3.546875 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -27,6 +27,7 @@
|
||||
"css": "css",
|
||||
"csv": "storage",
|
||||
"cts": "typescript",
|
||||
"coffee": "coffeescript",
|
||||
"dart": "dart",
|
||||
"dat": "storage",
|
||||
"db": "storage",
|
||||
@@ -48,6 +49,7 @@
|
||||
"fmp": "storage",
|
||||
"fp7": "storage",
|
||||
"frm": "storage",
|
||||
"fs": "fsharp",
|
||||
"gdb": "storage",
|
||||
"gif": "image",
|
||||
"gitattributes": "vcs",
|
||||
@@ -104,6 +106,7 @@
|
||||
"myd": "storage",
|
||||
"myi": "storage",
|
||||
"nu": "terminal",
|
||||
"nim": "nim",
|
||||
"odp": "document",
|
||||
"ods": "document",
|
||||
"odt": "document",
|
||||
@@ -138,6 +141,9 @@
|
||||
"sqlite": "storage",
|
||||
"svelte": "template",
|
||||
"svg": "image",
|
||||
"sc": "scala",
|
||||
"scala": "scala",
|
||||
"sql": "storage",
|
||||
"swift": "swift",
|
||||
"tf": "terraform",
|
||||
"tfvars": "terraform",
|
||||
@@ -148,6 +154,7 @@
|
||||
"ttf": "font",
|
||||
"tsx": "code",
|
||||
"txt": "document",
|
||||
"tcl": "tcl",
|
||||
"vue": "vue",
|
||||
"wav": "audio",
|
||||
"webm": "video",
|
||||
@@ -189,6 +196,9 @@
|
||||
"css": {
|
||||
"icon": "icons/file_icons/css.svg"
|
||||
},
|
||||
"coffeescript": {
|
||||
"icon": "icons/file_icons/coffeescript.svg"
|
||||
},
|
||||
"dart": {
|
||||
"icon": "icons/file_icons/dart.svg"
|
||||
},
|
||||
@@ -222,6 +232,9 @@
|
||||
"font": {
|
||||
"icon": "icons/file_icons/font.svg"
|
||||
},
|
||||
"fsharp": {
|
||||
"icon": "icons/file_icons/fsharp.svg"
|
||||
},
|
||||
"haskell": {
|
||||
"icon": "icons/file_icons/haskell.svg"
|
||||
},
|
||||
@@ -258,6 +271,9 @@
|
||||
"ocaml": {
|
||||
"icon": "icons/file_icons/ocaml.svg"
|
||||
},
|
||||
"nim": {
|
||||
"icon": "icons/file_icons/nim.svg"
|
||||
},
|
||||
"phoenix": {
|
||||
"icon": "icons/file_icons/phoenix.svg"
|
||||
},
|
||||
@@ -288,6 +304,9 @@
|
||||
"storage": {
|
||||
"icon": "icons/file_icons/database.svg"
|
||||
},
|
||||
"scala": {
|
||||
"icon": "icons/file_icons/scala.svg"
|
||||
},
|
||||
"swift": {
|
||||
"icon": "icons/file_icons/swift.svg"
|
||||
},
|
||||
@@ -306,6 +325,9 @@
|
||||
"typescript": {
|
||||
"icon": "icons/file_icons/typescript.svg"
|
||||
},
|
||||
"tcl": {
|
||||
"icon": "icons/file_icons/tcl.svg"
|
||||
},
|
||||
"vcs": {
|
||||
"icon": "icons/file_icons/git.svg"
|
||||
},
|
||||
|
||||
8
assets/icons/file_icons/fsharp.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 3.324219 3.496094 C 1.507812 5.40625 0.0273438 6.984375 0.0273438 6.996094 C 0.0273438 7.015625 1.511719 8.59375 3.332031 10.503906 L 6.632812 13.980469 L 6.632812 10.472656 L 4.984375 8.734375 L 3.332031 6.996094 L 4.984375 5.257812 L 6.632812 3.519531 L 6.628906 1.773438 L 6.621094 0.0273438 Z M 3.324219 3.496094 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 7.125 1.773438 L 7.125 3.492188 L 8.769531 5.222656 C 9.675781 6.171875 10.421875 6.96875 10.425781 6.984375 C 10.433594 7.003906 9.691406 7.800781 8.78125 8.761719 L 7.125 10.503906 L 7.125 13.972656 L 7.214844 13.890625 C 7.296875 13.820312 8.203125 12.90625 9.167969 11.910156 C 9.398438 11.671875 9.605469 11.464844 9.621094 11.449219 C 9.671875 11.402344 11.261719 9.789062 11.601562 9.4375 C 11.773438 9.261719 11.957031 9.082031 12 9.035156 C 12.046875 8.988281 12.433594 8.59375 12.863281 8.160156 C 13.289062 7.726562 13.722656 7.289062 13.824219 7.183594 L 14.007812 6.996094 L 13.808594 6.796875 C 13.179688 6.167969 12.527344 5.503906 11.820312 4.785156 C 11.574219 4.53125 11.105469 4.058594 10.785156 3.734375 C 10.460938 3.414062 9.871094 2.8125 9.472656 2.402344 C 9.074219 1.996094 8.609375 1.515625 8.4375 1.339844 C 8.265625 1.160156 7.910156 0.800781 7.652344 0.539062 C 7.394531 0.273438 7.167969 0.0585938 7.15625 0.0585938 C 7.132812 0.0585938 7.125 0.59375 7.125 1.773438 Z M 7.125 1.773438 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 5.453125 5.757812 C 4.308594 6.964844 4.285156 6.992188 4.332031 7.050781 C 4.359375 7.082031 4.886719 7.636719 5.5 8.289062 L 6.621094 9.464844 L 6.628906 8.222656 C 6.632812 7.539062 6.632812 6.429688 6.628906 5.753906 L 6.621094 4.527344 Z M 5.453125 5.757812 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -1,13 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg977"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs981" />
|
||||
<path
|
||||
id="path973"
|
||||
d="M 10.699219 8.9003906 L 10.699219 9 C 12.199219 11.3 13.800781 13.600391 15.300781 15.900391 L 15.300781 16 C 13.800781 18.3 12.199219 20.600391 10.699219 22.900391 L 10.699219 23 L 14.199219 23 L 14.300781 22.900391 C 15.200781 21.500391 16.199609 20.099219 17.099609 18.699219 C 17.199609 18.599219 17.099219 18.599219 17.199219 18.699219 C 18.099219 20.099219 19.1 21.500391 20 22.900391 L 20.099609 23 L 23.599609 23 C 21.699609 20 19.699219 17.099609 17.699219 14.099609 C 16.499219 12.399609 15.399219 10.600391 14.199219 8.9003906 L 10.699219 8.9003906 z M 6 9 C 7.6 11.3 9.0996094 13.6 10.599609 16 L 10.599609 16.099609 C 9.4996094 17.799609 8.4007813 19.399609 7.3007812 21.099609 C 6.8007813 21.699609 6.4 22.4 6 23 L 6 23.099609 L 9.5 23.099609 C 11.1 20.699609 12.699219 18.399609 14.199219 16.099609 L 14.199219 16 C 13.499219 14.8 12.700391 13.7 11.900391 12.5 C 11.100391 11.4 10.399609 10.199609 9.5996094 9.0996094 L 9.5 9 L 6 9 z M 18.199219 13 L 18.199219 13.099609 C 18.699219 13.899609 19.199219 14.600391 19.699219 15.400391 L 26 15.400391 L 26 13 L 18.199219 13 z M 20.5 16.599609 L 20.5 16.699219 C 21 17.499219 21.5 18.2 22 19 L 26 19 L 26 16.599609 L 20.5 16.599609 z " />
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 4.679688 3.894531 L 4.679688 3.9375 C 5.335938 4.945312 6.039062 5.949219 6.695312 6.957031 L 6.695312 7 C 6.039062 8.007812 5.335938 9.011719 4.679688 10.019531 L 4.679688 10.0625 L 6.210938 10.0625 L 6.257812 10.019531 C 6.648438 9.40625 7.085938 8.792969 7.480469 8.179688 C 7.523438 8.136719 7.480469 8.136719 7.523438 8.179688 C 7.917969 8.792969 8.355469 9.40625 8.75 10.019531 L 8.792969 10.0625 L 10.324219 10.0625 C 9.492188 8.75 8.617188 7.480469 7.742188 6.167969 C 7.21875 5.425781 6.738281 4.636719 6.210938 3.894531 Z M 2.625 3.9375 C 3.324219 4.945312 3.980469 5.949219 4.636719 7 L 4.636719 7.042969 C 4.15625 7.789062 3.675781 8.488281 3.195312 9.230469 C 2.976562 9.492188 2.800781 9.800781 2.625 10.0625 L 2.625 10.105469 L 4.15625 10.105469 C 4.855469 9.054688 5.554688 8.050781 6.210938 7.042969 L 6.210938 7 C 5.90625 6.476562 5.554688 5.992188 5.207031 5.46875 C 4.855469 4.988281 4.550781 4.460938 4.199219 3.980469 L 4.15625 3.9375 Z M 7.960938 5.6875 L 7.960938 5.730469 C 8.179688 6.082031 8.398438 6.386719 8.617188 6.738281 L 11.375 6.738281 L 11.375 5.6875 Z M 8.96875 7.261719 L 8.96875 7.304688 C 9.1875 7.65625 9.40625 7.960938 9.625 8.3125 L 11.375 8.3125 L 11.375 7.261719 Z M 8.96875 7.261719 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
6
assets/icons/file_icons/nim.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 7.054688 1.691406 C 7.054688 1.691406 6.519531 2.148438 5.972656 2.59375 C 5.410156 2.578125 4.304688 2.710938 3.710938 2.945312 C 3.15625 2.570312 2.671875 2.15625 2.671875 2.15625 C 2.671875 2.15625 2.257812 2.917969 2 3.367188 C 1.613281 3.585938 1.226562 3.832031 0.882812 4.160156 C 0.480469 3.988281 0.015625 3.78125 0 3.777344 C 0.53125 4.921875 0.886719 6.070312 1.863281 6.761719 C 3.410156 4.144531 10.601562 4.386719 12.183594 6.746094 C 13.203125 6.175781 13.597656 4.953125 14 3.820312 C 13.957031 3.835938 13.410156 4.03125 13.058594 4.175781 C 12.84375 3.929688 12.347656 3.550781 12.0625 3.367188 C 11.847656 2.949219 11.628906 2.539062 11.402344 2.128906 C 11.402344 2.128906 10.941406 2.496094 10.402344 2.902344 C 9.675781 2.757812 8.800781 2.585938 8.0625 2.628906 C 7.71875 2.320312 7.382812 2.011719 7.054688 1.691406 Z M 0.550781 6.210938 L 1.828125 9.519531 C 4.046875 12.652344 9.707031 12.867188 12.175781 9.582031 C 12.757812 8.171875 13.546875 6.191406 13.546875 6.191406 C 12.914062 7.199219 11.882812 7.890625 11.246094 8.261719 C 10.796875 8.527344 9.757812 8.6875 9.757812 8.6875 L 7.023438 7.171875 L 4.277344 8.65625 C 4.277344 8.65625 3.25 8.480469 2.785156 8.25 C 1.847656 7.714844 1.214844 7.078125 0.550781 6.210938 Z M 0.550781 6.210938 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1 +1,6 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="512" height="512"><path d="M170.322 349.808c-2.4-15.66-9-28.38-25.020-34.531-6.27-2.4-11.7-6.78-17.88-9.54-7.020-3.15-14.16-6.15-21.57-8.1-5.61-1.5-10.83 1.020-14.16 5.94-3.15 4.62-0.87 8.97 1.77 12.84 2.97 4.35 6.27 8.49 9.6 12.57 5.52 6.78 11.37 13.29 16.74 20.161 5.13 6.57 9.51 13.86 8.76 22.56-1.65 19.080-10.29 34.891-24.21 47.76-1.53 1.38-4.23 2.37-6.21 2.19-8.88-0.96-16.95-4.32-23.46-10.53-7.47-7.11-6.33-15.48 2.61-20.67 2.13-1.23 4.35-2.37 6.3-3.87 5.46-4.11 7.29-11.13 4.32-17.22-1.41-2.94-3-6.12-5.34-8.25-11.43-10.41-22.651-21.151-34.891-30.63-29.671-23.041-44.91-53.52-47.251-90.421-2.64-40.981 6.87-79.231 28.5-114.242 8.19-13.29 17.73-25.951 32.37-32.52 9.96-4.47 20.88-6.99 31.531-9.78 29.311-7.71 58.89-13.5 89.401-8.34 26.28 4.41 45.511 17.94 54.331 43.77 5.79 16.89 7.17 34.35 5.37 52.231-3.54 35.131-29.49 66.541-63.331 75.841-14.67 4.020-22.68 1.77-31.5-10.44-6.33-8.79-11.58-18.36-17.25-27.631-0.84-1.38-1.44-2.97-2.16-4.44-0.69-1.47-1.44-2.88-2.16-4.35 2.13 15.24 5.67 29.911 13.98 42.99 4.5 7.11 10.5 12.36 19.29 13.14 32.34 2.91 59.641-7.71 79.021-33.721 21.69-29.101 26.461-62.581 20.19-97.831-1.23-6.96-3.3-13.77-4.77-20.7-0.99-4.47 0.78-7.77 5.19-9.33 2.040-0.69 4.14-1.26 6.18-1.68 26.461-5.7 53.221-7.59 80.191-4.86 30.601 3.060 59.551 11.46 85.441 28.471 40.531 26.67 65.641 64.621 79.291 110.522 1.98 6.66 2.28 13.95 2.46 20.971 0.12 4.68-2.88 5.91-6.45 2.97-3.93-3.21-7.53-6.87-10.92-10.65-3.15-3.57-5.67-7.65-8.73-11.4-2.37-2.94-4.44-2.49-5.58 1.17-0.72 2.22-1.35 4.41-1.98 6.63-7.080 25.26-18.24 48.3-36.33 67.711-2.52 2.73-4.77 6.78-5.070 10.38-0.78 9.96-1.35 20.13-0.39 30.060 1.98 21.331 5.070 42.57 7.47 63.871 1.35 12.030-2.52 19.11-13.83 23.281-7.95 2.91-16.47 5.040-24.87 5.64-13.38 0.93-26.88 0.27-40.32 0.27-0.36-15 0.93-29.731-13.17-37.771 2.73-11.13 5.88-21.69 7.77-32.49 1.56-8.97 0.24-17.79-6.060-25.14-5.91-6.93-13.32-8.82-20.101-4.86-20.43 11.91-41.671 11.97-63.301 4.17-9.93-3.6-16.86-1.56-22.351 7.5-5.91 9.75-8.4 20.7-7.74 31.771 0.84 13.95 3.27 27.75 5.13 41.64 1.020 7.77 0.15 9.78-7.56 11.76-17.13 4.35-34.56 4.83-52.081 3.42-0.93-0.090-1.86-0.48-2.46-0.63-0.87-14.55 0.66-29.671-16.68-37.411 7.68-16.29 6.63-33.18 3.99-50.070l-0.060-0.15zM66.761 292.718c2.55-2.4 4.59-6.15 5.31-9.6 1.8-8.64-4.68-20.22-12.18-23.43-3.99-1.74-7.47-1.11-10.29 2.070-6.87 7.77-13.65 15.63-20.401 23.521-1.14 1.35-2.16 2.94-2.97 4.53-2.7 5.19-1.11 8.97 4.65 10.38 3.48 0.87 7.080 1.050 10.65 1.56 9.3-0.9 18.3-2.46 25.23-9v-0.030zM67.541 206.347c-0.030-6.18-5.19-11.34-11.28-11.37-6.27-0.030-11.67 5.58-11.46 11.76 0.27 6.21 5.43 11.19 11.61 11.070 6.24-0.090 11.22-5.19 11.16-11.43l-0.030-0.030z"></path></svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 4.65625 9.566406 C 4.589844 9.136719 4.410156 8.789062 3.972656 8.621094 C 3.800781 8.554688 3.652344 8.433594 3.484375 8.359375 C 3.292969 8.273438 3.097656 8.191406 2.894531 8.136719 C 2.742188 8.097656 2.597656 8.167969 2.507812 8.300781 C 2.421875 8.425781 2.484375 8.546875 2.554688 8.652344 C 2.636719 8.769531 2.726562 8.882812 2.816406 8.996094 C 2.96875 9.179688 3.128906 9.359375 3.277344 9.546875 C 3.417969 9.726562 3.535156 9.925781 3.515625 10.164062 C 3.46875 10.6875 3.234375 11.117188 2.851562 11.46875 C 2.8125 11.507812 2.738281 11.535156 2.683594 11.53125 C 2.441406 11.503906 2.21875 11.410156 2.042969 11.242188 C 1.835938 11.046875 1.867188 10.820312 2.113281 10.675781 C 2.171875 10.644531 2.230469 10.613281 2.285156 10.570312 C 2.433594 10.457031 2.484375 10.265625 2.402344 10.101562 C 2.367188 10.019531 2.320312 9.933594 2.257812 9.875 C 1.945312 9.589844 1.636719 9.296875 1.304688 9.035156 C 0.492188 8.40625 0.0742188 7.574219 0.0117188 6.5625 C -0.0585938 5.445312 0.199219 4.398438 0.792969 3.441406 C 1.015625 3.078125 1.277344 2.730469 1.675781 2.550781 C 1.949219 2.429688 2.246094 2.359375 2.539062 2.285156 C 3.339844 2.074219 4.148438 1.914062 4.984375 2.054688 C 5.703125 2.175781 6.226562 2.546875 6.46875 3.253906 C 6.625 3.714844 6.664062 4.191406 6.617188 4.679688 C 6.519531 5.640625 5.808594 6.5 4.882812 6.753906 C 4.484375 6.863281 4.261719 6.804688 4.023438 6.46875 C 3.847656 6.230469 3.707031 5.96875 3.550781 5.714844 C 3.527344 5.675781 3.511719 5.632812 3.492188 5.59375 C 3.472656 5.550781 3.453125 5.511719 3.433594 5.472656 C 3.492188 5.890625 3.585938 6.292969 3.816406 6.648438 C 3.9375 6.84375 4.101562 6.988281 4.34375 7.007812 C 5.226562 7.085938 5.972656 6.796875 6.503906 6.085938 C 7.097656 5.289062 7.226562 4.375 7.054688 3.410156 C 7.019531 3.21875 6.964844 3.035156 6.925781 2.84375 C 6.898438 2.722656 6.945312 2.632812 7.066406 2.589844 C 7.121094 2.570312 7.179688 2.554688 7.234375 2.542969 C 7.960938 2.386719 8.691406 2.335938 9.429688 2.410156 C 10.265625 2.496094 11.054688 2.722656 11.765625 3.191406 C 12.871094 3.917969 13.558594 4.957031 13.933594 6.210938 C 13.988281 6.394531 13.996094 6.59375 14 6.785156 C 14.003906 6.914062 13.921875 6.945312 13.824219 6.867188 C 13.714844 6.777344 13.617188 6.679688 13.523438 6.574219 C 13.4375 6.476562 13.371094 6.367188 13.285156 6.261719 C 13.222656 6.183594 13.164062 6.195312 13.132812 6.296875 C 13.113281 6.355469 13.097656 6.414062 13.078125 6.476562 C 12.886719 7.167969 12.582031 7.796875 12.085938 8.328125 C 12.015625 8.402344 11.957031 8.511719 11.949219 8.613281 C 11.925781 8.882812 11.910156 9.164062 11.9375 9.433594 C 11.992188 10.015625 12.074219 10.597656 12.140625 11.179688 C 12.179688 11.507812 12.070312 11.703125 11.761719 11.816406 C 11.546875 11.894531 11.3125 11.953125 11.082031 11.972656 C 10.71875 11.996094 10.347656 11.976562 9.980469 11.976562 C 9.96875 11.566406 10.003906 11.164062 9.621094 10.945312 C 9.695312 10.640625 9.78125 10.351562 9.832031 10.058594 C 9.875 9.8125 9.839844 9.570312 9.667969 9.371094 C 9.503906 9.179688 9.304688 9.128906 9.117188 9.238281 C 8.558594 9.5625 7.976562 9.5625 7.386719 9.351562 C 7.113281 9.253906 6.925781 9.308594 6.773438 9.554688 C 6.613281 9.824219 6.546875 10.121094 6.5625 10.425781 C 6.585938 10.804688 6.652344 11.183594 6.703125 11.5625 C 6.730469 11.777344 6.707031 11.832031 6.496094 11.886719 C 6.027344 12.003906 5.550781 12.015625 5.074219 11.976562 C 5.046875 11.976562 5.023438 11.964844 5.007812 11.960938 C 4.980469 11.5625 5.023438 11.148438 4.550781 10.9375 C 4.761719 10.492188 4.730469 10.03125 4.660156 9.570312 Z M 1.824219 8.003906 C 1.894531 7.9375 1.949219 7.835938 1.96875 7.742188 C 2.019531 7.503906 1.84375 7.1875 1.636719 7.101562 C 1.527344 7.054688 1.433594 7.070312 1.355469 7.15625 C 1.167969 7.371094 0.984375 7.585938 0.796875 7.800781 C 0.765625 7.835938 0.738281 7.882812 0.71875 7.925781 C 0.644531 8.066406 0.6875 8.167969 0.84375 8.207031 C 0.941406 8.230469 1.039062 8.238281 1.136719 8.25 C 1.390625 8.226562 1.636719 8.183594 1.824219 8.003906 Z M 1.847656 5.640625 C 1.847656 5.472656 1.703125 5.332031 1.539062 5.332031 C 1.367188 5.332031 1.21875 5.484375 1.226562 5.652344 C 1.230469 5.824219 1.375 5.957031 1.542969 5.957031 C 1.714844 5.953125 1.847656 5.8125 1.847656 5.644531 Z M 1.847656 5.640625 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 4.5 KiB |
6
assets/icons/file_icons/scala.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 11.214844 0.015625 C 11.214844 1.125 5.859375 1.976562 2.785156 2.179688 L 2.785156 5.335938 C 4.082031 5.421875 5.789062 5.628906 7.324219 5.917969 C 5.789062 6.207031 4.082031 6.410156 2.785156 6.492188 L 2.785156 9.648438 C 4.082031 9.734375 5.785156 9.9375 7.320312 10.226562 C 5.785156 10.515625 4.082031 10.71875 2.785156 10.804688 L 2.785156 13.988281 L 4.308594 13.765625 C 7.152344 13.339844 10.285156 12.53125 11.214844 11.820312 L 11.214844 8.632812 C 11.214844 8.433594 10.851562 8.21875 10.265625 8 C 10.675781 7.832031 11.003906 7.667969 11.214844 7.507812 L 11.214844 4.324219 C 11.214844 4.125 10.855469 3.90625 10.277344 3.6875 C 10.683594 3.523438 11.003906 3.355469 11.214844 3.195312 Z M 10.269531 8.339844 C 10.6875 8.503906 10.839844 8.617188 10.890625 8.664062 C 10.855469 8.734375 10.726562 8.871094 10.359375 9.050781 C 10.300781 9.078125 10.238281 9.105469 10.183594 9.128906 C 9.550781 9.402344 8.570312 9.667969 7.320312 9.90625 C 6.707031 9.792969 6.046875 9.6875 5.382812 9.597656 C 7.25 9.261719 9.09375 8.796875 10.269531 8.339844 Z M 10.277344 4.027344 C 10.691406 4.195312 10.839844 4.308594 10.890625 4.355469 C 10.863281 4.40625 10.78125 4.5 10.578125 4.625 L 10.574219 4.625 C 10.535156 4.648438 10.488281 4.675781 10.441406 4.699219 C 9.839844 5.011719 8.765625 5.324219 7.335938 5.59375 C 6.71875 5.480469 6.058594 5.375 5.394531 5.285156 C 7.269531 4.953125 9.097656 4.492188 10.277344 4.027344 Z M 10.277344 4.027344 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
8
assets/icons/file_icons/tcl.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style="fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M 21.946429 2.875 C 21.982143 5.348214 21.910714 7.785714 19.776786 10.107143 L 19.696429 10.196429 L 19.8125 10.196429 L 20.6875 10.205357 C 19.267857 13.160714 18.348214 16.098214 16.303571 19.035714 L 16.232143 19.142857 L 16.357143 19.125 L 17.4375 18.919643 C 16.883929 20.598214 15.607143 21.946429 13.955357 22.571429 C 13.5625 17.116071 16.285714 12.303571 18.598214 7.5 L 18.607143 7.491071 L 18.517857 7.428571 C 14.732143 11.660714 13.026786 17.625 12.383929 22.553571 C 11.285714 21.901786 10.5 20.821429 10.241071 19.5625 L 11.133929 19.946429 L 11.232143 19.982143 L 11.214286 19.883929 C 10.526786 16.857143 11.589286 14.678571 12.607143 11.830357 L 13.348214 12.321429 L 13.4375 12.383929 L 13.4375 12.276786 C 13.375 9.964286 14.9375 7.633929 17.008929 5.553571 L 17.294643 6.321429 L 17.339286 6.419643 L 17.392857 6.321429 L 18.026786 5.267857 C 18.973214 3.991071 20.375 3.133929 21.946429 2.875 Z M 21.946429 2.875 " transform="matrix(0.4375,0,0,0.4375,0,0)"/>
|
||||
<path style="fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:4;" d="M 21.946429 2.875 C 20.375 3.133929 18.973214 3.991071 18.017857 5.258929 L 18.017857 5.267857 L 17.392857 6.321429 L 17.339286 6.419643 L 17.294643 6.321429 L 17 5.544643 C 14.928571 7.625 13.366071 9.955357 13.419643 12.267857 L 13.419643 12.375 L 13.339286 12.3125 L 12.598214 11.821429 C 11.571429 14.669643 10.517857 16.848214 11.196429 19.875 L 11.223214 19.973214 L 11.125 19.9375 L 10.241071 19.5625 C 10.241071 19.580357 10.25 19.598214 10.25 19.616071 C 10.517857 20.839286 11.294643 21.901786 12.375 22.544643 C 12.428571 22.160714 12.482143 21.776786 12.544643 21.383929 C 11 17.767857 12.348214 15.107143 12.955357 12.723214 L 13.892857 13.258929 C 13.758929 11.026786 15.080357 8.607143 16.785714 6.508929 L 17.285714 7.375 C 18.553571 4.767857 19.5625 3.723214 21.946429 2.875 Z M 21.946429 2.875 " transform="matrix(0.4375,0,0,0.4375,0,0)"/>
|
||||
<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M 22.517857 2 L 22.464286 2.008929 C 20.383929 2.375 18.339286 3.133929 17.446429 4.973214 L 17.071429 4.3125 L 17.035714 4.25 L 16.991071 4.303571 C 15.883929 5.357143 14.892857 6.526786 14.044643 7.803571 C 13.339286 8.723214 12.919643 9.839286 12.839286 11 L 12.303571 10.339286 L 12.25 10.267857 L 12.214286 10.348214 C 11.508929 11.857143 10.9375 13.428571 10.517857 15.044643 C 10.116071 16.25 10.0625 17.535714 10.366071 18.767857 L 9.482143 18.258929 L 9.410714 18.214286 L 9.401786 18.294643 C 9.223214 20.151786 9.982143 21.973214 11.419643 23.142857 L 10.455357 23.383929 L 10.25 23.428571 L 10.455357 23.482143 C 10.973214 23.598214 11.464286 23.794643 11.901786 24.089286 C 12.3125 24.383929 12.508929 24.892857 12.419643 25.383929 L 12.419643 28.098214 L 12.428571 28.116071 L 13.651786 29.857143 L 13.75 30 L 13.75 25.723214 C 13.839286 25.178571 14.053571 24.678571 14.366071 24.232143 C 14.660714 23.910714 15.071429 23.723214 15.5 23.696429 L 15.678571 23.678571 L 15.517857 23.598214 L 14.875 23.303571 C 16.714286 22.035714 18.035714 20.142857 18.571429 17.982143 L 18.589286 17.892857 L 18.508929 17.919643 L 17.714286 18.133929 C 18.607143 17.098214 19.3125 15.910714 19.803571 14.633929 C 20.508929 13 21.178571 11.169643 21.732143 9.696429 L 21.758929 9.625 L 21.678571 9.625 L 21.0625 9.669643 C 21.857143 8.651786 22.339286 7.428571 22.446429 6.142857 C 22.633929 4.785714 22.660714 3.419643 22.526786 2.053571 Z M 21.946429 2.875 C 21.982143 5.348214 21.910714 7.785714 19.776786 10.107143 L 19.696429 10.196429 L 19.8125 10.196429 L 20.6875 10.205357 C 19.267857 13.160714 18.348214 16.098214 16.303571 19.035714 L 16.232143 19.142857 L 16.357143 19.125 L 17.4375 18.919643 C 16.883929 20.598214 15.607143 21.946429 13.955357 22.571429 C 13.5625 17.116071 16.285714 12.303571 18.598214 7.5 L 18.607143 7.491071 L 18.517857 7.428571 C 14.732143 11.660714 13.026786 17.625 12.383929 22.553571 C 11.285714 21.901786 10.508929 20.821429 10.241071 19.5625 L 11.142857 19.946429 L 11.232143 19.982143 L 11.214286 19.883929 C 10.526786 16.857143 11.589286 14.678571 12.616071 11.830357 L 13.348214 12.321429 L 13.4375 12.383929 L 13.4375 12.276786 C 13.375 9.964286 14.9375 7.633929 17.008929 5.553571 L 17.303571 6.321429 L 17.339286 6.419643 L 17.392857 6.321429 L 18.026786 5.267857 C 18.973214 3.991071 20.375 3.133929 21.946429 2.875 Z M 21.946429 2.875 " transform="matrix(0.4375,0,0,0.4375,0,0)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
@@ -1,6 +1,9 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5066 8.01531L19.2375 12.1073V20.2894L12.5066 16.1992V8.01531Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0294 12.1073V20.2894L27.1563 16.1992V8.01531L20.0294 12.1073Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.58781 3.66V11.5787L11.7147 15.5381V7.61937L4.58781 3.66Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5066 25.04L19.2375 29V21.1348V21.0818L12.5066 17.1219V25.04Z" fill="black"/>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 5.472656 3.507812 L 8.417969 5.296875 L 8.417969 8.875 L 5.472656 7.085938 Z M 5.472656 3.507812 "/>
|
||||
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 8.761719 5.296875 L 8.761719 8.875 L 11.882812 7.085938 L 11.882812 3.507812 Z M 8.761719 5.296875 "/>
|
||||
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 2.007812 1.601562 L 2.007812 5.066406 L 5.125 6.796875 L 5.125 3.332031 Z M 2.007812 1.601562 "/>
|
||||
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 5.472656 10.953125 L 8.417969 12.6875 L 8.417969 9.222656 L 5.472656 7.492188 Z M 5.472656 10.953125 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 620 B After Width: | Height: | Size: 961 B |
@@ -341,6 +341,13 @@
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
// Change the default action on `menu::Confirm` by setting the parameter
|
||||
// "alt-cmd-o": [
|
||||
// "projects::OpenRecent",
|
||||
// {
|
||||
// "create_new_window": true
|
||||
// }
|
||||
// ]
|
||||
"ctrl-alt-o": "projects::OpenRecent",
|
||||
"ctrl-alt-b": "branches::OpenRecent",
|
||||
"ctrl-~": "workspace::NewTerminal",
|
||||
|
||||
@@ -383,6 +383,13 @@
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
// Change the default action on `menu::Confirm` by setting the parameter
|
||||
// "alt-cmd-o": [
|
||||
// "projects::OpenRecent",
|
||||
// {
|
||||
// "create_new_window": true
|
||||
// }
|
||||
// ]
|
||||
"alt-cmd-o": "projects::OpenRecent",
|
||||
"alt-cmd-b": "branches::OpenRecent",
|
||||
"ctrl-~": "workspace::NewTerminal",
|
||||
|
||||
@@ -20,7 +20,7 @@ use smol::io::AsyncReadExt;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use smol::{fs::File, process::Command};
|
||||
|
||||
use release_channel::{AppCommitSha, ReleaseChannel};
|
||||
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
|
||||
use std::{
|
||||
env::consts::{ARCH, OS},
|
||||
ffi::OsString,
|
||||
@@ -190,7 +190,7 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
|
||||
|
||||
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||
let release_channel = ReleaseChannel::global(cx);
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
let version = AppVersion::global(cx).to_string();
|
||||
|
||||
let client = client::Client::global(cx).http_client();
|
||||
let url = client.build_url(&format!(
|
||||
|
||||
@@ -373,8 +373,10 @@ async fn test_basic_following(
|
||||
editor_a1.update(cx_a, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
|
||||
});
|
||||
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
executor.run_until_parked();
|
||||
cx_b.background_executor.run_until_parked();
|
||||
|
||||
editor_b1.update(cx_b, |editor, cx| {
|
||||
assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
|
||||
});
|
||||
@@ -387,6 +389,7 @@ async fn test_basic_following(
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
|
||||
editor.set_scroll_position(point(0., 100.), cx);
|
||||
});
|
||||
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
executor.run_until_parked();
|
||||
editor_b1.update(cx_b, |editor, cx| {
|
||||
assert_eq!(editor.selections.ranges(cx), &[3..3]);
|
||||
@@ -1600,6 +1603,8 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([1..1]))
|
||||
});
|
||||
cx_a.executor()
|
||||
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
cx_a.run_until_parked();
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(editor.selections.ranges(cx), vec![1..1])
|
||||
@@ -1618,6 +1623,8 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([2..2]))
|
||||
});
|
||||
cx_a.executor()
|
||||
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
cx_a.run_until_parked();
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(editor.selections.ranges(cx), vec![1..1])
|
||||
@@ -1722,6 +1729,7 @@ async fn test_following_into_excluded_file(
|
||||
|
||||
// When client B starts following client A, currently visible file is replicated
|
||||
workspace_b.update(cx_b, |workspace, cx| workspace.follow(peer_id_a, cx));
|
||||
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
executor.run_until_parked();
|
||||
|
||||
let editor_for_excluded_b = workspace_b.update(cx_b, |workspace, cx| {
|
||||
@@ -1743,6 +1751,7 @@ async fn test_following_into_excluded_file(
|
||||
editor_for_excluded_a.update(cx_a, |editor, cx| {
|
||||
editor.select_right(&Default::default(), cx);
|
||||
});
|
||||
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
executor.run_until_parked();
|
||||
|
||||
// Changes from B to the excluded file are replicated in A's editor
|
||||
|
||||
@@ -204,7 +204,7 @@ impl ChatPanel {
|
||||
let panel = Self::new(workspace, cx);
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.width = serialized_panel.width;
|
||||
panel.width = serialized_panel.width.map(|r| r.round());
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ impl CollabPanel {
|
||||
let panel = CollabPanel::new(workspace, cx);
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.width = serialized_panel.width;
|
||||
panel.width = serialized_panel.width.map(|w| w.round());
|
||||
panel.collapsed_channels = serialized_panel
|
||||
.collapsed_channels
|
||||
.unwrap_or_else(|| Vec::new())
|
||||
|
||||
@@ -183,7 +183,7 @@ impl NotificationPanel {
|
||||
let panel = Self::new(workspace, cx);
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.width = serialized_panel.width;
|
||||
panel.width = serialized_panel.width.map(|w| w.round());
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8098,6 +8098,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
|
||||
project_settings.lsp.insert(
|
||||
"Some other server name".into(),
|
||||
LspSettings {
|
||||
settings: None,
|
||||
initialization_options: Some(json!({
|
||||
"some other init value": false
|
||||
})),
|
||||
@@ -8115,6 +8116,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
|
||||
project_settings.lsp.insert(
|
||||
language_server_name.into(),
|
||||
LspSettings {
|
||||
settings: None,
|
||||
initialization_options: Some(json!({
|
||||
"anotherInitValue": false
|
||||
})),
|
||||
@@ -8132,6 +8134,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
|
||||
project_settings.lsp.insert(
|
||||
language_server_name.into(),
|
||||
LspSettings {
|
||||
settings: None,
|
||||
initialization_options: Some(json!({
|
||||
"anotherInitValue": false
|
||||
})),
|
||||
@@ -8149,6 +8152,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
|
||||
project_settings.lsp.insert(
|
||||
language_server_name.into(),
|
||||
LspSettings {
|
||||
settings: None,
|
||||
initialization_options: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -716,6 +716,11 @@ impl EditorElement {
|
||||
let scroll_position = layout.position_map.snapshot.scroll_position();
|
||||
let scroll_top = scroll_position.y * line_height;
|
||||
|
||||
if bounds.contains(&cx.mouse_position()) {
|
||||
let stacking_order = cx.stacking_order().clone();
|
||||
cx.set_cursor_style(CursorStyle::Arrow, stacking_order);
|
||||
}
|
||||
|
||||
let show_git_gutter = matches!(
|
||||
ProjectSettings::get_global(cx).git.git_gutter,
|
||||
Some(GitGutterSetting::TrackedFiles)
|
||||
@@ -915,7 +920,7 @@ impl EditorElement {
|
||||
bounds: text_bounds,
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
};
|
||||
if interactive_text_bounds.visibly_contains(&cx.mouse_position(), cx) {
|
||||
if text_bounds.contains(&cx.mouse_position()) {
|
||||
if self
|
||||
.editor
|
||||
.read(cx)
|
||||
@@ -923,9 +928,15 @@ impl EditorElement {
|
||||
.as_ref()
|
||||
.is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
|
||||
{
|
||||
cx.set_cursor_style(CursorStyle::PointingHand);
|
||||
cx.set_cursor_style(
|
||||
CursorStyle::PointingHand,
|
||||
interactive_text_bounds.stacking_order.clone(),
|
||||
);
|
||||
} else {
|
||||
cx.set_cursor_style(CursorStyle::IBeam);
|
||||
cx.set_cursor_style(
|
||||
CursorStyle::IBeam,
|
||||
interactive_text_bounds.stacking_order.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1551,8 +1562,11 @@ impl EditorElement {
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
};
|
||||
let mut mouse_position = cx.mouse_position();
|
||||
if interactive_track_bounds.visibly_contains(&mouse_position, cx) {
|
||||
cx.set_cursor_style(CursorStyle::Arrow);
|
||||
if track_bounds.contains(&mouse_position) {
|
||||
cx.set_cursor_style(
|
||||
CursorStyle::Arrow,
|
||||
interactive_track_bounds.stacking_order.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
cx.on_mouse_event({
|
||||
|
||||
@@ -365,14 +365,7 @@ impl FileFinderDelegate {
|
||||
history_items: Vec<FoundPath>,
|
||||
cx: &mut ViewContext<FileFinder>,
|
||||
) -> Self {
|
||||
cx.observe(&project, |file_finder, _, cx| {
|
||||
//todo We should probably not re-render on every project anything
|
||||
file_finder
|
||||
.picker
|
||||
.update(cx, |picker, cx| picker.refresh(cx))
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self::subscribe_to_updates(&project, cx);
|
||||
Self {
|
||||
file_finder,
|
||||
workspace,
|
||||
@@ -389,6 +382,20 @@ impl FileFinderDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fn subscribe_to_updates(project: &Model<Project>, cx: &mut ViewContext<FileFinder>) {
|
||||
cx.subscribe(project, |file_finder, _, event, cx| {
|
||||
match event {
|
||||
project::Event::WorktreeUpdatedEntries(_, _)
|
||||
| project::Event::WorktreeAdded
|
||||
| project::Event::WorktreeRemoved(_) => file_finder
|
||||
.picker
|
||||
.update(cx, |picker, cx| picker.refresh(cx)),
|
||||
_ => {}
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn spawn_search(
|
||||
&mut self,
|
||||
query: PathLikeWithPosition<FileSearchQuery>,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::{assert_eq, path::Path, time::Duration};
|
||||
use std::{assert_eq, future::IntoFuture, path::Path, time::Duration};
|
||||
|
||||
use super::*;
|
||||
use editor::Editor;
|
||||
use gpui::{Entity, TestAppContext, VisualTestContext};
|
||||
use menu::{Confirm, SelectNext};
|
||||
use project::worktree::FS_WATCH_LATENCY;
|
||||
use serde_json::json;
|
||||
use workspace::{AppState, Workspace};
|
||||
|
||||
@@ -1337,6 +1338,137 @@ async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext)
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_search_results_refreshed_on_worktree_updates(cx: &mut gpui::TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(
|
||||
"/src",
|
||||
json!({
|
||||
"lib.rs": "// Lib file",
|
||||
"main.rs": "// Bar file",
|
||||
"read.me": "// Readme file",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||
|
||||
// Initial state
|
||||
let picker = open_file_picker(&workspace, cx);
|
||||
cx.simulate_input("rs");
|
||||
picker.update(cx, |finder, _| {
|
||||
assert_eq!(finder.delegate.matches.len(), 2);
|
||||
assert_match_at_position(finder, 0, "lib.rs");
|
||||
assert_match_at_position(finder, 1, "main.rs");
|
||||
});
|
||||
|
||||
// Delete main.rs
|
||||
app_state
|
||||
.fs
|
||||
.remove_file("/src/main.rs".as_ref(), Default::default())
|
||||
.await
|
||||
.expect("unable to remove file");
|
||||
cx.executor().advance_clock(FS_WATCH_LATENCY);
|
||||
|
||||
// main.rs is in not among search results anymore
|
||||
picker.update(cx, |finder, _| {
|
||||
assert_eq!(finder.delegate.matches.len(), 1);
|
||||
assert_match_at_position(finder, 0, "lib.rs");
|
||||
});
|
||||
|
||||
// Create util.rs
|
||||
app_state
|
||||
.fs
|
||||
.create_file("/src/util.rs".as_ref(), Default::default())
|
||||
.await
|
||||
.expect("unable to create file");
|
||||
cx.executor().advance_clock(FS_WATCH_LATENCY);
|
||||
|
||||
// util.rs is among search results
|
||||
picker.update(cx, |finder, _| {
|
||||
assert_eq!(finder.delegate.matches.len(), 2);
|
||||
assert_match_at_position(finder, 0, "lib.rs");
|
||||
assert_match_at_position(finder, 1, "util.rs");
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_search_results_refreshed_on_adding_and_removing_worktrees(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
let app_state = init_test(cx);
|
||||
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(
|
||||
"/test",
|
||||
json!({
|
||||
"project_1": {
|
||||
"bar.rs": "// Bar file",
|
||||
"lib.rs": "// Lib file",
|
||||
},
|
||||
"project_2": {
|
||||
"Cargo.toml": "// Cargo file",
|
||||
"main.rs": "// Main file",
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), ["/test/project_1".as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||
let worktree_1_id = project.update(cx, |project, cx| {
|
||||
let worktree = project.worktrees().last().expect("worktree not found");
|
||||
worktree.read(cx).id()
|
||||
});
|
||||
|
||||
// Initial state
|
||||
let picker = open_file_picker(&workspace, cx);
|
||||
cx.simulate_input("rs");
|
||||
picker.update(cx, |finder, _| {
|
||||
assert_eq!(finder.delegate.matches.len(), 2);
|
||||
assert_match_at_position(finder, 0, "bar.rs");
|
||||
assert_match_at_position(finder, 1, "lib.rs");
|
||||
});
|
||||
|
||||
// Add new worktree
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project
|
||||
.find_or_create_local_worktree("/test/project_2", true, cx)
|
||||
.into_future()
|
||||
})
|
||||
.await
|
||||
.expect("unable to create workdir");
|
||||
cx.executor().advance_clock(FS_WATCH_LATENCY);
|
||||
|
||||
// main.rs is among search results
|
||||
picker.update(cx, |finder, _| {
|
||||
assert_eq!(finder.delegate.matches.len(), 3);
|
||||
assert_match_at_position(finder, 0, "bar.rs");
|
||||
assert_match_at_position(finder, 1, "lib.rs");
|
||||
assert_match_at_position(finder, 2, "main.rs");
|
||||
});
|
||||
|
||||
// Remove the first worktree
|
||||
project.update(cx, |project, cx| {
|
||||
project.remove_worktree(worktree_1_id, cx);
|
||||
});
|
||||
cx.executor().advance_clock(FS_WATCH_LATENCY);
|
||||
|
||||
// Files from the first worktree are not in the search results anymore
|
||||
picker.update(cx, |finder, _| {
|
||||
assert_eq!(finder.delegate.matches.len(), 1);
|
||||
assert_match_at_position(finder, 0, "main.rs");
|
||||
});
|
||||
}
|
||||
|
||||
async fn open_close_queried_buffer(
|
||||
input: &str,
|
||||
expected_matches: usize,
|
||||
|
||||
@@ -1221,7 +1221,8 @@ pub struct InteractiveBounds {
|
||||
}
|
||||
|
||||
impl InteractiveBounds {
|
||||
/// Checks whether this point was inside these bounds, and that these bounds where the topmost layer
|
||||
/// Checks whether this point was inside these bounds in the rendered frame, and that these bounds where the topmost layer
|
||||
/// Never call this during paint to perform hover calculations. It will reference the previous frame and could cause flicker.
|
||||
pub fn visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
|
||||
self.bounds.contains(point) && cx.was_top_layer(point, &self.stacking_order)
|
||||
}
|
||||
@@ -1449,11 +1450,12 @@ impl Interactivity {
|
||||
|
||||
if !cx.has_active_drag() {
|
||||
if let Some(mouse_cursor) = style.mouse_cursor {
|
||||
let mouse_position = &cx.mouse_position();
|
||||
let hovered =
|
||||
interactive_bounds.visibly_contains(mouse_position, cx);
|
||||
let hovered = bounds.contains(&cx.mouse_position());
|
||||
if hovered {
|
||||
cx.set_cursor_style(mouse_cursor);
|
||||
cx.set_cursor_style(
|
||||
mouse_cursor,
|
||||
interactive_bounds.stacking_order.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1955,9 +1957,7 @@ impl Interactivity {
|
||||
if let Some(group_bounds) =
|
||||
GroupBounds::get(&group_hover.group, cx.deref_mut())
|
||||
{
|
||||
if group_bounds.contains(&mouse_position)
|
||||
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
|
||||
{
|
||||
if group_bounds.contains(&mouse_position) {
|
||||
style.refine(&group_hover.style);
|
||||
}
|
||||
}
|
||||
@@ -1967,7 +1967,6 @@ impl Interactivity {
|
||||
if bounds
|
||||
.intersect(&cx.content_mask().bounds)
|
||||
.contains(&mouse_position)
|
||||
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
|
||||
{
|
||||
style.refine(hover_style);
|
||||
}
|
||||
|
||||
@@ -427,9 +427,9 @@ impl Element for InteractiveText {
|
||||
.clickable_ranges
|
||||
.iter()
|
||||
.any(|range| range.contains(&ix))
|
||||
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
|
||||
{
|
||||
cx.set_cursor_style(crate::CursorStyle::PointingHand)
|
||||
let stacking_order = cx.stacking_order().clone();
|
||||
cx.set_cursor_style(crate::CursorStyle::PointingHand, stacking_order);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -195,8 +195,8 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
||||
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
|
||||
fn draw(&self, scene: &Scene);
|
||||
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
||||
fn set_graphics_profiler_enabled(&self, enabled: bool);
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
||||
|
||||
@@ -396,10 +396,6 @@ impl PlatformWindow for WaylandWindow {
|
||||
let inner = self.0.inner.lock();
|
||||
inner.renderer.sprite_atlas().clone()
|
||||
}
|
||||
|
||||
fn set_graphics_profiler_enabled(&self, enabled: bool) {
|
||||
//todo!(linux)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
|
||||
@@ -513,8 +513,4 @@ impl PlatformWindow for X11Window {
|
||||
let inner = self.0.inner.lock();
|
||||
inner.renderer.sprite_atlas().clone()
|
||||
}
|
||||
|
||||
fn set_graphics_profiler_enabled(&self, enabled: bool) {
|
||||
unimplemented!("linux")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1060,27 +1060,6 @@ impl PlatformWindow for MacWindow {
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||
self.0.lock().renderer.sprite_atlas().clone()
|
||||
}
|
||||
|
||||
/// Enables or disables the Metal HUD for debugging purposes. Note that this only works
|
||||
/// when the app is bundled and it has the `MetalHudEnabled` key set to true in Info.plist.
|
||||
fn set_graphics_profiler_enabled(&self, enabled: bool) {
|
||||
let this_lock = self.0.lock();
|
||||
let layer = this_lock.renderer.layer();
|
||||
|
||||
unsafe {
|
||||
if enabled {
|
||||
let hud_properties = NSDictionary::dictionaryWithObject_forKey_(
|
||||
nil,
|
||||
ns_string("default"),
|
||||
ns_string("mode"),
|
||||
);
|
||||
let _: () = msg_send![layer, setDeveloperHUDProperties: hud_properties];
|
||||
} else {
|
||||
let _: () =
|
||||
msg_send![layer, setDeveloperHUDProperties: NSDictionary::dictionary(nil)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWindowHandle for MacWindow {
|
||||
|
||||
@@ -251,8 +251,6 @@ impl PlatformWindow for TestWindow {
|
||||
self.0.lock().sprite_atlas.clone()
|
||||
}
|
||||
|
||||
fn set_graphics_profiler_enabled(&self, _enabled: bool) {}
|
||||
|
||||
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
@@ -46,10 +46,10 @@ pub fn run_test(
|
||||
let starting_seed = env::var("SEED")
|
||||
.map(|seed| seed.parse().expect("invalid SEED variable"))
|
||||
.unwrap_or(0);
|
||||
let is_randomized = num_iterations > 1;
|
||||
if let Ok(iterations) = env::var("ITERATIONS") {
|
||||
num_iterations = iterations.parse().expect("invalid ITERATIONS variable");
|
||||
}
|
||||
let is_randomized = num_iterations > 1;
|
||||
|
||||
for seed in starting_seed..starting_seed + num_iterations {
|
||||
let mut retry = 0;
|
||||
|
||||
@@ -280,7 +280,6 @@ pub struct Window {
|
||||
pub(crate) focus: Option<FocusId>,
|
||||
focus_enabled: bool,
|
||||
pending_input: Option<PendingInput>,
|
||||
graphics_profiler_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
@@ -474,7 +473,6 @@ impl Window {
|
||||
focus: None,
|
||||
focus_enabled: true,
|
||||
pending_input: None,
|
||||
graphics_profiler_enabled: false,
|
||||
}
|
||||
}
|
||||
fn new_focus_listener(
|
||||
@@ -1023,13 +1021,10 @@ impl<'a> WindowContext<'a> {
|
||||
self.window.root_view = Some(root_view);
|
||||
|
||||
// Set the cursor only if we're the active window.
|
||||
let cursor_style = self
|
||||
.window
|
||||
.next_frame
|
||||
.requested_cursor_style
|
||||
.take()
|
||||
.unwrap_or(CursorStyle::Arrow);
|
||||
let cursor_style_request = self.window.next_frame.requested_cursor_style.take();
|
||||
if self.is_window_active() {
|
||||
let cursor_style =
|
||||
cursor_style_request.map_or(CursorStyle::Arrow, |request| request.style);
|
||||
self.platform.set_cursor_style(cursor_style);
|
||||
}
|
||||
|
||||
@@ -1522,14 +1517,6 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle the graphics profiler to debug your application's rendering performance.
|
||||
pub fn toggle_graphics_profiler(&mut self) {
|
||||
self.window.graphics_profiler_enabled = !self.window.graphics_profiler_enabled;
|
||||
self.window
|
||||
.platform_window
|
||||
.set_graphics_profiler_enabled(self.window.graphics_profiler_enabled);
|
||||
}
|
||||
|
||||
/// Register the given handler to be invoked whenever the global of the given type
|
||||
/// is updated.
|
||||
pub fn observe_global<G: Global>(
|
||||
|
||||
@@ -51,6 +51,12 @@ pub(crate) struct TooltipRequest {
|
||||
pub(crate) tooltip: AnyTooltip,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CursorStyleRequest {
|
||||
pub(crate) style: CursorStyle,
|
||||
stacking_order: StackingOrder,
|
||||
}
|
||||
|
||||
pub(crate) struct Frame {
|
||||
pub(crate) focus: Option<FocusId>,
|
||||
pub(crate) window_active: bool,
|
||||
@@ -66,8 +72,8 @@ pub(crate) struct Frame {
|
||||
pub(crate) element_offset_stack: Vec<Point<Pixels>>,
|
||||
pub(crate) requested_input_handler: Option<RequestedInputHandler>,
|
||||
pub(crate) tooltip_request: Option<TooltipRequest>,
|
||||
pub(crate) cursor_styles: FxHashMap<EntityId, CursorStyle>,
|
||||
pub(crate) requested_cursor_style: Option<CursorStyle>,
|
||||
pub(crate) cursor_styles: FxHashMap<EntityId, CursorStyleRequest>,
|
||||
pub(crate) requested_cursor_style: Option<CursorStyleRequest>,
|
||||
pub(crate) view_stack: Vec<EntityId>,
|
||||
pub(crate) reused_views: FxHashSet<EntityId>,
|
||||
|
||||
@@ -346,9 +352,13 @@ impl<'a> ElementContext<'a> {
|
||||
}
|
||||
|
||||
// Reuse the cursor styles previously requested during painting of the reused view.
|
||||
if let Some(style) = self.window.rendered_frame.cursor_styles.remove(&view_id) {
|
||||
self.window.next_frame.cursor_styles.insert(view_id, style);
|
||||
self.window.next_frame.requested_cursor_style = Some(style);
|
||||
if let Some(cursor_style_request) =
|
||||
self.window.rendered_frame.cursor_styles.remove(&view_id)
|
||||
{
|
||||
self.set_cursor_style(
|
||||
cursor_style_request.style,
|
||||
cursor_style_request.stacking_order,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,10 +397,27 @@ impl<'a> ElementContext<'a> {
|
||||
}
|
||||
|
||||
/// Updates the cursor style at the platform level.
|
||||
pub fn set_cursor_style(&mut self, style: CursorStyle) {
|
||||
pub fn set_cursor_style(&mut self, style: CursorStyle, stacking_order: StackingOrder) {
|
||||
let view_id = self.parent_view_id();
|
||||
self.window.next_frame.cursor_styles.insert(view_id, style);
|
||||
self.window.next_frame.requested_cursor_style = Some(style);
|
||||
let style_request = CursorStyleRequest {
|
||||
style,
|
||||
stacking_order,
|
||||
};
|
||||
if self
|
||||
.window
|
||||
.next_frame
|
||||
.requested_cursor_style
|
||||
.as_ref()
|
||||
.map_or(true, |prev_style_request| {
|
||||
style_request.stacking_order >= prev_style_request.stacking_order
|
||||
})
|
||||
{
|
||||
self.window.next_frame.requested_cursor_style = Some(style_request.clone());
|
||||
}
|
||||
self.window
|
||||
.next_frame
|
||||
.cursor_styles
|
||||
.insert(view_id, style_request);
|
||||
}
|
||||
|
||||
/// Sets a tooltip to be rendered for the upcoming frame
|
||||
|
||||
@@ -97,14 +97,14 @@ fn test_select_language() {
|
||||
// matching file extension
|
||||
assert_eq!(
|
||||
registry
|
||||
.language_for_file("zed/lib.rs", None)
|
||||
.language_for_file("zed/lib.rs".as_ref(), None)
|
||||
.now_or_never()
|
||||
.and_then(|l| Some(l.ok()?.name())),
|
||||
Some("Rust".into())
|
||||
);
|
||||
assert_eq!(
|
||||
registry
|
||||
.language_for_file("zed/lib.mk", None)
|
||||
.language_for_file("zed/lib.mk".as_ref(), None)
|
||||
.now_or_never()
|
||||
.and_then(|l| Some(l.ok()?.name())),
|
||||
Some("Make".into())
|
||||
@@ -113,7 +113,7 @@ fn test_select_language() {
|
||||
// matching filename
|
||||
assert_eq!(
|
||||
registry
|
||||
.language_for_file("zed/Makefile", None)
|
||||
.language_for_file("zed/Makefile".as_ref(), None)
|
||||
.now_or_never()
|
||||
.and_then(|l| Some(l.ok()?.name())),
|
||||
Some("Make".into())
|
||||
@@ -122,21 +122,21 @@ fn test_select_language() {
|
||||
// matching suffix that is not the full file extension or filename
|
||||
assert_eq!(
|
||||
registry
|
||||
.language_for_file("zed/cars", None)
|
||||
.language_for_file("zed/cars".as_ref(), None)
|
||||
.now_or_never()
|
||||
.and_then(|l| Some(l.ok()?.name())),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
registry
|
||||
.language_for_file("zed/a.cars", None)
|
||||
.language_for_file("zed/a.cars".as_ref(), None)
|
||||
.now_or_never()
|
||||
.and_then(|l| Some(l.ok()?.name())),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
registry
|
||||
.language_for_file("zed/sumk", None)
|
||||
.language_for_file("zed/sumk".as_ref(), None)
|
||||
.now_or_never()
|
||||
.and_then(|l| Some(l.ok()?.name())),
|
||||
None
|
||||
|
||||
@@ -1522,16 +1522,16 @@ mod tests {
|
||||
});
|
||||
|
||||
languages
|
||||
.language_for_file("the/script", None)
|
||||
.language_for_file("the/script".as_ref(), None)
|
||||
.await
|
||||
.unwrap_err();
|
||||
languages
|
||||
.language_for_file("the/script", Some(&"nothing".into()))
|
||||
.language_for_file("the/script".as_ref(), Some(&"nothing".into()))
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
languages
|
||||
.language_for_file("the/script", Some(&"#!/bin/env node".into()))
|
||||
.language_for_file("the/script".as_ref(), Some(&"#!/bin/env node".into()))
|
||||
.await
|
||||
.unwrap()
|
||||
.name()
|
||||
|
||||
@@ -7,7 +7,7 @@ use collections::{hash_map, HashMap};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
future::Shared,
|
||||
FutureExt as _, TryFutureExt as _,
|
||||
Future, FutureExt as _, TryFutureExt as _,
|
||||
};
|
||||
use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
|
||||
use lsp::{LanguageServerBinary, LanguageServerId};
|
||||
@@ -24,7 +24,7 @@ use sum_tree::Bias;
|
||||
use text::{Point, Rope};
|
||||
use theme::Theme;
|
||||
use unicase::UniCase;
|
||||
use util::{paths::PathExt, post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
|
||||
use util::{paths::PathExt, post_inc, ResultExt};
|
||||
|
||||
pub struct LanguageRegistry {
|
||||
state: RwLock<LanguageRegistryState>,
|
||||
@@ -291,35 +291,36 @@ impl LanguageRegistry {
|
||||
pub fn language_for_name(
|
||||
self: &Arc<Self>,
|
||||
name: &str,
|
||||
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
|
||||
) -> impl Future<Output = Result<Arc<Language>>> {
|
||||
let name = UniCase::new(name);
|
||||
self.get_or_load_language(|language_name, _| UniCase::new(language_name) == name)
|
||||
let rx = self.get_or_load_language(|language_name, _| UniCase::new(language_name) == name);
|
||||
async move { rx.await? }
|
||||
}
|
||||
|
||||
pub fn language_for_name_or_extension(
|
||||
self: &Arc<Self>,
|
||||
string: &str,
|
||||
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
|
||||
) -> impl Future<Output = Result<Arc<Language>>> {
|
||||
let string = UniCase::new(string);
|
||||
self.get_or_load_language(|name, config| {
|
||||
let rx = self.get_or_load_language(|name, config| {
|
||||
UniCase::new(name) == string
|
||||
|| config
|
||||
.path_suffixes
|
||||
.iter()
|
||||
.any(|suffix| UniCase::new(suffix) == string)
|
||||
})
|
||||
});
|
||||
async move { rx.await? }
|
||||
}
|
||||
|
||||
pub fn language_for_file(
|
||||
self: &Arc<Self>,
|
||||
path: impl AsRef<Path>,
|
||||
path: &Path,
|
||||
content: Option<&Rope>,
|
||||
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
|
||||
let path = path.as_ref();
|
||||
) -> impl Future<Output = Result<Arc<Language>>> {
|
||||
let filename = path.file_name().and_then(|name| name.to_str());
|
||||
let extension = path.extension_or_hidden_file_name();
|
||||
let path_suffixes = [extension, filename];
|
||||
self.get_or_load_language(|_, config| {
|
||||
let rx = self.get_or_load_language(move |_, config| {
|
||||
let path_matches = config
|
||||
.path_suffixes
|
||||
.iter()
|
||||
@@ -334,13 +335,14 @@ impl LanguageRegistry {
|
||||
},
|
||||
);
|
||||
path_matches || content_matches
|
||||
})
|
||||
});
|
||||
async move { rx.await? }
|
||||
}
|
||||
|
||||
fn get_or_load_language(
|
||||
self: &Arc<Self>,
|
||||
callback: impl Fn(&str, &LanguageMatcher) -> bool,
|
||||
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
|
||||
) -> oneshot::Receiver<Result<Arc<Language>>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let mut state = self.state.write();
|
||||
@@ -421,13 +423,13 @@ impl LanguageRegistry {
|
||||
let _ = tx.send(Err(anyhow!("executor does not exist")));
|
||||
}
|
||||
|
||||
rx.unwrap()
|
||||
rx
|
||||
}
|
||||
|
||||
fn get_or_load_grammar(
|
||||
self: &Arc<Self>,
|
||||
name: Arc<str>,
|
||||
) -> UnwrapFuture<oneshot::Receiver<Result<tree_sitter::Language>>> {
|
||||
) -> impl Future<Output = Result<tree_sitter::Language>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let mut state = self.state.write();
|
||||
|
||||
@@ -483,7 +485,7 @@ impl LanguageRegistry {
|
||||
tx.send(Err(anyhow!("no such grammar {}", name))).ok();
|
||||
}
|
||||
|
||||
rx.unwrap()
|
||||
async move { rx.await? }
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Vec<Arc<Language>> {
|
||||
|
||||
@@ -823,7 +823,7 @@ impl Render for LspLogToolbarItemView {
|
||||
selection,
|
||||
Selection::Selected
|
||||
);
|
||||
view.toggle_logging_for_server(
|
||||
view.toggle_rpc_logging_for_server(
|
||||
row.server_id,
|
||||
enabled,
|
||||
cx,
|
||||
@@ -887,7 +887,7 @@ impl LspLogToolbarItemView {
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_logging_for_server(
|
||||
fn toggle_rpc_logging_for_server(
|
||||
&mut self,
|
||||
id: LanguageServerId,
|
||||
enabled: bool,
|
||||
@@ -899,6 +899,9 @@ impl LspLogToolbarItemView {
|
||||
if !enabled && Some(id) == log_view.current_server_id {
|
||||
log_view.show_logs_for_server(id, cx);
|
||||
cx.notify();
|
||||
} else if enabled {
|
||||
log_view.show_rpc_trace_for_server(id, cx);
|
||||
cx.notify();
|
||||
}
|
||||
cx.focus(&log_view.focus_handle);
|
||||
});
|
||||
|
||||
@@ -7,7 +7,9 @@ use gpui::AppContext;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary};
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::project_settings::ProjectSettings;
|
||||
use serde_json::{json, Value};
|
||||
use settings::Settings;
|
||||
use smol::{fs, io::BufReader, stream::StreamExt};
|
||||
use std::{
|
||||
any::Any,
|
||||
@@ -18,7 +20,7 @@ use std::{
|
||||
use util::{
|
||||
async_maybe,
|
||||
fs::remove_matching,
|
||||
github::{latest_github_release, GitHubLspBinaryVersion},
|
||||
github::{github_release_with_tag, GitHubLspBinaryVersion},
|
||||
ResultExt,
|
||||
};
|
||||
|
||||
@@ -219,6 +221,7 @@ pub struct EsLintLspAdapter {
|
||||
|
||||
impl EsLintLspAdapter {
|
||||
const SERVER_PATH: &'static str = "vscode-eslint/server/out/eslintServer.js";
|
||||
const SERVER_NAME: &'static str = "eslint";
|
||||
|
||||
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
|
||||
EsLintLspAdapter { node }
|
||||
@@ -227,7 +230,34 @@ impl EsLintLspAdapter {
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for EsLintLspAdapter {
|
||||
fn workspace_configuration(&self, workspace_root: &Path, _: &mut AppContext) -> Value {
|
||||
fn workspace_configuration(&self, workspace_root: &Path, cx: &mut AppContext) -> Value {
|
||||
let eslint_user_settings = ProjectSettings::get_global(cx)
|
||||
.lsp
|
||||
.get(Self::SERVER_NAME)
|
||||
.and_then(|s| s.settings.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut code_action_on_save = json!({
|
||||
// We enable this, but without also configuring `code_actions_on_format`
|
||||
// in the Zed configuration, it doesn't have an effect.
|
||||
"enable": true,
|
||||
});
|
||||
|
||||
if let Some(code_action_settings) = eslint_user_settings
|
||||
.get("codeActionOnSave")
|
||||
.and_then(|settings| settings.as_object())
|
||||
{
|
||||
if let Some(enable) = code_action_settings.get("enable") {
|
||||
code_action_on_save["enable"] = enable.clone();
|
||||
}
|
||||
if let Some(mode) = code_action_settings.get("mode") {
|
||||
code_action_on_save["mode"] = mode.clone();
|
||||
}
|
||||
if let Some(rules) = code_action_settings.get("rules") {
|
||||
code_action_on_save["rules"] = rules.clone();
|
||||
}
|
||||
}
|
||||
|
||||
json!({
|
||||
"": {
|
||||
"validate": "on",
|
||||
@@ -241,6 +271,7 @@ impl LspAdapter for EsLintLspAdapter {
|
||||
.unwrap_or_else(|| workspace_root.as_os_str()),
|
||||
},
|
||||
"problems": {},
|
||||
"codeActionOnSave": code_action_on_save,
|
||||
"experimental": {
|
||||
"useFlatConfig": workspace_root.join("eslint.config.js").is_file(),
|
||||
},
|
||||
@@ -249,7 +280,7 @@ impl LspAdapter for EsLintLspAdapter {
|
||||
}
|
||||
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("eslint".into())
|
||||
LanguageServerName(Self::SERVER_NAME.into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
@@ -260,13 +291,11 @@ impl LspAdapter for EsLintLspAdapter {
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
// At the time of writing the latest vscode-eslint release was released in 2020 and requires
|
||||
// special custom LSP protocol extensions be handled to fully initialize. Download the latest
|
||||
// prerelease instead to sidestep this issue
|
||||
let release = latest_github_release(
|
||||
// We're using this hardcoded release tag, because ESLint's API changed with
|
||||
// >= 2.3 and we haven't upgraded yet.
|
||||
let release = github_release_with_tag(
|
||||
"microsoft/vscode-eslint",
|
||||
false,
|
||||
true,
|
||||
"release/2.2.20-Insider",
|
||||
delegate.http_client(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -541,7 +541,14 @@ impl LanguageServer {
|
||||
}),
|
||||
data_support: Some(true),
|
||||
resolve_support: Some(CodeActionCapabilityResolveSupport {
|
||||
properties: vec!["edit".to_string(), "command".to_string()],
|
||||
properties: vec![
|
||||
"kind".to_string(),
|
||||
"diagnostics".to_string(),
|
||||
"isPreferred".to_string(),
|
||||
"disabled".to_string(),
|
||||
"edit".to_string(),
|
||||
"command".to_string(),
|
||||
],
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
|
||||
@@ -195,7 +195,7 @@ struct Excerpt {
|
||||
///
|
||||
/// Contains methods for getting the [`Buffer`] of the excerpt,
|
||||
/// as well as mapping offsets to/from buffer and multibuffer coordinates.
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct MultiBufferExcerpt<'a> {
|
||||
excerpt: &'a Excerpt,
|
||||
excerpt_offset: usize,
|
||||
@@ -2967,7 +2967,16 @@ impl MultiBufferSnapshot {
|
||||
excerpt
|
||||
.buffer()
|
||||
.enclosing_bracket_ranges(excerpt.map_range_to_buffer(range))
|
||||
.filter(move |(open, close)| excerpt.contains_buffer_range(open.start..close.end)),
|
||||
.filter_map(move |(open, close)| {
|
||||
if excerpt.contains_buffer_range(open.start..close.end) {
|
||||
Some((
|
||||
excerpt.map_range_from_buffer(open),
|
||||
excerpt.map_range_from_buffer(close),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ path = "src/picker.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
editor.workspace = true
|
||||
gpui.workspace = true
|
||||
menu.workspace = true
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use anyhow::Result;
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
div, list, prelude::*, uniform_list, AnyElement, AppContext, ClickEvent, DismissEvent,
|
||||
@@ -13,11 +14,16 @@ enum ElementContainer {
|
||||
UniformList(UniformListScrollHandle),
|
||||
}
|
||||
|
||||
struct PendingUpdateMatches {
|
||||
delegate_update_matches: Option<Task<()>>,
|
||||
_task: Task<Result<()>>,
|
||||
}
|
||||
|
||||
pub struct Picker<D: PickerDelegate> {
|
||||
pub delegate: D,
|
||||
element_container: ElementContainer,
|
||||
editor: View<Editor>,
|
||||
pending_update_matches: Option<Task<()>>,
|
||||
pending_update_matches: Option<PendingUpdateMatches>,
|
||||
confirm_on_update: Option<bool>,
|
||||
width: Option<Length>,
|
||||
max_height: Option<Length>,
|
||||
@@ -268,15 +274,32 @@ impl<D: PickerDelegate> Picker<D> {
|
||||
}
|
||||
|
||||
pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
|
||||
let update = self.delegate.update_matches(query, cx);
|
||||
let delegate_pending_update_matches = self.delegate.update_matches(query, cx);
|
||||
|
||||
self.matches_updated(cx);
|
||||
self.pending_update_matches = Some(cx.spawn(|this, mut cx| async move {
|
||||
update.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.matches_updated(cx);
|
||||
})
|
||||
.ok();
|
||||
}));
|
||||
// This struct ensures that we can synchronously drop the task returned by the
|
||||
// delegate's `update_matches` method and the task that the picker is spawning.
|
||||
// If we simply capture the delegate's task into the picker's task, when the picker's
|
||||
// task gets synchronously dropped, the delegate's task would keep running until
|
||||
// the picker's task has a chance of being scheduled, because dropping a task happens
|
||||
// asynchronously.
|
||||
self.pending_update_matches = Some(PendingUpdateMatches {
|
||||
delegate_update_matches: Some(delegate_pending_update_matches),
|
||||
_task: cx.spawn(|this, mut cx| async move {
|
||||
let delegate_pending_update_matches = this.update(&mut cx, |this, _| {
|
||||
this.pending_update_matches
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.delegate_update_matches
|
||||
.take()
|
||||
.unwrap()
|
||||
})?;
|
||||
delegate_pending_update_matches.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.matches_updated(cx);
|
||||
})
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
fn matches_updated(&mut self, cx: &mut ViewContext<Self>) {
|
||||
|
||||
@@ -1818,6 +1818,19 @@ impl LspCommand for GetCodeActions {
|
||||
}
|
||||
}
|
||||
|
||||
impl GetCodeActions {
|
||||
pub fn can_resolve_actions(capabilities: &ServerCapabilities) -> bool {
|
||||
capabilities
|
||||
.code_action_provider
|
||||
.as_ref()
|
||||
.and_then(|options| match options {
|
||||
lsp::CodeActionProviderCapability::Simple(_is_supported) => None,
|
||||
lsp::CodeActionProviderCapability::Options(options) => options.resolve_provider,
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for OnTypeFormatting {
|
||||
type Response = Option<Transaction>;
|
||||
|
||||
@@ -522,6 +522,9 @@ impl Project {
|
||||
buffer: &Model<Buffer>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Option<(Option<PathBuf>, PrettierTask)>> {
|
||||
if !self.is_local() {
|
||||
return Task::ready(None);
|
||||
}
|
||||
let buffer = buffer.read(cx);
|
||||
let buffer_file = buffer.file();
|
||||
let Some(buffer_language) = buffer.language() else {
|
||||
@@ -530,119 +533,105 @@ impl Project {
|
||||
if buffer_language.prettier_parser_name().is_none() {
|
||||
return Task::ready(None);
|
||||
}
|
||||
|
||||
if self.is_local() {
|
||||
let Some(node) = self.node.as_ref().map(Arc::clone) else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx)))
|
||||
{
|
||||
Some((worktree_id, buffer_path)) => {
|
||||
let fs = Arc::clone(&self.fs);
|
||||
let installed_prettiers = self.prettier_instances.keys().cloned().collect();
|
||||
return cx.spawn(|project, mut cx| async move {
|
||||
match cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&installed_prettiers,
|
||||
&buffer_path,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(ControlFlow::Break(())) => {
|
||||
return None;
|
||||
}
|
||||
Ok(ControlFlow::Continue(None)) => {
|
||||
let default_instance = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project
|
||||
.prettiers_per_worktree
|
||||
.entry(worktree_id)
|
||||
.or_default()
|
||||
.insert(None);
|
||||
project.default_prettier.prettier_task(
|
||||
&node,
|
||||
Some(worktree_id),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok()?;
|
||||
Some((None, default_instance?.log_err().await?))
|
||||
}
|
||||
Ok(ControlFlow::Continue(Some(prettier_dir))) => {
|
||||
project
|
||||
.update(&mut cx, |project, _| {
|
||||
project
|
||||
.prettiers_per_worktree
|
||||
.entry(worktree_id)
|
||||
.or_default()
|
||||
.insert(Some(prettier_dir.clone()))
|
||||
})
|
||||
.ok()?;
|
||||
if let Some(prettier_task) = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.prettier_instances.get_mut(&prettier_dir).map(
|
||||
|existing_instance| {
|
||||
existing_instance.prettier_task(
|
||||
&node,
|
||||
Some(&prettier_dir),
|
||||
Some(worktree_id),
|
||||
cx,
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
.ok()?
|
||||
{
|
||||
log::debug!(
|
||||
"Found already started prettier in {prettier_dir:?}"
|
||||
);
|
||||
return Some((
|
||||
Some(prettier_dir),
|
||||
prettier_task?.await.log_err()?,
|
||||
));
|
||||
}
|
||||
|
||||
log::info!("Found prettier in {prettier_dir:?}, starting.");
|
||||
let new_prettier_task = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
let new_prettier_task = start_prettier(
|
||||
node,
|
||||
prettier_dir.clone(),
|
||||
Some(worktree_id),
|
||||
cx,
|
||||
);
|
||||
project.prettier_instances.insert(
|
||||
prettier_dir.clone(),
|
||||
PrettierInstance {
|
||||
attempt: 0,
|
||||
prettier: Some(new_prettier_task.clone()),
|
||||
},
|
||||
);
|
||||
new_prettier_task
|
||||
})
|
||||
.ok()?;
|
||||
Some((Some(prettier_dir), new_prettier_task))
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to determine prettier path for buffer: {e:#}");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
None => {
|
||||
let new_task = self.default_prettier.prettier_task(&node, None, cx);
|
||||
return cx
|
||||
.spawn(|_, _| async move { Some((None, new_task?.log_err().await?)) });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let Some(node) = self.node.as_ref().map(Arc::clone) else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) {
|
||||
Some((worktree_id, buffer_path)) => {
|
||||
let fs = Arc::clone(&self.fs);
|
||||
let installed_prettiers = self.prettier_instances.keys().cloned().collect();
|
||||
cx.spawn(|project, mut cx| async move {
|
||||
match cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&installed_prettiers,
|
||||
&buffer_path,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(ControlFlow::Break(())) => None,
|
||||
Ok(ControlFlow::Continue(None)) => {
|
||||
let default_instance = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project
|
||||
.prettiers_per_worktree
|
||||
.entry(worktree_id)
|
||||
.or_default()
|
||||
.insert(None);
|
||||
project.default_prettier.prettier_task(
|
||||
&node,
|
||||
Some(worktree_id),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok()?;
|
||||
Some((None, default_instance?.log_err().await?))
|
||||
}
|
||||
Ok(ControlFlow::Continue(Some(prettier_dir))) => {
|
||||
project
|
||||
.update(&mut cx, |project, _| {
|
||||
project
|
||||
.prettiers_per_worktree
|
||||
.entry(worktree_id)
|
||||
.or_default()
|
||||
.insert(Some(prettier_dir.clone()))
|
||||
})
|
||||
.ok()?;
|
||||
if let Some(prettier_task) = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.prettier_instances.get_mut(&prettier_dir).map(
|
||||
|existing_instance| {
|
||||
existing_instance.prettier_task(
|
||||
&node,
|
||||
Some(&prettier_dir),
|
||||
Some(worktree_id),
|
||||
cx,
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
.ok()?
|
||||
{
|
||||
log::debug!("Found already started prettier in {prettier_dir:?}");
|
||||
return Some((Some(prettier_dir), prettier_task?.await.log_err()?));
|
||||
}
|
||||
|
||||
log::info!("Found prettier in {prettier_dir:?}, starting.");
|
||||
let new_prettier_task = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
let new_prettier_task = start_prettier(
|
||||
node,
|
||||
prettier_dir.clone(),
|
||||
Some(worktree_id),
|
||||
cx,
|
||||
);
|
||||
project.prettier_instances.insert(
|
||||
prettier_dir.clone(),
|
||||
PrettierInstance {
|
||||
attempt: 0,
|
||||
prettier: Some(new_prettier_task.clone()),
|
||||
},
|
||||
);
|
||||
new_prettier_task
|
||||
})
|
||||
.ok()?;
|
||||
Some((Some(prettier_dir), new_prettier_task))
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to determine prettier path for buffer: {e:#}");
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
None => {
|
||||
let new_task = self.default_prettier.prettier_task(&node, None, cx);
|
||||
cx.spawn(|_, _| async move { Some((None, new_task?.log_err().await?)) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,11 +35,11 @@ use language::{
|
||||
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
|
||||
serialize_anchor, serialize_version, split_operations,
|
||||
},
|
||||
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability,
|
||||
CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff,
|
||||
Documentation, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName,
|
||||
LocalFile, LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer,
|
||||
PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability, CodeAction,
|
||||
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Documentation,
|
||||
Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile,
|
||||
LspAdapterDelegate, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot,
|
||||
ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||
};
|
||||
use log::error;
|
||||
use lsp::{
|
||||
@@ -4129,17 +4129,13 @@ impl Project {
|
||||
cx: &mut ModelContext<Project>,
|
||||
) -> Task<anyhow::Result<ProjectTransaction>> {
|
||||
if self.is_local() {
|
||||
let mut buffers_with_paths_and_servers = buffers
|
||||
let mut buffers_with_paths = buffers
|
||||
.into_iter()
|
||||
.filter_map(|buffer_handle| {
|
||||
let buffer = buffer_handle.read(cx);
|
||||
let file = File::from_dyn(buffer.file())?;
|
||||
let buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
|
||||
let (adapter, server) = self
|
||||
.primary_language_server_for_buffer(buffer, cx)
|
||||
.map(|(a, s)| (Some(a.clone()), Some(s.clone())))
|
||||
.unwrap_or((None, None));
|
||||
Some((buffer_handle, buffer_abs_path, adapter, server))
|
||||
Some((buffer_handle, buffer_abs_path))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -4147,7 +4143,7 @@ impl Project {
|
||||
// Do not allow multiple concurrent formatting requests for the
|
||||
// same buffer.
|
||||
project.update(&mut cx, |this, cx| {
|
||||
buffers_with_paths_and_servers.retain(|(buffer, _, _, _)| {
|
||||
buffers_with_paths.retain(|(buffer, _)| {
|
||||
this.buffers_being_formatted
|
||||
.insert(buffer.read(cx).remote_id())
|
||||
});
|
||||
@@ -4156,10 +4152,10 @@ impl Project {
|
||||
let _cleanup = defer({
|
||||
let this = project.clone();
|
||||
let mut cx = cx.clone();
|
||||
let buffers = &buffers_with_paths_and_servers;
|
||||
let buffers = &buffers_with_paths;
|
||||
move || {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
for (buffer, _, _, _) in buffers {
|
||||
for (buffer, _) in buffers {
|
||||
this.buffers_being_formatted
|
||||
.remove(&buffer.read(cx).remote_id());
|
||||
}
|
||||
@@ -4169,9 +4165,14 @@ impl Project {
|
||||
});
|
||||
|
||||
let mut project_transaction = ProjectTransaction::default();
|
||||
for (buffer, buffer_abs_path, lsp_adapter, language_server) in
|
||||
&buffers_with_paths_and_servers
|
||||
{
|
||||
for (buffer, buffer_abs_path) in &buffers_with_paths {
|
||||
let adapters_and_servers: Vec<_> = project.update(&mut cx, |project, cx| {
|
||||
project
|
||||
.language_servers_for_buffer(&buffer.read(cx), cx)
|
||||
.map(|(adapter, lsp)| (adapter.clone(), lsp.clone()))
|
||||
.collect()
|
||||
})?;
|
||||
|
||||
let settings = buffer.update(&mut cx, |buffer, cx| {
|
||||
language_settings(buffer.language(), buffer.file(), cx).clone()
|
||||
})?;
|
||||
@@ -4202,9 +4203,7 @@ impl Project {
|
||||
buffer.end_transaction(cx)
|
||||
})?;
|
||||
|
||||
if let (Some(lsp_adapter), Some(language_server)) =
|
||||
(lsp_adapter, language_server)
|
||||
{
|
||||
for (lsp_adapter, language_server) in adapters_and_servers.iter() {
|
||||
// Apply the code actions on
|
||||
let code_actions: Vec<lsp::CodeActionKind> = settings
|
||||
.code_actions_on_format
|
||||
@@ -4236,11 +4235,15 @@ impl Project {
|
||||
})?
|
||||
.await?;
|
||||
|
||||
for action in actions {
|
||||
for mut action in actions {
|
||||
Self::try_resolve_code_action(&language_server, &mut action)
|
||||
.await
|
||||
.context("resolving a formatting code action")?;
|
||||
if let Some(edit) = action.lsp_action.edit {
|
||||
if edit.changes.is_none() && edit.document_changes.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let new = Self::deserialize_workspace_edit(
|
||||
project
|
||||
.upgrade()
|
||||
@@ -4255,6 +4258,7 @@ impl Project {
|
||||
project_transaction.0.extend(new.0);
|
||||
}
|
||||
|
||||
// TODO kb here too:
|
||||
if let Some(command) = action.lsp_action.command {
|
||||
project.update(&mut cx, |this, _| {
|
||||
this.last_workspace_edits_by_language_server
|
||||
@@ -4284,17 +4288,23 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
// Apply language-specific formatting using either a language server
|
||||
// Apply language-specific formatting using either the primary language server
|
||||
// or external command.
|
||||
let primary_language_server = adapters_and_servers
|
||||
.first()
|
||||
.cloned()
|
||||
.map(|(_, lsp)| lsp.clone());
|
||||
let server_and_buffer = primary_language_server
|
||||
.as_ref()
|
||||
.zip(buffer_abs_path.as_ref());
|
||||
|
||||
let mut format_operation = None;
|
||||
match (&settings.formatter, &settings.format_on_save) {
|
||||
(_, FormatOnSave::Off) if trigger == FormatTrigger::Save => {}
|
||||
|
||||
(Formatter::LanguageServer, FormatOnSave::On | FormatOnSave::Off)
|
||||
| (_, FormatOnSave::LanguageServer) => {
|
||||
if let Some((language_server, buffer_abs_path)) =
|
||||
language_server.as_ref().zip(buffer_abs_path.as_ref())
|
||||
{
|
||||
if let Some((language_server, buffer_abs_path)) = server_and_buffer {
|
||||
format_operation = Some(FormatOperation::Lsp(
|
||||
Self::format_via_lsp(
|
||||
&project,
|
||||
@@ -4338,7 +4348,7 @@ impl Project {
|
||||
{
|
||||
format_operation = Some(new_operation);
|
||||
} else if let Some((language_server, buffer_abs_path)) =
|
||||
language_server.as_ref().zip(buffer_abs_path.as_ref())
|
||||
server_and_buffer
|
||||
{
|
||||
format_operation = Some(FormatOperation::Lsp(
|
||||
Self::format_via_lsp(
|
||||
@@ -5290,33 +5300,10 @@ impl Project {
|
||||
} else {
|
||||
return Task::ready(Ok(Default::default()));
|
||||
};
|
||||
let range = action.range.to_point_utf16(buffer);
|
||||
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
if let Some(lsp_range) = action
|
||||
.lsp_action
|
||||
.data
|
||||
.as_mut()
|
||||
.and_then(|d| d.get_mut("codeActionParams"))
|
||||
.and_then(|d| d.get_mut("range"))
|
||||
{
|
||||
*lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap();
|
||||
action.lsp_action = lang_server
|
||||
.request::<lsp::request::CodeActionResolveRequest>(action.lsp_action)
|
||||
.await?;
|
||||
} else {
|
||||
let actions = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.code_actions(&buffer_handle, action.range, cx)
|
||||
})?
|
||||
.await?;
|
||||
action.lsp_action = actions
|
||||
.into_iter()
|
||||
.find(|a| a.lsp_action.title == action.lsp_action.title)
|
||||
.ok_or_else(|| anyhow!("code action is outdated"))?
|
||||
.lsp_action;
|
||||
}
|
||||
|
||||
Self::try_resolve_code_action(&lang_server, &mut action)
|
||||
.await
|
||||
.context("resolving a code action")?;
|
||||
if let Some(edit) = action.lsp_action.edit {
|
||||
if edit.changes.is_some() || edit.document_changes.is_some() {
|
||||
return Self::deserialize_workspace_edit(
|
||||
@@ -8137,6 +8124,23 @@ impl Project {
|
||||
})
|
||||
}
|
||||
|
||||
async fn try_resolve_code_action(
|
||||
lang_server: &LanguageServer,
|
||||
action: &mut CodeAction,
|
||||
) -> anyhow::Result<()> {
|
||||
if GetCodeActions::can_resolve_actions(&lang_server.capabilities()) {
|
||||
if action.lsp_action.data.is_some()
|
||||
&& (action.lsp_action.command.is_none() || action.lsp_action.edit.is_none())
|
||||
{
|
||||
action.lsp_action = lang_server
|
||||
.request::<lsp::request::CodeActionResolveRequest>(action.lsp_action.clone())
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
async fn handle_refresh_inlay_hints(
|
||||
this: Model<Self>,
|
||||
_: TypedEnvelope<proto::RefreshInlayHints>,
|
||||
|
||||
@@ -2564,7 +2564,20 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
code_action_provider: Some(lsp::CodeActionProviderCapability::Options(
|
||||
lsp::CodeActionOptions {
|
||||
resolve_provider: Some(true),
|
||||
..lsp::CodeActionOptions::default()
|
||||
},
|
||||
)),
|
||||
..lsp::ServerCapabilities::default()
|
||||
},
|
||||
..FakeLspAdapter::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
@@ -2591,16 +2604,14 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
|
||||
Ok(Some(vec![
|
||||
lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
|
||||
title: "The code action".into(),
|
||||
command: Some(lsp::Command {
|
||||
title: "The command".into(),
|
||||
command: "_the/command".into(),
|
||||
arguments: Some(vec![json!("the-argument")]),
|
||||
}),
|
||||
..Default::default()
|
||||
data: Some(serde_json::json!({
|
||||
"command": "_the/command",
|
||||
})),
|
||||
..lsp::CodeAction::default()
|
||||
}),
|
||||
lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
|
||||
title: "two".into(),
|
||||
..Default::default()
|
||||
..lsp::CodeAction::default()
|
||||
}),
|
||||
]))
|
||||
})
|
||||
@@ -2615,7 +2626,16 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
|
||||
// Resolving the code action does not populate its edits. In absence of
|
||||
// edits, we must execute the given command.
|
||||
fake_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
|
||||
|action, _| async move { Ok(action) },
|
||||
|mut action, _| async move {
|
||||
if action.data.is_some() {
|
||||
action.command = Some(lsp::Command {
|
||||
title: "The command".into(),
|
||||
command: "_the/command".into(),
|
||||
arguments: Some(vec![json!("the-argument")]),
|
||||
});
|
||||
}
|
||||
Ok(action)
|
||||
},
|
||||
);
|
||||
|
||||
// While executing the command, the language server sends the editor
|
||||
|
||||
@@ -2,17 +2,20 @@
|
||||
|
||||
use std::{any::TypeId, path::Path, sync::Arc};
|
||||
|
||||
use collections::{HashMap, VecDeque};
|
||||
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
|
||||
use task::{Source, Task, TaskId};
|
||||
use itertools::Itertools;
|
||||
use task::{Task, TaskId, TaskSource};
|
||||
use util::{post_inc, NumericPrefixWithSuffix};
|
||||
|
||||
/// Inventory tracks available tasks for a given project.
|
||||
pub struct Inventory {
|
||||
sources: Vec<SourceInInventory>,
|
||||
pub last_scheduled_task: Option<TaskId>,
|
||||
last_scheduled_tasks: VecDeque<TaskId>,
|
||||
}
|
||||
|
||||
struct SourceInInventory {
|
||||
source: Model<Box<dyn Source>>,
|
||||
source: Model<Box<dyn TaskSource>>,
|
||||
_subscription: Subscription,
|
||||
type_id: TypeId,
|
||||
}
|
||||
@@ -21,12 +24,12 @@ impl Inventory {
|
||||
pub(crate) fn new(cx: &mut AppContext) -> Model<Self> {
|
||||
cx.new_model(|_| Self {
|
||||
sources: Vec::new(),
|
||||
last_scheduled_task: None,
|
||||
last_scheduled_tasks: VecDeque::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Registers a new tasks source, that would be fetched for available tasks.
|
||||
pub fn add_source(&mut self, source: Model<Box<dyn Source>>, cx: &mut ModelContext<Self>) {
|
||||
pub fn add_source(&mut self, source: Model<Box<dyn TaskSource>>, cx: &mut ModelContext<Self>) {
|
||||
let _subscription = cx.observe(&source, |_, _, cx| {
|
||||
cx.notify();
|
||||
});
|
||||
@@ -39,7 +42,8 @@ impl Inventory {
|
||||
self.sources.push(source);
|
||||
cx.notify();
|
||||
}
|
||||
pub fn source<T: Source>(&self) -> Option<Model<Box<dyn Source>>> {
|
||||
|
||||
pub fn source<T: TaskSource>(&self) -> Option<Model<Box<dyn TaskSource>>> {
|
||||
let target_type_id = std::any::TypeId::of::<T>();
|
||||
self.sources.iter().find_map(
|
||||
|SourceInInventory {
|
||||
@@ -55,25 +59,306 @@ impl Inventory {
|
||||
}
|
||||
|
||||
/// Pulls its sources to list runanbles for the path given (up to the source to decide what to return for no path).
|
||||
pub fn list_tasks(&self, path: Option<&Path>, cx: &mut AppContext) -> Vec<Arc<dyn Task>> {
|
||||
let mut tasks = Vec::new();
|
||||
for source in &self.sources {
|
||||
tasks.extend(
|
||||
pub fn list_tasks(
|
||||
&self,
|
||||
path: Option<&Path>,
|
||||
lru: bool,
|
||||
cx: &mut AppContext,
|
||||
) -> Vec<Arc<dyn Task>> {
|
||||
let mut lru_score = 0_u32;
|
||||
let tasks_by_usage = if lru {
|
||||
self.last_scheduled_tasks
|
||||
.iter()
|
||||
.rev()
|
||||
.fold(HashMap::default(), |mut tasks, id| {
|
||||
tasks.entry(id).or_insert_with(|| post_inc(&mut lru_score));
|
||||
tasks
|
||||
})
|
||||
} else {
|
||||
HashMap::default()
|
||||
};
|
||||
let not_used_score = post_inc(&mut lru_score);
|
||||
|
||||
self.sources
|
||||
.iter()
|
||||
.flat_map(|source| {
|
||||
source
|
||||
.source
|
||||
.update(cx, |source, cx| source.tasks_for_path(path, cx)),
|
||||
);
|
||||
}
|
||||
tasks
|
||||
.update(cx, |source, cx| source.tasks_for_path(path, cx))
|
||||
})
|
||||
.map(|task| {
|
||||
let usages = if lru {
|
||||
tasks_by_usage
|
||||
.get(&task.id())
|
||||
.copied()
|
||||
.unwrap_or(not_used_score)
|
||||
} else {
|
||||
not_used_score
|
||||
};
|
||||
(task, usages)
|
||||
})
|
||||
.sorted_unstable_by(|(task_a, usages_a), (task_b, usages_b)| {
|
||||
usages_a.cmp(usages_b).then({
|
||||
NumericPrefixWithSuffix::from_numeric_prefixed_str(task_a.name())
|
||||
.cmp(&NumericPrefixWithSuffix::from_numeric_prefixed_str(
|
||||
task_b.name(),
|
||||
))
|
||||
.then(task_a.name().cmp(task_b.name()))
|
||||
})
|
||||
})
|
||||
.map(|(task, _)| task)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the last scheduled task, if any of the sources contains one with the matching id.
|
||||
pub fn last_scheduled_task(&self, cx: &mut AppContext) -> Option<Arc<dyn Task>> {
|
||||
self.last_scheduled_task.as_ref().and_then(|id| {
|
||||
self.last_scheduled_tasks.back().and_then(|id| {
|
||||
// TODO straighten the `Path` story to understand what has to be passed here: or it will break in the future.
|
||||
self.list_tasks(None, cx)
|
||||
self.list_tasks(None, false, cx)
|
||||
.into_iter()
|
||||
.find(|task| task.id() == id)
|
||||
})
|
||||
}
|
||||
|
||||
/// Registers task "usage" as being scheduled – to be used for LRU sorting when listing all tasks.
|
||||
pub fn task_scheduled(&mut self, id: TaskId) {
|
||||
self.last_scheduled_tasks.push_back(id);
|
||||
if self.last_scheduled_tasks.len() > 5_000 {
|
||||
self.last_scheduled_tasks.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use gpui::TestAppContext;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_task_list_sorting(cx: &mut TestAppContext) {
|
||||
let inventory = cx.update(Inventory::new);
|
||||
let initial_tasks = list_task_names(&inventory, None, true, cx);
|
||||
assert!(
|
||||
initial_tasks.is_empty(),
|
||||
"No tasks expected for empty inventory, but got {initial_tasks:?}"
|
||||
);
|
||||
let initial_tasks = list_task_names(&inventory, None, false, cx);
|
||||
assert!(
|
||||
initial_tasks.is_empty(),
|
||||
"No tasks expected for empty inventory, but got {initial_tasks:?}"
|
||||
);
|
||||
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
inventory.add_source(TestSource::new(vec!["3_task".to_string()], cx), cx);
|
||||
});
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
inventory.add_source(
|
||||
TestSource::new(
|
||||
vec![
|
||||
"1_task".to_string(),
|
||||
"2_task".to_string(),
|
||||
"1_a_task".to_string(),
|
||||
],
|
||||
cx,
|
||||
),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let expected_initial_state = [
|
||||
"1_a_task".to_string(),
|
||||
"1_task".to_string(),
|
||||
"2_task".to_string(),
|
||||
"3_task".to_string(),
|
||||
];
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, false, cx),
|
||||
&expected_initial_state,
|
||||
"Task list without lru sorting, should be sorted alphanumerically"
|
||||
);
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, true, cx),
|
||||
&expected_initial_state,
|
||||
"Tasks with equal amount of usages should be sorted alphanumerically"
|
||||
);
|
||||
|
||||
register_task_used(&inventory, "2_task", cx);
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, false, cx),
|
||||
&expected_initial_state,
|
||||
"Task list without lru sorting, should be sorted alphanumerically"
|
||||
);
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, true, cx),
|
||||
vec![
|
||||
"2_task".to_string(),
|
||||
"1_a_task".to_string(),
|
||||
"1_task".to_string(),
|
||||
"3_task".to_string()
|
||||
],
|
||||
);
|
||||
|
||||
register_task_used(&inventory, "1_task", cx);
|
||||
register_task_used(&inventory, "1_task", cx);
|
||||
register_task_used(&inventory, "1_task", cx);
|
||||
register_task_used(&inventory, "3_task", cx);
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, false, cx),
|
||||
&expected_initial_state,
|
||||
"Task list without lru sorting, should be sorted alphanumerically"
|
||||
);
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, true, cx),
|
||||
vec![
|
||||
"3_task".to_string(),
|
||||
"1_task".to_string(),
|
||||
"2_task".to_string(),
|
||||
"1_a_task".to_string(),
|
||||
],
|
||||
);
|
||||
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
inventory.add_source(
|
||||
TestSource::new(vec!["10_hello".to_string(), "11_hello".to_string()], cx),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
let expected_updated_state = [
|
||||
"1_a_task".to_string(),
|
||||
"1_task".to_string(),
|
||||
"2_task".to_string(),
|
||||
"3_task".to_string(),
|
||||
"10_hello".to_string(),
|
||||
"11_hello".to_string(),
|
||||
];
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, false, cx),
|
||||
&expected_updated_state,
|
||||
"Task list without lru sorting, should be sorted alphanumerically"
|
||||
);
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, true, cx),
|
||||
vec![
|
||||
"3_task".to_string(),
|
||||
"1_task".to_string(),
|
||||
"2_task".to_string(),
|
||||
"1_a_task".to_string(),
|
||||
"10_hello".to_string(),
|
||||
"11_hello".to_string(),
|
||||
],
|
||||
);
|
||||
|
||||
register_task_used(&inventory, "11_hello", cx);
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, false, cx),
|
||||
&expected_updated_state,
|
||||
"Task list without lru sorting, should be sorted alphanumerically"
|
||||
);
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, true, cx),
|
||||
vec![
|
||||
"11_hello".to_string(),
|
||||
"3_task".to_string(),
|
||||
"1_task".to_string(),
|
||||
"2_task".to_string(),
|
||||
"1_a_task".to_string(),
|
||||
"10_hello".to_string(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct TestTask {
|
||||
id: TaskId,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Task for TestTask {
|
||||
fn id(&self) -> &TaskId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn cwd(&self) -> Option<&Path> {
|
||||
None
|
||||
}
|
||||
|
||||
fn exec(&self, _cwd: Option<PathBuf>) -> Option<task::SpawnInTerminal> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
struct TestSource {
|
||||
tasks: Vec<TestTask>,
|
||||
}
|
||||
|
||||
impl TestSource {
|
||||
fn new(
|
||||
task_names: impl IntoIterator<Item = String>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Box<dyn TaskSource>> {
|
||||
cx.new_model(|_| {
|
||||
Box::new(Self {
|
||||
tasks: task_names
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, name)| TestTask {
|
||||
id: TaskId(format!("task_{i}_{name}")),
|
||||
name,
|
||||
})
|
||||
.collect(),
|
||||
}) as Box<dyn TaskSource>
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskSource for TestSource {
|
||||
fn tasks_for_path(
|
||||
&mut self,
|
||||
_path: Option<&Path>,
|
||||
_cx: &mut ModelContext<Box<dyn TaskSource>>,
|
||||
) -> Vec<Arc<dyn Task>> {
|
||||
self.tasks
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|task| Arc::new(task) as Arc<dyn Task>)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn list_task_names(
|
||||
inventory: &Model<Inventory>,
|
||||
path: Option<&Path>,
|
||||
lru: bool,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Vec<String> {
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
inventory
|
||||
.list_tasks(path, lru, cx)
|
||||
.into_iter()
|
||||
.map(|task| task.name().to_string())
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
fn register_task_used(inventory: &Model<Inventory>, task_name: &str, cx: &mut TestAppContext) {
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
let task = inventory
|
||||
.list_tasks(None, false, cx)
|
||||
.into_iter()
|
||||
.find(|task| task.name() == task_name)
|
||||
.unwrap_or_else(|| panic!("Failed to find task with name {task_name}"));
|
||||
inventory.task_scheduled(task.id().clone());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ pub enum GitGutterSetting {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct LspSettings {
|
||||
pub initialization_options: Option<serde_json::Value>,
|
||||
pub settings: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl Settings for ProjectSettings {
|
||||
|
||||
@@ -68,6 +68,11 @@ use util::{
|
||||
ResultExt,
|
||||
};
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
pub const FS_WATCH_LATENCY: Duration = Duration::from_millis(100);
|
||||
#[cfg(not(feature = "test-support"))]
|
||||
const FS_WATCH_LATENCY: Duration = Duration::from_millis(100);
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
|
||||
pub struct WorktreeId(usize);
|
||||
|
||||
@@ -652,7 +657,7 @@ fn start_background_scan_tasks(
|
||||
let abs_path = abs_path.to_path_buf();
|
||||
let background = cx.background_executor().clone();
|
||||
async move {
|
||||
let events = fs.watch(&abs_path, Duration::from_millis(100)).await;
|
||||
let events = fs.watch(&abs_path, FS_WATCH_LATENCY).await;
|
||||
let case_sensitive = fs.is_case_sensitive().await.unwrap_or_else(|e| {
|
||||
log::error!(
|
||||
"Failed to determine whether filesystem is case sensitive (falling back to true) due to error: {e:#}"
|
||||
@@ -4340,7 +4345,7 @@ impl BackgroundScanner {
|
||||
return self.executor.simulate_random_delay().await;
|
||||
}
|
||||
|
||||
smol::Timer::after(Duration::from_millis(100)).await;
|
||||
smol::Timer::after(FS_WATCH_LATENCY).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
unicase = "2.6"
|
||||
unicase.workspace = true
|
||||
util.workspace = true
|
||||
client.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -27,7 +27,7 @@ use std::{cmp::Ordering, ffi::OsStr, ops::Range, path::Path, sync::Arc};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, v_flex, ContextMenu, Icon, KeyBinding, Label, ListItem};
|
||||
use unicase::UniCase;
|
||||
use util::{maybe, ResultExt, TryFutureExt};
|
||||
use util::{maybe, NumericPrefixWithSuffix, ResultExt, TryFutureExt};
|
||||
use workspace::{
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
notifications::DetachAndPromptErr,
|
||||
@@ -338,7 +338,7 @@ impl ProjectPanel {
|
||||
let panel = ProjectPanel::new(workspace, cx);
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.width = serialized_panel.width;
|
||||
panel.width = serialized_panel.width.map(|px| px.round());
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
@@ -1182,11 +1182,15 @@ impl ProjectPanel {
|
||||
let num_and_remainder_a = Path::new(component_a.as_os_str())
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.and_then(NumericPrefixWithSuffix::from_str)?;
|
||||
.and_then(
|
||||
NumericPrefixWithSuffix::from_numeric_prefixed_str,
|
||||
)?;
|
||||
let num_and_remainder_b = Path::new(component_b.as_os_str())
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.and_then(NumericPrefixWithSuffix::from_str)?;
|
||||
.and_then(
|
||||
NumericPrefixWithSuffix::from_numeric_prefixed_str,
|
||||
)?;
|
||||
|
||||
num_and_remainder_a.partial_cmp(&num_and_remainder_b)
|
||||
});
|
||||
@@ -1498,35 +1502,6 @@ impl ProjectPanel {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct NumericPrefixWithSuffix<'a>(i32, &'a str);
|
||||
|
||||
impl<'a> NumericPrefixWithSuffix<'a> {
|
||||
fn from_str(str: &'a str) -> Option<Self> {
|
||||
let mut chars = str.chars();
|
||||
let prefix: String = chars.by_ref().take_while(|c| c.is_digit(10)).collect();
|
||||
let remainder = chars.as_str();
|
||||
|
||||
match prefix.parse::<i32>() {
|
||||
Ok(prefix) => Some(NumericPrefixWithSuffix(prefix, remainder)),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialOrd for NumericPrefixWithSuffix<'a> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
let NumericPrefixWithSuffix(num_a, remainder_a) = self;
|
||||
let NumericPrefixWithSuffix(num_b, remainder_b) = other;
|
||||
|
||||
Some(
|
||||
num_a
|
||||
.cmp(&num_b)
|
||||
.then_with(|| UniCase::new(remainder_a).cmp(&UniCase::new(remainder_b))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ProjectPanel {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
||||
let has_worktree = self.visible_entries.len() != 0;
|
||||
|
||||
@@ -15,7 +15,15 @@ gpui.workspace = true
|
||||
menu.workspace = true
|
||||
ordered-float.workspace = true
|
||||
picker.workspace = true
|
||||
serde.workspace = true
|
||||
smol.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
serde_json.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -8,12 +8,23 @@ use gpui::{
|
||||
use highlighted_workspace_location::HighlightedWorkspaceLocation;
|
||||
use ordered_float::OrderedFloat;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use ui::{prelude::*, tooltip_container, HighlightedLabel, ListItem, ListItemSpacing, Tooltip};
|
||||
use util::paths::PathExt;
|
||||
use workspace::{ModalView, Workspace, WorkspaceId, WorkspaceLocation, WORKSPACE_DB};
|
||||
|
||||
gpui::actions!(projects, [OpenRecent]);
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct OpenRecent {
|
||||
#[serde(default = "default_create_new_window")]
|
||||
pub create_new_window: bool,
|
||||
}
|
||||
|
||||
fn default_create_new_window() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
gpui::impl_actions!(projects, [OpenRecent]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(RecentProjects::register).detach();
|
||||
@@ -63,9 +74,9 @@ impl RecentProjects {
|
||||
}
|
||||
|
||||
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
|
||||
workspace.register_action(|workspace, _: &OpenRecent, cx| {
|
||||
workspace.register_action(|workspace, open_recent: &OpenRecent, cx| {
|
||||
let Some(recent_projects) = workspace.active_modal::<Self>(cx) else {
|
||||
if let Some(handler) = Self::open(workspace, cx) {
|
||||
if let Some(handler) = Self::open(workspace, open_recent.create_new_window, cx) {
|
||||
handler.detach_and_log_err(cx);
|
||||
}
|
||||
return;
|
||||
@@ -79,12 +90,17 @@ impl RecentProjects {
|
||||
});
|
||||
}
|
||||
|
||||
fn open(_: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<Task<Result<()>>> {
|
||||
fn open(
|
||||
_: &mut Workspace,
|
||||
create_new_window: bool,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
Some(cx.spawn(|workspace, mut cx| async move {
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let weak_workspace = cx.view().downgrade();
|
||||
workspace.toggle_modal(cx, |cx| {
|
||||
let delegate = RecentProjectsDelegate::new(weak_workspace, true);
|
||||
let delegate =
|
||||
RecentProjectsDelegate::new(weak_workspace, create_new_window, true);
|
||||
|
||||
let modal = Self::new(delegate, 34., cx);
|
||||
modal
|
||||
@@ -95,7 +111,13 @@ impl RecentProjects {
|
||||
}
|
||||
|
||||
pub fn open_popover(workspace: WeakView<Workspace>, cx: &mut WindowContext<'_>) -> View<Self> {
|
||||
cx.new_view(|cx| Self::new(RecentProjectsDelegate::new(workspace, false), 20., cx))
|
||||
cx.new_view(|cx| {
|
||||
Self::new(
|
||||
RecentProjectsDelegate::new(workspace, false, false),
|
||||
20.,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,17 +148,19 @@ pub struct RecentProjectsDelegate {
|
||||
selected_match_index: usize,
|
||||
matches: Vec<StringMatch>,
|
||||
render_paths: bool,
|
||||
create_new_window: bool,
|
||||
// Flag to reset index when there is a new query vs not reset index when user delete an item
|
||||
reset_selected_match_index: bool,
|
||||
}
|
||||
|
||||
impl RecentProjectsDelegate {
|
||||
fn new(workspace: WeakView<Workspace>, render_paths: bool) -> Self {
|
||||
fn new(workspace: WeakView<Workspace>, create_new_window: bool, render_paths: bool) -> Self {
|
||||
Self {
|
||||
workspace,
|
||||
workspaces: vec![],
|
||||
selected_match_index: 0,
|
||||
matches: Default::default(),
|
||||
create_new_window,
|
||||
render_paths,
|
||||
reset_selected_match_index: true,
|
||||
}
|
||||
@@ -147,10 +171,19 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn placeholder_text(&self, cx: &mut WindowContext) -> Arc<str> {
|
||||
let (create_window, reuse_window) = if self.create_new_window {
|
||||
(
|
||||
cx.keystroke_text_for(&menu::Confirm),
|
||||
cx.keystroke_text_for(&menu::SecondaryConfirm),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
cx.keystroke_text_for(&menu::SecondaryConfirm),
|
||||
cx.keystroke_text_for(&menu::Confirm),
|
||||
)
|
||||
};
|
||||
Arc::from(format!(
|
||||
"{} reuses the window, {} opens a new one",
|
||||
cx.keystroke_text_for(&menu::Confirm),
|
||||
cx.keystroke_text_for(&menu::SecondaryConfirm),
|
||||
"{reuse_window} reuses the window, {create_window} opens a new one",
|
||||
))
|
||||
}
|
||||
|
||||
@@ -219,15 +252,39 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||
{
|
||||
let (candidate_workspace_id, candidate_workspace_location) =
|
||||
&self.workspaces[selected_match.candidate_id];
|
||||
let replace_current_window = !secondary;
|
||||
let replace_current_window = if self.create_new_window {
|
||||
secondary
|
||||
} else {
|
||||
!secondary
|
||||
};
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
if workspace.database_id() != *candidate_workspace_id {
|
||||
workspace.open_workspace_for_paths(
|
||||
replace_current_window,
|
||||
candidate_workspace_location.paths().as_ref().clone(),
|
||||
cx,
|
||||
)
|
||||
let candidate_paths = candidate_workspace_location.paths().as_ref().clone();
|
||||
if replace_current_window {
|
||||
cx.spawn(move |workspace, mut cx| async move {
|
||||
let continue_replacing = workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.prepare_to_close(true, cx)
|
||||
})?
|
||||
.await?;
|
||||
if continue_replacing {
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.open_workspace_for_paths(
|
||||
true,
|
||||
candidate_paths,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
} else {
|
||||
workspace.open_workspace_for_paths(false, candidate_paths, cx)
|
||||
}
|
||||
} else {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
@@ -360,3 +417,141 @@ impl Render for MatchTooltip {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use editor::Editor;
|
||||
use gpui::{TestAppContext, WindowHandle};
|
||||
use project::Project;
|
||||
use serde_json::json;
|
||||
use workspace::{open_paths, AppState};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_prompts_on_dirty_before_submit(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
"main.ts": "a"
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
cx.update(|cx| open_paths(&[PathBuf::from("/dir/main.ts")], &app_state, None, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
||||
|
||||
let workspace = cx.update(|cx| cx.windows()[0].downcast::<Workspace>().unwrap());
|
||||
workspace
|
||||
.update(cx, |workspace, _| assert!(!workspace.is_edited()))
|
||||
.unwrap();
|
||||
|
||||
let editor = workspace
|
||||
.read_with(cx, |workspace, cx| {
|
||||
workspace
|
||||
.active_item(cx)
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
workspace
|
||||
.update(cx, |_, cx| {
|
||||
editor.update(cx, |editor, cx| editor.insert("EDIT", cx));
|
||||
})
|
||||
.unwrap();
|
||||
workspace
|
||||
.update(cx, |workspace, _| assert!(workspace.is_edited(), "After inserting more text into the editor without saving, we should have a dirty project"))
|
||||
.unwrap();
|
||||
|
||||
let recent_projects_picker = open_recent_projects(&workspace, cx);
|
||||
workspace
|
||||
.update(cx, |_, cx| {
|
||||
recent_projects_picker.update(cx, |picker, cx| {
|
||||
assert_eq!(picker.query(cx), "");
|
||||
let delegate = &mut picker.delegate;
|
||||
delegate.matches = vec![StringMatch {
|
||||
candidate_id: 0,
|
||||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
string: "fake candidate".to_string(),
|
||||
}];
|
||||
delegate.workspaces = vec![(0, WorkspaceLocation::new(vec!["/test/path/"]))];
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
!cx.has_pending_prompt(),
|
||||
"Should have no pending prompt on dirty project before opening the new recent project"
|
||||
);
|
||||
cx.dispatch_action((*workspace).into(), menu::Confirm);
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
assert!(
|
||||
workspace.active_modal::<RecentProjects>(cx).is_none(),
|
||||
"Should remove the modal after selecting new recent project"
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
assert!(
|
||||
cx.has_pending_prompt(),
|
||||
"Dirty workspace should prompt before opening the new recent project"
|
||||
);
|
||||
// Cancel
|
||||
cx.simulate_prompt_answer(0);
|
||||
assert!(
|
||||
!cx.has_pending_prompt(),
|
||||
"Should have no pending prompt after cancelling"
|
||||
);
|
||||
workspace
|
||||
.update(cx, |workspace, _| {
|
||||
assert!(
|
||||
workspace.is_edited(),
|
||||
"Should be in the same dirty project after cancelling"
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn open_recent_projects(
|
||||
workspace: &WindowHandle<Workspace>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> View<Picker<RecentProjectsDelegate>> {
|
||||
cx.dispatch_action(
|
||||
(*workspace).into(),
|
||||
OpenRecent {
|
||||
create_new_window: false,
|
||||
},
|
||||
);
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.active_modal::<RecentProjects>(cx)
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.picker
|
||||
.clone()
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
|
||||
cx.update(|cx| {
|
||||
let state = AppState::test(cx);
|
||||
language::init(cx);
|
||||
crate::init(cx);
|
||||
editor::init(cx);
|
||||
workspace::init_settings(cx);
|
||||
Project::init_settings(cx);
|
||||
state
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,10 +315,13 @@ impl Peer {
|
||||
"incoming response: requester resumed"
|
||||
);
|
||||
} else {
|
||||
let message_type = proto::build_typed_envelope(connection_id, incoming)
|
||||
.map(|p| p.payload_type_name());
|
||||
tracing::warn!(
|
||||
%connection_id,
|
||||
message_id,
|
||||
responding_to,
|
||||
message_type,
|
||||
"incoming response: unknown request"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,13 +56,13 @@ pub trait Task {
|
||||
///
|
||||
/// Implementations of this trait could be e.g. [`StaticSource`] that parses tasks from a .json files and provides process templates to be spawned;
|
||||
/// another one could be a language server providing lenses with tests or build server listing all targets for a given project.
|
||||
pub trait Source: Any {
|
||||
pub trait TaskSource: Any {
|
||||
/// A way to erase the type of the source, processing and storing them generically.
|
||||
fn as_any(&mut self) -> &mut dyn Any;
|
||||
/// Collects all tasks available for scheduling, for the path given.
|
||||
fn tasks_for_path(
|
||||
&mut self,
|
||||
path: Option<&Path>,
|
||||
cx: &mut ModelContext<Box<dyn Source>>,
|
||||
cx: &mut ModelContext<Box<dyn TaskSource>>,
|
||||
) -> Vec<Arc<dyn Task>>;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{Source, SpawnInTerminal, Task, TaskId};
|
||||
use crate::{SpawnInTerminal, Task, TaskId, TaskSource};
|
||||
use gpui::{AppContext, Context, Model};
|
||||
|
||||
/// A storage and source of tasks generated out of user command prompt inputs.
|
||||
@@ -54,8 +54,8 @@ impl Task for OneshotTask {
|
||||
|
||||
impl OneshotSource {
|
||||
/// Initializes the oneshot source, preparing to store user prompts.
|
||||
pub fn new(cx: &mut AppContext) -> Model<Box<dyn Source>> {
|
||||
cx.new_model(|_| Box::new(Self { tasks: Vec::new() }) as Box<dyn Source>)
|
||||
pub fn new(cx: &mut AppContext) -> Model<Box<dyn TaskSource>> {
|
||||
cx.new_model(|_| Box::new(Self { tasks: Vec::new() }) as Box<dyn TaskSource>)
|
||||
}
|
||||
|
||||
/// Spawns a certain task based on the user prompt.
|
||||
@@ -66,7 +66,7 @@ impl OneshotSource {
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for OneshotSource {
|
||||
impl TaskSource for OneshotSource {
|
||||
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
@@ -74,7 +74,7 @@ impl Source for OneshotSource {
|
||||
fn tasks_for_path(
|
||||
&mut self,
|
||||
_path: Option<&std::path::Path>,
|
||||
_cx: &mut gpui::ModelContext<Box<dyn Source>>,
|
||||
_cx: &mut gpui::ModelContext<Box<dyn TaskSource>>,
|
||||
) -> Vec<Arc<dyn Task>> {
|
||||
self.tasks.clone()
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use schemars::{gen::SchemaSettings, JsonSchema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::{Source, SpawnInTerminal, Task, TaskId};
|
||||
use crate::{SpawnInTerminal, Task, TaskId, TaskSource};
|
||||
use futures::channel::mpsc::UnboundedReceiver;
|
||||
|
||||
/// A single config file entry with the deserialized task definition.
|
||||
@@ -152,12 +152,12 @@ impl StaticSource {
|
||||
pub fn new(
|
||||
tasks_file_tracker: UnboundedReceiver<String>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Box<dyn Source>> {
|
||||
) -> Model<Box<dyn TaskSource>> {
|
||||
let definitions = TrackedFile::new(DefinitionProvider::default(), tasks_file_tracker, cx);
|
||||
cx.new_model(|cx| {
|
||||
let _subscription = cx.observe(
|
||||
&definitions,
|
||||
|source: &mut Box<(dyn Source + 'static)>, new_definitions, cx| {
|
||||
|source: &mut Box<(dyn TaskSource + 'static)>, new_definitions, cx| {
|
||||
if let Some(static_source) = source.as_any().downcast_mut::<Self>() {
|
||||
static_source.tasks = new_definitions
|
||||
.read(cx)
|
||||
@@ -181,11 +181,11 @@ impl StaticSource {
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for StaticSource {
|
||||
impl TaskSource for StaticSource {
|
||||
fn tasks_for_path(
|
||||
&mut self,
|
||||
_: Option<&Path>,
|
||||
_: &mut ModelContext<Box<dyn Source>>,
|
||||
_: &mut ModelContext<Box<dyn TaskSource>>,
|
||||
) -> Vec<Arc<dyn Task>> {
|
||||
self.tasks
|
||||
.clone()
|
||||
|
||||
@@ -41,7 +41,7 @@ fn schedule_task(workspace: &Workspace, task: &dyn Task, cx: &mut ViewContext<'_
|
||||
if let Some(spawn_in_terminal) = spawn_in_terminal {
|
||||
workspace.project().update(cx, |project, cx| {
|
||||
project.task_inventory().update(cx, |inventory, _| {
|
||||
inventory.last_scheduled_task = Some(task.id().clone());
|
||||
inventory.task_scheduled(task.id().clone());
|
||||
})
|
||||
});
|
||||
cx.emit(workspace::Event::SpawnTask(spawn_in_terminal));
|
||||
|
||||
@@ -24,7 +24,7 @@ pub(crate) struct TasksModalDelegate {
|
||||
matches: Vec<StringMatch>,
|
||||
selected_index: usize,
|
||||
workspace: WeakView<Workspace>,
|
||||
last_prompt: String,
|
||||
prompt: String,
|
||||
}
|
||||
|
||||
impl TasksModalDelegate {
|
||||
@@ -35,20 +35,21 @@ impl TasksModalDelegate {
|
||||
candidates: Vec::new(),
|
||||
matches: Vec::new(),
|
||||
selected_index: 0,
|
||||
last_prompt: String::default(),
|
||||
prompt: String::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_oneshot(&mut self, cx: &mut AppContext) -> Option<Arc<dyn Task>> {
|
||||
let oneshot_source = self
|
||||
.inventory
|
||||
.update(cx, |this, _| this.source::<OneshotSource>())?;
|
||||
oneshot_source.update(cx, |this, _| {
|
||||
let Some(this) = this.as_any().downcast_mut::<OneshotSource>() else {
|
||||
return None;
|
||||
};
|
||||
Some(this.spawn(self.last_prompt.clone()))
|
||||
})
|
||||
self.inventory
|
||||
.update(cx, |inventory, _| inventory.source::<OneshotSource>())?
|
||||
.update(cx, |oneshot_source, _| {
|
||||
Some(
|
||||
oneshot_source
|
||||
.as_any()
|
||||
.downcast_mut::<OneshotSource>()?
|
||||
.spawn(self.prompt.clone()),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,12 +133,7 @@ impl PickerDelegate for TasksModalDelegate {
|
||||
picker.delegate.candidates = picker
|
||||
.delegate
|
||||
.inventory
|
||||
.update(cx, |inventory, cx| inventory.list_tasks(None, cx));
|
||||
picker
|
||||
.delegate
|
||||
.candidates
|
||||
.sort_by(|a, b| a.name().cmp(&b.name()));
|
||||
|
||||
.update(cx, |inventory, cx| inventory.list_tasks(None, true, cx));
|
||||
picker
|
||||
.delegate
|
||||
.candidates
|
||||
@@ -167,7 +163,7 @@ impl PickerDelegate for TasksModalDelegate {
|
||||
.update(&mut cx, |picker, _| {
|
||||
let delegate = &mut picker.delegate;
|
||||
delegate.matches = matches;
|
||||
delegate.last_prompt = query;
|
||||
delegate.prompt = query;
|
||||
|
||||
if delegate.matches.is_empty() {
|
||||
delegate.selected_index = 0;
|
||||
@@ -184,7 +180,7 @@ impl PickerDelegate for TasksModalDelegate {
|
||||
let current_match_index = self.selected_index();
|
||||
|
||||
let task = if secondary {
|
||||
if !self.last_prompt.trim().is_empty() {
|
||||
if !self.prompt.trim().is_empty() {
|
||||
self.spawn_oneshot(cx)
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -489,15 +489,12 @@ impl TerminalElement {
|
||||
}
|
||||
});
|
||||
|
||||
let interactive_text_bounds = InteractiveBounds {
|
||||
bounds,
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
};
|
||||
if interactive_text_bounds.visibly_contains(&cx.mouse_position(), cx) {
|
||||
if bounds.contains(&cx.mouse_position()) {
|
||||
let stacking_order = cx.stacking_order().clone();
|
||||
if self.can_navigate_to_selected_word && last_hovered_word.is_some() {
|
||||
cx.set_cursor_style(gpui::CursorStyle::PointingHand)
|
||||
cx.set_cursor_style(gpui::CursorStyle::PointingHand, stacking_order);
|
||||
} else {
|
||||
cx.set_cursor_style(gpui::CursorStyle::IBeam)
|
||||
cx.set_cursor_style(gpui::CursorStyle::IBeam, stacking_order);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -192,8 +192,8 @@ impl TerminalPanel {
|
||||
let items = if let Some(serialized_panel) = serialized_panel.as_ref() {
|
||||
panel.update(cx, |panel, cx| {
|
||||
cx.notify();
|
||||
panel.height = serialized_panel.height;
|
||||
panel.width = serialized_panel.width;
|
||||
panel.height = serialized_panel.height.map(|h| h.round());
|
||||
panel.width = serialized_panel.width.map(|w| w.round());
|
||||
panel.pane.update(cx, |_, cx| {
|
||||
serialized_panel
|
||||
.items
|
||||
|
||||
@@ -31,6 +31,7 @@ serde_json.workspace = true
|
||||
smol.workspace = true
|
||||
take-until = "0.2.0"
|
||||
tempfile = { workspace = true, optional = true }
|
||||
unicase.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
|
||||
@@ -3,6 +3,7 @@ use anyhow::{anyhow, bail, Context, Result};
|
||||
use futures::AsyncReadExt;
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
|
||||
pub struct GitHubLspBinaryVersion {
|
||||
pub name: String,
|
||||
@@ -74,3 +75,70 @@ pub async fn latest_github_release(
|
||||
.find(|release| release.pre_release == pre_release)
|
||||
.ok_or(anyhow!("Failed to find a release"))
|
||||
}
|
||||
|
||||
pub async fn github_release_with_tag(
|
||||
repo_name_with_owner: &str,
|
||||
tag: &str,
|
||||
http: Arc<dyn HttpClient>,
|
||||
) -> Result<GithubRelease, anyhow::Error> {
|
||||
let url = build_tagged_release_url(repo_name_with_owner, tag)?;
|
||||
let mut response = http
|
||||
.get(&url, Default::default(), true)
|
||||
.await
|
||||
.context("error fetching latest release")?;
|
||||
|
||||
let mut body = Vec::new();
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut body)
|
||||
.await
|
||||
.context("error reading latest release")?;
|
||||
|
||||
if response.status().is_client_error() {
|
||||
let text = String::from_utf8_lossy(body.as_slice());
|
||||
bail!(
|
||||
"status error {}, response: {text:?}",
|
||||
response.status().as_u16()
|
||||
);
|
||||
}
|
||||
|
||||
match serde_json::from_slice::<GithubRelease>(body.as_slice()) {
|
||||
Ok(release) => Ok(release),
|
||||
|
||||
Err(err) => {
|
||||
log::error!("Error deserializing: {:?}", err);
|
||||
log::error!(
|
||||
"GitHub API response text: {:?}",
|
||||
String::from_utf8_lossy(body.as_slice())
|
||||
);
|
||||
Err(anyhow!("error deserializing latest release"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_tagged_release_url(repo_name_with_owner: &str, tag: &str) -> Result<String> {
|
||||
let mut url = Url::parse(&format!(
|
||||
"https://api.github.com/repos/{repo_name_with_owner}/releases/tags"
|
||||
))?;
|
||||
// We're pushing this here, because tags may contain `/` and other characters
|
||||
// that need to be escaped.
|
||||
url.path_segments_mut()
|
||||
.map_err(|_| anyhow!("cannot modify url path segments"))?
|
||||
.push(tag);
|
||||
Ok(url.to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::build_tagged_release_url;
|
||||
|
||||
#[test]
|
||||
fn test_build_tagged_release_url() {
|
||||
let tag = "release/2.2.20-Insider";
|
||||
let repo_name_with_owner = "microsoft/vscode-eslint";
|
||||
|
||||
let have = build_tagged_release_url(repo_name_with_owner, tag).unwrap();
|
||||
|
||||
assert_eq!(have, "https://api.github.com/repos/microsoft/vscode-eslint/releases/tags/release%2F2.2.20-Insider");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
time::Instant,
|
||||
};
|
||||
use unicase::UniCase;
|
||||
|
||||
pub use take_until::*;
|
||||
|
||||
@@ -487,6 +488,43 @@ impl<T: Ord + Clone> RangeExt<T> for RangeInclusive<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A way to sort strings with starting numbers numerically first, falling back to alphanumeric one,
|
||||
/// case-insensitive.
|
||||
///
|
||||
/// This is useful for turning regular alphanumerically sorted sequences as `1-abc, 10, 11-def, .., 2, 21-abc`
|
||||
/// into `1-abc, 2, 10, 11-def, .., 21-abc`
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct NumericPrefixWithSuffix<'a>(i32, &'a str);
|
||||
|
||||
impl<'a> NumericPrefixWithSuffix<'a> {
|
||||
pub fn from_numeric_prefixed_str(str: &'a str) -> Option<Self> {
|
||||
let mut chars = str.chars();
|
||||
let prefix: String = chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
|
||||
let remainder = chars.as_str();
|
||||
|
||||
match prefix.parse::<i32>() {
|
||||
Ok(prefix) => Some(NumericPrefixWithSuffix(prefix, remainder)),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for NumericPrefixWithSuffix<'_> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let NumericPrefixWithSuffix(num_a, remainder_a) = self;
|
||||
let NumericPrefixWithSuffix(num_b, remainder_b) = other;
|
||||
num_a
|
||||
.cmp(num_b)
|
||||
.then_with(|| UniCase::new(remainder_a).cmp(&UniCase::new(remainder_b)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialOrd for NumericPrefixWithSuffix<'a> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -526,4 +564,23 @@ mod tests {
|
||||
assert_eq!(truncate_and_trailoff("èèèèèè", 6), "èèèèèè");
|
||||
assert_eq!(truncate_and_trailoff("èèèèèè", 5), "èèèèè…");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_numeric_prefix_with_suffix() {
|
||||
let mut sorted = vec!["1-abc", "10", "11def", "2", "21-abc"];
|
||||
sorted.sort_by_key(|s| {
|
||||
NumericPrefixWithSuffix::from_numeric_prefixed_str(s).unwrap_or_else(|| {
|
||||
panic!("Cannot convert string `{s}` into NumericPrefixWithSuffix")
|
||||
})
|
||||
});
|
||||
assert_eq!(sorted, ["1-abc", "2", "10", "11def", "21-abc"]);
|
||||
|
||||
for numeric_prefix_less in ["numeric_prefix_less", "aaa", "~™£"] {
|
||||
assert_eq!(
|
||||
NumericPrefixWithSuffix::from_numeric_prefixed_str(numeric_prefix_less),
|
||||
None,
|
||||
"String without numeric prefix `{numeric_prefix_less}` should not be converted into NumericPrefixWithSuffix"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,7 +522,7 @@ impl Dock {
|
||||
|
||||
pub fn resize_active_panel(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
|
||||
if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) {
|
||||
let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE));
|
||||
let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE).round());
|
||||
entry.panel.set_size(size, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use client::{
|
||||
proto::{self, PeerId},
|
||||
Client,
|
||||
};
|
||||
use futures::{channel::mpsc, StreamExt};
|
||||
use gpui::{
|
||||
AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
|
||||
HighlightStyle, Model, Pixels, Point, SharedString, Task, View, ViewContext, WeakView,
|
||||
@@ -27,14 +28,13 @@ use std::{
|
||||
ops::Range,
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use theme::Theme;
|
||||
|
||||
pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200);
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ItemSettings {
|
||||
pub git_status: bool,
|
||||
@@ -405,7 +405,7 @@ impl<T: Item> ItemHandle for View<T> {
|
||||
followed_item.is_project_item(cx),
|
||||
proto::update_followers::Variant::CreateView(proto::View {
|
||||
id: followed_item
|
||||
.remote_id(&workspace.app_state.client, cx)
|
||||
.remote_id(&workspace.client(), cx)
|
||||
.map(|id| id.to_proto()),
|
||||
variant: Some(message),
|
||||
leader_id: workspace.leader_for_pane(&pane),
|
||||
@@ -421,8 +421,46 @@ impl<T: Item> ItemHandle for View<T> {
|
||||
.is_none()
|
||||
{
|
||||
let mut pending_autosave = DelayedDebouncedEditAction::new();
|
||||
let (pending_update_tx, mut pending_update_rx) = mpsc::unbounded();
|
||||
let pending_update = Rc::new(RefCell::new(None));
|
||||
let pending_update_scheduled = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let mut send_follower_updates = None;
|
||||
if let Some(item) = self.to_followable_item_handle(cx) {
|
||||
let is_project_item = item.is_project_item(cx);
|
||||
let item = item.downgrade();
|
||||
|
||||
send_follower_updates = Some(cx.spawn({
|
||||
let pending_update = pending_update.clone();
|
||||
|workspace, mut cx| async move {
|
||||
while let Some(mut leader_id) = pending_update_rx.next().await {
|
||||
while let Ok(Some(id)) = pending_update_rx.try_next() {
|
||||
leader_id = id;
|
||||
}
|
||||
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let item = item.upgrade().expect(
|
||||
"item to be alive, otherwise task would have been dropped",
|
||||
);
|
||||
workspace.update_followers(
|
||||
is_project_item,
|
||||
proto::update_followers::Variant::UpdateView(
|
||||
proto::UpdateView {
|
||||
id: item
|
||||
.remote_id(workspace.client(), cx)
|
||||
.map(|id| id.to_proto()),
|
||||
variant: pending_update.borrow_mut().take(),
|
||||
leader_id,
|
||||
},
|
||||
),
|
||||
cx,
|
||||
);
|
||||
})?;
|
||||
cx.background_executor().timer(LEADER_UPDATE_THROTTLE).await;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
let mut event_subscription =
|
||||
Some(cx.subscribe(self, move |workspace, item, event, cx| {
|
||||
@@ -438,9 +476,7 @@ impl<T: Item> ItemHandle for View<T> {
|
||||
};
|
||||
|
||||
if let Some(item) = item.to_followable_item_handle(cx) {
|
||||
let is_project_item = item.is_project_item(cx);
|
||||
let leader_id = workspace.leader_for_pane(&pane);
|
||||
|
||||
let follow_event = item.to_follow_event(event);
|
||||
if leader_id.is_some()
|
||||
&& matches!(follow_event, Some(FollowEvent::Unfollow))
|
||||
@@ -448,35 +484,13 @@ impl<T: Item> ItemHandle for View<T> {
|
||||
workspace.unfollow(&pane, cx);
|
||||
}
|
||||
|
||||
if item.focus_handle(cx).contains_focused(cx)
|
||||
&& item.add_event_to_update_proto(
|
||||
if item.focus_handle(cx).contains_focused(cx) {
|
||||
item.add_event_to_update_proto(
|
||||
event,
|
||||
&mut *pending_update.borrow_mut(),
|
||||
cx,
|
||||
)
|
||||
&& !pending_update_scheduled.load(Ordering::SeqCst)
|
||||
{
|
||||
pending_update_scheduled.store(true, Ordering::SeqCst);
|
||||
cx.defer({
|
||||
let pending_update = pending_update.clone();
|
||||
let pending_update_scheduled = pending_update_scheduled.clone();
|
||||
move |this, cx| {
|
||||
pending_update_scheduled.store(false, Ordering::SeqCst);
|
||||
this.update_followers(
|
||||
is_project_item,
|
||||
proto::update_followers::Variant::UpdateView(
|
||||
proto::UpdateView {
|
||||
id: item
|
||||
.remote_id(&this.app_state.client, cx)
|
||||
.map(|id| id.to_proto()),
|
||||
variant: pending_update.borrow_mut().take(),
|
||||
leader_id,
|
||||
},
|
||||
),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
});
|
||||
);
|
||||
pending_update_tx.unbounded_send(leader_id).ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,6 +539,7 @@ impl<T: Item> ItemHandle for View<T> {
|
||||
cx.observe_release(self, move |workspace, _, _| {
|
||||
workspace.panes_by_item.remove(&item_id);
|
||||
event_subscription.take();
|
||||
send_follower_updates.take();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -700,6 +715,7 @@ pub trait FollowableItem: Item {
|
||||
|
||||
pub trait FollowableItemHandle: ItemHandle {
|
||||
fn remote_id(&self, client: &Arc<Client>, cx: &WindowContext) -> Option<ViewId>;
|
||||
fn downgrade(&self) -> Box<dyn WeakFollowableItemHandle>;
|
||||
fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
|
||||
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
|
||||
fn add_event_to_update_proto(
|
||||
@@ -728,6 +744,10 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
|
||||
})
|
||||
}
|
||||
|
||||
fn downgrade(&self) -> Box<dyn WeakFollowableItemHandle> {
|
||||
Box::new(self.downgrade())
|
||||
}
|
||||
|
||||
fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext) {
|
||||
self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx))
|
||||
}
|
||||
@@ -767,6 +787,16 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WeakFollowableItemHandle: Send + Sync {
|
||||
fn upgrade(&self) -> Option<Box<dyn FollowableItemHandle>>;
|
||||
}
|
||||
|
||||
impl<T: FollowableItem> WeakFollowableItemHandle for WeakView<T> {
|
||||
fn upgrade(&self) -> Option<Box<dyn FollowableItemHandle>> {
|
||||
Some(Box::new(self.upgrade()?))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod test {
|
||||
use super::{Item, ItemEvent};
|
||||
|
||||
@@ -588,9 +588,9 @@ mod element {
|
||||
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
|
||||
|
||||
use gpui::{
|
||||
px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, InteractiveBounds,
|
||||
IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point,
|
||||
Size, Style, WeakView, WindowContext,
|
||||
px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, IntoElement,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style,
|
||||
WeakView, WindowContext,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use settings::Settings;
|
||||
@@ -754,15 +754,13 @@ mod element {
|
||||
};
|
||||
|
||||
cx.with_z_index(3, |cx| {
|
||||
let interactive_handle_bounds = InteractiveBounds {
|
||||
bounds: handle_bounds,
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
};
|
||||
if interactive_handle_bounds.visibly_contains(&cx.mouse_position(), cx) {
|
||||
cx.set_cursor_style(match axis {
|
||||
if handle_bounds.contains(&cx.mouse_position()) {
|
||||
let stacking_order = cx.stacking_order().clone();
|
||||
let cursor_style = match axis {
|
||||
Axis::Vertical => CursorStyle::ResizeUpDown,
|
||||
Axis::Horizontal => CursorStyle::ResizeLeftRight,
|
||||
})
|
||||
};
|
||||
cx.set_cursor_style(cursor_style, stacking_order);
|
||||
}
|
||||
|
||||
cx.add_opaque_layer(handle_bounds);
|
||||
@@ -885,7 +883,8 @@ mod element {
|
||||
|
||||
let child_size = bounds
|
||||
.size
|
||||
.apply_along(self.axis, |_| space_per_flex * child_flex);
|
||||
.apply_along(self.axis, |_| space_per_flex * child_flex)
|
||||
.map(|d| d.round());
|
||||
|
||||
let child_bounds = Bounds {
|
||||
origin,
|
||||
|
||||
@@ -22,6 +22,16 @@ impl WorkspaceLocation {
|
||||
pub fn paths(&self) -> Arc<Vec<PathBuf>> {
|
||||
self.0.clone()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn new<P: AsRef<Path>>(paths: Vec<P>) -> Self {
|
||||
Self(Arc::new(
|
||||
paths
|
||||
.into_iter()
|
||||
.map(|p| p.as_ref().to_path_buf())
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: AsRef<Path>, T: IntoIterator<Item = P>> From<T> for WorkspaceLocation {
|
||||
|
||||
@@ -121,7 +121,6 @@ actions!(
|
||||
ToggleRightDock,
|
||||
ToggleBottomDock,
|
||||
CloseAllDocks,
|
||||
ToggleGraphicsProfiler,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -1110,7 +1109,7 @@ impl Workspace {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn client(&self) -> &Client {
|
||||
pub fn client(&self) -> &Arc<Client> {
|
||||
&self.app_state.client
|
||||
}
|
||||
|
||||
@@ -3572,7 +3571,6 @@ impl Workspace {
|
||||
workspace.reopen_closed_item(cx).detach();
|
||||
}),
|
||||
)
|
||||
.on_action(|_: &ToggleGraphicsProfiler, cx| cx.toggle_graphics_profiler())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition = "2021"
|
||||
name = "zed"
|
||||
version = "0.125.0"
|
||||
version = "0.125.4"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
stable
|
||||
@@ -22,5 +22,3 @@
|
||||
<string>An application in Zed wants to use speech recognition.</string>
|
||||
<key>NSRemindersUsageDescription</key>
|
||||
<string>An application in Zed wants to use your reminders.</string>
|
||||
<key>MetalHudEnabled</key>
|
||||
<true />
|
||||
|
||||
@@ -37,7 +37,12 @@ pub fn app_menus() -> Vec<Menu<'static>> {
|
||||
MenuItem::action("New Window", workspace::NewWindow),
|
||||
MenuItem::separator(),
|
||||
MenuItem::action("Open…", workspace::Open),
|
||||
MenuItem::action("Open Recent...", recent_projects::OpenRecent),
|
||||
MenuItem::action(
|
||||
"Open Recent...",
|
||||
recent_projects::OpenRecent {
|
||||
create_new_window: true,
|
||||
},
|
||||
),
|
||||
MenuItem::separator(),
|
||||
MenuItem::action("Add Folder to Project…", workspace::AddFolderToProject),
|
||||
MenuItem::action("Save", workspace::Save { save_intent: None }),
|
||||
@@ -156,10 +161,6 @@ pub fn app_menus() -> Vec<Menu<'static>> {
|
||||
MenuItem::action("View Telemetry", crate::OpenTelemetryLog),
|
||||
MenuItem::action("View Dependency Licenses", crate::OpenLicenses),
|
||||
MenuItem::action("Show Welcome", workspace::Welcome),
|
||||
MenuItem::action(
|
||||
"Toggle Graphics Profiler",
|
||||
workspace::ToggleGraphicsProfiler,
|
||||
),
|
||||
MenuItem::separator(),
|
||||
MenuItem::action(
|
||||
"Documentation",
|
||||
|
||||
@@ -807,7 +807,7 @@ async fn upload_previous_crashes(
|
||||
.unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this.
|
||||
let mut uploaded = last_uploaded.clone();
|
||||
|
||||
let crash_report_url = http.build_url("/api/crash");
|
||||
let crash_report_url = http.build_zed_api_url("/telemetry/crashes");
|
||||
|
||||
for dir in [&*CRASHES_DIR, &*CRASHES_RETIRED_DIR] {
|
||||
let mut children = smol::fs::read_dir(&dir).await?;
|
||||
|
||||
@@ -2751,18 +2751,20 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_bundled_languages(cx: &mut AppContext) {
|
||||
let settings = SettingsStore::test(cx);
|
||||
async fn test_bundled_languages(cx: &mut TestAppContext) {
|
||||
let settings = cx.update(|cx| SettingsStore::test(cx));
|
||||
cx.set_global(settings);
|
||||
let mut languages = LanguageRegistry::test();
|
||||
languages.set_executor(cx.background_executor().clone());
|
||||
languages.set_executor(cx.executor().clone());
|
||||
let languages = Arc::new(languages);
|
||||
let node_runtime = node_runtime::FakeNodeRuntime::new();
|
||||
languages::init(languages.clone(), node_runtime, cx);
|
||||
cx.update(|cx| {
|
||||
languages::init(languages.clone(), node_runtime, cx);
|
||||
});
|
||||
for name in languages.language_names() {
|
||||
languages.language_for_name(&name);
|
||||
languages.language_for_name(&name).await.unwrap();
|
||||
}
|
||||
cx.background_executor().run_until_parked();
|
||||
cx.run_until_parked();
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
|
||||
|
||||