Detailed changes
@@ -36,11 +36,11 @@ dependencies = [
[[package]]
name = "addr2line"
-version = "0.20.0"
+version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
+checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
dependencies = [
- "gimli 0.27.3",
+ "gimli 0.27.2",
]
[[package]]
@@ -61,7 +61,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
- "getrandom 0.2.10",
+ "getrandom 0.2.9",
"once_cell",
"version_check",
]
@@ -88,9 +88,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
-version = "1.0.2"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
+checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
dependencies = [
"memchr",
]
@@ -118,7 +118,7 @@ dependencies = [
"settings",
"smol",
"theme",
- "tiktoken-rs 0.4.5",
+ "tiktoken-rs 0.4.2",
"util",
"workspace",
]
@@ -151,7 +151,7 @@ dependencies = [
"alacritty_config",
"alacritty_config_derive",
"base64 0.13.1",
- "bitflags 1.3.2",
+ "bitflags",
"dirs 4.0.0",
"libc",
"log",
@@ -177,12 +177,6 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
-[[package]]
-name = "allocator-api2"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9"
-
[[package]]
name = "alsa"
version = "0.7.0"
@@ -190,7 +184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8512c9117059663fb5606788fbca3619e2a91dac0e3fe516242eab1fa6be5e44"
dependencies = [
"alsa-sys",
- "bitflags 1.3.2",
+ "bitflags",
"libc",
"nix",
]
@@ -211,12 +205,6 @@ version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec8ad6edb4840b78c5c3d88de606b22252d552b55f3a4699fbb10fc070ec3049"
-[[package]]
-name = "android-tzdata"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
-
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -237,7 +225,7 @@ dependencies = [
"anstyle-query",
"anstyle-wincon",
"colorchoice",
- "is-terminal 0.4.9",
+ "is-terminal 0.4.7",
"utf8parse",
]
@@ -262,7 +250,7 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
- "windows-sys",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -272,7 +260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
dependencies = [
"anstyle",
- "windows-sys",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -295,9 +283,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "arrayvec"
-version = "0.7.4"
+version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "ascii"
@@ -318,9 +306,9 @@ dependencies = [
[[package]]
name = "async-channel"
-version = "1.9.0"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
+checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
dependencies = [
"concurrent-queue",
"event-listener",
@@ -336,7 +324,7 @@ dependencies = [
"futures-core",
"futures-io",
"once_cell",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.9",
"tokio",
]
@@ -350,7 +338,7 @@ dependencies = [
"futures-core",
"futures-io",
"memchr",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.9",
]
[[package]]
@@ -374,7 +362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
dependencies = [
"async-lock",
- "autocfg",
+ "autocfg 1.1.0",
"blocking",
"futures-lite",
]
@@ -401,14 +389,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
dependencies = [
"async-lock",
- "autocfg",
+ "autocfg 1.1.0",
"cfg-if 1.0.0",
"concurrent-queue",
"futures-lite",
"log",
"parking",
"polling",
- "rustix 0.37.23",
+ "rustix 0.37.19",
"slab",
"socket2",
"waker-fn",
@@ -430,7 +418,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4051e67316bc7eff608fe723df5d32ed639946adcd69e07df41fd42a7b411f1f"
dependencies = [
"async-io",
- "autocfg",
+ "autocfg 1.1.0",
"blocking",
"futures-lite",
]
@@ -452,14 +440,14 @@ checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9"
dependencies = [
"async-io",
"async-lock",
- "autocfg",
+ "autocfg 1.1.0",
"blocking",
"cfg-if 1.0.0",
"event-listener",
"futures-lite",
- "rustix 0.37.23",
+ "rustix 0.37.19",
"signal-hook",
- "windows-sys",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -481,7 +469,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.25",
+ "syn 2.0.18",
]
[[package]]
@@ -494,7 +482,7 @@ dependencies = [
"async-global-executor",
"async-io",
"async-lock",
- "crossbeam-utils",
+ "crossbeam-utils 0.8.15",
"futures-channel",
"futures-core",
"futures-io",
@@ -504,7 +492,7 @@ dependencies = [
"log",
"memchr",
"once_cell",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.9",
"pin-utils",
"slab",
"wasm-bindgen-futures",
@@ -518,7 +506,7 @@ checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
dependencies = [
"async-stream-impl",
"futures-core",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.9",
]
[[package]]
@@ -529,7 +517,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.25",
+ "syn 2.0.18",
]
[[package]]
@@ -566,13 +554,13 @@ dependencies = [
[[package]]
name = "async-trait"
-version = "0.1.71"
+version = "0.1.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf"
+checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.25",
+ "syn 2.0.18",
]
[[package]]
@@ -585,7 +573,7 @@ dependencies = [
"futures-io",
"futures-util",
"log",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.9",
"tungstenite 0.16.0",
]
@@ -600,9 +588,12 @@ dependencies = [
[[package]]
name = "atomic"
-version = "0.5.3"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
+checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c"
+dependencies = [
+ "autocfg 1.1.0",
+]
[[package]]
name = "atomic-waker"
@@ -658,6 +649,15 @@ dependencies = [
"workspace",
]
+[[package]]
+name = "autocfg"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
+dependencies = [
+ "autocfg 1.1.0",
+]
+
[[package]]
name = "autocfg"
version = "1.1.0"
@@ -673,19 +673,19 @@ dependencies = [
"async-trait",
"axum-core",
"base64 0.13.1",
- "bitflags 1.3.2",
+ "bitflags",
"bytes 1.4.0",
"futures-util",
"headers",
"http",
"http-body",
"hyper",
- "itoa 1.0.8",
+ "itoa 1.0.6",
"matchit",
"memchr",
"mime",
"percent-encoding",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.9",
"serde",
"serde_json",
"serde_urlencoded",
@@ -726,7 +726,7 @@ dependencies = [
"futures-util",
"http",
"mime",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.9",
"serde",
"serde_json",
"tokio",
@@ -738,16 +738,16 @@ dependencies = [
[[package]]
name = "backtrace"
-version = "0.3.68"
+version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
+checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
dependencies = [
- "addr2line 0.20.0",
+ "addr2line 0.19.0",
"cc",
"cfg-if 1.0.0",
"libc",
- "miniz_oxide 0.7.1",
- "object 0.31.1",
+ "miniz_oxide 0.6.2",
+ "object 0.30.3",
"rustc-demangle",
]
@@ -797,7 +797,7 @@ version = "0.64.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"cexpr",
"clang-sys",
"lazy_static",
@@ -817,7 +817,7 @@ version = "0.65.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"cexpr",
"clang-sys",
"lazy_static",
@@ -830,7 +830,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
- "syn 2.0.25",
+ "syn 2.0.18",
"which",
]
@@ -855,24 +855,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-[[package]]
-name = "bitflags"
-version = "2.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
-
-[[package]]
-name = "bitvec"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
-dependencies = [
- "funty",
- "radium",
- "tap",
- "wyz",
-]
-
[[package]]
name = "block"
version = "0.1.6"
@@ -998,15 +980,15 @@ dependencies = [
[[package]]
name = "bumpalo"
-version = "3.13.0"
+version = "3.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
+checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b"
[[package]]
name = "bytecheck"
-version = "0.6.11"
+version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627"
+checksum = "13fe11640a23eb24562225322cd3e452b93a3d4091d62fab69c70542fcd17d1f"
dependencies = [
"bytecheck_derive",
"ptr_meta",
@@ -1015,9 +997,9 @@ dependencies = [
[[package]]
name = "bytecheck_derive"
-version = "0.6.11"
+version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61"
+checksum = "e31225543cb46f81a7e224762764f4a6a0f097b1db0b175f69e8065efaa42de5"
dependencies = [
"proc-macro2",
"quote",
@@ -1184,13 +1166,13 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
-version = "0.4.26"
+version = "0.4.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
+checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
dependencies = [
- "android-tzdata",
"iana-time-zone",
"js-sys",
+ "num-integer",
"num-traits",
"serde",
"time 0.1.45",
@@ -1221,7 +1203,7 @@ checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
dependencies = [
"glob",
"libc",
- "libloading 0.7.4",
+ "libloading",
]
[[package]]
@@ -1231,7 +1213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
dependencies = [
"atty",
- "bitflags 1.3.2",
+ "bitflags",
"clap_derive 3.2.25",
"clap_lex 0.2.4",
"indexmap 1.9.3",
@@ -1243,9 +1225,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.3.11"
+version = "4.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d"
+checksum = "2686c4115cb0810d9a984776e197823d08ec94f176549a89a9efded477c456dc"
dependencies = [
"clap_builder",
"clap_derive 4.3.2",
@@ -1254,12 +1236,13 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.3.11"
+version = "4.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b"
+checksum = "2e53afce1efce6ed1f633cf0e57612fe51db54a1ee4fd8f8503d078fe02d69ae"
dependencies = [
"anstream",
"anstyle",
+ "bitflags",
"clap_lex 0.5.0",
"strsim",
]
@@ -1286,7 +1269,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
- "syn 2.0.25",
+ "syn 2.0.18",
]
[[package]]
@@ -1353,11 +1336,11 @@ dependencies = [
"sum_tree",
"tempfile",
"thiserror",
- "time 0.3.23",
+ "time 0.3.21",
"tiny_http",
"url",
"util",
- "uuid 1.4.0",
+ "uuid 1.3.2",
]
[[package]]
@@ -1381,7 +1364,7 @@ name = "cocoa"
version = "0.24.0"
source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"block",
"cocoa-foundation",
"core-foundation",
@@ -1396,7 +1379,7 @@ name = "cocoa-foundation"
version = "0.1.1"
source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"block",
"core-foundation",
"core-graphics-types",
@@ -1405,6 +1388,16 @@ dependencies = [
"objc",
]
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
[[package]]
name = "collab"
version = "0.16.0"
@@ -1455,7 +1448,7 @@ dependencies = [
"sha-1 0.9.8",
"sqlx",
"theme",
- "time 0.3.23",
+ "time 0.3.21",
"tokio",
"tokio-tungstenite",
"toml",
@@ -1557,7 +1550,7 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
dependencies = [
- "crossbeam-utils",
+ "crossbeam-utils 0.8.15",
]
[[package]]
@@ -1648,7 +1641,7 @@ name = "core-graphics"
version = "0.22.3"
source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"core-foundation",
"core-graphics-types",
"foreign-types",
@@ -1660,7 +1653,7 @@ name = "core-graphics-types"
version = "0.1.1"
source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"core-foundation",
"foreign-types",
"libc",
@@ -1693,7 +1686,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb17e2d1795b1996419648915df94bc7103c28f7b48062d7acf4652fc371b2ff"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"core-foundation-sys 0.6.2",
"coreaudio-sys",
]
@@ -1743,9 +1736,9 @@ dependencies = [
[[package]]
name = "cpufeatures"
-version = "0.2.9"
+version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
+checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
dependencies = [
"libc",
]
@@ -1870,6 +1863,16 @@ dependencies = [
"cfg-if 1.0.0",
]
+[[package]]
+name = "crossbeam-channel"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
+dependencies = [
+ "crossbeam-utils 0.7.2",
+ "maybe-uninit",
+]
+
[[package]]
name = "crossbeam-channel"
version = "0.5.8"
@@ -1877,7 +1880,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
dependencies = [
"cfg-if 1.0.0",
- "crossbeam-utils",
+ "crossbeam-utils 0.8.15",
]
[[package]]
@@ -1888,19 +1891,19 @@ checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-epoch",
- "crossbeam-utils",
+ "crossbeam-utils 0.8.15",
]
[[package]]
name = "crossbeam-epoch"
-version = "0.9.15"
+version = "0.9.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
+checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
dependencies = [
- "autocfg",
+ "autocfg 1.1.0",
"cfg-if 1.0.0",
- "crossbeam-utils",
- "memoffset 0.9.0",
+ "crossbeam-utils 0.8.15",
+ "memoffset 0.8.0",
"scopeguard",
]
@@ -1911,14 +1914,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
dependencies = [
"cfg-if 1.0.0",
- "crossbeam-utils",
+ "crossbeam-utils 0.8.15",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
+dependencies = [
+ "autocfg 1.1.0",
+ "cfg-if 0.1.10",
+ "lazy_static",
]
[[package]]
name = "crossbeam-utils"
-version = "0.8.16"
+version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
dependencies = [
"cfg-if 1.0.0",
]
@@ -1970,9 +1984,9 @@ dependencies = [
[[package]]
name = "curl-sys"
-version = "0.4.63+curl-8.1.2"
+version = "0.4.61+curl-8.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aeb0fef7046022a1e2ad67a004978f0e3cacb9e3123dc62ce768f92197b771dc"
+checksum = "14d05c10f541ae6f3bc5b3d923c20001f47db7d5f0b2bc6ad16490133842db79"
dependencies = [
"cc",
"libc",
@@ -1983,17 +1997,61 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "cxx"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
[[package]]
name = "dashmap"
-version = "5.5.0"
+version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d"
+checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
dependencies = [
"cfg-if 1.0.0",
- "hashbrown 0.14.0",
+ "hashbrown 0.12.3",
"lock_api",
"once_cell",
- "parking_lot_core 0.9.8",
+ "parking_lot_core 0.9.7",
]
[[package]]
@@ -2098,9 +2156,9 @@ dependencies = [
[[package]]
name = "digest"
-version = "0.10.7"
+version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [
"block-buffer 0.10.4",
"crypto-common",
@@ -2169,11 +2227,11 @@ dependencies = [
[[package]]
name = "dlib"
-version = "0.5.2"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
+checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794"
dependencies = [
- "libloading 0.8.0",
+ "libloading",
]
[[package]]
@@ -2295,7 +2353,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
dependencies = [
"humantime",
- "is-terminal 0.4.9",
+ "is-terminal 0.4.7",
"log",
"regex",
"termcolor",
@@ -2312,15 +2370,15 @@ dependencies = [
[[package]]
name = "equivalent"
-version = "1.0.1"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
[[package]]
name = "erased-serde"
-version = "0.3.27"
+version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f94c0e13118e7d7533271f754a168ae8400e6a1cc043f2bfd53cc7290f1a1de3"
+checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569"
dependencies = [
"serde",
]
@@ -2344,7 +2402,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"errno-dragonfly",
"libc",
- "windows-sys",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -2359,9 +2417,9 @@ dependencies = [
[[package]]
name = "etagere"
-version = "0.2.8"
+version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcf22f748754352918e082e0039335ee92454a5d62bcaf69b5e8daf5907d9644"
+checksum = "6301151a318f367f392c31395beb1cfba5ccd9abc44d1db0db3a4b27b9601c89"
dependencies = [
"euclid",
"svg_fmt",
@@ -2484,7 +2542,7 @@ dependencies = [
"cfg-if 1.0.0",
"libc",
"redox_syscall 0.2.16",
- "windows-sys",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -2538,7 +2596,7 @@ name = "font-kit"
version = "0.11.0"
source = "git+https://github.com/zed-industries/font-kit?rev=b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18#b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"byteorder",
"core-foundation",
"core-graphics",
@@ -2585,9 +2643,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
-version = "1.2.0"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [
"percent-encoding",
]
@@ -2638,7 +2696,7 @@ dependencies = [
"smol",
"sum_tree",
"tempfile",
- "time 0.3.23",
+ "time 0.3.21",
"util",
]
@@ -2657,7 +2715,7 @@ dependencies = [
name = "fsevent"
version = "2.0.2"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"fsevent-sys",
"parking_lot 0.11.2",
"tempdir",
@@ -2684,7 +2742,7 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"fuchsia-zircon-sys",
]
@@ -2694,12 +2752,6 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
-[[package]]
-name = "funty"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
-
[[package]]
name = "futures"
version = "0.1.31"
@@ -2776,7 +2828,7 @@ dependencies = [
"futures-io",
"memchr",
"parking",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.9",
"waker-fn",
]
@@ -2788,7 +2840,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.25",
+ "syn 2.0.18",
]
[[package]]
@@ -2817,7 +2869,7 @@ dependencies = [
"futures-sink",
"futures-task",
"memchr",
- "pin-project-lite 0.2.10",
+ "pin-project-lite 0.2.9",
"pin-utils",
"slab",
"tokio-io",
@@ -2863,9 +2915,9 @@ dependencies = [
[[package]]
name = "getrandom"
-version = "0.2.10"
+version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
dependencies = [
"cfg-if 1.0.0",
"libc",
@@ -2895,9 +2947,9 @@ dependencies = [
[[package]]
name = "gimli"
-version = "0.27.3"
+version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
+checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
[[package]]
name = "git"
@@ -2925,7 +2977,7 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"libc",
"libgit2-sys",
"log",
@@ -2940,11 +2992,11 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globset"
-version = "0.4.11"
+version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1391ab1f92ffcc08911957149833e682aa3fe252b9f45f966d2ef972274c97df"
+checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
dependencies = [
- "aho-corasick 1.0.2",
+ "aho-corasick 0.7.20",
"bstr",
"fnv",
"log",
@@ -107,6 +107,7 @@ tree-sitter = "0.20"
unindent = { version = "0.1.7" }
pretty_assertions = "1.3.0"
+tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "1b0321ee85701d5036c334a6f04761cdc672e64c" }
tree-sitter-c = "0.20.1"
tree-sitter-cpp = "0.20.0"
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
@@ -117,6 +118,7 @@ tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
tree-sitter-rust = "0.20.3"
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
+tree-sitter-php = { git = "https://github.com/tree-sitter/tree-sitter-php", rev = "d43130fd1525301e9826f420c5393a4d169819fc" }
tree-sitter-python = "0.20.2"
tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" }
tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" }
@@ -101,6 +101,10 @@
"vim::SwitchMode",
"Normal"
],
+ "ctrl+[": [
+ "vim::SwitchMode",
+ "Normal"
+ ],
"*": "vim::MoveToNext",
"#": "vim::MoveToPrev",
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
@@ -239,10 +243,6 @@
"h": "editor::Hover",
"t": "pane::ActivateNextItem",
"shift-t": "pane::ActivatePrevItem",
- "escape": [
- "vim::SwitchMode",
- "Normal"
- ],
"d": "editor::GoToDefinition",
"shift-d": "editor::GoToTypeDefinition",
"*": [
@@ -283,10 +283,6 @@
"t": "editor::ScrollCursorTop",
"z": "editor::ScrollCursorCenter",
"b": "editor::ScrollCursorBottom",
- "escape": [
- "vim::SwitchMode",
- "Normal"
- ]
}
},
{
@@ -340,7 +336,8 @@
"context": "Editor && vim_mode == insert",
"bindings": {
"escape": "vim::NormalBefore",
- "ctrl-c": "vim::NormalBefore"
+ "ctrl-c": "vim::NormalBefore",
+ "ctrl-[": "vim::NormalBefore",
}
},
{
@@ -351,6 +348,10 @@
"escape": [
"vim::SwitchMode",
"Normal"
+ ],
+ "ctrl+[": [
+ "vim::SwitchMode",
+ "Normal"
]
}
},
@@ -128,6 +128,13 @@
// 4. Save when idle for a certain amount of time:
// "autosave": { "after_delay": {"milliseconds": 500} },
"autosave": "off",
+ // Settings related to the editor's tabs
+ "tabs": {
+ // Show git status colors in the editor tabs.
+ "git_status": false,
+ // Position of the close button on the editor tabs.
+ "close_position": "right"
+ },
// Whether or not to remove any trailing whitespace from lines of a buffer
// before saving it.
"remove_trailing_whitespace_on_save": true,
@@ -40,6 +40,7 @@ lazy_static! {
struct ClickhouseEventRequestBody {
token: &'static str,
installation_id: Option<Arc<str>>,
+ is_staff: Option<bool>,
app_version: Option<Arc<str>>,
os_name: &'static str,
os_version: Option<Arc<str>>,
@@ -224,6 +225,7 @@ impl Telemetry {
&ClickhouseEventRequestBody {
token: ZED_SECRET_CLIENT_TOKEN,
installation_id: state.installation_id.clone(),
+ is_staff: state.is_staff.clone(),
app_version: state.app_version.clone(),
os_name: state.os_name,
os_version: state.os_version.clone(),
@@ -2672,11 +2672,16 @@ impl Editor {
InlayRefreshReason::RefreshRequested => (InvalidationStrategy::RefreshRequested, None),
};
- self.inlay_hint_cache.refresh_inlay_hints(
+ if let Some(InlaySplice {
+ to_remove,
+ to_insert,
+ }) = self.inlay_hint_cache.spawn_hint_refresh(
self.excerpt_visible_offsets(required_languages.as_ref(), cx),
invalidate_cache,
cx,
- )
+ ) {
+ self.splice_inlay_hints(to_remove, to_insert, cx);
+ }
}
fn visible_inlay_hints(&self, cx: &ViewContext<'_, '_, Editor>) -> Vec<Inlay> {
@@ -198,7 +198,7 @@ fn show_hover(
// Construct new hover popover from hover request
let hover_popover = hover_request.await.ok().flatten().and_then(|hover_result| {
- if hover_result.contents.is_empty() {
+ if hover_result.is_empty() {
return None;
}
@@ -420,7 +420,7 @@ fn render_blocks(
RenderedInfo {
theme_id,
- text,
+ text: text.trim().to_string(),
highlights,
region_ranges,
regions,
@@ -816,6 +816,118 @@ mod tests {
});
}
+ #[gpui::test]
+ async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorLspTestContext::new_rust(
+ lsp::ServerCapabilities {
+ hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
+ ..Default::default()
+ },
+ cx,
+ )
+ .await;
+
+ // Hover with keyboard has no delay
+ cx.set_state(indoc! {"
+ fหn test() { println!(); }
+ "});
+ cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
+ let symbol_range = cx.lsp_range(indoc! {"
+ ยซfnยป test() { println!(); }
+ "});
+ cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
+ Ok(Some(lsp::Hover {
+ contents: lsp::HoverContents::Array(vec![
+ lsp::MarkedString::String("regular text for hover to show".to_string()),
+ lsp::MarkedString::String("".to_string()),
+ lsp::MarkedString::LanguageString(lsp::LanguageString {
+ language: "Rust".to_string(),
+ value: "".to_string(),
+ }),
+ ]),
+ range: Some(symbol_range),
+ }))
+ })
+ .next()
+ .await;
+
+ cx.condition(|editor, _| editor.hover_state.visible()).await;
+ cx.editor(|editor, _| {
+ assert_eq!(
+ editor.hover_state.info_popover.clone().unwrap().blocks,
+ vec![HoverBlock {
+ text: "regular text for hover to show".to_string(),
+ kind: HoverBlockKind::Markdown,
+ }],
+ "No empty string hovers should be shown"
+ );
+ });
+ }
+
+ #[gpui::test]
+ async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorLspTestContext::new_rust(
+ lsp::ServerCapabilities {
+ hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
+ ..Default::default()
+ },
+ cx,
+ )
+ .await;
+
+ // Hover with keyboard has no delay
+ cx.set_state(indoc! {"
+ fหn test() { println!(); }
+ "});
+ cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
+ let symbol_range = cx.lsp_range(indoc! {"
+ ยซfnยป test() { println!(); }
+ "});
+
+ let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n";
+ let markdown_string = format!("\n```rust\n{code_str}```");
+
+ let closure_markdown_string = markdown_string.clone();
+ cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| {
+ let future_markdown_string = closure_markdown_string.clone();
+ async move {
+ Ok(Some(lsp::Hover {
+ contents: lsp::HoverContents::Markup(lsp::MarkupContent {
+ kind: lsp::MarkupKind::Markdown,
+ value: future_markdown_string,
+ }),
+ range: Some(symbol_range),
+ }))
+ }
+ })
+ .next()
+ .await;
+
+ cx.condition(|editor, _| editor.hover_state.visible()).await;
+ cx.editor(|editor, cx| {
+ let blocks = editor.hover_state.info_popover.clone().unwrap().blocks;
+ assert_eq!(
+ blocks,
+ vec![HoverBlock {
+ text: markdown_string,
+ kind: HoverBlockKind::Markdown,
+ }],
+ );
+
+ let style = editor.style(cx);
+ let rendered = render_blocks(0, &blocks, &Default::default(), None, &style);
+ assert_eq!(
+ rendered.text,
+ code_str.trim(),
+ "Should not have extra line breaks at end of rendered hover"
+ );
+ });
+ }
+
#[gpui::test]
async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -195,20 +195,41 @@ impl InlayHintCache {
}
}
- pub fn refresh_inlay_hints(
+ pub fn spawn_hint_refresh(
&mut self,
mut excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)>,
invalidate: InvalidationStrategy,
cx: &mut ViewContext<Editor>,
- ) {
- if !self.enabled || excerpts_to_query.is_empty() {
- return;
+ ) -> Option<InlaySplice> {
+ if !self.enabled {
+ return None;
}
+
let update_tasks = &mut self.update_tasks;
+ let mut invalidated_hints = Vec::new();
if invalidate.should_invalidate() {
- update_tasks
- .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id));
+ let mut changed = false;
+ update_tasks.retain(|task_excerpt_id, _| {
+ let retain = excerpts_to_query.contains_key(task_excerpt_id);
+ changed |= !retain;
+ retain
+ });
+ self.hints.retain(|cached_excerpt, cached_hints| {
+ let retain = excerpts_to_query.contains_key(cached_excerpt);
+ changed |= !retain;
+ if !retain {
+ invalidated_hints.extend(cached_hints.read().hints.iter().map(|&(id, _)| id));
+ }
+ retain
+ });
+ if changed {
+ self.version += 1;
+ }
}
+ if excerpts_to_query.is_empty() && invalidated_hints.is_empty() {
+ return None;
+ }
+
let cache_version = self.version;
excerpts_to_query.retain(|visible_excerpt_id, _| {
match update_tasks.entry(*visible_excerpt_id) {
@@ -229,6 +250,15 @@ impl InlayHintCache {
.ok();
})
.detach();
+
+ if invalidated_hints.is_empty() {
+ None
+ } else {
+ Some(InlaySplice {
+ to_remove: invalidated_hints,
+ to_insert: Vec::new(),
+ })
+ }
}
fn new_allowed_hint_kinds_splice(
@@ -684,7 +714,7 @@ async fn fetch_and_update_hints(
if query.invalidate.should_invalidate() {
let mut outdated_excerpt_caches = HashSet::default();
- for (excerpt_id, excerpt_hints) in editor.inlay_hint_cache().hints.iter() {
+ for (excerpt_id, excerpt_hints) in &editor.inlay_hint_cache().hints {
let excerpt_hints = excerpt_hints.read();
if excerpt_hints.buffer_id == query.buffer_id
&& excerpt_id != &query.excerpt_id
@@ -1022,9 +1052,9 @@ mod tests {
"Should get its first hints when opening the editor"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, edits_made,
+ editor.inlay_hint_cache().version,
+ edits_made,
"The editor update the cache version after every cache/view change"
);
});
@@ -1053,9 +1083,9 @@ mod tests {
"Should not update hints while the work task is running"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, edits_made,
+ editor.inlay_hint_cache().version,
+ edits_made,
"Should not update the cache while the work task is running"
);
});
@@ -1077,9 +1107,9 @@ mod tests {
"New hints should be queried after the work task is done"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, edits_made,
+ editor.inlay_hint_cache().version,
+ edits_made,
"Cache version should udpate once after the work task is done"
);
});
@@ -1194,9 +1224,9 @@ mod tests {
"Should get its first hints when opening the editor"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, 1,
+ editor.inlay_hint_cache().version,
+ 1,
"Rust editor update the cache version after every cache/view change"
);
});
@@ -1252,8 +1282,7 @@ mod tests {
"Markdown editor should have a separate verison, repeating Rust editor rules"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 1);
+ assert_eq!(editor.inlay_hint_cache().version, 1);
});
rs_editor.update(cx, |editor, cx| {
@@ -1269,9 +1298,9 @@ mod tests {
"Rust inlay cache should change after the edit"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, 2,
+ editor.inlay_hint_cache().version,
+ 2,
"Every time hint cache changes, cache version should be incremented"
);
});
@@ -1283,8 +1312,7 @@ mod tests {
"Markdown editor should not be affected by Rust editor changes"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 1);
+ assert_eq!(editor.inlay_hint_cache().version, 1);
});
md_editor.update(cx, |editor, cx| {
@@ -1300,8 +1328,7 @@ mod tests {
"Rust editor should not be affected by Markdown editor changes"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 2);
+ assert_eq!(editor.inlay_hint_cache().version, 2);
});
rs_editor.update(cx, |editor, cx| {
let expected_layers = vec!["1".to_string()];
@@ -1311,8 +1338,7 @@ mod tests {
"Markdown editor should also change independently"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 2);
+ assert_eq!(editor.inlay_hint_cache().version, 2);
});
}
@@ -1433,9 +1459,9 @@ mod tests {
vec!["other hint".to_string(), "type hint".to_string()],
visible_hint_labels(editor, cx)
);
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, edits_made,
+ editor.inlay_hint_cache().version,
+ edits_made,
"Should not update cache version due to new loaded hints being the same"
);
});
@@ -1568,9 +1594,8 @@ mod tests {
);
assert!(cached_hint_labels(editor).is_empty());
assert!(visible_hint_labels(editor, cx).is_empty());
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, edits_made,
+ editor.inlay_hint_cache().version, edits_made,
"The editor should not update the cache version after /refresh query without updates"
);
});
@@ -1641,8 +1666,7 @@ mod tests {
vec!["parameter hint".to_string()],
visible_hint_labels(editor, cx),
);
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, edits_made);
+ assert_eq!(editor.inlay_hint_cache().version, edits_made);
});
}
@@ -1720,9 +1744,8 @@ mod tests {
"Should get hints from the last edit landed only"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, 1,
+ editor.inlay_hint_cache().version, 1,
"Only one update should be registered in the cache after all cancellations"
);
});
@@ -1766,9 +1789,9 @@ mod tests {
"Should get hints from the last edit landed only"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, 2,
+ editor.inlay_hint_cache().version,
+ 2,
"Should update the cache version once more, for the new change"
);
});
@@ -1886,9 +1909,8 @@ mod tests {
"Should have hints from both LSP requests made for a big file"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, 2,
+ editor.inlay_hint_cache().version, 2,
"Both LSP queries should've bumped the cache version"
);
});
@@ -1918,8 +1940,7 @@ mod tests {
assert_eq!(expected_layers, cached_hint_labels(editor),
"Should have hints from the new LSP response after edit");
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 5, "Should update the cache for every LSP response with hints added");
+ assert_eq!(editor.inlay_hint_cache().version, 5, "Should update the cache for every LSP response with hints added");
});
}
@@ -2075,6 +2096,7 @@ mod tests {
panic!("unexpected uri: {:?}", params.text_document.uri);
};
+ // one hint per excerpt
let positions = [
lsp::Position::new(0, 2),
lsp::Position::new(4, 2),
@@ -2138,8 +2160,7 @@ mod tests {
"When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 4, "Every visible excerpt hints should bump the verison");
+ assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(), "Every visible excerpt hints should bump the verison");
});
editor.update(cx, |editor, cx| {
@@ -2169,8 +2190,8 @@ mod tests {
assert_eq!(expected_layers, cached_hint_labels(editor),
"With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 9);
+ assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(),
+ "Due to every excerpt having one hint, we update cache per new excerpt scrolled");
});
editor.update(cx, |editor, cx| {
@@ -2179,7 +2200,7 @@ mod tests {
});
});
cx.foreground().run_until_parked();
- editor.update(cx, |editor, cx| {
+ let last_scroll_update_version = editor.update(cx, |editor, cx| {
let expected_layers = vec![
"main hint #0".to_string(),
"main hint #1".to_string(),
@@ -2197,8 +2218,8 @@ mod tests {
assert_eq!(expected_layers, cached_hint_labels(editor),
"After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 12);
+ assert_eq!(editor.inlay_hint_cache().version, expected_layers.len());
+ expected_layers.len()
});
editor.update(cx, |editor, cx| {
@@ -2225,12 +2246,14 @@ mod tests {
assert_eq!(expected_layers, cached_hint_labels(editor),
"After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 12, "No updates should happen during scrolling already scolled buffer");
+ assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
});
editor_edited.store(true, Ordering::Release);
editor.update(cx, |editor, cx| {
+ editor.change_selections(None, cx, |s| {
+ s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
+ });
editor.handle_input("++++more text++++", cx);
});
cx.foreground().run_until_parked();
@@ -2240,19 +2263,253 @@ mod tests {
"main hint(edited) #1".to_string(),
"main hint(edited) #2".to_string(),
"main hint(edited) #3".to_string(),
- "other hint #0".to_string(),
- "other hint #1".to_string(),
- "other hint #2".to_string(),
- "other hint #3".to_string(),
- "other hint #4".to_string(),
- "other hint #5".to_string(),
+ "main hint(edited) #4".to_string(),
+ "main hint(edited) #5".to_string(),
+ "other hint(edited) #0".to_string(),
+ "other hint(edited) #1".to_string(),
];
- assert_eq!(expected_layers, cached_hint_labels(editor),
- "After multibuffer was edited, hints for the edited buffer (1st) should be invalidated and requeried for all of its visible excerpts, \
-unedited (2nd) buffer should have the same hint");
+ assert_eq!(
+ expected_layers,
+ cached_hint_labels(editor),
+ "After multibuffer edit, editor gets scolled back to the last selection; \
+all hints should be invalidated and requeried for all of its visible excerpts"
+ );
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 16);
+ assert_eq!(
+ editor.inlay_hint_cache().version,
+ last_scroll_update_version + expected_layers.len() + 1,
+ "Due to every excerpt having one hint, cache should update per new excerpt received + 1 for outdated hints removal"
+ );
+ });
+ }
+
+ #[gpui::test]
+ async fn test_excerpts_removed(
+ deterministic: Arc<Deterministic>,
+ cx: &mut gpui::TestAppContext,
+ ) {
+ init_test(cx, |settings| {
+ settings.defaults.inlay_hints = Some(InlayHintSettings {
+ enabled: true,
+ show_type_hints: false,
+ show_parameter_hints: false,
+ show_other_hints: false,
+ })
+ });
+
+ let mut language = Language::new(
+ LanguageConfig {
+ name: "Rust".into(),
+ path_suffixes: vec!["rs".to_string()],
+ ..Default::default()
+ },
+ Some(tree_sitter_rust::language()),
+ );
+ let mut fake_servers = language
+ .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+ capabilities: lsp::ServerCapabilities {
+ inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+ ..Default::default()
+ },
+ ..Default::default()
+ }))
+ .await;
+ let language = Arc::new(language);
+ let fs = FakeFs::new(cx.background());
+ fs.insert_tree(
+ "/a",
+ json!({
+ "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
+ "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
+ }),
+ )
+ .await;
+ let project = Project::test(fs, ["/a".as_ref()], cx).await;
+ project.update(cx, |project, _| {
+ project.languages().add(Arc::clone(&language))
+ });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let worktree_id = workspace.update(cx, |workspace, cx| {
+ workspace.project().read_with(cx, |project, cx| {
+ project.worktrees(cx).next().unwrap().read(cx).id()
+ })
+ });
+
+ let buffer_1 = project
+ .update(cx, |project, cx| {
+ project.open_buffer((worktree_id, "main.rs"), cx)
+ })
+ .await
+ .unwrap();
+ let buffer_2 = project
+ .update(cx, |project, cx| {
+ project.open_buffer((worktree_id, "other.rs"), cx)
+ })
+ .await
+ .unwrap();
+ let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
+ let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
+ let buffer_1_excerpts = multibuffer.push_excerpts(
+ buffer_1.clone(),
+ [ExcerptRange {
+ context: Point::new(0, 0)..Point::new(2, 0),
+ primary: None,
+ }],
+ cx,
+ );
+ let buffer_2_excerpts = multibuffer.push_excerpts(
+ buffer_2.clone(),
+ [ExcerptRange {
+ context: Point::new(0, 1)..Point::new(2, 1),
+ primary: None,
+ }],
+ cx,
+ );
+ (buffer_1_excerpts, buffer_2_excerpts)
+ });
+
+ assert!(!buffer_1_excerpts.is_empty());
+ assert!(!buffer_2_excerpts.is_empty());
+
+ deterministic.run_until_parked();
+ cx.foreground().run_until_parked();
+ let (_, editor) =
+ cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
+ let editor_edited = Arc::new(AtomicBool::new(false));
+ let fake_server = fake_servers.next().await.unwrap();
+ let closure_editor_edited = Arc::clone(&editor_edited);
+ fake_server
+ .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
+ let task_editor_edited = Arc::clone(&closure_editor_edited);
+ async move {
+ let hint_text = if params.text_document.uri
+ == lsp::Url::from_file_path("/a/main.rs").unwrap()
+ {
+ "main hint"
+ } else if params.text_document.uri
+ == lsp::Url::from_file_path("/a/other.rs").unwrap()
+ {
+ "other hint"
+ } else {
+ panic!("unexpected uri: {:?}", params.text_document.uri);
+ };
+
+ let positions = [
+ lsp::Position::new(0, 2),
+ lsp::Position::new(4, 2),
+ lsp::Position::new(22, 2),
+ lsp::Position::new(44, 2),
+ lsp::Position::new(56, 2),
+ lsp::Position::new(67, 2),
+ ];
+ let out_of_range_hint = lsp::InlayHint {
+ position: lsp::Position::new(
+ params.range.start.line + 99,
+ params.range.start.character + 99,
+ ),
+ label: lsp::InlayHintLabel::String(
+ "out of excerpt range, should be ignored".to_string(),
+ ),
+ kind: None,
+ text_edits: None,
+ tooltip: None,
+ padding_left: None,
+ padding_right: None,
+ data: None,
+ };
+
+ let edited = task_editor_edited.load(Ordering::Acquire);
+ Ok(Some(
+ std::iter::once(out_of_range_hint)
+ .chain(positions.into_iter().enumerate().map(|(i, position)| {
+ lsp::InlayHint {
+ position,
+ label: lsp::InlayHintLabel::String(format!(
+ "{hint_text}{} #{i}",
+ if edited { "(edited)" } else { "" },
+ )),
+ kind: None,
+ text_edits: None,
+ tooltip: None,
+ padding_left: None,
+ padding_right: None,
+ data: None,
+ }
+ }))
+ .collect(),
+ ))
+ }
+ })
+ .next()
+ .await;
+ cx.foreground().run_until_parked();
+
+ editor.update(cx, |editor, cx| {
+ assert_eq!(
+ vec!["main hint #0".to_string(), "other hint #0".to_string()],
+ cached_hint_labels(editor),
+ "Cache should update for both excerpts despite hints display was disabled"
+ );
+ assert!(
+ visible_hint_labels(editor, cx).is_empty(),
+ "All hints are disabled and should not be shown despite being present in the cache"
+ );
+ assert_eq!(
+ editor.inlay_hint_cache().version,
+ 2,
+ "Cache should update once per excerpt query"
+ );
+ });
+
+ editor.update(cx, |editor, cx| {
+ editor.buffer().update(cx, |multibuffer, cx| {
+ multibuffer.remove_excerpts(buffer_2_excerpts, cx)
+ })
+ });
+ cx.foreground().run_until_parked();
+ editor.update(cx, |editor, cx| {
+ assert_eq!(
+ vec!["main hint #0".to_string()],
+ cached_hint_labels(editor),
+ "For the removed excerpt, should clean corresponding cached hints"
+ );
+ assert!(
+ visible_hint_labels(editor, cx).is_empty(),
+ "All hints are disabled and should not be shown despite being present in the cache"
+ );
+ assert_eq!(
+ editor.inlay_hint_cache().version,
+ 3,
+ "Excerpt removal should trigger cache update"
+ );
+ });
+
+ update_test_language_settings(cx, |settings| {
+ settings.defaults.inlay_hints = Some(InlayHintSettings {
+ enabled: true,
+ show_type_hints: true,
+ show_parameter_hints: true,
+ show_other_hints: true,
+ })
+ });
+ cx.foreground().run_until_parked();
+ editor.update(cx, |editor, cx| {
+ let expected_hints = vec!["main hint #0".to_string()];
+ assert_eq!(
+ expected_hints,
+ cached_hint_labels(editor),
+ "Hint display settings change should not change the cache"
+ );
+ assert_eq!(
+ expected_hints,
+ visible_hint_labels(editor, cx),
+ "Settings change should make cached hints visible"
+ );
+ assert_eq!(
+ editor.inlay_hint_cache().version,
+ 4,
+ "Settings change should trigger cache update"
+ );
});
}
@@ -60,6 +60,7 @@ pub(crate) struct FeedbackEditor {
system_specs: SystemSpecs,
editor: ViewHandle<Editor>,
project: ModelHandle<Project>,
+ pub allow_submission: bool,
}
impl FeedbackEditor {
@@ -82,10 +83,15 @@ impl FeedbackEditor {
system_specs: system_specs.clone(),
editor,
project,
+ allow_submission: true,
}
}
pub fn submit(&mut self, cx: &mut ViewContext<Self>) -> Task<anyhow::Result<()>> {
+ if !self.allow_submission {
+ return Task::ready(Ok(()));
+ }
+
let feedback_text = self.editor.read(cx).text(cx);
let feedback_char_count = feedback_text.chars().count();
let feedback_text = feedback_text.trim().to_string();
@@ -122,19 +128,26 @@ impl FeedbackEditor {
let answer = answer.recv().await;
if answer == Some(0) {
+ this.update(&mut cx, |feedback_editor, cx| {
+ feedback_editor.set_allow_submission(false, cx);
+ })
+ .log_err();
+
match FeedbackEditor::submit_feedback(&feedback_text, client, specs).await {
Ok(_) => {
this.update(&mut cx, |_, cx| cx.emit(editor::Event::Closed))
.log_err();
}
+
Err(error) => {
log::error!("{}", error);
- this.update(&mut cx, |_, cx| {
+ this.update(&mut cx, |feedback_editor, cx| {
cx.prompt(
PromptLevel::Critical,
FEEDBACK_SUBMISSION_ERROR_TEXT,
&["OK"],
);
+ feedback_editor.set_allow_submission(true, cx);
})
.log_err();
}
@@ -146,6 +159,11 @@ impl FeedbackEditor {
Task::ready(Ok(()))
}
+ fn set_allow_submission(&mut self, allow_submission: bool, cx: &mut ViewContext<Self>) {
+ self.allow_submission = allow_submission;
+ cx.notify();
+ }
+
async fn submit_feedback(
feedback_text: &str,
zed_client: Arc<Client>,
@@ -46,10 +46,28 @@ impl View for SubmitFeedbackButton {
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let theme = theme::current(cx).clone();
+ let allow_submission = self
+ .active_item
+ .as_ref()
+ .map_or(true, |i| i.read(cx).allow_submission);
+
enum SubmitFeedbackButton {}
MouseEventHandler::<SubmitFeedbackButton, Self>::new(0, cx, |state, _| {
- let style = theme.feedback.submit_button.style_for(state);
- Label::new("Submit as Markdown", style.text.clone())
+ let text;
+ let style = if allow_submission {
+ text = "Submit as Markdown";
+ theme.feedback.submit_button.style_for(state)
+ } else {
+ text = "Submitting...";
+ theme
+ .feedback
+ .submit_button
+ .disabled
+ .as_ref()
+ .unwrap_or(&theme.feedback.submit_button.default)
+ };
+
+ Label::new(text, style.text.clone())
.contained()
.with_style(style.container)
})
@@ -1073,7 +1073,7 @@ impl AppContext {
pub fn is_action_available(&self, action: &dyn Action) -> bool {
let mut available_in_window = false;
- let action_type = action.as_any().type_id();
+ let action_id = action.id();
if let Some(window_id) = self.platform.main_window_id() {
available_in_window = self
.read_window(window_id, |cx| {
@@ -1083,7 +1083,7 @@ impl AppContext {
cx.views_metadata.get(&(window_id, view_id))
{
if let Some(actions) = cx.actions.get(&view_metadata.type_id) {
- if actions.contains_key(&action_type) {
+ if actions.contains_key(&action_id) {
return true;
}
}
@@ -1094,7 +1094,7 @@ impl AppContext {
})
.unwrap_or(false);
}
- available_in_window || self.global_actions.contains_key(&action_type)
+ available_in_window || self.global_actions.contains_key(&action_id)
}
fn actions_mut(
@@ -3399,7 +3399,7 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
for (i, view_id) in self.ancestors(view_id).enumerate() {
if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
- if actions.contains_key(&action.as_any().type_id()) {
+ if actions.contains_key(&action.id()) {
handler_depth = Some(i);
}
}
@@ -3407,12 +3407,12 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
}
}
- if self.global_actions.contains_key(&action.as_any().type_id()) {
+ if self.global_actions.contains_key(&action.id()) {
handler_depth = Some(contexts.len())
}
self.keystroke_matcher
- .bindings_for_action_type(action.as_any().type_id())
+ .bindings_for_action(action.id())
.find_map(|b| {
let highest_handler = handler_depth?;
if action.eq(b.action())
@@ -14,8 +14,8 @@ use crate::{
text_layout::TextLayoutCache,
util::post_inc,
Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect,
- Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, NoAction, SceneBuilder,
- Subscription, View, ViewContext, ViewHandle, WindowInvalidation,
+ Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, SceneBuilder, Subscription,
+ View, ViewContext, ViewHandle, WindowInvalidation,
};
use anyhow::{anyhow, bail, Result};
use collections::{HashMap, HashSet};
@@ -363,17 +363,13 @@ impl<'a> WindowContext<'a> {
) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
let window_id = self.window_id;
let mut contexts = Vec::new();
- let mut handler_depths_by_action_type = HashMap::<TypeId, usize>::default();
+ let mut handler_depths_by_action_id = HashMap::<TypeId, usize>::default();
for (depth, view_id) in self.ancestors(view_id).enumerate() {
if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
contexts.push(view_metadata.keymap_context.clone());
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
- handler_depths_by_action_type.extend(
- actions
- .keys()
- .copied()
- .map(|action_type| (action_type, depth)),
- );
+ handler_depths_by_action_id
+ .extend(actions.keys().copied().map(|action_id| (action_id, depth)));
}
} else {
log::error!(
@@ -383,21 +379,21 @@ impl<'a> WindowContext<'a> {
}
}
- handler_depths_by_action_type.extend(
+ handler_depths_by_action_id.extend(
self.global_actions
.keys()
.copied()
- .map(|action_type| (action_type, contexts.len())),
+ .map(|action_id| (action_id, contexts.len())),
);
self.action_deserializers
.iter()
- .filter_map(move |(name, (type_id, deserialize))| {
- if let Some(action_depth) = handler_depths_by_action_type.get(type_id).copied() {
+ .filter_map(move |(name, (action_id, deserialize))| {
+ if let Some(action_depth) = handler_depths_by_action_id.get(action_id).copied() {
let action = deserialize(serde_json::Value::Object(Default::default())).ok()?;
let bindings = self
.keystroke_matcher
- .bindings_for_action_type(*type_id)
+ .bindings_for_action(*action_id)
.filter(|b| {
action.eq(b.action())
&& (0..=action_depth)
@@ -434,11 +430,7 @@ impl<'a> WindowContext<'a> {
MatchResult::None => false,
MatchResult::Pending => true,
MatchResult::Matches(matches) => {
- let no_action_id = (NoAction {}).id();
for (view_id, action) in matches {
- if action.id() == no_action_id {
- return false;
- }
if self.dispatch_action(Some(*view_id), action.as_ref()) {
self.keystroke_matcher.clear_pending();
handled_by = Some(action.boxed_clone());
@@ -8,7 +8,7 @@ use std::{any::TypeId, fmt::Debug};
use collections::HashMap;
use smallvec::SmallVec;
-use crate::Action;
+use crate::{Action, NoAction};
pub use binding::{Binding, BindingMatchResult};
pub use keymap::Keymap;
@@ -47,8 +47,8 @@ impl KeymapMatcher {
self.keymap.clear();
}
- pub fn bindings_for_action_type(&self, action_type: TypeId) -> impl Iterator<Item = &Binding> {
- self.keymap.bindings_for_action_type(action_type)
+ pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &Binding> {
+ self.keymap.bindings_for_action(action_id)
}
pub fn clear_pending(&mut self) {
@@ -81,6 +81,7 @@ impl KeymapMatcher {
// The key is the reverse position of the binding in the bindings list so that later bindings
// match before earlier ones in the user's config
let mut matched_bindings: Vec<(usize, Box<dyn Action>)> = Default::default();
+ let no_action_id = (NoAction {}).id();
let first_keystroke = self.pending_keystrokes.is_empty();
self.pending_keystrokes.push(keystroke.clone());
@@ -108,7 +109,9 @@ impl KeymapMatcher {
match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..])
{
BindingMatchResult::Complete(action) => {
- matched_bindings.push((*view_id, action));
+ if action.id() != no_action_id {
+ matched_bindings.push((*view_id, action));
+ }
}
BindingMatchResult::Partial => {
self.pending_views
@@ -7,8 +7,8 @@ use super::{KeymapContext, KeymapContextPredicate, Keystroke};
pub struct Binding {
action: Box<dyn Action>,
- keystrokes: SmallVec<[Keystroke; 2]>,
- context_predicate: Option<KeymapContextPredicate>,
+ pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
+ pub(super) context_predicate: Option<KeymapContextPredicate>,
}
impl std::fmt::Debug for Binding {
@@ -1,61 +1,388 @@
+use collections::HashSet;
use smallvec::SmallVec;
-use std::{
- any::{Any, TypeId},
- collections::HashMap,
-};
+use std::{any::TypeId, collections::HashMap};
-use super::Binding;
+use crate::{Action, NoAction};
+
+use super::{Binding, KeymapContextPredicate, Keystroke};
#[derive(Default)]
pub struct Keymap {
bindings: Vec<Binding>,
- binding_indices_by_action_type: HashMap<TypeId, SmallVec<[usize; 3]>>,
+ binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
+ disabled_keystrokes: HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeymapContextPredicate>>>,
}
impl Keymap {
- pub fn new(bindings: Vec<Binding>) -> Self {
- let mut binding_indices_by_action_type = HashMap::new();
- for (ix, binding) in bindings.iter().enumerate() {
- binding_indices_by_action_type
- .entry(binding.action().type_id())
- .or_insert_with(SmallVec::new)
- .push(ix);
- }
-
- Self {
- binding_indices_by_action_type,
- bindings,
- }
+ #[cfg(test)]
+ pub(super) fn new(bindings: Vec<Binding>) -> Self {
+ let mut this = Self::default();
+ this.add_bindings(bindings);
+ this
}
- pub(crate) fn bindings_for_action_type(
+ pub(crate) fn bindings_for_action(
&self,
- action_type: TypeId,
+ action_id: TypeId,
) -> impl Iterator<Item = &'_ Binding> {
- self.binding_indices_by_action_type
- .get(&action_type)
+ self.binding_indices_by_action_id
+ .get(&action_id)
.map(SmallVec::as_slice)
.unwrap_or(&[])
.iter()
.map(|ix| &self.bindings[*ix])
+ .filter(|binding| !self.binding_disabled(binding))
}
pub(crate) fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
+ let no_action_id = (NoAction {}).id();
+ let mut new_bindings = Vec::new();
+ let mut has_new_disabled_keystrokes = false;
for binding in bindings {
- self.binding_indices_by_action_type
- .entry(binding.action().as_any().type_id())
- .or_default()
- .push(self.bindings.len());
- self.bindings.push(binding);
+ if binding.action().id() == no_action_id {
+ has_new_disabled_keystrokes |= self
+ .disabled_keystrokes
+ .entry(binding.keystrokes)
+ .or_default()
+ .insert(binding.context_predicate);
+ } else {
+ new_bindings.push(binding);
+ }
+ }
+
+ if has_new_disabled_keystrokes {
+ self.binding_indices_by_action_id.retain(|_, indices| {
+ indices.retain(|ix| {
+ let binding = &self.bindings[*ix];
+ match self.disabled_keystrokes.get(&binding.keystrokes) {
+ Some(disabled_predicates) => {
+ !disabled_predicates.contains(&binding.context_predicate)
+ }
+ None => true,
+ }
+ });
+ !indices.is_empty()
+ });
+ }
+
+ for new_binding in new_bindings {
+ if !self.binding_disabled(&new_binding) {
+ self.binding_indices_by_action_id
+ .entry(new_binding.action().id())
+ .or_default()
+ .push(self.bindings.len());
+ self.bindings.push(new_binding);
+ }
}
}
pub(crate) fn clear(&mut self) {
self.bindings.clear();
- self.binding_indices_by_action_type.clear();
+ self.binding_indices_by_action_id.clear();
+ self.disabled_keystrokes.clear();
+ }
+
+ pub fn bindings(&self) -> Vec<&Binding> {
+ self.bindings
+ .iter()
+ .filter(|binding| !self.binding_disabled(binding))
+ .collect()
+ }
+
+ fn binding_disabled(&self, binding: &Binding) -> bool {
+ match self.disabled_keystrokes.get(&binding.keystrokes) {
+ Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate),
+ None => false,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::actions;
+
+ use super::*;
+
+ actions!(
+ keymap_test,
+ [Present1, Present2, Present3, Duplicate, Missing]
+ );
+
+ #[test]
+ fn regular_keymap() {
+ let present_1 = Binding::new("ctrl-q", Present1 {}, None);
+ let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
+ let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
+ let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
+ let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
+ let missing = Binding::new("ctrl-r", Missing {}, None);
+ let all_bindings = [
+ &present_1,
+ &present_2,
+ &present_3,
+ &keystroke_duplicate_to_1,
+ &full_duplicate_to_2,
+ &missing,
+ ];
+
+ let mut keymap = Keymap::default();
+ assert_absent(&keymap, &all_bindings);
+ assert!(keymap.bindings().is_empty());
+
+ keymap.add_bindings([present_1.clone(), present_2.clone(), present_3.clone()]);
+ assert_absent(&keymap, &[&keystroke_duplicate_to_1, &missing]);
+ assert_present(
+ &keymap,
+ &[(&present_1, "q"), (&present_2, "w"), (&present_3, "e")],
+ );
+
+ keymap.add_bindings([
+ keystroke_duplicate_to_1.clone(),
+ full_duplicate_to_2.clone(),
+ ]);
+ assert_absent(&keymap, &[&missing]);
+ assert!(
+ !keymap.binding_disabled(&keystroke_duplicate_to_1),
+ "Duplicate binding 1 was added and should not be disabled"
+ );
+ assert!(
+ !keymap.binding_disabled(&full_duplicate_to_2),
+ "Duplicate binding 2 was added and should not be disabled"
+ );
+
+ assert_eq!(
+ keymap
+ .bindings_for_action(keystroke_duplicate_to_1.action().id())
+ .map(|binding| &binding.keystrokes)
+ .flatten()
+ .collect::<Vec<_>>(),
+ vec![&Keystroke {
+ ctrl: true,
+ alt: false,
+ shift: false,
+ cmd: false,
+ function: false,
+ key: "q".to_string()
+ }],
+ "{keystroke_duplicate_to_1:?} should have the expected keystroke in the keymap"
+ );
+ assert_eq!(
+ keymap
+ .bindings_for_action(full_duplicate_to_2.action().id())
+ .map(|binding| &binding.keystrokes)
+ .flatten()
+ .collect::<Vec<_>>(),
+ vec![
+ &Keystroke {
+ ctrl: true,
+ alt: false,
+ shift: false,
+ cmd: false,
+ function: false,
+ key: "w".to_string()
+ },
+ &Keystroke {
+ ctrl: true,
+ alt: false,
+ shift: false,
+ cmd: false,
+ function: false,
+ key: "w".to_string()
+ }
+ ],
+ "{full_duplicate_to_2:?} should have a duplicated keystroke in the keymap"
+ );
+
+ let updated_bindings = keymap.bindings();
+ let expected_updated_bindings = vec![
+ &present_1,
+ &present_2,
+ &present_3,
+ &keystroke_duplicate_to_1,
+ &full_duplicate_to_2,
+ ];
+ assert_eq!(
+ updated_bindings.len(),
+ expected_updated_bindings.len(),
+ "Unexpected updated keymap bindings {updated_bindings:?}"
+ );
+ for (i, expected) in expected_updated_bindings.iter().enumerate() {
+ let keymap_binding = &updated_bindings[i];
+ assert_eq!(
+ keymap_binding.context_predicate, expected.context_predicate,
+ "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
+ );
+ assert_eq!(
+ keymap_binding.keystrokes, expected.keystrokes,
+ "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
+ );
+ }
+
+ keymap.clear();
+ assert_absent(&keymap, &all_bindings);
+ assert!(keymap.bindings().is_empty());
}
- pub fn bindings(&self) -> &Vec<Binding> {
- &self.bindings
+ #[test]
+ fn keymap_with_ignored() {
+ let present_1 = Binding::new("ctrl-q", Present1 {}, None);
+ let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
+ let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
+ let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
+ let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
+ let ignored_1 = Binding::new("ctrl-q", NoAction {}, None);
+ let ignored_2 = Binding::new("ctrl-w", NoAction {}, Some("pane"));
+ let ignored_3_with_other_context =
+ Binding::new("ctrl-e", NoAction {}, Some("other_context"));
+
+ let mut keymap = Keymap::default();
+
+ keymap.add_bindings([
+ ignored_1.clone(),
+ ignored_2.clone(),
+ ignored_3_with_other_context.clone(),
+ ]);
+ assert_absent(&keymap, &[&present_3]);
+ assert_disabled(
+ &keymap,
+ &[
+ &present_1,
+ &present_2,
+ &ignored_1,
+ &ignored_2,
+ &ignored_3_with_other_context,
+ ],
+ );
+ assert!(keymap.bindings().is_empty());
+ keymap.clear();
+
+ keymap.add_bindings([
+ present_1.clone(),
+ present_2.clone(),
+ present_3.clone(),
+ ignored_1.clone(),
+ ignored_2.clone(),
+ ignored_3_with_other_context.clone(),
+ ]);
+ assert_present(&keymap, &[(&present_3, "e")]);
+ assert_disabled(
+ &keymap,
+ &[
+ &present_1,
+ &present_2,
+ &ignored_1,
+ &ignored_2,
+ &ignored_3_with_other_context,
+ ],
+ );
+ keymap.clear();
+
+ keymap.add_bindings([
+ present_1.clone(),
+ present_2.clone(),
+ present_3.clone(),
+ ignored_1.clone(),
+ ]);
+ assert_present(&keymap, &[(&present_2, "w"), (&present_3, "e")]);
+ assert_disabled(&keymap, &[&present_1, &ignored_1]);
+ assert_absent(&keymap, &[&ignored_2, &ignored_3_with_other_context]);
+ keymap.clear();
+
+ keymap.add_bindings([
+ present_1.clone(),
+ present_2.clone(),
+ present_3.clone(),
+ keystroke_duplicate_to_1.clone(),
+ full_duplicate_to_2.clone(),
+ ignored_1.clone(),
+ ignored_2.clone(),
+ ignored_3_with_other_context.clone(),
+ ]);
+ assert_present(&keymap, &[(&present_3, "e")]);
+ assert_disabled(
+ &keymap,
+ &[
+ &present_1,
+ &present_2,
+ &keystroke_duplicate_to_1,
+ &full_duplicate_to_2,
+ &ignored_1,
+ &ignored_2,
+ &ignored_3_with_other_context,
+ ],
+ );
+ keymap.clear();
+ }
+
+ #[track_caller]
+ fn assert_present(keymap: &Keymap, expected_bindings: &[(&Binding, &str)]) {
+ let keymap_bindings = keymap.bindings();
+ assert_eq!(
+ expected_bindings.len(),
+ keymap_bindings.len(),
+ "Unexpected keymap bindings {keymap_bindings:?}"
+ );
+ for (i, (expected, expected_key)) in expected_bindings.iter().enumerate() {
+ assert!(
+ !keymap.binding_disabled(expected),
+ "{expected:?} should not be disabled as it was added into keymap for element {i}"
+ );
+ assert_eq!(
+ keymap
+ .bindings_for_action(expected.action().id())
+ .map(|binding| &binding.keystrokes)
+ .flatten()
+ .collect::<Vec<_>>(),
+ vec![&Keystroke {
+ ctrl: true,
+ alt: false,
+ shift: false,
+ cmd: false,
+ function: false,
+ key: expected_key.to_string()
+ }],
+ "{expected:?} should have the expected keystroke with key '{expected_key}' in the keymap for element {i}"
+ );
+
+ let keymap_binding = &keymap_bindings[i];
+ assert_eq!(
+ keymap_binding.context_predicate, expected.context_predicate,
+ "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
+ );
+ assert_eq!(
+ keymap_binding.keystrokes, expected.keystrokes,
+ "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
+ );
+ }
+ }
+
+ #[track_caller]
+ fn assert_absent(keymap: &Keymap, bindings: &[&Binding]) {
+ for binding in bindings.iter() {
+ assert!(
+ !keymap.binding_disabled(binding),
+ "{binding:?} should not be disabled in the keymap where was not added"
+ );
+ assert_eq!(
+ keymap.bindings_for_action(binding.action().id()).count(),
+ 0,
+ "{binding:?} should have no actions in the keymap where was not added"
+ );
+ }
+ }
+
+ #[track_caller]
+ fn assert_disabled(keymap: &Keymap, bindings: &[&Binding]) {
+ for binding in bindings.iter() {
+ assert!(
+ keymap.binding_disabled(binding),
+ "{binding:?} should be disabled in the keymap"
+ );
+ assert_eq!(
+ keymap.bindings_for_action(binding.action().id()).count(),
+ 0,
+ "{binding:?} should have no actions in the keymap where it was disabled"
+ );
+ }
}
}
@@ -44,7 +44,7 @@ impl KeymapContext {
}
}
-#[derive(Clone, Debug, Eq, PartialEq)]
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum KeymapContextPredicate {
Identifier(String),
Equal(String, String),
@@ -3,7 +3,7 @@ use std::fmt::Write;
use anyhow::anyhow;
use serde::Deserialize;
-#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize)]
+#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
pub struct Keystroke {
pub ctrl: bool,
pub alt: bool,
@@ -231,7 +231,7 @@ impl MacForegroundPlatform {
} => {
// TODO
let keystrokes = keystroke_matcher
- .bindings_for_action_type(action.as_any().type_id())
+ .bindings_for_action(action.id())
.find(|binding| binding.action().eq(action.as_ref()))
.map(|binding| binding.keystrokes());
let selector = match os_action {
@@ -831,6 +831,7 @@ impl LanguageRegistry {
Ok(language) => {
let language = Arc::new(language);
let mut state = this.state.write();
+
state.add(language.clone());
state.mark_language_loaded(id);
if let Some(mut txs) = state.loading_languages.remove(&id) {
@@ -151,16 +151,17 @@ impl LanguageServer {
let stdin = server.stdin.take().unwrap();
let stout = server.stdout.take().unwrap();
let mut server = Self::new_internal(
- server_id,
+ server_id.clone(),
stdin,
stout,
Some(server),
root_path,
code_action_kinds,
cx,
- |notification| {
+ move |notification| {
log::info!(
- "unhandled notification {}:\n{}",
+ "{} unhandled notification {}:\n{}",
+ server_id,
notification.method,
serde_json::to_string_pretty(
¬ification
@@ -6,13 +6,13 @@ use futures::{future::Shared, FutureExt};
use gpui::{executor::Background, Task};
use serde::Deserialize;
use smol::{fs, io::BufReader, process::Command};
-use std::process::Output;
+use std::process::{Output, Stdio};
use std::{
env::consts,
path::{Path, PathBuf},
sync::{Arc, OnceLock},
};
-use util::{http::HttpClient, ResultExt};
+use util::http::HttpClient;
const VERSION: &str = "v18.15.0";
@@ -84,9 +84,8 @@ impl NodeRuntime {
};
let installation_path = self.install_if_needed().await?;
- let mut output = attempt(installation_path).await;
+ let mut output = attempt(installation_path.clone()).await;
if output.is_err() {
- let installation_path = self.reinstall().await?;
output = attempt(installation_path).await;
if output.is_err() {
return Err(anyhow!(
@@ -158,29 +157,6 @@ impl NodeRuntime {
Ok(())
}
- async fn reinstall(&self) -> Result<PathBuf> {
- log::info!("beginnning to reinstall Node runtime");
- let mut installation_path = self.installation_path.lock().await;
-
- if let Some(task) = installation_path.as_ref().cloned() {
- if let Ok(installation_path) = task.await {
- smol::fs::remove_dir_all(&installation_path)
- .await
- .context("node dir removal")
- .log_err();
- }
- }
-
- let http = self.http.clone();
- let task = self
- .background
- .spawn(async move { Self::install(http).await.map_err(Arc::new) })
- .shared();
-
- *installation_path = Some(task.clone());
- task.await.map_err(|e| anyhow!("{}", e))
- }
-
async fn install_if_needed(&self) -> Result<PathBuf> {
let task = self
.installation_path
@@ -209,8 +185,19 @@ impl NodeRuntime {
let node_containing_dir = util::paths::SUPPORT_DIR.join("node");
let node_dir = node_containing_dir.join(folder_name);
let node_binary = node_dir.join("bin/node");
-
- if fs::metadata(&node_binary).await.is_err() {
+ let npm_file = node_dir.join("bin/npm");
+
+ let result = Command::new(&node_binary)
+ .arg(npm_file)
+ .arg("--version")
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .stderr(Stdio::null())
+ .status()
+ .await;
+ let valid = matches!(result, Ok(status) if status.success());
+
+ if !valid {
_ = fs::remove_dir_all(&node_containing_dir).await;
fs::create_dir(&node_containing_dir)
.await
@@ -425,6 +425,12 @@ pub struct Hover {
pub language: Option<Arc<Language>>,
}
+impl Hover {
+ pub fn is_empty(&self) -> bool {
+ self.contents.iter().all(|block| block.text.is_empty())
+ }
+}
+
#[derive(Default)]
pub struct ProjectTransaction(pub HashMap<ModelHandle<Buffer>, language::Transaction>);
@@ -1909,7 +1915,9 @@ impl Project {
return;
}
- let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
+ let abs_path = file.abs_path(cx);
+ let uri = lsp::Url::from_file_path(&abs_path)
+ .unwrap_or_else(|()| panic!("Failed to register file {abs_path:?}"));
let initial_snapshot = buffer.text_snapshot();
let language = buffer.language().cloned();
let worktree_id = file.worktree_id(cx);
@@ -2709,7 +2717,6 @@ impl Project {
Some(language_server) => language_server,
None => return Ok(None),
};
-
let this = match this.upgrade(cx) {
Some(this) => this,
None => return Err(anyhow!("failed to upgrade project handle")),
@@ -51,7 +51,7 @@ use gpui::{
fonts,
geometry::vector::{vec2f, Vector2F},
keymap_matcher::Keystroke,
- platform::{MouseButton, MouseMovedEvent, TouchPhase},
+ platform::{Modifiers, MouseButton, MouseMovedEvent, TouchPhase},
scene::{MouseDown, MouseDrag, MouseScrollWheel, MouseUp},
AppContext, ClipboardItem, Entity, ModelContext, Task,
};
@@ -72,14 +72,15 @@ const DEBUG_TERMINAL_HEIGHT: f32 = 30.;
const DEBUG_CELL_WIDTH: f32 = 5.;
const DEBUG_LINE_HEIGHT: f32 = 5.;
-// Regex Copied from alacritty's ui_config.rs
-
lazy_static! {
+ // Regex Copied from alacritty's ui_config.rs
static ref URL_REGEX: RegexSearch = RegexSearch::new("(ipfs:|ipns:|magnet:|mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>\"\\s{-}\\^โจโฉ`]+").unwrap();
+
+ static ref WORD_REGEX: RegexSearch = RegexSearch::new("[\\w.:/@-]+").unwrap();
}
///Upward flowing events, for changing the title and such
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Debug)]
pub enum Event {
TitleChanged,
BreadcrumbsChanged,
@@ -88,6 +89,18 @@ pub enum Event {
Wakeup,
BlinkChanged,
SelectionsChanged,
+ NewNavigationTarget(Option<MaybeNavigationTarget>),
+ Open(MaybeNavigationTarget),
+}
+
+/// A string inside terminal, potentially useful as a URI that can be opened.
+#[derive(Clone, Debug)]
+pub enum MaybeNavigationTarget {
+ /// HTTP, git, etc. string determined by the [`URL_REGEX`] regex.
+ Url(String),
+ /// File system path, absolute or relative, existing or not.
+ /// Might have line and column number(s) attached as `file.rs:1:23`
+ PathLike(String),
}
#[derive(Clone)]
@@ -493,6 +506,8 @@ impl TerminalBuilder {
last_mouse_position: None,
next_link_id: 0,
selection_phase: SelectionPhase::Ended,
+ cmd_pressed: false,
+ hovered_word: false,
};
Ok(TerminalBuilder {
@@ -589,7 +604,14 @@ pub struct TerminalContent {
pub cursor: RenderableCursor,
pub cursor_char: char,
pub size: TerminalSize,
- pub last_hovered_hyperlink: Option<(String, RangeInclusive<Point>, usize)>,
+ pub last_hovered_word: Option<HoveredWord>,
+}
+
+#[derive(Clone)]
+pub struct HoveredWord {
+ pub word: String,
+ pub word_match: RangeInclusive<Point>,
+ pub id: usize,
}
impl Default for TerminalContent {
@@ -606,7 +628,7 @@ impl Default for TerminalContent {
},
cursor_char: Default::default(),
size: Default::default(),
- last_hovered_hyperlink: None,
+ last_hovered_word: None,
}
}
}
@@ -623,7 +645,7 @@ pub struct Terminal {
events: VecDeque<InternalEvent>,
/// This is only used for mouse mode cell change detection
last_mouse: Option<(Point, AlacDirection)>,
- /// This is only used for terminal hyperlink checking
+ /// This is only used for terminal hovered word checking
last_mouse_position: Option<Vector2F>,
pub matches: Vec<RangeInclusive<Point>>,
pub last_content: TerminalContent,
@@ -637,6 +659,8 @@ pub struct Terminal {
scroll_px: f32,
next_link_id: usize,
selection_phase: SelectionPhase,
+ cmd_pressed: bool,
+ hovered_word: bool,
}
impl Terminal {
@@ -769,7 +793,7 @@ impl Terminal {
}
InternalEvent::Scroll(scroll) => {
term.scroll_display(*scroll);
- self.refresh_hyperlink();
+ self.refresh_hovered_word();
}
InternalEvent::SetSelection(selection) => {
term.selection = selection.as_ref().map(|(sel, _)| sel.clone());
@@ -804,20 +828,20 @@ impl Terminal {
}
InternalEvent::ScrollToPoint(point) => {
term.scroll_to_point(*point);
- self.refresh_hyperlink();
+ self.refresh_hovered_word();
}
InternalEvent::FindHyperlink(position, open) => {
- let prev_hyperlink = self.last_content.last_hovered_hyperlink.take();
+ let prev_hovered_word = self.last_content.last_hovered_word.take();
let point = grid_point(
*position,
self.last_content.size,
term.grid().display_offset(),
)
- .grid_clamp(term, alacritty_terminal::index::Boundary::Cursor);
+ .grid_clamp(term, alacritty_terminal::index::Boundary::Grid);
let link = term.grid().index(point).hyperlink();
- let found_url = if link.is_some() {
+ let found_word = if link.is_some() {
let mut min_index = point;
loop {
let new_min_index =
@@ -847,42 +871,78 @@ impl Terminal {
let url = link.unwrap().uri().to_owned();
let url_match = min_index..=max_index;
- Some((url, url_match))
- } else if let Some(url_match) = regex_match_at(term, point, &URL_REGEX) {
- let url = term.bounds_to_string(*url_match.start(), *url_match.end());
+ Some((url, true, url_match))
+ } else if let Some(word_match) = regex_match_at(term, point, &WORD_REGEX) {
+ let maybe_url_or_path =
+ term.bounds_to_string(*word_match.start(), *word_match.end());
+ let is_url = regex_match_at(term, point, &URL_REGEX).is_some();
- Some((url, url_match))
+ Some((maybe_url_or_path, is_url, word_match))
} else {
None
};
- if let Some((url, url_match)) = found_url {
- if *open {
- cx.platform().open_url(url.as_str());
- } else {
- self.update_hyperlink(prev_hyperlink, url, url_match);
+ match found_word {
+ Some((maybe_url_or_path, is_url, url_match)) => {
+ if *open {
+ let target = if is_url {
+ MaybeNavigationTarget::Url(maybe_url_or_path)
+ } else {
+ MaybeNavigationTarget::PathLike(maybe_url_or_path)
+ };
+ cx.emit(Event::Open(target));
+ } else {
+ self.update_selected_word(
+ prev_hovered_word,
+ url_match,
+ maybe_url_or_path,
+ is_url,
+ cx,
+ );
+ }
+ self.hovered_word = true;
+ }
+ None => {
+ if self.hovered_word {
+ cx.emit(Event::NewNavigationTarget(None));
+ }
+ self.hovered_word = false;
}
}
}
}
}
- fn update_hyperlink(
+ fn update_selected_word(
&mut self,
- prev_hyperlink: Option<(String, RangeInclusive<Point>, usize)>,
- url: String,
- url_match: RangeInclusive<Point>,
+ prev_word: Option<HoveredWord>,
+ word_match: RangeInclusive<Point>,
+ word: String,
+ is_url: bool,
+ cx: &mut ModelContext<Self>,
) {
- if let Some(prev_hyperlink) = prev_hyperlink {
- if prev_hyperlink.0 == url && prev_hyperlink.1 == url_match {
- self.last_content.last_hovered_hyperlink = Some((url, url_match, prev_hyperlink.2));
- } else {
- self.last_content.last_hovered_hyperlink =
- Some((url, url_match, self.next_link_id()));
+ if let Some(prev_word) = prev_word {
+ if prev_word.word == word && prev_word.word_match == word_match {
+ self.last_content.last_hovered_word = Some(HoveredWord {
+ word,
+ word_match,
+ id: prev_word.id,
+ });
+ return;
}
- } else {
- self.last_content.last_hovered_hyperlink = Some((url, url_match, self.next_link_id()));
}
+
+ self.last_content.last_hovered_word = Some(HoveredWord {
+ word: word.clone(),
+ word_match,
+ id: self.next_link_id(),
+ });
+ let navigation_target = if is_url {
+ MaybeNavigationTarget::Url(word)
+ } else {
+ MaybeNavigationTarget::PathLike(word)
+ };
+ cx.emit(Event::NewNavigationTarget(Some(navigation_target)));
}
fn next_link_id(&mut self) -> usize {
@@ -964,6 +1024,15 @@ impl Terminal {
}
}
+ pub fn try_modifiers_change(&mut self, modifiers: &Modifiers) -> bool {
+ let changed = self.cmd_pressed != modifiers.cmd;
+ if !self.cmd_pressed && modifiers.cmd {
+ self.refresh_hovered_word();
+ }
+ self.cmd_pressed = modifiers.cmd;
+ changed
+ }
+
///Paste text into the terminal
pub fn paste(&mut self, text: &str) {
let paste_text = if self.last_content.mode.contains(TermMode::BRACKETED_PASTE) {
@@ -1035,7 +1104,7 @@ impl Terminal {
cursor: content.cursor,
cursor_char: term.grid()[content.cursor.point].c,
size: last_content.size,
- last_hovered_hyperlink: last_content.last_hovered_hyperlink.clone(),
+ last_hovered_word: last_content.last_hovered_word.clone(),
}
}
@@ -1089,14 +1158,14 @@ impl Terminal {
self.pty_tx.notify(bytes);
}
}
- } else {
- self.hyperlink_from_position(Some(position));
+ } else if self.cmd_pressed {
+ self.word_from_position(Some(position));
}
}
- fn hyperlink_from_position(&mut self, position: Option<Vector2F>) {
+ fn word_from_position(&mut self, position: Option<Vector2F>) {
if self.selection_phase == SelectionPhase::Selecting {
- self.last_content.last_hovered_hyperlink = None;
+ self.last_content.last_hovered_word = None;
} else if let Some(position) = position {
self.events
.push_back(InternalEvent::FindHyperlink(position, false));
@@ -1208,7 +1277,7 @@ impl Terminal {
let mouse_cell_index = content_index_for_mouse(position, &self.last_content.size);
if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() {
cx.platform().open_url(link.uri());
- } else {
+ } else if self.cmd_pressed {
self.events
.push_back(InternalEvent::FindHyperlink(position, true));
}
@@ -1255,8 +1324,8 @@ impl Terminal {
}
}
- pub fn refresh_hyperlink(&mut self) {
- self.hyperlink_from_position(self.last_mouse_position);
+ fn refresh_hovered_word(&mut self) {
+ self.word_from_position(self.last_mouse_position);
}
fn determine_scroll_lines(&mut self, e: &MouseScrollWheel, mouse_mode: bool) -> Option<i32> {
@@ -1334,6 +1403,10 @@ impl Terminal {
})
.unwrap_or_else(|| "Terminal".to_string())
}
+
+ pub fn can_navigate_to_selected_word(&self) -> bool {
+ self.cmd_pressed && self.hovered_word
+ }
}
impl Drop for Terminal {
@@ -163,6 +163,7 @@ pub struct TerminalElement {
terminal: WeakModelHandle<Terminal>,
focused: bool,
cursor_visible: bool,
+ can_navigate_to_selected_word: bool,
}
impl TerminalElement {
@@ -170,11 +171,13 @@ impl TerminalElement {
terminal: WeakModelHandle<Terminal>,
focused: bool,
cursor_visible: bool,
+ can_navigate_to_selected_word: bool,
) -> TerminalElement {
TerminalElement {
terminal,
focused,
cursor_visible,
+ can_navigate_to_selected_word,
}
}
@@ -580,20 +583,30 @@ impl Element<TerminalView> for TerminalElement {
let background_color = terminal_theme.background;
let terminal_handle = self.terminal.upgrade(cx).unwrap();
- let last_hovered_hyperlink = terminal_handle.update(cx, |terminal, cx| {
+ let last_hovered_word = terminal_handle.update(cx, |terminal, cx| {
terminal.set_size(dimensions);
terminal.try_sync(cx);
- terminal.last_content.last_hovered_hyperlink.clone()
+ if self.can_navigate_to_selected_word && terminal.can_navigate_to_selected_word() {
+ terminal.last_content.last_hovered_word.clone()
+ } else {
+ None
+ }
});
- let hyperlink_tooltip = last_hovered_hyperlink.map(|(uri, _, id)| {
+ let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| {
let mut tooltip = Overlay::new(
Empty::new()
.contained()
.constrained()
.with_width(dimensions.width())
.with_height(dimensions.height())
- .with_tooltip::<TerminalElement>(id, uri, None, tooltip_style, cx),
+ .with_tooltip::<TerminalElement>(
+ hovered_word.id,
+ hovered_word.word,
+ None,
+ tooltip_style,
+ cx,
+ ),
)
.with_position_mode(gpui::elements::OverlayPositionMode::Local)
.into_any();
@@ -613,7 +626,6 @@ impl Element<TerminalView> for TerminalElement {
cursor_char,
selection,
cursor,
- last_hovered_hyperlink,
..
} = { &terminal_handle.read(cx).last_content };
@@ -634,9 +646,9 @@ impl Element<TerminalView> for TerminalElement {
&terminal_theme,
cx.text_layout_cache(),
cx.font_cache(),
- last_hovered_hyperlink
+ last_hovered_word
.as_ref()
- .map(|(_, range, _)| (link_style, range)),
+ .map(|last_hovered_word| (link_style, &last_hovered_word.word_match)),
);
//Layout cursor. Rectangle is used for IME, so we should lay it out even
@@ -261,10 +261,14 @@ impl TerminalPanel {
.create_terminal(working_directory, window_id, cx)
.log_err()
}) {
- let terminal =
- Box::new(cx.add_view(|cx| {
- TerminalView::new(terminal, workspace.database_id(), cx)
- }));
+ let terminal = Box::new(cx.add_view(|cx| {
+ TerminalView::new(
+ terminal,
+ workspace.weak_handle(),
+ workspace.database_id(),
+ cx,
+ )
+ }));
pane.update(cx, |pane, cx| {
let focus = pane.has_focus();
pane.add_item(terminal, true, focus, None, cx);
@@ -3,18 +3,21 @@ pub mod terminal_element;
pub mod terminal_panel;
use crate::{persistence::TERMINAL_DB, terminal_element::TerminalElement};
+use anyhow::Context;
use context_menu::{ContextMenu, ContextMenuItem};
use dirs::home_dir;
+use editor::{scroll::autoscroll::Autoscroll, Editor};
use gpui::{
actions,
elements::{AnchorCorner, ChildView, Flex, Label, ParentElement, Stack},
geometry::vector::Vector2F,
impl_actions,
keymap_matcher::{KeymapContext, Keystroke},
- platform::KeyDownEvent,
+ platform::{KeyDownEvent, ModifiersChangedEvent},
AnyElement, AnyViewHandle, AppContext, Element, Entity, ModelHandle, Task, View, ViewContext,
ViewHandle, WeakViewHandle,
};
+use language::Bias;
use project::{LocalWorktree, Project};
use serde::Deserialize;
use smallvec::{smallvec, SmallVec};
@@ -30,9 +33,9 @@ use terminal::{
index::Point,
term::{search::RegexSearch, TermMode},
},
- Event, Terminal, TerminalBlink, WorkingDirectory,
+ Event, MaybeNavigationTarget, Terminal, TerminalBlink, WorkingDirectory,
};
-use util::ResultExt;
+use util::{paths::PathLikeWithPosition, ResultExt};
use workspace::{
item::{BreadcrumbText, Item, ItemEvent},
notifications::NotifyResultExt,
@@ -90,6 +93,7 @@ pub struct TerminalView {
blinking_on: bool,
blinking_paused: bool,
blink_epoch: usize,
+ can_navigate_to_selected_word: bool,
workspace_id: WorkspaceId,
}
@@ -117,19 +121,27 @@ impl TerminalView {
.notify_err(workspace, cx);
if let Some(terminal) = terminal {
- let view = cx.add_view(|cx| TerminalView::new(terminal, workspace.database_id(), cx));
+ let view = cx.add_view(|cx| {
+ TerminalView::new(
+ terminal,
+ workspace.weak_handle(),
+ workspace.database_id(),
+ cx,
+ )
+ });
workspace.add_item(Box::new(view), cx)
}
}
pub fn new(
terminal: ModelHandle<Terminal>,
+ workspace: WeakViewHandle<Workspace>,
workspace_id: WorkspaceId,
cx: &mut ViewContext<Self>,
) -> Self {
let view_id = cx.view_id();
cx.observe(&terminal, |_, _, cx| cx.notify()).detach();
- cx.subscribe(&terminal, |this, _, event, cx| match event {
+ cx.subscribe(&terminal, move |this, _, event, cx| match event {
Event::Wakeup => {
if !cx.is_self_focused() {
this.has_new_content = true;
@@ -158,7 +170,63 @@ impl TerminalView {
.detach();
}
}
- _ => cx.emit(*event),
+ Event::NewNavigationTarget(maybe_navigation_target) => {
+ this.can_navigate_to_selected_word = match maybe_navigation_target {
+ Some(MaybeNavigationTarget::Url(_)) => true,
+ Some(MaybeNavigationTarget::PathLike(maybe_path)) => {
+ !possible_open_targets(&workspace, maybe_path, cx).is_empty()
+ }
+ None => false,
+ }
+ }
+ Event::Open(maybe_navigation_target) => match maybe_navigation_target {
+ MaybeNavigationTarget::Url(url) => cx.platform().open_url(url),
+ MaybeNavigationTarget::PathLike(maybe_path) => {
+ if !this.can_navigate_to_selected_word {
+ return;
+ }
+ let potential_abs_paths = possible_open_targets(&workspace, maybe_path, cx);
+ if let Some(path) = potential_abs_paths.into_iter().next() {
+ let visible = path.path_like.is_dir();
+ let task_workspace = workspace.clone();
+ cx.spawn(|_, mut cx| async move {
+ let opened_item = task_workspace
+ .update(&mut cx, |workspace, cx| {
+ workspace.open_abs_path(path.path_like, visible, cx)
+ })
+ .context("workspace update")?
+ .await
+ .context("workspace update")?;
+ if let Some(row) = path.row {
+ let col = path.column.unwrap_or(0);
+ if let Some(active_editor) = opened_item.downcast::<Editor>() {
+ active_editor
+ .downgrade()
+ .update(&mut cx, |editor, cx| {
+ let snapshot = editor.snapshot(cx).display_snapshot;
+ let point = snapshot.buffer_snapshot.clip_point(
+ language::Point::new(
+ row.saturating_sub(1),
+ col.saturating_sub(1),
+ ),
+ Bias::Left,
+ );
+ editor.change_selections(
+ Some(Autoscroll::center()),
+ cx,
+ |s| s.select_ranges([point..point]),
+ );
+ })
+ .log_err();
+ }
+ }
+ anyhow::Ok(())
+ })
+ .detach_and_log_err(cx);
+ }
+ }
+ },
+ _ => cx.emit(event.clone()),
})
.detach();
@@ -171,6 +239,7 @@ impl TerminalView {
blinking_on: false,
blinking_paused: false,
blink_epoch: 0,
+ can_navigate_to_selected_word: false,
workspace_id,
}
}
@@ -344,6 +413,40 @@ impl TerminalView {
}
}
+fn possible_open_targets(
+ workspace: &WeakViewHandle<Workspace>,
+ maybe_path: &String,
+ cx: &mut ViewContext<'_, '_, TerminalView>,
+) -> Vec<PathLikeWithPosition<PathBuf>> {
+ let path_like = PathLikeWithPosition::parse_str(maybe_path.as_str(), |path_str| {
+ Ok::<_, std::convert::Infallible>(Path::new(path_str).to_path_buf())
+ })
+ .expect("infallible");
+ let maybe_path = path_like.path_like;
+ let potential_abs_paths = if maybe_path.is_absolute() {
+ vec![maybe_path]
+ } else if let Some(workspace) = workspace.upgrade(cx) {
+ workspace.update(cx, |workspace, cx| {
+ workspace
+ .worktrees(cx)
+ .map(|worktree| worktree.read(cx).abs_path().join(&maybe_path))
+ .collect()
+ })
+ } else {
+ Vec::new()
+ };
+
+ potential_abs_paths
+ .into_iter()
+ .filter(|path| path.exists())
+ .map(|path| PathLikeWithPosition {
+ path_like: path,
+ row: path_like.row,
+ column: path_like.column,
+ })
+ .collect()
+}
+
pub fn regex_search_for_query(query: project::search::SearchQuery) -> Option<RegexSearch> {
let searcher = match query {
project::search::SearchQuery::Text { query, .. } => RegexSearch::new(&query),
@@ -372,6 +475,7 @@ impl View for TerminalView {
terminal_handle,
focused,
self.should_show_cursor(focused, cx),
+ self.can_navigate_to_selected_word,
)
.contained(),
)
@@ -393,6 +497,20 @@ impl View for TerminalView {
cx.notify();
}
+ fn modifiers_changed(
+ &mut self,
+ event: &ModifiersChangedEvent,
+ cx: &mut ViewContext<Self>,
+ ) -> bool {
+ let handled = self
+ .terminal()
+ .update(cx, |term, _| term.try_modifiers_change(&event.modifiers));
+ if handled {
+ cx.notify();
+ }
+ handled
+ }
+
fn key_down(&mut self, event: &KeyDownEvent, cx: &mut ViewContext<Self>) -> bool {
self.clear_bel(cx);
self.pause_cursor_blinking(cx);
@@ -618,7 +736,7 @@ impl Item for TerminalView {
project.create_terminal(cwd, window_id, cx)
})?;
Ok(pane.update(&mut cx, |_, cx| {
- cx.add_view(|cx| TerminalView::new(terminal, workspace_id, cx))
+ cx.add_view(|cx| TerminalView::new(terminal, workspace, workspace_id, cx))
})?)
})
}
@@ -350,6 +350,7 @@ pub struct Tab {
pub icon_close_active: Color,
pub icon_dirty: Color,
pub icon_conflict: Color,
+ pub git: GitProjectStatus,
}
#[derive(Clone, Deserialize, Default, JsonSchema)]
@@ -722,12 +723,12 @@ pub struct Scrollbar {
pub thumb: ContainerStyle,
pub width: f32,
pub min_height_factor: f32,
- pub git: GitDiffColors,
+ pub git: BufferGitDiffColors,
pub selections: Color,
}
#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct GitDiffColors {
+pub struct BufferGitDiffColors {
pub inserted: Color,
pub modified: Color,
pub deleted: Color,
@@ -67,11 +67,13 @@ impl EmbeddingProvider for DummyEmbeddings {
}
}
+const INPUT_LIMIT: usize = 8190;
+
impl OpenAIEmbeddings {
- async fn truncate(span: String) -> String {
+ fn truncate(span: String) -> String {
let mut tokens = OPENAI_BPE_TOKENIZER.encode_with_special_tokens(span.as_ref());
- if tokens.len() > 8190 {
- tokens.truncate(8190);
+ if tokens.len() > INPUT_LIMIT {
+ tokens.truncate(INPUT_LIMIT);
let result = OPENAI_BPE_TOKENIZER.decode(tokens.clone());
if result.is_ok() {
let transformed = result.unwrap();
@@ -80,7 +82,7 @@ impl OpenAIEmbeddings {
}
}
- return span.to_string();
+ span
}
async fn send_request(&self, api_key: &str, spans: Vec<&str>) -> Result<Response<AsyncBody>> {
@@ -137,7 +139,7 @@ impl EmbeddingProvider for OpenAIEmbeddings {
// Don't worry about delaying bad request, as we can assume
// we haven't been rate limited yet.
for span in spans.iter_mut() {
- *span = Self::truncate(span.to_string()).await;
+ *span = Self::truncate(span.to_string());
}
}
StatusCode::OK => {
@@ -63,7 +63,7 @@ impl CodeContextRetriever {
) {
// log::info!("-----MATCH-----");
- let mut name: Vec<&str> = vec![];
+ let mut name = Vec::new();
let mut item: Option<&str> = None;
let mut offset: Option<usize> = None;
for capture in mat.captures {
@@ -91,11 +91,8 @@ impl CodeContextRetriever {
.replace("<language>", &pending_file.language.name().to_lowercase())
.replace("<item>", item.unwrap());
- let mut truncated_span = context_span.clone();
- truncated_span.truncate(100);
-
// log::info!("Name: {:?}", name);
- // log::info!("Span: {:?}", truncated_span);
+ // log::info!("Span: {:?}", util::truncate(&context_span, 100));
context_spans.push(context_span);
documents.push(Document {
@@ -10,6 +10,9 @@ use gpui::{
ViewContext, ViewHandle, WeakViewHandle, WindowContext,
};
use project::{Project, ProjectEntryId, ProjectPath};
+use schemars::JsonSchema;
+use serde_derive::{Deserialize, Serialize};
+use settings::Setting;
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
@@ -27,6 +30,49 @@ use std::{
};
use theme::Theme;
+#[derive(Deserialize)]
+pub struct ItemSettings {
+ pub git_status: bool,
+ pub close_position: ClosePosition,
+}
+
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "lowercase")]
+pub enum ClosePosition {
+ Left,
+ #[default]
+ Right,
+}
+
+impl ClosePosition {
+ pub fn right(&self) -> bool {
+ match self {
+ ClosePosition::Left => false,
+ ClosePosition::Right => true,
+ }
+ }
+}
+
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+pub struct ItemSettingsContent {
+ git_status: Option<bool>,
+ close_position: Option<ClosePosition>,
+}
+
+impl Setting for ItemSettings {
+ const KEY: Option<&'static str> = Some("tabs");
+
+ type FileContent = ItemSettingsContent;
+
+ fn load(
+ default_value: &Self::FileContent,
+ user_values: &[&Self::FileContent],
+ _: &gpui::AppContext,
+ ) -> anyhow::Result<Self> {
+ Self::load_via_json_merge(default_value, user_values)
+ }
+}
+
#[derive(Eq, PartialEq, Hash, Debug)]
pub enum ItemEvent {
CloseItem,
@@ -3,14 +3,16 @@ mod dragged_item_receiver;
use super::{ItemHandle, SplitDirection};
pub use crate::toolbar::Toolbar;
use crate::{
- item::WeakItemHandle, notify_of_new_dock, AutosaveSetting, Item, NewCenterTerminal, NewFile,
- NewSearch, ToggleZoom, Workspace, WorkspaceSettings,
+ item::{ItemSettings, WeakItemHandle},
+ notify_of_new_dock, AutosaveSetting, Item, NewCenterTerminal, NewFile, NewSearch, ToggleZoom,
+ Workspace, WorkspaceSettings,
};
use anyhow::Result;
use collections::{HashMap, HashSet, VecDeque};
use context_menu::{ContextMenu, ContextMenuItem};
use drag_and_drop::{DragAndDrop, Draggable};
use dragged_item_receiver::dragged_item_receiver;
+use fs::repository::GitFileStatus;
use futures::StreamExt;
use gpui::{
actions,
@@ -866,6 +868,7 @@ impl Pane {
.paths_by_item
.get(&item.id())
.and_then(|(_, abs_path)| abs_path.clone());
+
self.nav_history
.0
.borrow_mut()
@@ -1157,6 +1160,11 @@ impl Pane {
.zip(self.tab_details(cx))
.enumerate()
{
+ let git_status = item
+ .project_path(cx)
+ .and_then(|path| self.project.read(cx).entry_for_path(&path, cx))
+ .and_then(|entry| entry.git_status());
+
let detail = if detail == 0 { None } else { Some(detail) };
let tab_active = ix == self.active_item_index;
@@ -1174,9 +1182,21 @@ impl Pane {
let tab_tooltip_text =
item.tab_tooltip_text(cx).map(|text| text.into_owned());
+ let mut tab_style = theme
+ .workspace
+ .tab_bar
+ .tab_style(pane_active, tab_active)
+ .clone();
+ let should_show_status = settings::get::<ItemSettings>(cx).git_status;
+ if should_show_status && git_status != None {
+ tab_style.label.text.color = match git_status.unwrap() {
+ GitFileStatus::Added => tab_style.git.inserted,
+ GitFileStatus::Modified => tab_style.git.modified,
+ GitFileStatus::Conflict => tab_style.git.conflict,
+ };
+ }
+
move |mouse_state, cx| {
- let tab_style =
- theme.workspace.tab_bar.tab_style(pane_active, tab_active);
let hovered = mouse_state.hovered();
enum Tab {}
@@ -1188,7 +1208,7 @@ impl Pane {
ix == 0,
detail,
hovered,
- tab_style,
+ &tab_style,
cx,
)
})
@@ -1350,81 +1370,94 @@ impl Pane {
container.border.left = false;
}
- Flex::row()
- .with_child({
- let diameter = 7.0;
- let icon_color = if item.has_conflict(cx) {
- Some(tab_style.icon_conflict)
- } else if item.is_dirty(cx) {
- Some(tab_style.icon_dirty)
- } else {
- None
- };
+ let buffer_jewel_element = {
+ let diameter = 7.0;
+ let icon_color = if item.has_conflict(cx) {
+ Some(tab_style.icon_conflict)
+ } else if item.is_dirty(cx) {
+ Some(tab_style.icon_dirty)
+ } else {
+ None
+ };
- Canvas::new(move |scene, bounds, _, _, _| {
- if let Some(color) = icon_color {
- let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
- scene.push_quad(Quad {
- bounds: square,
- background: Some(color),
- border: Default::default(),
- corner_radius: diameter / 2.,
- });
- }
- })
- .constrained()
- .with_width(diameter)
- .with_height(diameter)
- .aligned()
+ Canvas::new(move |scene, bounds, _, _, _| {
+ if let Some(color) = icon_color {
+ let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
+ scene.push_quad(Quad {
+ bounds: square,
+ background: Some(color),
+ border: Default::default(),
+ corner_radius: diameter / 2.,
+ });
+ }
})
- .with_child(title.aligned().contained().with_style(ContainerStyle {
- margin: Margin {
- left: tab_style.spacing,
- right: tab_style.spacing,
- ..Default::default()
- },
+ .constrained()
+ .with_width(diameter)
+ .with_height(diameter)
+ .aligned()
+ };
+
+ let title_element = title.aligned().contained().with_style(ContainerStyle {
+ margin: Margin {
+ left: tab_style.spacing,
+ right: tab_style.spacing,
..Default::default()
- }))
- .with_child(
- if hovered {
- let item_id = item.id();
- enum TabCloseButton {}
- let icon = Svg::new("icons/x_mark_8.svg");
- MouseEventHandler::<TabCloseButton, _>::new(item_id, cx, |mouse_state, _| {
- if mouse_state.hovered() {
- icon.with_color(tab_style.icon_close_active)
- } else {
- icon.with_color(tab_style.icon_close)
- }
- })
- .with_padding(Padding::uniform(4.))
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, {
- let pane = pane.clone();
- move |_, _, cx| {
- let pane = pane.clone();
- cx.window_context().defer(move |cx| {
- if let Some(pane) = pane.upgrade(cx) {
- pane.update(cx, |pane, cx| {
- pane.close_item_by_id(item_id, cx).detach_and_log_err(cx);
- });
- }
+ },
+ ..Default::default()
+ });
+
+ let close_element = if hovered {
+ let item_id = item.id();
+ enum TabCloseButton {}
+ let icon = Svg::new("icons/x_mark_8.svg");
+ MouseEventHandler::<TabCloseButton, _>::new(item_id, cx, |mouse_state, _| {
+ if mouse_state.hovered() {
+ icon.with_color(tab_style.icon_close_active)
+ } else {
+ icon.with_color(tab_style.icon_close)
+ }
+ })
+ .with_padding(Padding::uniform(4.))
+ .with_cursor_style(CursorStyle::PointingHand)
+ .on_click(MouseButton::Left, {
+ let pane = pane.clone();
+ move |_, _, cx| {
+ let pane = pane.clone();
+ cx.window_context().defer(move |cx| {
+ if let Some(pane) = pane.upgrade(cx) {
+ pane.update(cx, |pane, cx| {
+ pane.close_item_by_id(item_id, cx).detach_and_log_err(cx);
});
}
- })
- .into_any_named("close-tab-icon")
- .constrained()
- } else {
- Empty::new().constrained()
+ });
}
- .with_width(tab_style.close_icon_width)
- .aligned(),
- )
- .contained()
- .with_style(container)
+ })
+ .into_any_named("close-tab-icon")
.constrained()
- .with_height(tab_style.height)
- .into_any()
+ } else {
+ Empty::new().constrained()
+ }
+ .with_width(tab_style.close_icon_width)
+ .aligned();
+
+ let close_right = settings::get::<ItemSettings>(cx).close_position.right();
+
+ if close_right {
+ Flex::row()
+ .with_child(buffer_jewel_element)
+ .with_child(title_element)
+ .with_child(close_element)
+ } else {
+ Flex::row()
+ .with_child(close_element)
+ .with_child(title_element)
+ .with_child(buffer_jewel_element)
+ }
+ .contained()
+ .with_style(container)
+ .constrained()
+ .with_height(tab_style.height)
+ .into_any()
}
pub fn render_tab_bar_button<
@@ -203,6 +203,7 @@ pub type WorkspaceId = i64;
pub fn init_settings(cx: &mut AppContext) {
settings::register::<WorkspaceSettings>(cx);
+ settings::register::<item::ItemSettings>(cx);
}
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
@@ -104,6 +104,7 @@ thiserror.workspace = true
tiny_http = "0.8"
toml.workspace = true
tree-sitter.workspace = true
+tree-sitter-bash.workspace = true
tree-sitter-c.workspace = true
tree-sitter-cpp.workspace = true
tree-sitter-css.workspace = true
@@ -119,6 +120,7 @@ tree-sitter-toml.workspace = true
tree-sitter-typescript.workspace = true
tree-sitter-ruby.workspace = true
tree-sitter-html.workspace = true
+tree-sitter-php.workspace = true
tree-sitter-scheme.workspace = true
tree-sitter-svelte.workspace = true
tree-sitter-racket.workspace = true
@@ -13,6 +13,7 @@ mod json;
#[cfg(feature = "plugin_runtime")]
mod language_plugin;
mod lua;
+mod php;
mod python;
mod ruby;
mod rust;
@@ -39,6 +40,7 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: Arc<NodeRuntime>) {
languages.register(name, load_config(name), grammar, adapters, load_queries)
};
+ language("bash", tree_sitter_bash::language(), vec![]);
language(
"c",
tree_sitter_c::language(),
@@ -145,6 +147,11 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: Arc<NodeRuntime>) {
node_runtime.clone(),
))],
);
+ language(
+ "php",
+ tree_sitter_php::language(),
+ vec![Arc::new(php::IntelephenseLspAdapter::new(node_runtime))],
+ );
}
#[cfg(any(test, feature = "test-support"))]
@@ -0,0 +1,3 @@
+("(" @open ")" @close)
+("[" @open "]" @close)
+("{" @open "}" @close)
@@ -0,0 +1,8 @@
+name = "Shell Script"
+path_suffixes = [".sh", ".bash", ".bashrc", ".bash_profile", ".bash_aliases", ".bash_logout", ".profile", ".zsh", ".zshrc", ".zshenv", ".zsh_profile", ".zsh_aliases", ".zsh_histfile", ".zlogin"]
+first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b"
+brackets = [
+ { start = "[", end = "]", close = true, newline = false },
+ { start = "(", end = ")", close = true, newline = false },
+ { start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] },
+]
@@ -0,0 +1,56 @@
+[
+ (string)
+ (raw_string)
+ (heredoc_body)
+ (heredoc_start)
+] @string
+
+(command_name) @function
+
+(variable_name) @property
+
+[
+ "case"
+ "do"
+ "done"
+ "elif"
+ "else"
+ "esac"
+ "export"
+ "fi"
+ "for"
+ "function"
+ "if"
+ "in"
+ "select"
+ "then"
+ "unset"
+ "until"
+ "while"
+] @keyword
+
+(comment) @comment
+
+(function_definition name: (word) @function)
+
+(file_descriptor) @number
+
+[
+ (command_substitution)
+ (process_substitution)
+ (expansion)
+]@embedded
+
+[
+ "$"
+ "&&"
+ ">"
+ ">>"
+ "<"
+ "|"
+] @operator
+
+(
+ (command (_) @constant)
+ (#match? @constant "^-")
+)
@@ -0,0 +1,133 @@
+use anyhow::{anyhow, Result};
+
+use async_trait::async_trait;
+use collections::HashMap;
+
+use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
+use lsp::LanguageServerBinary;
+use node_runtime::NodeRuntime;
+
+use smol::{fs, stream::StreamExt};
+use std::{
+ any::Any,
+ ffi::OsString,
+ path::{Path, PathBuf},
+ sync::Arc,
+};
+use util::ResultExt;
+
+fn intelephense_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
+ vec![server_path.into(), "--stdio".into()]
+}
+
+pub struct IntelephenseVersion(String);
+
+pub struct IntelephenseLspAdapter {
+ node: Arc<NodeRuntime>,
+}
+
+impl IntelephenseLspAdapter {
+ const SERVER_PATH: &'static str = "node_modules/intelephense/lib/intelephense.js";
+
+ #[allow(unused)]
+ pub fn new(node: Arc<NodeRuntime>) -> Self {
+ Self { node }
+ }
+}
+
+#[async_trait]
+impl LspAdapter for IntelephenseLspAdapter {
+ async fn name(&self) -> LanguageServerName {
+ LanguageServerName("intelephense".into())
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ _delegate: &dyn LspAdapterDelegate,
+ ) -> Result<Box<dyn 'static + Send + Any>> {
+ Ok(Box::new(IntelephenseVersion(
+ self.node.npm_package_latest_version("intelephense").await?,
+ )) as Box<_>)
+ }
+
+ async fn fetch_server_binary(
+ &self,
+ version: Box<dyn 'static + Send + Any>,
+ container_dir: PathBuf,
+ _delegate: &dyn LspAdapterDelegate,
+ ) -> Result<LanguageServerBinary> {
+ let version = version.downcast::<IntelephenseVersion>().unwrap();
+ let server_path = container_dir.join(Self::SERVER_PATH);
+
+ if fs::metadata(&server_path).await.is_err() {
+ self.node
+ .npm_install_packages(&container_dir, [("intelephense", version.0.as_str())])
+ .await?;
+ }
+ Ok(LanguageServerBinary {
+ path: self.node.binary_path().await?,
+ arguments: intelephense_server_binary_arguments(&server_path),
+ })
+ }
+
+ async fn cached_server_binary(
+ &self,
+ container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Option<LanguageServerBinary> {
+ get_cached_server_binary(container_dir, &self.node).await
+ }
+
+ async fn installation_test_binary(
+ &self,
+ container_dir: PathBuf,
+ ) -> Option<LanguageServerBinary> {
+ get_cached_server_binary(container_dir, &self.node).await
+ }
+
+ async fn label_for_completion(
+ &self,
+ _item: &lsp::CompletionItem,
+ _language: &Arc<language::Language>,
+ ) -> Option<language::CodeLabel> {
+ None
+ }
+
+ async fn initialization_options(&self) -> Option<serde_json::Value> {
+ None
+ }
+ async fn language_ids(&self) -> HashMap<String, String> {
+ HashMap::from_iter([("PHP".into(), "php".into())])
+ }
+}
+
+async fn get_cached_server_binary(
+ container_dir: PathBuf,
+ node: &NodeRuntime,
+) -> Option<LanguageServerBinary> {
+ (|| async move {
+ let mut last_version_dir = None;
+ let mut entries = fs::read_dir(&container_dir).await?;
+ while let Some(entry) = entries.next().await {
+ let entry = entry?;
+ if entry.file_type().await?.is_dir() {
+ last_version_dir = Some(entry.path());
+ }
+ }
+ let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
+ let server_path = last_version_dir.join(IntelephenseLspAdapter::SERVER_PATH);
+ if server_path.exists() {
+ Ok(LanguageServerBinary {
+ path: node.binary_path().await?,
+ arguments: intelephense_server_binary_arguments(&server_path),
+ })
+ } else {
+ Err(anyhow!(
+ "missing executable in directory {:?}",
+ last_version_dir
+ ))
+ }
+ })()
+ .await
+ .log_err()
+}
@@ -0,0 +1,11 @@
+name = "PHP"
+path_suffixes = ["php"]
+first_line_pattern = '^#!.*php'
+line_comment = "// "
+autoclose_before = ";:.,=}])>"
+brackets = [
+ { start = "{", end = "}", close = true, newline = true },
+ { start = "[", end = "]", close = true, newline = true },
+ { start = "(", end = ")", close = true, newline = true },
+ { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
+]
@@ -0,0 +1,123 @@
+(php_tag) @tag
+"?>" @tag
+
+; Types
+
+(primitive_type) @type.builtin
+(cast_type) @type.builtin
+(named_type (name) @type) @type
+(named_type (qualified_name) @type) @type
+
+; Functions
+
+(array_creation_expression "array" @function.builtin)
+(list_literal "list" @function.builtin)
+
+(method_declaration
+ name: (name) @function.method)
+
+(function_call_expression
+ function: [(qualified_name (name)) (name)] @function)
+
+(scoped_call_expression
+ name: (name) @function)
+
+(member_call_expression
+ name: (name) @function.method)
+
+(function_definition
+ name: (name) @function)
+
+; Member
+
+(property_element
+ (variable_name) @property)
+
+(member_access_expression
+ name: (variable_name (name)) @property)
+(member_access_expression
+ name: (name) @property)
+
+; Variables
+
+(relative_scope) @variable.builtin
+
+((name) @constant
+ (#match? @constant "^_?[A-Z][A-Z\\d_]+$"))
+((name) @constant.builtin
+ (#match? @constant.builtin "^__[A-Z][A-Z\d_]+__$"))
+
+((name) @constructor
+ (#match? @constructor "^[A-Z]"))
+
+((name) @variable.builtin
+ (#eq? @variable.builtin "this"))
+
+(variable_name) @variable
+
+; Basic tokens
+[
+ (string)
+ (string_value)
+ (encapsed_string)
+ (heredoc)
+ (heredoc_body)
+ (nowdoc_body)
+] @string
+(boolean) @constant.builtin
+(null) @constant.builtin
+(integer) @number
+(float) @number
+(comment) @comment
+
+"$" @operator
+
+; Keywords
+
+"abstract" @keyword
+"as" @keyword
+"break" @keyword
+"case" @keyword
+"catch" @keyword
+"class" @keyword
+"const" @keyword
+"continue" @keyword
+"declare" @keyword
+"default" @keyword
+"do" @keyword
+"echo" @keyword
+"else" @keyword
+"elseif" @keyword
+"enum" @keyword
+"enddeclare" @keyword
+"endforeach" @keyword
+"endif" @keyword
+"endswitch" @keyword
+"endwhile" @keyword
+"extends" @keyword
+"final" @keyword
+"finally" @keyword
+"foreach" @keyword
+"function" @keyword
+"global" @keyword
+"if" @keyword
+"implements" @keyword
+"include_once" @keyword
+"include" @keyword
+"insteadof" @keyword
+"interface" @keyword
+"namespace" @keyword
+"new" @keyword
+"private" @keyword
+"protected" @keyword
+"public" @keyword
+"require_once" @keyword
+"require" @keyword
+"return" @keyword
+"static" @keyword
+"switch" @keyword
+"throw" @keyword
+"trait" @keyword
+"try" @keyword
+"use" @keyword
+"while" @keyword
@@ -0,0 +1,3 @@
+((text) @content
+ (#set! "language" "html")
+ (#set! "combined"))
@@ -0,0 +1,26 @@
+(class_declaration
+ "class" @context
+ name: (name) @name
+ ) @item
+
+(function_definition
+ "function" @context
+ name: (_) @name
+ ) @item
+
+
+
+(method_declaration
+ "function" @context
+ name: (_) @name
+ ) @item
+
+(interface_declaration
+ "interface" @context
+ name: (_) @name
+ ) @item
+
+(enum_declaration
+ "enum" @context
+ name: (_) @name
+ ) @item
@@ -0,0 +1,40 @@
+(namespace_definition
+ name: (namespace_name) @name) @module
+
+(interface_declaration
+ name: (name) @name) @definition.interface
+
+(trait_declaration
+ name: (name) @name) @definition.interface
+
+(class_declaration
+ name: (name) @name) @definition.class
+
+(class_interface_clause [(name) (qualified_name)] @name) @impl
+
+(property_declaration
+ (property_element (variable_name (name) @name))) @definition.field
+
+(function_definition
+ name: (name) @name) @definition.function
+
+(method_declaration
+ name: (name) @name) @definition.function
+
+(object_creation_expression
+ [
+ (qualified_name (name) @name)
+ (variable_name (name) @name)
+ ]) @reference.class
+
+(function_call_expression
+ function: [
+ (qualified_name (name) @name)
+ (variable_name (name)) @name
+ ]) @reference.call
+
+(scoped_call_expression
+ name: (name) @name) @reference.call
+
+(member_call_expression
+ name: (name) @name) @reference.call
@@ -6,4 +6,4 @@
(function_definition
"async"? @context
"def" @context
- name: (_) @name) @item
+ name: (_) @name) @item
@@ -36,7 +36,7 @@ use std::{
path::{Path, PathBuf},
str,
sync::{
- atomic::{AtomicBool, Ordering},
+ atomic::{AtomicBool, AtomicU32, Ordering},
Arc, Weak,
},
thread,
@@ -405,11 +405,18 @@ struct PanicRequest {
token: String,
}
+static PANIC_COUNT: AtomicU32 = AtomicU32::new(0);
+
fn init_panic_hook(app: &App, installation_id: Option<String>) {
let is_pty = stdout_is_a_pty();
let platform = app.platform();
panic::set_hook(Box::new(move |info| {
+ let prior_panic_count = PANIC_COUNT.fetch_add(1, Ordering::SeqCst);
+ if prior_panic_count > 0 {
+ std::panic::resume_unwind(Box::new(()));
+ }
+
let app_version = ZED_APP_VERSION
.or_else(|| platform.app_version().ok())
.map_or("dev".to_string(), |v| v.to_string());
@@ -464,7 +471,6 @@ fn init_panic_hook(app: &App, installation_id: Option<String>) {
if is_pty {
if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() {
eprintln!("{}", panic_data_json);
- return;
}
} else {
if let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() {
@@ -481,6 +487,8 @@ fn init_panic_hook(app: &App, installation_id: Option<String>) {
}
}
}
+
+ std::process::abort();
}));
}
@@ -887,7 +895,14 @@ pub fn dock_default_item_factory(
})
.notify_err(workspace, cx)?;
- let terminal_view = cx.add_view(|cx| TerminalView::new(terminal, workspace.database_id(), cx));
+ let terminal_view = cx.add_view(|cx| {
+ TerminalView::new(
+ terminal,
+ workspace.weak_handle(),
+ workspace.database_id(),
+ cx,
+ )
+ });
Some(Box::new(terminal_view))
}
@@ -517,11 +517,7 @@ pub fn handle_keymap_file_changes(
let mut settings_subscription = None;
while let Some(user_keymap_content) = user_keymap_file_rx.next().await {
if let Ok(keymap_content) = KeymapFile::parse(&user_keymap_content) {
- cx.update(|cx| {
- cx.clear_bindings();
- load_default_keymap(cx);
- keymap_content.clone().add_to_cx(cx).log_err();
- });
+ cx.update(|cx| reload_keymaps(cx, &keymap_content));
let mut old_base_keymap = cx.read(|cx| *settings::get::<BaseKeymap>(cx));
drop(settings_subscription);
@@ -530,10 +526,7 @@ pub fn handle_keymap_file_changes(
let new_base_keymap = *settings::get::<BaseKeymap>(cx);
if new_base_keymap != old_base_keymap {
old_base_keymap = new_base_keymap.clone();
-
- cx.clear_bindings();
- load_default_keymap(cx);
- keymap_content.clone().add_to_cx(cx).log_err();
+ reload_keymaps(cx, &keymap_content);
}
})
.detach();
@@ -544,6 +537,13 @@ pub fn handle_keymap_file_changes(
.detach();
}
+fn reload_keymaps(cx: &mut AppContext, keymap_content: &KeymapFile) {
+ cx.clear_bindings();
+ load_default_keymap(cx);
+ keymap_content.clone().add_to_cx(cx).log_err();
+ cx.set_menus(menus::menus());
+}
+
fn open_local_settings_file(
workspace: &mut Workspace,
_: &OpenLocalSettings,
@@ -33,6 +33,11 @@ export default function feedback(): any {
background: background(theme.highest, "on", "hovered"),
border: border(theme.highest, "on", "hovered"),
},
+ disabled: {
+ ...text(theme.highest, "mono", "on", "disabled"),
+ background: background(theme.highest, "on", "disabled"),
+ border: border(theme.highest, "on", "disabled"),
+ }
},
}),
button_margin: 8,
@@ -6,6 +6,8 @@ import { useTheme } from "../common"
export default function tab_bar(): any {
const theme = useTheme()
+ const { is_light } = theme
+
const height = 32
const active_layer = theme.highest
@@ -38,6 +40,18 @@ export default function tab_bar(): any {
icon_conflict: foreground(layer, "warning"),
icon_dirty: foreground(layer, "accent"),
+ git: {
+ modified: is_light
+ ? theme.ramps.yellow(0.6).hex()
+ : theme.ramps.yellow(0.5).hex(),
+ inserted: is_light
+ ? theme.ramps.green(0.45).hex()
+ : theme.ramps.green(0.5).hex(),
+ conflict: is_light
+ ? theme.ramps.red(0.6).hex()
+ : theme.ramps.red(0.5).hex(),
+ },
+
// When two tabs of the same name are open, a label appears next to them
description: {
margin: { left: 8 },