Compare commits

..

1 Commits

Author SHA1 Message Date
Conrad Irwin
408fe83b90 linux:Add zed:// url support 2024-07-10 14:45:41 -06:00
156 changed files with 3084 additions and 7523 deletions

View File

@@ -1,24 +0,0 @@
# .git-blame-ignore-revs
#
# This file consists of a list of commits that should be ignored for
# `git blame` purposes. This is useful for ignoring commits that only
# changed whitespace / indentation / formatting, but did not change
# the underlying syntax tree.
#
# GitHub will pick this up automatically for blame views:
# https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view
# To use this file locally, run:
# git blame --ignore-revs-file .git-blame-ignore-revs
# To always use this file by default, run:
# git config --local blame.ignoreRevsFile .git-blame-ignore-revs
# To disable this functionality, run:
# git config --local blame.ignoreRevsFile ""
# Comments are optional, but may provide helpful context.
# 2023-04-20 Set default tab_size for JSON to 2 and apply new formatting
# https://github.com/zed-industries/zed/pull/2394
eca93c124a488b4e538946cd2d313bd571aa2b86
# 2024-07-05 Improved formatting of default keymaps (single line per bind)
# https://github.com/zed-industries/zed/pull/13887
813cc3f5e537372fc86720b5e71b6e1c815440ab

187
Cargo.lock generated
View File

@@ -87,7 +87,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6d1ea4484c8676f295307a4892d478c70ac8da1dbd8c7c10830a504b7f1022f"
dependencies = [
"base64 0.22.0",
"bitflags 2.6.0",
"bitflags 2.4.2",
"home",
"libc",
"log",
@@ -110,7 +110,7 @@ version = "0.24.1-dev"
source = "git+https://github.com/alacritty/alacritty?rev=cacdb5bb3b72bad2c729227537979d95af75978f#cacdb5bb3b72bad2c729227537979d95af75978f"
dependencies = [
"base64 0.22.0",
"bitflags 2.6.0",
"bitflags 2.4.2",
"home",
"libc",
"log",
@@ -1583,16 +1583,7 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [
"bit-vec 0.6.3",
]
[[package]]
name = "bit-set"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f"
dependencies = [
"bit-vec 0.7.0",
"bit-vec",
]
[[package]]
@@ -1601,12 +1592,6 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bit-vec"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22"
[[package]]
name = "bit_field"
version = "0.10.2"
@@ -1621,9 +1606,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.6.0"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
dependencies = [
"serde",
]
@@ -1649,11 +1634,11 @@ dependencies = [
[[package]]
name = "blade-graphics"
version = "0.4.0"
source = "git+https://github.com/zed-industries/blade?rev=a477c2008db27db0b9f745715e119b3ee7ab7818#a477c2008db27db0b9f745715e119b3ee7ab7818"
source = "git+https://github.com/kvark/blade?rev=21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7#21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7"
dependencies = [
"ash",
"ash-window",
"bitflags 2.6.0",
"bitflags 2.4.2",
"block",
"bytemuck",
"codespan-reporting",
@@ -1679,7 +1664,7 @@ dependencies = [
[[package]]
name = "blade-macros"
version = "0.2.1"
source = "git+https://github.com/zed-industries/blade?rev=a477c2008db27db0b9f745715e119b3ee7ab7818#a477c2008db27db0b9f745715e119b3ee7ab7818"
source = "git+https://github.com/kvark/blade?rev=21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7#21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7"
dependencies = [
"proc-macro2",
"quote",
@@ -1689,7 +1674,7 @@ dependencies = [
[[package]]
name = "blade-util"
version = "0.1.0"
source = "git+https://github.com/zed-industries/blade?rev=a477c2008db27db0b9f745715e119b3ee7ab7818#a477c2008db27db0b9f745715e119b3ee7ab7818"
source = "git+https://github.com/kvark/blade?rev=21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7#21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7"
dependencies = [
"blade-graphics",
"bytemuck",
@@ -1937,7 +1922,7 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"log",
"polling 3.3.2",
"rustix 0.38.32",
@@ -2406,6 +2391,16 @@ dependencies = [
"worktree",
]
[[package]]
name = "clipboard-win"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342"
dependencies = [
"lazy-bytes-cast",
"winapi",
]
[[package]]
name = "clock"
version = "0.1.0"
@@ -2864,7 +2859,7 @@ name = "cosmic-text"
version = "0.11.2"
source = "git+https://github.com/pop-os/cosmic-text?rev=542b20c#542b20ca4376a3b5de5fa629db1a4ace44e18e0c"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"fontdb",
"log",
"rangemap",
@@ -3610,7 +3605,6 @@ dependencies = [
"linkify",
"log",
"lsp",
"markdown",
"multi_buffer",
"ordered-float 2.10.0",
"parking_lot",
@@ -4018,7 +4012,7 @@ version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7493d4c459da9f84325ad297371a6b2b8a162800873a22e3b6b6512e61d18c05"
dependencies = [
"bit-set 0.5.3",
"bit-set",
"regex",
]
@@ -4075,7 +4069,7 @@ name = "feedback"
version = "0.1.0"
dependencies = [
"anyhow",
"bitflags 2.6.0",
"bitflags 2.4.2",
"client",
"db",
"editor",
@@ -4275,7 +4269,7 @@ checksum = "e32eac81c1135c1df01d4e6d4233c47ba11f6a6d07f33e0bba09d18797077770"
dependencies = [
"fontconfig-parser",
"log",
"memmap2",
"memmap2 0.9.4",
"slotmap",
"tinyvec",
"ttf-parser",
@@ -4409,7 +4403,7 @@ dependencies = [
name = "fsevent"
version = "0.1.0"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"core-foundation",
"fsevent-sys 3.1.0",
"parking_lot",
@@ -4720,7 +4714,7 @@ version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"libc",
"libgit2-sys",
"log",
@@ -4819,7 +4813,6 @@ name = "google_ai"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"futures 0.3.28",
"http 0.1.0",
"serde",
@@ -4832,7 +4825,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"gpu-alloc-types",
]
@@ -4853,7 +4846,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
]
[[package]]
@@ -4874,6 +4867,7 @@ dependencies = [
"calloop",
"calloop-wayland-source",
"cbindgen",
"clipboard-win",
"cocoa",
"collections",
"core-foundation",
@@ -5109,7 +5103,7 @@ version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f7acb9683d7c7068aa46d47557bfa4e35a277964b350d9504a87b03610163fd"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"byteorder",
"heed-traits",
"heed-types",
@@ -6058,6 +6052,12 @@ dependencies = [
"workspace",
]
[[package]]
name = "lazy-bytes-cast"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b"
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -6524,6 +6524,15 @@ dependencies = [
"rustix 0.38.32",
]
[[package]]
name = "memmap2"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed"
dependencies = [
"libc",
]
[[package]]
name = "memmap2"
version = "0.9.4"
@@ -6657,17 +6666,17 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
[[package]]
name = "naga"
version = "0.20.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=425526828f738c95ec50b016c6a761bc00d2fb25#425526828f738c95ec50b016c6a761bc00d2fb25"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae585df4b6514cf8842ac0f1ab4992edc975892704835b549cf818dc0191249e"
dependencies = [
"arrayvec",
"bit-set 0.6.0",
"bitflags 2.6.0",
"cfg_aliases",
"bit-set",
"bitflags 2.4.2",
"codespan-reporting",
"hexf-parse",
"indexmap 2.2.6",
"log",
"num-traits",
"rustc-hash",
"spirv",
"termcolor",
@@ -6763,7 +6772,7 @@ version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"cfg-if",
"libc",
"memoffset",
@@ -6775,7 +6784,7 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"cfg-if",
"cfg_aliases",
"libc",
@@ -6844,7 +6853,7 @@ version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"crossbeam-channel",
"filetime",
"fsevent-sys 4.1.0",
@@ -7057,15 +7066,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
name = "nvim-rs"
version = "0.6.0-pre"
@@ -7215,7 +7215,7 @@ version = "0.10.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"cfg-if",
"foreign-types 0.3.2",
"libc",
@@ -8183,7 +8183,6 @@ version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"futures 0.3.28",
"prost",
"prost-build",
"serde",
@@ -8230,7 +8229,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce76ce678ffc8e5675b22aa1405de0b7037e2fdf8913fea40d1926c6fe1e6e7"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"memchr",
"unicase",
]
@@ -8664,7 +8663,6 @@ dependencies = [
"image",
"language",
"log",
"multi_buffer",
"project",
"runtimelib",
"schemars",
@@ -9043,7 +9041,7 @@ version = "0.38.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"errno 0.3.8",
"itoa",
"libc",
@@ -9118,7 +9116,7 @@ version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"bytemuck",
"libm",
"smallvec",
@@ -9341,7 +9339,7 @@ version = "0.1.0"
dependencies = [
"any_vec",
"anyhow",
"bitflags 2.6.0",
"bitflags 2.4.2",
"client",
"collections",
"editor",
@@ -9769,13 +9767,13 @@ dependencies = [
[[package]]
name = "simplelog"
version = "0.12.2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0"
checksum = "4bc0ffd69814a9b251d43afcabf96dad1b29f5028378056257be9e3fecc9f720"
dependencies = [
"chrono",
"log",
"termcolor",
"time",
]
[[package]]
@@ -9928,11 +9926,12 @@ dependencies = [
[[package]]
name = "spirv"
version = "0.3.0+sdk-1.3.268.0"
version = "0.2.0+1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830"
dependencies = [
"bitflags 2.6.0",
"bitflags 1.3.2",
"num-traits",
]
[[package]]
@@ -10108,7 +10107,7 @@ dependencies = [
"atoi",
"base64 0.21.7",
"bigdecimal",
"bitflags 2.6.0",
"bitflags 2.4.2",
"byteorder",
"bytes 1.5.0",
"chrono",
@@ -10155,7 +10154,7 @@ dependencies = [
"atoi",
"base64 0.21.7",
"bigdecimal",
"bitflags 2.6.0",
"bitflags 2.4.2",
"byteorder",
"chrono",
"crc",
@@ -10577,7 +10576,7 @@ version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aef1f9d4c1dbdd1cb3a63be9efd2f04d8ddbc919d46112982c76818ffc2f1a7"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"cap-fs-ext",
"cap-std",
"fd-lock",
@@ -10718,9 +10717,9 @@ dependencies = [
[[package]]
name = "termcolor"
version = "1.4.1"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
@@ -10737,7 +10736,6 @@ dependencies = [
"gpui",
"libc",
"rand 0.8.5",
"release_channel",
"schemars",
"serde",
"serde_derive",
@@ -10881,18 +10879,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.61"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.61"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
dependencies = [
"proc-macro2",
"quote",
@@ -10943,9 +10941,7 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [
"deranged",
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
@@ -11304,7 +11300,7 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"bytes 1.5.0",
"futures-core",
"futures-util",
@@ -12075,7 +12071,7 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40eb22ae96f050e0c0d6f7ce43feeae26c348fc4dea56928ca81537cfaa6188b"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"cursor-icon",
"log",
"serde",
@@ -12227,7 +12223,7 @@ version = "0.201.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"indexmap 2.2.6",
"semver",
]
@@ -12494,7 +12490,7 @@ checksum = "371d828b6849ea06d598ae7dd1c316e8dd9e99b76f77d93d5886cb25c7f8e188"
dependencies = [
"anyhow",
"async-trait",
"bitflags 2.6.0",
"bitflags 2.4.2",
"bytes 1.5.0",
"cap-fs-ext",
"cap-net-ext",
@@ -12581,7 +12577,7 @@ version = "0.31.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"rustix 0.38.32",
"wayland-backend",
"wayland-scanner",
@@ -12604,7 +12600,7 @@ version = "0.31.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"wayland-backend",
"wayland-client",
"wayland-scanner",
@@ -12616,7 +12612,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"wayland-backend",
"wayland-client",
"wayland-protocols",
@@ -12735,7 +12731,7 @@ checksum = "ae1136a209614ace00b0c11f04dc7cf42540773be3b22eff6ad165110aba29c1"
dependencies = [
"anyhow",
"async-trait",
"bitflags 2.6.0",
"bitflags 2.4.2",
"thiserror",
"tracing",
"wasmtime",
@@ -13165,7 +13161,7 @@ version = "0.36.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9643b83820c0cd246ecabe5fa454dd04ba4fa67996369466d0747472d337346"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"windows-sys 0.52.0",
]
@@ -13184,7 +13180,7 @@ version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "288f992ea30e6b5c531b52cdd5f3be81c148554b09ea416f058d16556ba92c27"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
"wit-bindgen-rt",
"wit-bindgen-rust-macro",
]
@@ -13240,7 +13236,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825"
dependencies = [
"anyhow",
"bitflags 2.6.0",
"bitflags 2.4.2",
"indexmap 2.2.6",
"log",
"serde",
@@ -13446,17 +13442,18 @@ name = "xim-parser"
version = "0.2.1"
source = "git+https://github.com/npmania/xim-rs?rev=27132caffc5b9bc9c432ca4afad184ab6e7c16af#27132caffc5b9bc9c432ca4afad184ab6e7c16af"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.4.2",
]
[[package]]
name = "xkbcommon"
version = "0.7.0"
source = "git+https://github.com/ConradIrwin/xkbcommon-rs?rev=2d4c4439160c7846ede0f0ece93bf73b1e613339#2d4c4439160c7846ede0f0ece93bf73b1e613339"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e"
dependencies = [
"as-raw-xcb-connection",
"libc",
"memmap2",
"memmap2 0.8.0",
"xkeysym",
]
@@ -13717,7 +13714,7 @@ dependencies = [
[[package]]
name = "zed_dart"
version = "0.0.3"
version = "0.0.2"
dependencies = [
"zed_extension_api 0.0.6",
]
@@ -13867,7 +13864,7 @@ dependencies = [
[[package]]
name = "zed_svelte"
version = "0.0.2"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.6",
]

View File

@@ -284,10 +284,10 @@ async-trait = "0.1"
async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.13"
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/zed-industries/blade", rev = "a477c2008db27db0b9f745715e119b3ee7ab7818" }
blade-macros = { git = "https://github.com/zed-industries/blade", rev = "a477c2008db27db0b9f745715e119b3ee7ab7818" }
blade-util = { git = "https://github.com/zed-industries/blade", rev = "a477c2008db27db0b9f745715e119b3ee7ab7818" }
bitflags = "2.4.2"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
blade-util = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
cap-std = "3.0"
cargo_toml = "0.20"
chrono = { version = "0.4", features = ["serde"] }
@@ -365,7 +365,6 @@ shellexpand = "2.1.0"
shlex = "1.3.0"
signal-hook = "0.3.17"
similar = "1.3"
simplelog = "0.12.2"
smallvec = { version = "1.6", features = ["union"] }
smol = "1.2"
strum = { version = "0.25.0", features = ["derive"] }
@@ -452,7 +451,6 @@ features = [
"Win32_System_Com_StructuredStorage",
"Win32_System_DataExchange",
"Win32_System_LibraryLoader",
"Win32_System_Memory",
"Win32_System_Ole",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",

View File

@@ -100,7 +100,6 @@
"ctrl-k ctrl-r": "editor::RevertSelectedHunks",
"ctrl-'": "editor::ToggleHunkDiff",
"ctrl-\"": "editor::ExpandAllHunkDiffs",
"ctrl-i": "editor::ShowSignatureHelp",
"alt-g b": "editor::ToggleGitBlame"
}
},
@@ -398,7 +397,7 @@
"bindings": {
"ctrl-shift-k": "editor::DeleteLine",
"ctrl-shift-d": "editor::DuplicateLineDown",
"ctrl-shift-j": "editor::JoinLines",
"ctrl-j": "editor::JoinLines",
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
"ctrl-alt-delete": "editor::DeleteToNextSubwordEnd",

View File

@@ -126,8 +126,7 @@
"cmd-alt-z": "editor::RevertSelectedHunks",
"cmd-'": "editor::ToggleHunkDiff",
"cmd-\"": "editor::ExpandAllHunkDiffs",
"cmd-alt-g b": "editor::ToggleGitBlame",
"cmd-i": "editor::ShowSignatureHelp"
"cmd-alt-g b": "editor::ToggleGitBlame"
}
},
{

View File

@@ -24,8 +24,6 @@
"ctrl-shift-f12": "editor::FindAllReferences",
"ctrl-.": "editor::GoToHunk",
"ctrl-,": "editor::GoToPrevHunk",
"ctrl-k ctrl-u": "editor::ConvertToUpperCase",
"ctrl-k ctrl-l": "editor::ConvertToLowerCase",
"shift-alt-m": "markdown::OpenPreviewToTheSide",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd"

View File

@@ -27,7 +27,6 @@
"ctrl-,": "editor::GoToPrevHunk",
"cmd-k cmd-u": "editor::ConvertToUpperCase",
"cmd-k cmd-l": "editor::ConvertToLowerCase",
"cmd-shift-j": "editor::JoinLines",
"shift-alt-m": "markdown::OpenPreviewToTheSide",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd"

View File

@@ -1,6 +1,12 @@
[
{
"context": "VimControl && !menu",
"context": "ProjectPanel || Editor",
"bindings": {
"ctrl-6": "pane::AlternateFile"
}
},
{
"context": "Editor && VimControl && !VimWaiting && !menu",
"bindings": {
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }],
@@ -12,8 +18,6 @@
"down": "vim::Down",
"enter": "vim::NextLineStart",
"ctrl-m": "vim::NextLineStart",
"+": "vim::NextLineStart",
"-": "vim::PreviousLineStart",
"tab": "vim::Tab",
"shift-tab": "vim::Tab",
"k": "vim::Up",
@@ -194,20 +198,20 @@
"ctrl-w g shift-d": "editor::GoToTypeDefinitionSplit",
"ctrl-w space": "editor::OpenExcerptsSplit",
"ctrl-w g space": "editor::OpenExcerptsSplit",
"ctrl-6": "pane::AlternateFile"
"-": "pane::RevealInProjectPanel"
}
},
{
"context": "VimControl && VimCount",
"bindings": {
"0": ["vim::Number", 0]
}
},
{
"context": "vim_mode == normal",
// escape is in its own section so that it cancels a pending count.
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
"bindings": {
"escape": "editor::Cancel",
"ctrl-[": "editor::Cancel",
"ctrl-[": "editor::Cancel"
}
},
{
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
"bindings": {
".": "vim::Repeat",
"c": ["vim::PushOperator", "Change"],
"shift-c": "vim::ChangeToEndOfLine",
@@ -251,12 +255,127 @@
"] d": "editor::GoToDiagnostic",
"[ d": "editor::GoToPrevDiagnostic",
"] c": "editor::GoToHunk",
"[ c": "editor::GoToPrevHunk",
"g c c": "vim::ToggleComments"
"[ c": "editor::GoToPrevHunk"
}
},
{
"context": "vim_mode == visual",
"context": "Editor && vim_mode == visual && vim_operator == none && !VimWaiting",
"bindings": {
"\"": ["vim::PushOperator", "Register"],
// tree-sitter related commands
"[ x": "editor::SelectLargerSyntaxNode",
"] x": "editor::SelectSmallerSyntaxNode"
}
},
{
"context": "Editor && VimCount && vim_mode != insert",
"bindings": {
"0": ["vim::Number", 0]
}
},
{
"context": "Editor && vim_operator == c",
"bindings": {
"c": "vim::CurrentLine",
"d": "editor::Rename" // zed specific
}
},
{
"context": "Editor && vim_mode == normal && vim_operator == c",
"bindings": {
"s": ["vim::PushOperator", { "ChangeSurrounds": {} }]
}
},
{
"context": "Editor && vim_operator == d",
"bindings": {
"d": "vim::CurrentLine"
}
},
{
"context": "Editor && vim_operator == gu",
"bindings": {
"g u": "vim::CurrentLine",
"u": "vim::CurrentLine"
}
},
{
"context": "Editor && vim_operator == gU",
"bindings": {
"g shift-u": "vim::CurrentLine",
"shift-u": "vim::CurrentLine"
}
},
{
"context": "Editor && vim_operator == g~",
"bindings": {
"g ~": "vim::CurrentLine",
"~": "vim::CurrentLine"
}
},
{
"context": "Editor && vim_mode == normal && vim_operator == d",
"bindings": {
"s": ["vim::PushOperator", "DeleteSurrounds"]
}
},
{
"context": "Editor && vim_operator == y",
"bindings": {
"y": "vim::CurrentLine"
}
},
{
"context": "Editor && vim_mode == normal && vim_operator == y",
"bindings": {
"s": ["vim::PushOperator", { "AddSurrounds": {} }]
}
},
{
"context": "Editor && vim_operator == ys",
"bindings": {
"s": "vim::CurrentLine"
}
},
{
"context": "Editor && vim_operator == >",
"bindings": {
">": "vim::CurrentLine"
}
},
{
"context": "Editor && vim_operator == <",
"bindings": {
"<": "vim::CurrentLine"
}
},
{
"context": "Editor && VimObject",
"bindings": {
"w": "vim::Word",
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
"t": "vim::Tag",
"s": "vim::Sentence",
"p": "vim::Paragraph",
"'": "vim::Quotes",
"`": "vim::BackQuotes",
"\"": "vim::DoubleQuotes",
"|": "vim::VerticalBars",
"(": "vim::Parentheses",
")": "vim::Parentheses",
"b": "vim::Parentheses",
"[": "vim::SquareBrackets",
"]": "vim::SquareBrackets",
"{": "vim::CurlyBrackets",
"}": "vim::CurlyBrackets",
"shift-b": "vim::CurlyBrackets",
"<": "vim::AngleBrackets",
">": "vim::AngleBrackets",
"a": "vim::Argument"
}
},
{
"context": "Editor && vim_mode == visual && !VimWaiting && !VimObject",
"bindings": {
"u": "vim::ConvertToLowerCase",
"U": "vim::ConvertToUpperCase",
@@ -291,16 +410,23 @@
">": "vim::Indent",
"<": "vim::Outdent",
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }],
"g c": "vim::ToggleComments",
"\"": ["vim::PushOperator", "Register"],
// tree-sitter related commands
"[ x": "editor::SelectLargerSyntaxNode",
"] x": "editor::SelectSmallerSyntaxNode"
"a": ["vim::PushOperator", { "Object": { "around": true } }]
}
},
{
"context": "vim_mode == insert",
"context": "Editor && vim_mode == normal && !VimWaiting",
"bindings": {
"g c c": "vim::ToggleComments"
}
},
{
"context": "Editor && vim_mode == visual",
"bindings": {
"g c": "vim::ToggleComments"
}
},
{
"context": "Editor && vim_mode == insert",
"bindings": {
"escape": "vim::NormalBefore",
"ctrl-c": "vim::NormalBefore",
@@ -319,115 +445,30 @@
}
},
{
"context": "vim_mode == replace",
"context": "Editor && vim_mode == replace",
"bindings": {
"escape": "vim::NormalBefore",
"ctrl-c": "vim::NormalBefore",
"ctrl-[": "vim::NormalBefore",
"backspace": "vim::UndoReplace",
"tab": "vim::Tab",
"enter": "vim::Enter"
"enter": "vim::Enter",
"backspace": "vim::UndoReplace"
}
},
{
"context": "vim_mode == waiting",
"context": "Editor && vim_mode != replace && VimWaiting",
"bindings": {
"tab": "vim::Tab",
"enter": "vim::Enter"
"enter": "vim::Enter",
"escape": ["vim::SwitchMode", "Normal"],
"ctrl-[": ["vim::SwitchMode", "Normal"]
}
},
{
"context": "vim_mode == operator",
"context": "Editor && vim_mode == insert && VimWaiting",
"bindings": {
"escape": "vim::ClearOperators",
"ctrl-c": "vim::ClearOperators",
"ctrl-[": "vim::ClearOperators"
}
},
{
"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
"bindings": {
"w": "vim::Word",
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
"t": "vim::Tag",
"s": "vim::Sentence",
"p": "vim::Paragraph",
"'": "vim::Quotes",
"`": "vim::BackQuotes",
"\"": "vim::DoubleQuotes",
"|": "vim::VerticalBars",
"(": "vim::Parentheses",
")": "vim::Parentheses",
"b": "vim::Parentheses",
"[": "vim::SquareBrackets",
"]": "vim::SquareBrackets",
"{": "vim::CurlyBrackets",
"}": "vim::CurlyBrackets",
"shift-b": "vim::CurlyBrackets",
"<": "vim::AngleBrackets",
">": "vim::AngleBrackets",
"a": "vim::Argument"
}
},
{
"context": "vim_operator == c",
"bindings": {
"c": "vim::CurrentLine",
"d": "editor::Rename", // zed specific
"s": ["vim::PushOperator", { "ChangeSurrounds": {} }]
}
},
{
"context": "vim_operator == d",
"bindings": {
"d": "vim::CurrentLine",
"s": ["vim::PushOperator", "DeleteSurrounds"]
}
},
{
"context": "vim_operator == gu",
"bindings": {
"g u": "vim::CurrentLine",
"u": "vim::CurrentLine"
}
},
{
"context": "vim_operator == gU",
"bindings": {
"g shift-u": "vim::CurrentLine",
"shift-u": "vim::CurrentLine"
}
},
{
"context": "vim_operator == g~",
"bindings": {
"g ~": "vim::CurrentLine",
"~": "vim::CurrentLine"
}
},
{
"context": "vim_operator == y",
"bindings": {
"y": "vim::CurrentLine",
"s": ["vim::PushOperator", { "AddSurrounds": {} }]
}
},
{
"context": "vim_operator == ys",
"bindings": {
"s": "vim::CurrentLine"
}
},
{
"context": "vim_operator == >",
"bindings": {
">": "vim::CurrentLine"
}
},
{
"context": "vim_operator == <",
"bindings": {
"<": "vim::CurrentLine"
"escape": "vim::NormalBefore",
"ctrl-[": "vim::NormalBefore"
}
},
{
@@ -467,8 +508,7 @@
"x": "project_panel::RevealInFileManager",
"shift-g": "menu::SelectLast",
"g g": "menu::SelectFirst",
"-": "project_panel::SelectParent",
"ctrl-6": "pane::AlternateFile"
"-": "project_panel::SelectParent"
}
},
{

View File

@@ -47,7 +47,7 @@
// },
"buffer_line_height": "comfortable",
// The name of a font to use for rendering text in the UI
// (On macOS) You can set this to ".SystemUIFont" to use the system font
// (On macOS) You can set this to ".SysmtemUIFont" to use the system font
"ui_font_family": "Zed Plex Sans",
// The OpenType features to enable for text in the UI
"ui_font_features": {
@@ -116,11 +116,6 @@
// The debounce delay before re-querying the language server for completion
// documentation when not included in original completion list.
"completion_documentation_secondary_query_debounce": 300,
// Show method signatures in the editor, when inside parentheses.
"auto_signature_help": false,
/// Whether to show the signature help after completion or a bracket pair inserted.
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
"show_signature_help_after_edits": true,
// Whether to show wrap guides (vertical rulers) in the editor.
// Setting this to true will show a guide at the 'preferred_line_length' value
// if softwrap is set to 'preferred_line_length', and will show any
@@ -133,7 +128,14 @@
// The default number of lines to expand excerpts in the multibuffer by.
"expand_excerpt_lines": 3,
// Globs to match against file paths to determine if a file is private.
"private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
"private_files": [
"**/.env*",
"**/*.pem",
"**/*.key",
"**/*.cert",
"**/*.crt",
"**/secrets.yml"
],
// Whether to use additional LSP queries to format (and amend) the code after
// every "trigger" symbol input, defined by LSP server capabilities.
"use_on_type_format": true,
@@ -260,8 +262,6 @@
// to both the horizontal and vertical delta values while scrolling.
"scroll_sensitivity": 1.0,
"relative_line_numbers": false,
// If 'search_wrap' is disabled, search result do not wrap around the end of the file.
"search_wrap": true,
// When to populate a new search's query based on the text under the cursor.
// This setting can take the following three values:
//
@@ -539,14 +539,6 @@
// "delay_ms": 600
}
},
// Configuration for how direnv configuration should be loaded. May take 2 values:
// 1. Load direnv configuration through the shell hook, works for POSIX shells and fish.
// "load_direnv": "shell_hook"
// 2. Load direnv configuration using `direnv export json` directly.
// This can help with some shells that otherwise would not detect
// the direnv environment, such as nushell or elvish.
// "load_direnv": "direct"
"load_direnv": "shell_hook",
"inline_completions": {
// A list of globs representing files that inline completions should be disabled for.
"disabled_globs": [".env"]
@@ -722,12 +714,10 @@
}
},
"C": {
"format_on_save": "off",
"use_on_type_format": false
"format_on_save": "off"
},
"C++": {
"format_on_save": "off",
"use_on_type_format": false
"format_on_save": "off"
},
"CSS": {
"prettier": {
@@ -779,7 +769,6 @@
},
"Markdown": {
"format_on_save": "off",
"use_on_type_format": false,
"prettier": {
"allowed": true
}

View File

@@ -38,7 +38,6 @@
"icon.accent": "#10a793ff",
"status_bar.background": "#262933ff",
"title_bar.background": "#262933ff",
"title_bar.inactive_background": "#21242bff",
"toolbar.background": "#1e2025ff",
"tab_bar.background": "#21242bff",
"tab.inactive_background": "#21242bff",

View File

@@ -38,7 +38,6 @@
"icon.accent": "#566ddaff",
"status_bar.background": "#3a353fff",
"title_bar.background": "#3a353fff",
"title_bar.inactive_background": "#221f26ff",
"toolbar.background": "#19171cff",
"tab_bar.background": "#221f26ff",
"tab.inactive_background": "#221f26ff",
@@ -423,7 +422,6 @@
"icon.accent": "#586cdaff",
"status_bar.background": "#bfbcc5ff",
"title_bar.background": "#bfbcc5ff",
"title_bar.inactive_background": "#e6e3ebff",
"toolbar.background": "#efecf4ff",
"tab_bar.background": "#e6e3ebff",
"tab.inactive_background": "#e6e3ebff",
@@ -808,7 +806,6 @@
"icon.accent": "#6684e0ff",
"status_bar.background": "#45433bff",
"title_bar.background": "#45433bff",
"title_bar.inactive_background": "#262622ff",
"toolbar.background": "#20201dff",
"tab_bar.background": "#262622ff",
"tab.inactive_background": "#262622ff",
@@ -1193,7 +1190,6 @@
"icon.accent": "#6684dfff",
"status_bar.background": "#cecab4ff",
"title_bar.background": "#cecab4ff",
"title_bar.inactive_background": "#eeebd7ff",
"toolbar.background": "#fefbecff",
"tab_bar.background": "#eeebd7ff",
"tab.inactive_background": "#eeebd7ff",
@@ -1578,7 +1574,6 @@
"icon.accent": "#36a165ff",
"status_bar.background": "#424136ff",
"title_bar.background": "#424136ff",
"title_bar.inactive_background": "#2c2b23ff",
"toolbar.background": "#22221bff",
"tab_bar.background": "#2c2b23ff",
"tab.inactive_background": "#2c2b23ff",
@@ -1963,7 +1958,6 @@
"icon.accent": "#37a165ff",
"status_bar.background": "#c5c4b9ff",
"title_bar.background": "#c5c4b9ff",
"title_bar.inactive_background": "#ebeae3ff",
"toolbar.background": "#f4f3ecff",
"tab_bar.background": "#ebeae3ff",
"tab.inactive_background": "#ebeae3ff",
@@ -2348,7 +2342,6 @@
"icon.accent": "#407ee6ff",
"status_bar.background": "#443c39ff",
"title_bar.background": "#443c39ff",
"title_bar.inactive_background": "#27211eff",
"toolbar.background": "#1b1918ff",
"tab_bar.background": "#27211eff",
"tab.inactive_background": "#27211eff",
@@ -2733,7 +2726,6 @@
"icon.accent": "#407ee6ff",
"status_bar.background": "#ccc7c5ff",
"title_bar.background": "#ccc7c5ff",
"title_bar.inactive_background": "#e9e6e4ff",
"toolbar.background": "#f0eeedff",
"tab_bar.background": "#e9e6e4ff",
"tab.inactive_background": "#e9e6e4ff",
@@ -3118,7 +3110,6 @@
"icon.accent": "#5169ebff",
"status_bar.background": "#433a43ff",
"title_bar.background": "#433a43ff",
"title_bar.inactive_background": "#252025ff",
"toolbar.background": "#1b181bff",
"tab_bar.background": "#252025ff",
"tab.inactive_background": "#252025ff",
@@ -3503,7 +3494,6 @@
"icon.accent": "#5169ebff",
"status_bar.background": "#c6b8c6ff",
"title_bar.background": "#c6b8c6ff",
"title_bar.inactive_background": "#e0d5e0ff",
"toolbar.background": "#f7f3f7ff",
"tab_bar.background": "#e0d5e0ff",
"tab.inactive_background": "#e0d5e0ff",
@@ -3888,7 +3878,6 @@
"icon.accent": "#267eadff",
"status_bar.background": "#33444dff",
"title_bar.background": "#33444dff",
"title_bar.inactive_background": "#1c2529ff",
"toolbar.background": "#161b1dff",
"tab_bar.background": "#1c2529ff",
"tab.inactive_background": "#1c2529ff",
@@ -4273,7 +4262,6 @@
"icon.accent": "#267eadff",
"status_bar.background": "#a6cadcff",
"title_bar.background": "#a6cadcff",
"title_bar.inactive_background": "#cdeaf9ff",
"toolbar.background": "#ebf8ffff",
"tab_bar.background": "#cdeaf9ff",
"tab.inactive_background": "#cdeaf9ff",
@@ -4658,7 +4646,6 @@
"icon.accent": "#7272caff",
"status_bar.background": "#3b3535ff",
"title_bar.background": "#3b3535ff",
"title_bar.inactive_background": "#252020ff",
"toolbar.background": "#1b1818ff",
"tab_bar.background": "#252020ff",
"tab.inactive_background": "#252020ff",
@@ -5043,7 +5030,6 @@
"icon.accent": "#7272caff",
"status_bar.background": "#c1bbbbff",
"title_bar.background": "#c1bbbbff",
"title_bar.inactive_background": "#ebe3e3ff",
"toolbar.background": "#f4ececff",
"tab_bar.background": "#ebe3e3ff",
"tab.inactive_background": "#ebe3e3ff",
@@ -5428,7 +5414,6 @@
"icon.accent": "#468b8fff",
"status_bar.background": "#353f39ff",
"title_bar.background": "#353f39ff",
"title_bar.inactive_background": "#1f2621ff",
"toolbar.background": "#171c19ff",
"tab_bar.background": "#1f2621ff",
"tab.inactive_background": "#1f2621ff",
@@ -5813,7 +5798,6 @@
"icon.accent": "#488b90ff",
"status_bar.background": "#bcc5bfff",
"title_bar.background": "#bcc5bfff",
"title_bar.inactive_background": "#e3ebe6ff",
"toolbar.background": "#ecf4eeff",
"tab_bar.background": "#e3ebe6ff",
"tab.inactive_background": "#e3ebe6ff",
@@ -6198,7 +6182,6 @@
"icon.accent": "#3e62f4ff",
"status_bar.background": "#3b453bff",
"title_bar.background": "#3b453bff",
"title_bar.inactive_background": "#1f231fff",
"toolbar.background": "#131513ff",
"tab_bar.background": "#1f231fff",
"tab.inactive_background": "#1f231fff",
@@ -6583,7 +6566,6 @@
"icon.accent": "#3e61f4ff",
"status_bar.background": "#b4ceb4ff",
"title_bar.background": "#b4ceb4ff",
"title_bar.inactive_background": "#daeedaff",
"toolbar.background": "#f3faf3ff",
"tab_bar.background": "#daeedaff",
"tab.inactive_background": "#daeedaff",
@@ -6968,7 +6950,6 @@
"icon.accent": "#3e8ed0ff",
"status_bar.background": "#3e4769ff",
"title_bar.background": "#3e4769ff",
"title_bar.inactive_background": "#262f51ff",
"toolbar.background": "#202646ff",
"tab_bar.background": "#262f51ff",
"tab.inactive_background": "#262f51ff",
@@ -7353,7 +7334,6 @@
"icon.accent": "#3e8fd0ff",
"status_bar.background": "#c1c5d8ff",
"title_bar.background": "#c1c5d8ff",
"title_bar.inactive_background": "#e5e8f5ff",
"toolbar.background": "#f5f7ffff",
"tab_bar.background": "#e5e8f5ff",
"tab.inactive_background": "#e5e8f5ff",

View File

@@ -38,7 +38,6 @@
"icon.accent": "#5ac1feff",
"status_bar.background": "#313337ff",
"title_bar.background": "#313337ff",
"title_bar.inactive_background": "#1f2127ff",
"toolbar.background": "#0d1016ff",
"tab_bar.background": "#1f2127ff",
"tab.inactive_background": "#1f2127ff",
@@ -408,7 +407,6 @@
"icon.accent": "#3b9ee5ff",
"status_bar.background": "#dcdddeff",
"title_bar.background": "#dcdddeff",
"title_bar.inactive_background": "#ececedff",
"toolbar.background": "#fcfcfcff",
"tab_bar.background": "#ececedff",
"tab.inactive_background": "#ececedff",
@@ -778,7 +776,6 @@
"icon.accent": "#72cffeff",
"status_bar.background": "#464a52ff",
"title_bar.background": "#464a52ff",
"title_bar.inactive_background": "#353944ff",
"toolbar.background": "#242835ff",
"tab_bar.background": "#353944ff",
"tab.inactive_background": "#353944ff",

View File

@@ -47,7 +47,6 @@
"icon.accent": "#83a598ff",
"status_bar.background": "#4c4642ff",
"title_bar.background": "#4c4642ff",
"title_bar.inactive_background": "#3a3735ff",
"toolbar.background": "#282828ff",
"tab_bar.background": "#3a3735ff",
"tab.inactive_background": "#3a3735ff",
@@ -431,7 +430,6 @@
"icon.accent": "#83a598ff",
"status_bar.background": "#4c4642ff",
"title_bar.background": "#4c4642ff",
"title_bar.inactive_background": "#393634ff",
"toolbar.background": "#1d2021ff",
"tab_bar.background": "#393634ff",
"tab.inactive_background": "#393634ff",
@@ -815,7 +813,6 @@
"icon.accent": "#83a598ff",
"status_bar.background": "#4c4642ff",
"title_bar.background": "#4c4642ff",
"title_bar.inactive_background": "#3b3735ff",
"toolbar.background": "#32302fff",
"tab_bar.background": "#3b3735ff",
"tab.inactive_background": "#3b3735ff",
@@ -1199,7 +1196,6 @@
"icon.accent": "#0b6678ff",
"status_bar.background": "#d9c8a4ff",
"title_bar.background": "#d9c8a4ff",
"title_bar.inactive_background": "#ecddb4ff",
"toolbar.background": "#fbf1c7ff",
"tab_bar.background": "#ecddb4ff",
"tab.inactive_background": "#ecddb4ff",
@@ -1583,7 +1579,6 @@
"icon.accent": "#0b6678ff",
"status_bar.background": "#d9c8a4ff",
"title_bar.background": "#d9c8a4ff",
"title_bar.inactive_background": "#ecddb5ff",
"toolbar.background": "#f9f5d7ff",
"tab_bar.background": "#ecddb5ff",
"tab.inactive_background": "#ecddb5ff",
@@ -1967,7 +1962,6 @@
"icon.accent": "#0b6678ff",
"status_bar.background": "#d9c8a4ff",
"title_bar.background": "#d9c8a4ff",
"title_bar.inactive_background": "#ecdcb3ff",
"toolbar.background": "#f2e5bcff",
"tab_bar.background": "#ecdcb3ff",
"tab.inactive_background": "#ecdcb3ff",

View File

@@ -38,7 +38,6 @@
"icon.accent": "#74ade8ff",
"status_bar.background": "#3b414dff",
"title_bar.background": "#3b414dff",
"title_bar.inactive_background": "#2e343eff",
"toolbar.background": "#282c33ff",
"tab_bar.background": "#2f343eff",
"tab.inactive_background": "#2f343eff",
@@ -413,7 +412,6 @@
"icon.accent": "#5c78e2ff",
"status_bar.background": "#dcdcddff",
"title_bar.background": "#dcdcddff",
"title_bar.inactive_background": "#ebebecff",
"toolbar.background": "#fafafaff",
"tab_bar.background": "#ebebecff",
"tab.inactive_background": "#ebebecff",

View File

@@ -38,7 +38,6 @@
"icon.accent": "#9bced6ff",
"status_bar.background": "#292738ff",
"title_bar.background": "#292738ff",
"title_bar.inactive_background": "#1c1b2aff",
"toolbar.background": "#191724ff",
"tab_bar.background": "#1c1b2aff",
"tab.inactive_background": "#1c1b2aff",
@@ -418,7 +417,6 @@
"icon.accent": "#57949fff",
"status_bar.background": "#dcd8d8ff",
"title_bar.background": "#dcd8d8ff",
"title_bar.inactive_background": "#fef9f2ff",
"toolbar.background": "#faf4edff",
"tab_bar.background": "#fef9f2ff",
"tab.inactive_background": "#fef9f2ff",
@@ -798,7 +796,6 @@
"icon.accent": "#9bced6ff",
"status_bar.background": "#38354eff",
"title_bar.background": "#38354eff",
"title_bar.inactive_background": "#28253cff",
"toolbar.background": "#232136ff",
"tab_bar.background": "#28253cff",
"tab.inactive_background": "#28253cff",

View File

@@ -38,7 +38,6 @@
"icon.accent": "#518b8bff",
"status_bar.background": "#333944ff",
"title_bar.background": "#333944ff",
"title_bar.inactive_background": "#2b3038ff",
"toolbar.background": "#282c33ff",
"tab_bar.background": "#2b3038ff",
"tab.inactive_background": "#2b3038ff",

View File

@@ -38,7 +38,6 @@
"icon.accent": "#278ad1ff",
"status_bar.background": "#073743ff",
"title_bar.background": "#073743ff",
"title_bar.inactive_background": "#04313bff",
"toolbar.background": "#002a35ff",
"tab_bar.background": "#04313bff",
"tab.inactive_background": "#04313bff",
@@ -408,7 +407,6 @@
"icon.accent": "#288bd1ff",
"status_bar.background": "#cfd0c4ff",
"title_bar.background": "#cfd0c4ff",
"title_bar.inactive_background": "#f3eddaff",
"toolbar.background": "#fdf6e3ff",
"tab_bar.background": "#f3eddaff",
"tab.inactive_background": "#f3eddaff",

View File

@@ -38,7 +38,6 @@
"icon.accent": "#499befff",
"status_bar.background": "#2a261cff",
"title_bar.background": "#2a261cff",
"title_bar.inactive_background": "#231f16ff",
"toolbar.background": "#1b1810ff",
"tab_bar.background": "#231f16ff",
"tab.inactive_background": "#231f16ff",

View File

@@ -189,12 +189,8 @@ pub struct LanguageModelRequest {
pub messages: Vec<LanguageModelRequestMessage>,
pub stop: Vec<String>,
pub temperature: f32,
pub cached_contents: Vec<CachedContentId>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct CachedContentId(String);
impl LanguageModelRequest {
pub fn to_proto(&self) -> proto::CompleteWithLanguageModel {
proto::CompleteWithLanguageModel {
@@ -204,7 +200,6 @@ impl LanguageModelRequest {
temperature: self.temperature,
tool_choice: None,
tools: Vec::new(),
cached_contents: self.cached_contents.iter().map(|id| id.0.clone()).collect(),
}
}

View File

@@ -2445,43 +2445,19 @@ fn render_docs_slash_command_trailer(
return Empty.into_any();
};
let mut children = Vec::new();
if store.is_indexing(&package) {
children.push(
div()
.id(("crates-being-indexed", row.0))
.child(Icon::new(IconName::ArrowCircle).with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(4)).repeat(),
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
))
.tooltip({
let package = package.clone();
move |cx| Tooltip::text(format!("Indexing {package}"), cx)
})
.into_any_element(),
);
}
if let Some(latest_error) = store.latest_error_for_package(&package) {
children.push(
div()
.id(("latest-error", row.0))
.child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning))
.tooltip(move |cx| Tooltip::text(format!("failed to index: {latest_error}"), cx))
.into_any_element(),
)
}
let is_indexing = store.is_indexing(&package);
let latest_error = store.latest_error_for_package(&package);
if !is_indexing && latest_error.is_none() {
if !store.is_indexing(&package) {
return Empty.into_any();
}
h_flex().gap_2().children(children).into_any_element()
div()
.id(("crates-being-indexed", row.0))
.child(Icon::new(IconName::ArrowCircle).with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(4)).repeat(),
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
))
.tooltip(move |cx| Tooltip::text(format!("Indexing {package}"), cx))
.into_any_element()
}
fn make_lsp_adapter_delegate(

View File

@@ -27,8 +27,6 @@ pub enum CloudModel {
Claude3Opus,
Claude3Sonnet,
Claude3Haiku,
Gemini15Pro,
Gemini15Flash,
Custom(String),
}
@@ -111,8 +109,6 @@ impl CloudModel {
Self::Claude3Opus => "claude-3-opus",
Self::Claude3Sonnet => "claude-3-sonnet",
Self::Claude3Haiku => "claude-3-haiku",
Self::Gemini15Pro => "gemini-1.5-pro",
Self::Gemini15Flash => "gemini-1.5-flash",
Self::Custom(id) => id,
}
}
@@ -127,8 +123,6 @@ impl CloudModel {
Self::Claude3Opus => "Claude 3 Opus",
Self::Claude3Sonnet => "Claude 3 Sonnet",
Self::Claude3Haiku => "Claude 3 Haiku",
Self::Gemini15Pro => "Gemini 1.5 Pro",
Self::Gemini15Flash => "Gemini 1.5 Flash",
Self::Custom(id) => id.as_str(),
}
}
@@ -142,8 +136,6 @@ impl CloudModel {
| Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3Haiku => 200000,
Self::Gemini15Pro => 128000,
Self::Gemini15Flash => 32000,
Self::Custom(_) => 4096, // TODO: Make this configurable
}
}

View File

@@ -152,11 +152,6 @@ impl LanguageModelCompletionProvider for CloudCompletionProvider {
temperature: request.temperature,
tools: Vec::new(),
tool_choice: None,
cached_contents: request
.cached_contents
.iter()
.map(|id| id.0.clone())
.collect(),
};
self.client

View File

@@ -1258,7 +1258,6 @@ impl Context {
messages: messages.collect(),
stop: vec![],
temperature: 1.0,
cached_contents: Vec::new(), // todo!("support context caching")
}
}
@@ -1514,7 +1513,6 @@ impl Context {
messages: messages.collect(),
stop: vec![],
temperature: 1.0,
cached_contents: Vec::new(), // todo!
};
let stream = CompletionProvider::global(cx).complete(request, cx);

View File

@@ -851,7 +851,6 @@ impl InlineAssistant {
messages,
stop: vec!["|END|>".to_string()],
temperature,
cached_contents: Vec::new(),
})
})
}

View File

@@ -744,7 +744,6 @@ impl PromptLibrary {
}],
stop: Vec::new(),
temperature: 1.,
cached_contents: Vec::new(),
},
cx,
)

View File

@@ -8,8 +8,7 @@ use assistant_slash_command::{
};
use gpui::{AppContext, Model, Task, WeakView};
use indexed_docs::{
DocsDotRsProvider, IndexedDocsRegistry, IndexedDocsStore, LocalRustdocProvider, PackageName,
ProviderId,
IndexedDocsRegistry, IndexedDocsStore, LocalProvider, PackageName, ProviderId, RustdocIndexer,
};
use language::LspAdapterDelegate;
use project::{Project, ProjectPath};
@@ -35,22 +34,22 @@ impl DocsSlashCommand {
))
}
/// Ensures that the indexed doc providers for Rust are registered.
/// Ensures that the rustdoc provider is registered.
///
/// Ideally we would do this sooner, but we need to wait until we're able to
/// access the workspace so we can read the project.
fn ensure_rust_doc_providers_are_registered(
fn ensure_rustdoc_provider_is_registered(
&self,
workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) {
let indexed_docs_registry = IndexedDocsRegistry::global(cx);
if indexed_docs_registry
.get_provider_store(LocalRustdocProvider::id())
.get_provider_store(ProviderId::rustdoc())
.is_none()
{
let index_provider_deps = maybe!({
let workspace = workspace.clone().ok_or_else(|| anyhow!("no workspace"))?;
let workspace = workspace.ok_or_else(|| anyhow!("no workspace"))?;
let workspace = workspace
.upgrade()
.ok_or_else(|| anyhow!("workspace was dropped"))?;
@@ -64,29 +63,9 @@ impl DocsSlashCommand {
});
if let Some((fs, cargo_workspace_root)) = index_provider_deps.log_err() {
indexed_docs_registry.register_provider(Box::new(LocalRustdocProvider::new(
fs,
cargo_workspace_root,
)));
}
}
if indexed_docs_registry
.get_provider_store(DocsDotRsProvider::id())
.is_none()
{
let http_client = maybe!({
let workspace = workspace.ok_or_else(|| anyhow!("no workspace"))?;
let workspace = workspace
.upgrade()
.ok_or_else(|| anyhow!("workspace was dropped"))?;
let project = workspace.read(cx).project().clone();
anyhow::Ok(project.read(cx).client().http_client().clone())
});
if let Some(http_client) = http_client.log_err() {
indexed_docs_registry
.register_provider(Box::new(DocsDotRsProvider::new(http_client)));
indexed_docs_registry.register_provider(Box::new(RustdocIndexer::new(Box::new(
LocalProvider::new(fs, cargo_workspace_root),
))));
}
}
}
@@ -116,7 +95,7 @@ impl SlashCommand for DocsSlashCommand {
workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
self.ensure_rust_doc_providers_are_registered(workspace, cx);
self.ensure_rustdoc_provider_is_registered(workspace, cx);
let indexed_docs_registry = IndexedDocsRegistry::global(cx);
let args = DocsSlashCommandArgs::parse(&query);
@@ -142,14 +121,6 @@ impl SlashCommand for DocsSlashCommand {
match args {
DocsSlashCommandArgs::NoProvider => {
let providers = indexed_docs_registry.list_providers();
if providers.is_empty() {
return Ok(vec![ArgumentCompletion {
label: "No available docs providers.".to_string(),
new_text: String::new(),
run_command: false,
}]);
}
Ok(providers
.into_iter()
.map(|provider| ArgumentCompletion {
@@ -200,65 +171,46 @@ impl SlashCommand for DocsSlashCommand {
};
let args = DocsSlashCommandArgs::parse(argument);
let task = cx.background_executor().spawn({
let text = cx.background_executor().spawn({
let store = args
.provider()
.ok_or_else(|| anyhow!("no docs provider specified"))
.and_then(|provider| IndexedDocsStore::try_global(provider, cx));
async move {
let (provider, key) = match args {
match args {
DocsSlashCommandArgs::NoProvider => bail!("no docs provider specified"),
DocsSlashCommandArgs::SearchPackageDocs {
provider, package, ..
} => (provider, package),
} => {
let store = store?;
let item_docs = store.load(package.clone()).await?;
anyhow::Ok((provider, package, item_docs.to_string()))
}
DocsSlashCommandArgs::SearchItemDocs {
provider,
item_path,
..
} => (provider, item_path),
};
} => {
let store = store?;
let item_docs = store.load(item_path.clone()).await?;
let store = store?;
let (text, ranges) = if let Some((prefix, _)) = key.split_once('*') {
let docs = store.load_many_by_prefix(prefix.to_string()).await?;
let mut text = String::new();
let mut ranges = Vec::new();
for (key, docs) in docs {
let prev_len = text.len();
text.push_str(&docs.0);
text.push_str("\n");
ranges.push((key, prev_len..text.len()));
text.push_str("\n");
anyhow::Ok((provider, item_path, item_docs.to_string()))
}
(text, ranges)
} else {
let item_docs = store.load(key.clone()).await?;
let text = item_docs.to_string();
let range = 0..text.len();
(text, vec![(key, range)])
};
anyhow::Ok((provider, text, ranges))
}
}
});
cx.foreground_executor().spawn(async move {
let (provider, text, ranges) = task.await?;
let (provider, path, text) = text.await?;
let range = 0..text.len();
Ok(SlashCommandOutput {
text,
sections: ranges
.into_iter()
.map(|(key, range)| SlashCommandOutputSection {
range,
icon: IconName::FileDoc,
label: format!("docs ({provider}): {key}",).into(),
})
.collect(),
sections: vec![SlashCommandOutputSection {
range,
icon: IconName::FileRust,
label: format!("docs ({provider}): {path}",).into(),
}],
run_commands_in_text: false,
})
})

View File

@@ -27,7 +27,7 @@ pub(crate) struct FetchSlashCommand;
impl FetchSlashCommand {
async fn build_message(http_client: Arc<HttpClientWithUrl>, url: &str) -> Result<String> {
let mut url = url.to_owned();
if !url.starts_with("https://") && !url.starts_with("http://") {
if !url.starts_with("https://") {
url = format!("https://{url}");
}

View File

@@ -269,7 +269,6 @@ impl TerminalInlineAssistant {
messages,
stop: Vec::new(),
temperature: 1.0,
cached_contents: Vec::new(), // todo!
})
}

View File

@@ -13,9 +13,8 @@ use async_tungstenite::tungstenite::{
use clock::SystemClock;
use collections::HashMap;
use futures::{
channel::oneshot,
future::{BoxFuture, LocalBoxFuture},
AsyncReadExt, FutureExt, SinkExt, Stream, StreamExt, TryFutureExt as _, TryStreamExt,
channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, Stream, StreamExt,
TryFutureExt as _, TryStreamExt,
};
use gpui::{
actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Global, Model, Task, WeakModel,
@@ -24,7 +23,6 @@ use http::{HttpClient, HttpClientWithUrl};
use lazy_static::lazy_static;
use parking_lot::RwLock;
use postage::watch;
use proto::ProtoClient;
use rand::prelude::*;
use release_channel::{AppVersion, ReleaseChannel};
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
@@ -1410,11 +1408,6 @@ impl Client {
self.peer.send(self.connection_id()?, message)
}
fn send_dynamic(&self, envelope: proto::Envelope) -> Result<()> {
let connection_id = self.connection_id()?;
self.peer.send_dynamic(connection_id, envelope)
}
pub fn request<T: RequestMessage>(
&self,
request: T,
@@ -1613,20 +1606,6 @@ impl Client {
}
}
impl ProtoClient for Client {
fn request(
&self,
envelope: proto::Envelope,
request_type: &'static str,
) -> BoxFuture<'static, Result<proto::Envelope>> {
self.request_dynamic(envelope, request_type).boxed()
}
fn send(&self, envelope: proto::Envelope) -> Result<()> {
self.send_dynamic(envelope)
}
}
#[derive(Serialize, Deserialize)]
struct DevelopmentCredentials {
user_id: u64,

View File

@@ -101,7 +101,6 @@ pub fn language_model_request_to_google_ai(
.collect::<Result<Vec<_>>>()?,
generation_config: None,
safety_settings: None,
cached_content: request.cached_contents.into_iter().next(),
})
}
@@ -126,60 +125,6 @@ pub fn language_model_request_message_to_google_ai(
})
}
pub fn cache_language_model_content_request_to_google_ai(
request: proto::CacheLanguageModelContent,
) -> Result<google_ai::CreateCachedContentRequest> {
Ok(google_ai::CreateCachedContentRequest {
contents: request
.messages
.into_iter()
.map(language_model_request_message_to_google_ai)
.collect::<Result<Vec<_>>>()?,
tools: if request.tools.is_empty() {
None
} else {
Some(
request
.tools
.into_iter()
.try_fold(Vec::new(), |mut acc, tool| {
if let Some(variant) = tool.variant {
match variant {
proto::chat_completion_tool::Variant::Function(f) => {
let description = f.description.ok_or_else(|| {
anyhow!("Function tool is missing a description")
})?;
let parameters = f.parameters.ok_or_else(|| {
anyhow!("Function tool is missing parameters")
})?;
let parsed_parameters = serde_json::from_str(&parameters)
.map_err(|e| {
anyhow!("Failed to parse parameters: {}", e)
})?;
acc.push(google_ai::Tool {
function_declarations: vec![
google_ai::FunctionDeclaration {
name: f.name,
description,
parameters: parsed_parameters,
},
],
});
}
}
}
anyhow::Ok(acc)
})?,
)
},
ttl: request.ttl_seconds.map(|s| format!("{}s", s)),
display_name: None,
model: request.model,
system_instruction: None,
tool_config: None,
})
}
pub fn count_tokens_request_to_google_ai(
request: proto::CountTokensWithLanguageModel,
) -> Result<google_ai::CountTokensRequest> {

View File

@@ -616,17 +616,6 @@ impl Server {
)
}
})
.add_request_handler({
let app_state = app_state.clone();
user_handler(move |request, response, session| {
cache_language_model_content(
request,
response,
session,
app_state.config.google_ai_api_key.clone(),
)
})
})
.add_request_handler({
let app_state = app_state.clone();
user_handler(move |request, response, session| {
@@ -4734,43 +4723,6 @@ async fn complete_with_anthropic(
Ok(())
}
async fn cache_language_model_content(
request: proto::CacheLanguageModelContent,
response: Response<proto::CacheLanguageModelContent>,
session: UserSession,
google_ai_api_key: Option<Arc<str>>,
) -> Result<()> {
authorize_access_to_language_models(&session).await?;
if !request.model.starts_with("gemini") {
return Err(anyhow!(
"caching content for model: {:?} is not supported",
request.model
))?;
}
let api_key = google_ai_api_key
.ok_or_else(|| anyhow!("no Google AI API key configured on the server"))?;
let cached_content = google_ai::create_cached_content(
session.http_client.as_ref(),
google_ai::API_URL,
&api_key,
crate::ai::cache_language_model_content_request_to_google_ai(request)?,
)
.await?;
response.send(proto::CacheLanguageModelContentResponse {
name: cached_content.name,
create_time: cached_content.create_time.timestamp() as u64,
update_time: cached_content.update_time.timestamp() as u64,
expire_time: cached_content.expire_time.map(|t| t.timestamp() as u64),
total_token_count: cached_content.usage_metadata.total_token_count,
})?;
Ok(())
}
struct CountTokensWithLanguageModelRateLimit;
impl RateLimit for CountTokensWithLanguageModelRateLimit {

View File

@@ -3327,7 +3327,7 @@ async fn test_local_settings(
let store = cx.global::<SettingsStore>();
assert_eq!(
store
.local_settings(worktree_b.entity_id().as_u64() as _)
.local_settings(worktree_b.read(cx).id().to_usize())
.collect::<Vec<_>>(),
&[
(Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
@@ -3346,7 +3346,7 @@ async fn test_local_settings(
let store = cx.global::<SettingsStore>();
assert_eq!(
store
.local_settings(worktree_b.entity_id().as_u64() as _)
.local_settings(worktree_b.read(cx).id().to_usize())
.collect::<Vec<_>>(),
&[
(Path::new("").into(), r#"{}"#.to_string()),
@@ -3375,7 +3375,7 @@ async fn test_local_settings(
let store = cx.global::<SettingsStore>();
assert_eq!(
store
.local_settings(worktree_b.entity_id().as_u64() as _)
.local_settings(worktree_b.read(cx).id().to_usize())
.collect::<Vec<_>>(),
&[
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
@@ -3407,7 +3407,7 @@ async fn test_local_settings(
let store = cx.global::<SettingsStore>();
assert_eq!(
store
.local_settings(worktree_b.entity_id().as_u64() as _)
.local_settings(worktree_b.read(cx).id().to_usize())
.collect::<Vec<_>>(),
&[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
)

View File

@@ -1237,7 +1237,7 @@ impl RandomizedTest for ProjectCollaborationTest {
}
}
for buffer in guest_project.opened_buffers(cx) {
for buffer in guest_project.opened_buffers() {
let buffer = buffer.read(cx);
assert_eq!(
buffer.deferred_ops_len(),
@@ -1287,8 +1287,8 @@ impl RandomizedTest for ProjectCollaborationTest {
for guest_buffer in guest_buffers {
let buffer_id =
guest_buffer.read_with(client_cx, |buffer, _| buffer.remote_id());
let host_buffer = host_project.read_with(host_cx, |project, cx| {
project.buffer_for_id(buffer_id, cx).unwrap_or_else(|| {
let host_buffer = host_project.read_with(host_cx, |project, _| {
project.buffer_for_id(buffer_id).unwrap_or_else(|| {
panic!(
"host does not have buffer for guest:{}, peer:{:?}, id:{}",
client.username,

View File

@@ -49,7 +49,6 @@ lazy_static.workspace = true
linkify.workspace = true
log.workspace = true
lsp.workspace = true
markdown.workspace = true
multi_buffer.workspace = true
ordered-float.workspace = true
parking_lot.workspace = true

View File

@@ -286,14 +286,12 @@ gpui::actions!(
SelectPageUp,
ShowCharacterPalette,
ShowInlineCompletion,
ShowSignatureHelp,
ShuffleLines,
SortLinesCaseInsensitive,
SortLinesCaseSensitive,
SplitSelectionIntoLines,
Tab,
TabPrev,
ToggleAutoSignatureHelp,
ToggleGitBlame,
ToggleGitBlameInline,
ToggleSelectionMenu,

View File

@@ -39,10 +39,8 @@ pub mod tasks;
#[cfg(test)]
mod editor_tests;
mod signature_help;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
use ::git::diff::{DiffHunk, DiffHunkStatus};
use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};
pub(crate) use actions::*;
@@ -156,7 +154,6 @@ use workspace::{
use workspace::{OpenInTerminal, OpenTerminal, TabBarSettings, Toast};
use crate::hover_links::find_url;
use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
pub const FILE_HEADER_HEIGHT: u8 = 1;
pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u8 = 1;
@@ -504,8 +501,6 @@ pub struct Editor {
context_menu: RwLock<Option<ContextMenu>>,
mouse_context_menu: Option<MouseContextMenu>,
completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
signature_help_state: SignatureHelpState,
auto_signature_help: Option<bool>,
find_all_references_task_sources: Vec<Anchor>,
next_completion_id: CompletionId,
completion_documentation_pre_resolve_debounce: DebouncedDelay,
@@ -1824,8 +1819,6 @@ impl Editor {
context_menu: RwLock::new(None),
mouse_context_menu: None,
completion_tasks: Default::default(),
signature_help_state: SignatureHelpState::default(),
auto_signature_help: None,
find_all_references_task_sources: Vec::new(),
next_completion_id: 0,
completion_documentation_pre_resolve_debounce: DebouncedDelay::new(),
@@ -2163,10 +2156,6 @@ impl Editor {
pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
self.cursor_shape = cursor_shape;
// Disrupt blink for immediate user feedback that the cursor shape has changed
self.blink_manager.update(cx, BlinkManager::show_cursor);
cx.notify();
}
@@ -2422,15 +2411,6 @@ impl Editor {
self.request_autoscroll(autoscroll, cx);
}
self.selections_did_change(true, &old_cursor_position, request_completions, cx);
if self.should_open_signature_help_automatically(
&old_cursor_position,
self.signature_help_state.backspace_pressed(),
cx,
) {
self.show_signature_help(&ShowSignatureHelp, cx);
}
self.signature_help_state.set_backspace_pressed(false);
}
result
@@ -2886,10 +2866,6 @@ impl Editor {
return true;
}
if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
return true;
}
if self.hide_context_menu(cx).is_some() {
return true;
}
@@ -2966,7 +2942,7 @@ impl Editor {
}
let selections = self.selections.all_adjusted(cx);
let mut bracket_inserted = false;
let mut brace_inserted = false;
let mut edits = Vec::new();
let mut linked_edits = HashMap::<_, Vec<_>>::default();
let mut new_selections = Vec::with_capacity(selections.len());
@@ -3028,7 +3004,6 @@ impl Editor {
),
&bracket_pair.start[..prefix_len],
));
if autoclose
&& bracket_pair.close
&& following_text_allows_autoclose
@@ -3046,7 +3021,7 @@ impl Editor {
selection.range(),
format!("{}{}", text, bracket_pair.end).into(),
));
bracket_inserted = true;
brace_inserted = true;
continue;
}
}
@@ -3092,7 +3067,7 @@ impl Editor {
selection.end..selection.end,
bracket_pair.end.as_str().into(),
));
bracket_inserted = true;
brace_inserted = true;
new_selections.push((
Selection {
id: selection.id,
@@ -3249,7 +3224,7 @@ impl Editor {
s.select(new_selections)
});
if !bracket_inserted && EditorSettings::get_global(cx).use_on_type_format {
if !brace_inserted && EditorSettings::get_global(cx).use_on_type_format {
if let Some(on_type_format_task) =
this.trigger_on_type_formatting(text.to_string(), cx)
{
@@ -3257,14 +3232,6 @@ impl Editor {
}
}
let editor_settings = EditorSettings::get_global(cx);
if bracket_inserted
&& (editor_settings.auto_signature_help
|| editor_settings.show_signature_help_after_edits)
{
this.show_signature_help(&ShowSignatureHelp, cx);
}
let trigger_in_words = !had_active_inline_completion;
this.trigger_completion_on_input(&text, trigger_in_words, cx);
linked_editing_ranges::refresh_linked_ranges(this, cx);
@@ -4338,14 +4305,6 @@ impl Editor {
true,
cx,
);
let editor_settings = EditorSettings::get_global(cx);
if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
// After the code completion is finished, users often want to know what signatures are needed.
// so we should automatically call signature_help
self.show_signature_help(&ShowSignatureHelp, cx);
}
Some(cx.foreground_executor().spawn(async move {
apply_edits.await?;
Ok(())
@@ -5369,7 +5328,6 @@ impl Editor {
}
}
this.signature_help_state.set_backspace_pressed(true);
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
this.insert("", cx);
let empty_str: Arc<str> = Arc::from("");
@@ -8529,7 +8487,7 @@ impl Editor {
) -> Vec<(TaskSourceKind, TaskTemplate)> {
let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
let (worktree_id, file) = project
.buffer_for_id(runnable.buffer, cx)
.buffer_for_id(runnable.buffer)
.and_then(|buffer| buffer.read(cx).file())
.map(|file| (WorktreeId::from_usize(file.worktree_id()), file.clone()))
.unzip();
@@ -11646,11 +11604,8 @@ impl Editor {
if let Some(blame) = self.blame.as_ref() {
blame.update(cx, GitBlame::blur)
}
if !self.hover_state.focused(cx) {
hide_hover(self, cx);
}
self.hide_context_menu(cx);
hide_hover(self, cx);
cx.emit(EditorEvent::Blurred);
cx.notify();
}

View File

@@ -25,9 +25,6 @@ pub struct EditorSettings {
pub expand_excerpt_lines: u32,
#[serde(default)]
pub double_click_in_multibuffer: DoubleClickInMultibuffer,
pub search_wrap: bool,
pub auto_signature_help: bool,
pub show_signature_help_after_edits: bool,
#[serde(default)]
pub jupyter: Jupyter,
}
@@ -231,20 +228,6 @@ pub struct EditorSettingsContent {
///
/// Default: select
pub double_click_in_multibuffer: Option<DoubleClickInMultibuffer>,
/// Whether the editor search results will loop
///
/// Default: true
pub search_wrap: Option<bool>,
/// Whether to automatically show a signature help pop-up or not.
///
/// Default: false
pub auto_signature_help: Option<bool>,
/// Whether to show the signature help pop-up after completions or bracket pairs inserted.
///
/// Default: true
pub show_signature_help_after_edits: Option<bool>,
/// Jupyter REPL settings.
pub jupyter: Option<Jupyter>,

View File

@@ -21,16 +21,13 @@ use language::{
BracketPairConfig,
Capability::ReadWrite,
FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override,
ParsedMarkdown, Point,
Point,
};
use language_settings::IndentGuideSettings;
use multi_buffer::MultiBufferIndentGuide;
use parking_lot::Mutex;
use project::project_settings::{LspSettings, ProjectSettings};
use project::FakeFs;
use project::{
lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
project_settings::{LspSettings, ProjectSettings},
};
use serde_json::{self, json};
use std::sync::atomic;
use std::sync::atomic::AtomicUsize;
@@ -6834,626 +6831,6 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
);
}
#[gpui::test]
async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
cx: &mut gpui::TestAppContext,
) {
init_test(cx, |_| {});
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.auto_signature_help = Some(true);
});
});
});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
signature_help_provider: Some(lsp::SignatureHelpOptions {
..Default::default()
}),
..Default::default()
},
cx,
)
.await;
let language = Language::new(
LanguageConfig {
name: "Rust".into(),
brackets: BracketPairConfig {
pairs: vec![
BracketPair {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "/*".to_string(),
end: " */".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "[".to_string(),
end: "]".to_string(),
close: false,
surround: false,
newline: true,
},
BracketPair {
start: "\"".to_string(),
end: "\"".to_string(),
close: true,
surround: true,
newline: false,
},
BracketPair {
start: "<".to_string(),
end: ">".to_string(),
close: false,
surround: true,
newline: true,
},
],
..Default::default()
},
autoclose_before: "})]".to_string(),
..Default::default()
},
Some(tree_sitter_rust::language()),
);
let language = Arc::new(language);
cx.language_registry().add(language.clone());
cx.update_buffer(|buffer, cx| {
buffer.set_language(Some(language), cx);
});
cx.set_state(
&r#"
fn main() {
sampleˇ
}
"#
.unindent(),
);
cx.update_editor(|view, cx| {
view.handle_input("(", cx);
});
cx.assert_editor_state(
&"
fn main() {
sample(ˇ)
}
"
.unindent(),
);
let mocked_response = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn sample(param1: u8, param2: u8)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(0),
};
handle_signature_help_request(&mut cx, mocked_response).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
cx.editor(|editor, _| {
let signature_help_state = editor.signature_help_state.popover().cloned();
assert!(signature_help_state.is_some());
let ParsedMarkdown {
text, highlights, ..
} = signature_help_state.unwrap().parsed_content;
assert_eq!(text, "param1: u8, param2: u8");
assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
});
}
#[gpui::test]
async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.auto_signature_help = Some(false);
settings.show_signature_help_after_edits = Some(false);
});
});
});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
signature_help_provider: Some(lsp::SignatureHelpOptions {
..Default::default()
}),
..Default::default()
},
cx,
)
.await;
let language = Language::new(
LanguageConfig {
name: "Rust".into(),
brackets: BracketPairConfig {
pairs: vec![
BracketPair {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "/*".to_string(),
end: " */".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "[".to_string(),
end: "]".to_string(),
close: false,
surround: false,
newline: true,
},
BracketPair {
start: "\"".to_string(),
end: "\"".to_string(),
close: true,
surround: true,
newline: false,
},
BracketPair {
start: "<".to_string(),
end: ">".to_string(),
close: false,
surround: true,
newline: true,
},
],
..Default::default()
},
autoclose_before: "})]".to_string(),
..Default::default()
},
Some(tree_sitter_rust::language()),
);
let language = Arc::new(language);
cx.language_registry().add(language.clone());
cx.update_buffer(|buffer, cx| {
buffer.set_language(Some(language), cx);
});
// Ensure that signature_help is not called when no signature help is enabled.
cx.set_state(
&r#"
fn main() {
sampleˇ
}
"#
.unindent(),
);
cx.update_editor(|view, cx| {
view.handle_input("(", cx);
});
cx.assert_editor_state(
&"
fn main() {
sample(ˇ)
}
"
.unindent(),
);
cx.editor(|editor, _| {
assert!(editor.signature_help_state.task().is_none());
});
let mocked_response = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn sample(param1: u8, param2: u8)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(0),
};
// Ensure that signature_help is called when enabled afte edits
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.auto_signature_help = Some(false);
settings.show_signature_help_after_edits = Some(true);
});
});
});
cx.set_state(
&r#"
fn main() {
sampleˇ
}
"#
.unindent(),
);
cx.update_editor(|view, cx| {
view.handle_input("(", cx);
});
cx.assert_editor_state(
&"
fn main() {
sample(ˇ)
}
"
.unindent(),
);
handle_signature_help_request(&mut cx, mocked_response.clone()).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
cx.update_editor(|editor, _| {
let signature_help_state = editor.signature_help_state.popover().cloned();
assert!(signature_help_state.is_some());
let ParsedMarkdown {
text, highlights, ..
} = signature_help_state.unwrap().parsed_content;
assert_eq!(text, "param1: u8, param2: u8");
assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
editor.signature_help_state = SignatureHelpState::default();
});
// Ensure that signature_help is called when auto signature help override is enabled
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.auto_signature_help = Some(true);
settings.show_signature_help_after_edits = Some(false);
});
});
});
cx.set_state(
&r#"
fn main() {
sampleˇ
}
"#
.unindent(),
);
cx.update_editor(|view, cx| {
view.handle_input("(", cx);
});
cx.assert_editor_state(
&"
fn main() {
sample(ˇ)
}
"
.unindent(),
);
handle_signature_help_request(&mut cx, mocked_response).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
cx.editor(|editor, _| {
let signature_help_state = editor.signature_help_state.popover().cloned();
assert!(signature_help_state.is_some());
let ParsedMarkdown {
text, highlights, ..
} = signature_help_state.unwrap().parsed_content;
assert_eq!(text, "param1: u8, param2: u8");
assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
});
}
#[gpui::test]
async fn test_signature_help(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.auto_signature_help = Some(true);
});
});
});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
signature_help_provider: Some(lsp::SignatureHelpOptions {
..Default::default()
}),
..Default::default()
},
cx,
)
.await;
// A test that directly calls `show_signature_help`
cx.update_editor(|editor, cx| {
editor.show_signature_help(&ShowSignatureHelp, cx);
});
let mocked_response = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn sample(param1: u8, param2: u8)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(0),
};
handle_signature_help_request(&mut cx, mocked_response).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
cx.editor(|editor, _| {
let signature_help_state = editor.signature_help_state.popover().cloned();
assert!(signature_help_state.is_some());
let ParsedMarkdown {
text, highlights, ..
} = signature_help_state.unwrap().parsed_content;
assert_eq!(text, "param1: u8, param2: u8");
assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
});
// When exiting outside from inside the brackets, `signature_help` is closed.
cx.set_state(indoc! {"
fn main() {
sample(ˇ);
}
fn sample(param1: u8, param2: u8) {}
"});
cx.update_editor(|editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
});
let mocked_response = lsp::SignatureHelp {
signatures: Vec::new(),
active_signature: None,
active_parameter: None,
};
handle_signature_help_request(&mut cx, mocked_response).await;
cx.condition(|editor, _| !editor.signature_help_state.is_shown())
.await;
cx.editor(|editor, _| {
assert!(!editor.signature_help_state.is_shown());
});
// When entering inside the brackets from outside, `show_signature_help` is automatically called.
cx.set_state(indoc! {"
fn main() {
sample(ˇ);
}
fn sample(param1: u8, param2: u8) {}
"});
let mocked_response = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn sample(param1: u8, param2: u8)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(0),
};
handle_signature_help_request(&mut cx, mocked_response.clone()).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
cx.editor(|editor, _| {
assert!(editor.signature_help_state.is_shown());
});
// Restore the popover with more parameter input
cx.set_state(indoc! {"
fn main() {
sample(param1, param2ˇ);
}
fn sample(param1: u8, param2: u8) {}
"});
let mocked_response = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn sample(param1: u8, param2: u8)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(1),
};
handle_signature_help_request(&mut cx, mocked_response.clone()).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
// When selecting a range, the popover is gone.
// Avoid using `cx.set_state` to not actually edit the document, just change its selections.
cx.update_editor(|editor, cx| {
editor.change_selections(None, cx, |s| {
s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
})
});
cx.assert_editor_state(indoc! {"
fn main() {
sample(param1, «ˇparam2»);
}
fn sample(param1: u8, param2: u8) {}
"});
cx.editor(|editor, _| {
assert!(!editor.signature_help_state.is_shown());
});
// When unselecting again, the popover is back if within the brackets.
cx.update_editor(|editor, cx| {
editor.change_selections(None, cx, |s| {
s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
})
});
cx.assert_editor_state(indoc! {"
fn main() {
sample(param1, ˇparam2);
}
fn sample(param1: u8, param2: u8) {}
"});
handle_signature_help_request(&mut cx, mocked_response).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
cx.editor(|editor, _| {
assert!(editor.signature_help_state.is_shown());
});
// Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
cx.update_editor(|editor, cx| {
editor.change_selections(None, cx, |s| {
s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
})
});
cx.assert_editor_state(indoc! {"
fn main() {
sample(param1, ˇparam2);
}
fn sample(param1: u8, param2: u8) {}
"});
let mocked_response = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn sample(param1: u8, param2: u8)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(1),
};
handle_signature_help_request(&mut cx, mocked_response.clone()).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
cx.update_editor(|editor, cx| {
editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
});
cx.condition(|editor, _| !editor.signature_help_state.is_shown())
.await;
cx.update_editor(|editor, cx| {
editor.change_selections(None, cx, |s| {
s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
})
});
cx.assert_editor_state(indoc! {"
fn main() {
sample(param1, «ˇparam2»);
}
fn sample(param1: u8, param2: u8) {}
"});
cx.update_editor(|editor, cx| {
editor.change_selections(None, cx, |s| {
s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
})
});
cx.assert_editor_state(indoc! {"
fn main() {
sample(param1, ˇparam2);
}
fn sample(param1: u8, param2: u8) {}
"});
cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
.await;
}
#[gpui::test]
async fn test_completion(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -13073,21 +12450,6 @@ fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewCo
);
}
pub fn handle_signature_help_request(
cx: &mut EditorLspTestContext,
mocked_response: lsp::SignatureHelp,
) -> impl Future<Output = ()> {
let mut request =
cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
let mocked_response = mocked_response.clone();
async move { Ok(Some(mocked_response)) }
});
async move {
request.next().await;
}
}
/// Handle completion request passing a marked string specifying where the completion
/// should be triggered from using '|' character, what range should be replaced, and what completions
/// should be returned using '<' and '>' to delimit the range

View File

@@ -382,7 +382,6 @@ impl EditorElement {
cx.propagate();
}
});
register_action(view, cx, Editor::show_signature_help);
register_action(view, cx, Editor::next_inline_completion);
register_action(view, cx, Editor::previous_inline_completion);
register_action(view, cx, Editor::show_inline_completion);
@@ -710,24 +709,18 @@ impl EditorElement {
let Some(hub) = editor.collaboration_hub() else {
return;
};
let start = snapshot.display_snapshot.clip_point(
DisplayPoint::new(point.row(), point.column().saturating_sub(1)),
Bias::Left,
);
let end = snapshot.display_snapshot.clip_point(
DisplayPoint::new(
let range = DisplayPoint::new(point.row(), point.column().saturating_sub(1))
..DisplayPoint::new(
point.row(),
(point.column() + 1).min(snapshot.line_len(point.row())),
),
Bias::Right,
);
);
let range = snapshot
.buffer_snapshot
.anchor_at(start.to_point(&snapshot.display_snapshot), Bias::Left)
.anchor_at(range.start.to_point(&snapshot.display_snapshot), Bias::Left)
..snapshot
.buffer_snapshot
.anchor_at(end.to_point(&snapshot.display_snapshot), Bias::Right);
.anchor_at(range.end.to_point(&snapshot.display_snapshot), Bias::Right);
let Some(selection) = snapshot.remote_selections_in_range(&range, hub, cx).next() else {
return;
@@ -2636,73 +2629,6 @@ impl EditorElement {
}
}
#[allow(clippy::too_many_arguments)]
fn layout_signature_help(
&self,
hitbox: &Hitbox,
content_origin: gpui::Point<Pixels>,
scroll_pixel_position: gpui::Point<Pixels>,
newest_selection_head: Option<DisplayPoint>,
start_row: DisplayRow,
line_layouts: &[LineWithInvisibles],
line_height: Pixels,
em_width: Pixels,
cx: &mut WindowContext,
) {
let Some(newest_selection_head) = newest_selection_head else {
return;
};
let selection_row = newest_selection_head.row();
if selection_row < start_row {
return;
}
let Some(cursor_row_layout) = line_layouts.get(selection_row.minus(start_row) as usize)
else {
return;
};
let start_x = cursor_row_layout.x_for_index(newest_selection_head.column() as usize)
- scroll_pixel_position.x
+ content_origin.x;
let start_y =
selection_row.as_f32() * line_height + content_origin.y - scroll_pixel_position.y;
let max_size = size(
(120. * em_width) // Default size
.min(hitbox.size.width / 2.) // Shrink to half of the editor width
.max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
(16. * line_height) // Default size
.min(hitbox.size.height / 2.) // Shrink to half of the editor height
.max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
);
let maybe_element = self.editor.update(cx, |editor, cx| {
if let Some(popover) = editor.signature_help_state.popover_mut() {
let element = popover.render(
&self.style,
max_size,
editor.workspace.as_ref().map(|(w, _)| w.clone()),
cx,
);
Some(element)
} else {
None
}
});
if let Some(mut element) = maybe_element {
let window_size = cx.viewport_size();
let size = element.layout_as_root(Size::<AvailableSpace>::default(), cx);
let mut point = point(start_x, start_y - size.height);
// Adjusting to ensure the popover does not overflow in the X-axis direction.
if point.x + size.width >= window_size.width {
point.x = window_size.width - size.width;
}
cx.defer_draw(element, point, 1)
}
}
fn paint_background(&self, layout: &EditorLayout, cx: &mut WindowContext) {
cx.paint_layer(layout.hitbox.bounds, |cx| {
let scroll_top = layout.position_map.snapshot.scroll_position().y;
@@ -3808,9 +3734,6 @@ impl EditorElement {
move |event: &MouseMoveEvent, phase, cx| {
if phase == DispatchPhase::Bubble {
editor.update(cx, |editor, cx| {
if editor.hover_state.focused(cx) {
return;
}
if event.pressed_button == Some(MouseButton::Left)
|| event.pressed_button == Some(MouseButton::Middle)
{
@@ -5140,18 +5063,6 @@ impl Element for EditorElement {
vec![]
};
self.layout_signature_help(
&hitbox,
content_origin,
scroll_pixel_position,
newest_selection_head,
start_row,
&line_layouts,
line_height,
em_width,
cx,
);
if !cx.has_active_drag() {
self.layout_hover_popovers(
&snapshot,

View File

@@ -5,26 +5,24 @@ use crate::{
Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot,
EditorStyle, Hover, RangeToAnchorExt,
};
use futures::{stream::FuturesUnordered, FutureExt};
use gpui::{
div, px, AnyElement, AsyncWindowContext, CursorStyle, FontWeight, Hsla, InteractiveElement,
IntoElement, MouseButton, ParentElement, Pixels, ScrollHandle, SharedString, Size,
StatefulInteractiveElement, StyleRefinement, Styled, Task, TextStyleRefinement, View,
ViewContext, WeakView,
div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, MouseButton,
ParentElement, Pixels, ScrollHandle, SharedString, Size, StatefulInteractiveElement, Styled,
Task, ViewContext, WeakView,
};
use itertools::Itertools;
use language::{DiagnosticEntry, Language, LanguageRegistry};
use language::{markdown, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown};
use lsp::DiagnosticSeverity;
use markdown::{Markdown, MarkdownStyle};
use multi_buffer::ToOffset;
use project::{HoverBlock, InlayHintLabelPart};
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
use settings::Settings;
use std::rc::Rc;
use std::{borrow::Cow, cell::RefCell};
use smol::stream::StreamExt;
use std::{ops::Range, sync::Arc, time::Duration};
use theme::ThemeSettings;
use ui::{prelude::*, window_is_transparent, Tooltip};
use util::TryFutureExt;
use workspace::Workspace;
pub const HOVER_DELAY_MILLIS: u64 = 350;
pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
@@ -42,9 +40,6 @@ pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
/// depending on whether a point to hover over is provided.
pub fn hover_at(editor: &mut Editor, anchor: Option<Anchor>, cx: &mut ViewContext<Editor>) {
if EditorSettings::get_global(cx).hover_popover_enabled {
if show_keyboard_hover(editor, cx) {
return;
}
if let Some(anchor) = anchor {
show_hover(editor, anchor, false, cx);
} else {
@@ -53,20 +48,6 @@ pub fn hover_at(editor: &mut Editor, anchor: Option<Anchor>, cx: &mut ViewContex
}
}
pub fn show_keyboard_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {
let info_popovers = editor.hover_state.info_popovers.clone();
for p in info_popovers {
let keyboard_grace = p.keyboard_grace.borrow();
if *keyboard_grace {
if let Some(anchor) = p.anchor {
show_hover(editor, anchor, false, cx);
return true;
}
}
}
return false;
}
pub struct InlayHover {
pub range: InlayHighlight,
pub tooltip: HoverBlock,
@@ -132,14 +113,12 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;
let blocks = vec![inlay_hover.tooltip];
let parsed_content = parse_blocks(&blocks, &language_registry, None, &mut cx).await;
let parsed_content = parse_blocks(&blocks, &language_registry, None).await;
let hover_popover = InfoPopover {
symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
parsed_content,
scroll_handle: ScrollHandle::new(),
keyboard_grace: Rc::new(RefCell::new(false)),
anchor: None,
};
this.update(&mut cx, |this, cx| {
@@ -312,40 +291,39 @@ fn show_hover(
let snapshot = this.update(&mut cx, |this, cx| this.snapshot(cx))?;
let mut hover_highlights = Vec::with_capacity(hovers_response.len());
let mut info_popovers = Vec::with_capacity(hovers_response.len());
let mut info_popover_tasks = Vec::with_capacity(hovers_response.len());
let mut info_popover_tasks = hovers_response
.into_iter()
.map(|hover_result| async {
// Create symbol range of anchors for highlighting and filtering of future requests.
let range = hover_result
.range
.and_then(|range| {
let start = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.start)?;
let end = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.end)?;
for hover_result in hovers_response {
// Create symbol range of anchors for highlighting and filtering of future requests.
let range = hover_result
.range
.and_then(|range| {
let start = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.start)?;
let end = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.end)?;
Some(start..end)
})
.unwrap_or_else(|| anchor..anchor);
Some(start..end)
})
.unwrap_or_else(|| anchor..anchor);
let blocks = hover_result.contents;
let language = hover_result.language;
let parsed_content = parse_blocks(&blocks, &language_registry, language).await;
let blocks = hover_result.contents;
let language = hover_result.language;
let parsed_content =
parse_blocks(&blocks, &language_registry, language, &mut cx).await;
info_popover_tasks.push((
range.clone(),
InfoPopover {
symbol_range: RangeInEditor::Text(range),
parsed_content,
scroll_handle: ScrollHandle::new(),
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
anchor: Some(anchor),
},
));
}
for (highlight_range, info_popover) in info_popover_tasks {
(
range.clone(),
InfoPopover {
symbol_range: RangeInEditor::Text(range),
parsed_content,
scroll_handle: ScrollHandle::new(),
},
)
})
.collect::<FuturesUnordered<_>>();
while let Some((highlight_range, info_popover)) = info_popover_tasks.next().await {
hover_highlights.push(highlight_range);
info_popovers.push(info_popover);
}
@@ -379,81 +357,72 @@ async fn parse_blocks(
blocks: &[HoverBlock],
language_registry: &Arc<LanguageRegistry>,
language: Option<Arc<Language>>,
cx: &mut AsyncWindowContext,
) -> Option<View<Markdown>> {
let fallback_language_name = if let Some(ref l) = language {
let l = Arc::clone(l);
Some(l.lsp_id().clone())
} else {
None
};
) -> markdown::ParsedMarkdown {
let mut text = String::new();
let mut highlights = Vec::new();
let mut region_ranges = Vec::new();
let mut regions = Vec::new();
let combined_text = blocks
.iter()
.map(|block| match &block.kind {
project::HoverBlockKind::PlainText | project::HoverBlockKind::Markdown => {
Cow::Borrowed(block.text.trim())
for block in blocks {
match &block.kind {
HoverBlockKind::PlainText => {
markdown::new_paragraph(&mut text, &mut Vec::new());
text.push_str(&block.text.replace("\\n", "\n"));
}
project::HoverBlockKind::Code { language } => {
Cow::Owned(format!("```{}\n{}\n```", language, block.text.trim()))
HoverBlockKind::Markdown => {
markdown::parse_markdown_block(
&block.text.replace("\\n", "\n"),
language_registry,
language.clone(),
&mut text,
&mut highlights,
&mut region_ranges,
&mut regions,
)
.await
}
})
.join("\n\n");
let rendered_block = cx
.new_view(|cx| {
let settings = ThemeSettings::get_global(cx);
let buffer_font_family = settings.buffer_font.family.clone();
let mut base_style = cx.text_style();
base_style.refine(&TextStyleRefinement {
font_family: Some(buffer_font_family.clone()),
color: Some(cx.theme().colors().editor_foreground),
..Default::default()
});
HoverBlockKind::Code { language } => {
if let Some(language) = language_registry
.language_for_name(language)
.now_or_never()
.and_then(Result::ok)
{
markdown::highlight_code(&mut text, &mut highlights, &block.text, &language);
} else {
text.push_str(&block.text);
}
}
}
}
let markdown_style = MarkdownStyle {
base_text_style: base_style,
code_block: StyleRefinement::default().mt(rems(1.)).mb(rems(1.)),
inline_code: TextStyleRefinement {
background_color: Some(cx.theme().colors().background),
..Default::default()
},
rule_color: Color::Muted.color(cx),
block_quote_border_color: Color::Muted.color(cx),
block_quote: TextStyleRefinement {
color: Some(Color::Muted.color(cx)),
..Default::default()
},
link: TextStyleRefinement {
color: Some(cx.theme().colors().editor_foreground),
underline: Some(gpui::UnderlineStyle {
thickness: px(1.),
color: Some(cx.theme().colors().editor_foreground),
wavy: false,
}),
..Default::default()
},
syntax: cx.theme().syntax().clone(),
selection_background_color: { cx.theme().players().local().selection },
break_style: Default::default(),
heading: StyleRefinement::default()
.font_weight(FontWeight::BOLD)
.text_base()
.mt(rems(1.))
.mb_0(),
};
let leading_space = text.chars().take_while(|c| c.is_whitespace()).count();
if leading_space > 0 {
highlights = highlights
.into_iter()
.map(|(range, style)| {
(
range.start.saturating_sub(leading_space)
..range.end.saturating_sub(leading_space),
style,
)
})
.collect();
region_ranges = region_ranges
.into_iter()
.map(|range| {
range.start.saturating_sub(leading_space)..range.end.saturating_sub(leading_space)
})
.collect();
}
Markdown::new(
combined_text,
markdown_style.clone(),
Some(language_registry.clone()),
cx,
fallback_language_name,
)
})
.ok();
rendered_block
ParsedMarkdown {
text: text.trim().to_string(),
highlights,
region_ranges,
regions,
}
}
#[derive(Default, Debug)]
@@ -475,7 +444,7 @@ impl HoverState {
style: &EditorStyle,
visible_rows: Range<DisplayRow>,
max_size: Size<Pixels>,
_workspace: Option<WeakView<Workspace>>,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> Option<(DisplayPoint, Vec<AnyElement>)> {
// If there is a diagnostic, position the popovers based on that.
@@ -513,39 +482,29 @@ impl HoverState {
elements.push(diagnostic_popover.render(style, max_size, cx));
}
for info_popover in &mut self.info_popovers {
elements.push(info_popover.render(max_size, cx));
elements.push(info_popover.render(style, max_size, workspace.clone(), cx));
}
Some((point, elements))
}
pub fn focused(&self, cx: &mut ViewContext<Editor>) -> bool {
let mut hover_popover_is_focused = false;
for info_popover in &self.info_popovers {
for markdown_view in &info_popover.parsed_content {
if markdown_view.focus_handle(cx).is_focused(cx) {
hover_popover_is_focused = true;
}
}
}
return hover_popover_is_focused;
}
}
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
pub struct InfoPopover {
pub symbol_range: RangeInEditor,
pub parsed_content: Option<View<Markdown>>,
pub parsed_content: ParsedMarkdown,
pub scroll_handle: ScrollHandle,
pub keyboard_grace: Rc<RefCell<bool>>,
pub anchor: Option<Anchor>,
}
impl InfoPopover {
pub fn render(&mut self, max_size: Size<Pixels>, cx: &mut ViewContext<Editor>) -> AnyElement {
let keyboard_grace = Rc::clone(&self.keyboard_grace);
let mut d = div()
pub fn render(
&mut self,
style: &EditorStyle,
max_size: Size<Pixels>,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> AnyElement {
div()
.id("info_popover")
.elevation_2(cx)
.overflow_y_scroll()
@@ -555,17 +514,15 @@ impl InfoPopover {
// Prevent a mouse down/move on the popover from being propagated to the editor,
// because that would dismiss the popover.
.on_mouse_move(|_, cx| cx.stop_propagation())
.on_mouse_down(MouseButton::Left, move |_, cx| {
let mut keyboard_grace = keyboard_grace.borrow_mut();
*keyboard_grace = false;
cx.stop_propagation();
})
.p_2();
if let Some(markdown) = &self.parsed_content {
d = d.child(markdown.clone());
}
d.into_any_element()
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
.child(div().p_2().child(crate::render_parsed_markdown(
"content",
&self.parsed_content,
style,
workspace,
cx,
)))
.into_any_element()
}
pub fn scroll(&self, amount: &ScrollAmount, cx: &mut ViewContext<Editor>) {
@@ -636,6 +593,8 @@ impl DiagnosticPopover {
.when(window_is_transparent(cx), |this| {
this.bg(gpui::transparent_black())
})
.max_w(max_size.width)
.max_h(max_size.height)
.cursor(CursorStyle::PointingHand)
.tooltip(move |cx| Tooltip::for_action("Go To Diagnostic", &crate::GoToDiagnostic, cx))
// Prevent a mouse move on the popover from being propagated to the editor,
@@ -649,8 +608,6 @@ impl DiagnosticPopover {
div()
.id("diagnostic-inner")
.overflow_y_scroll()
.max_w(max_size.width)
.max_h(max_size.height)
.px_2()
.py_1()
.bg(diagnostic_colors.background)
@@ -685,33 +642,17 @@ mod tests {
InlayId, PointForPosition,
};
use collections::BTreeSet;
use gpui::{FontWeight, HighlightStyle, UnderlineStyle};
use indoc::indoc;
use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet};
use lsp::LanguageServerId;
use markdown::parser::MarkdownEvent;
use project::{HoverBlock, HoverBlockKind};
use smol::stream::StreamExt;
use std::sync::atomic;
use std::sync::atomic::AtomicUsize;
use text::Bias;
impl InfoPopover {
fn get_rendered_text(&self, cx: &gpui::AppContext) -> String {
let mut rendered_text = String::new();
if let Some(parsed_content) = self.parsed_content.clone() {
let markdown = parsed_content.read(cx);
let text = markdown.parsed_markdown().source().to_string();
let data = markdown.parsed_markdown().events();
let slice = data;
for (range, event) in slice.iter() {
if [MarkdownEvent::Text, MarkdownEvent::Code].contains(event) {
rendered_text.push_str(&text[range.clone()])
}
}
}
rendered_text
}
}
use unindent::Unindent;
use util::test::marked_text_ranges;
#[gpui::test]
async fn test_mouse_hover_info_popover_with_autocomplete_popover(
@@ -795,7 +736,7 @@ mod tests {
.advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
requests.next().await;
cx.editor(|editor, cx| {
cx.editor(|editor, _| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.info_popovers.len(),
@@ -803,13 +744,14 @@ mod tests {
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
let rendered_text = editor
let rendered = editor
.hover_state
.info_popovers
.first()
.cloned()
.unwrap()
.get_rendered_text(cx);
assert_eq!(rendered_text, "some basic docs".to_string())
.parsed_content;
assert_eq!(rendered.text, "some basic docs".to_string())
});
// check that the completion menu is still visible and that there still has only been 1 completion request
@@ -835,7 +777,7 @@ mod tests {
assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
//verify the information popover is still visible and unchanged
cx.editor(|editor, cx| {
cx.editor(|editor, _| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.info_popovers.len(),
@@ -843,14 +785,14 @@ mod tests {
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
let rendered_text = editor
let rendered = editor
.hover_state
.info_popovers
.first()
.cloned()
.unwrap()
.get_rendered_text(cx);
assert_eq!(rendered_text, "some basic docs".to_string())
.parsed_content;
assert_eq!(rendered.text, "some basic docs".to_string())
});
// Mouse moved with no hover response dismisses
@@ -928,7 +870,7 @@ mod tests {
.advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
requests.next().await;
cx.editor(|editor, cx| {
cx.editor(|editor, _| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.info_popovers.len(),
@@ -936,14 +878,14 @@ mod tests {
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
let rendered_text = editor
let rendered = editor
.hover_state
.info_popovers
.first()
.cloned()
.unwrap()
.get_rendered_text(cx);
assert_eq!(rendered_text, "some basic docs".to_string())
.parsed_content;
assert_eq!(rendered.text, "some basic docs".to_string())
});
// Mouse moved with no hover response dismisses
@@ -989,49 +931,34 @@ mod tests {
let symbol_range = cx.lsp_range(indoc! {"
«fn» test() { println!(); }
"});
cx.editor(|editor, _cx| {
assert!(!editor.hover_state.visible());
assert_eq!(
editor.hover_state.info_popovers.len(),
0,
"Expected no hovers but got but got: {:?}",
editor.hover_state.info_popovers
);
});
let mut requests =
cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown,
value: "some other basic docs".to_string(),
}),
range: Some(symbol_range),
}))
});
requests.next().await;
cx.dispatch_action(Hover);
cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown,
value: "some other basic docs".to_string(),
}),
range: Some(symbol_range),
}))
})
.next()
.await;
cx.condition(|editor, _| editor.hover_state.visible()).await;
cx.editor(|editor, cx| {
cx.editor(|editor, _| {
assert_eq!(
editor.hover_state.info_popovers.len(),
1,
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
let rendered_text = editor
let rendered = editor
.hover_state
.info_popovers
.first()
.cloned()
.unwrap()
.get_rendered_text(cx);
assert_eq!(rendered_text, "some other basic docs".to_string())
.parsed_content;
assert_eq!(rendered.text, "some other basic docs".to_string())
});
}
@@ -1071,25 +998,24 @@ mod tests {
})
.next()
.await;
cx.dispatch_action(Hover);
cx.condition(|editor, _| editor.hover_state.visible()).await;
cx.editor(|editor, cx| {
cx.editor(|editor, _| {
assert_eq!(
editor.hover_state.info_popovers.len(),
1,
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
let rendered_text = editor
let rendered = editor
.hover_state
.info_popovers
.first()
.cloned()
.unwrap()
.get_rendered_text(cx);
.parsed_content;
assert_eq!(
rendered_text,
rendered.text,
"regular text for hover to show".to_string(),
"No empty string hovers should be shown"
);
@@ -1137,25 +1063,24 @@ mod tests {
.next()
.await;
cx.dispatch_action(Hover);
cx.condition(|editor, _| editor.hover_state.visible()).await;
cx.editor(|editor, cx| {
cx.editor(|editor, _| {
assert_eq!(
editor.hover_state.info_popovers.len(),
1,
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
let rendered_text = editor
let rendered = editor
.hover_state
.info_popovers
.first()
.cloned()
.unwrap()
.get_rendered_text(cx);
.parsed_content;
assert_eq!(
rendered_text, code_str,
rendered.text,
code_str.trim(),
"Should not have extra line breaks at end of rendered hover"
);
});
@@ -1231,6 +1156,153 @@ mod tests {
});
}
#[gpui::test]
fn test_render_blocks(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let languages = Arc::new(LanguageRegistry::test(cx.executor()));
let editor = cx.add_window(|cx| Editor::single_line(cx));
editor
.update(cx, |editor, _cx| {
let style = editor.style.clone().unwrap();
struct Row {
blocks: Vec<HoverBlock>,
expected_marked_text: String,
expected_styles: Vec<HighlightStyle>,
}
let rows = &[
// Strong emphasis
Row {
blocks: vec![HoverBlock {
text: "one **two** three".to_string(),
kind: HoverBlockKind::Markdown,
}],
expected_marked_text: "one «two» three".to_string(),
expected_styles: vec![HighlightStyle {
font_weight: Some(FontWeight::BOLD),
..Default::default()
}],
},
// Links
Row {
blocks: vec![HoverBlock {
text: "one [two](https://the-url) three".to_string(),
kind: HoverBlockKind::Markdown,
}],
expected_marked_text: "one «two» three".to_string(),
expected_styles: vec![HighlightStyle {
underline: Some(UnderlineStyle {
thickness: 1.0.into(),
..Default::default()
}),
..Default::default()
}],
},
// Lists
Row {
blocks: vec![HoverBlock {
text: "
lists:
* one
- a
- b
* two
- [c](https://the-url)
- d"
.unindent(),
kind: HoverBlockKind::Markdown,
}],
expected_marked_text: "
lists:
- one
- a
- b
- two
- «c»
- d"
.unindent(),
expected_styles: vec![HighlightStyle {
underline: Some(UnderlineStyle {
thickness: 1.0.into(),
..Default::default()
}),
..Default::default()
}],
},
// Multi-paragraph list items
Row {
blocks: vec![HoverBlock {
text: "
* one two
three
* four five
* six seven
eight
nine
* ten
* six"
.unindent(),
kind: HoverBlockKind::Markdown,
}],
expected_marked_text: "
- one two three
- four five
- six seven eight
nine
- ten
- six"
.unindent(),
expected_styles: vec![HighlightStyle {
underline: Some(UnderlineStyle {
thickness: 1.0.into(),
..Default::default()
}),
..Default::default()
}],
},
];
for Row {
blocks,
expected_marked_text,
expected_styles,
} in &rows[0..]
{
let rendered = smol::block_on(parse_blocks(&blocks, &languages, None));
let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
let expected_highlights = ranges
.into_iter()
.zip(expected_styles.iter().cloned())
.collect::<Vec<_>>();
assert_eq!(
rendered.text, expected_text,
"wrong text for input {blocks:?}"
);
let rendered_highlights: Vec<_> = rendered
.highlights
.iter()
.filter_map(|(range, highlight)| {
let highlight = highlight.to_highlight_style(&style.syntax)?;
Some((range.clone(), highlight))
})
.collect();
assert_eq!(
rendered_highlights, expected_highlights,
"wrong highlights for input {blocks:?}"
);
}
})
.unwrap();
}
#[gpui::test]
async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) {
init_test(cx, |settings| {
@@ -1474,8 +1546,9 @@ mod tests {
"Popover range should match the new type label part"
);
assert_eq!(
popover.get_rendered_text(cx),
format!("A tooltip for {new_type_label}"),
popover.parsed_content.text,
format!("A tooltip for `{new_type_label}`"),
"Rendered text should not anyhow alter backticks"
);
});
@@ -1529,7 +1602,7 @@ mod tests {
"Popover range should match the struct label part"
);
assert_eq!(
popover.get_rendered_text(cx),
popover.parsed_content.text,
format!("A tooltip for {struct_label}"),
"Rendered markdown element should remove backticks from text"
);

View File

@@ -371,7 +371,7 @@ async fn update_editor_from_message(
continue;
};
let buffer_id = BufferId::new(excerpt.buffer_id)?;
let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else {
let Some(buffer) = project.read(cx).buffer_for_id(buffer_id) else {
continue;
};

View File

@@ -49,11 +49,13 @@ fn display_ranges<'a>(
.pending
.as_ref()
.map(|pending| &pending.selection);
selections
.disjoint
.iter()
.chain(pending)
.map(move |s| s.start.to_display_point(&display_map)..s.end.to_display_point(&display_map))
selections.disjoint.iter().chain(pending).map(move |s| {
if s.reversed {
s.end.to_display_point(&display_map)..s.start.to_display_point(&display_map)
} else {
s.start.to_display_point(&display_map)..s.end.to_display_point(&display_map)
}
})
}
pub fn deploy_context_menu(

View File

@@ -1,225 +0,0 @@
mod popover;
mod state;
use crate::actions::ShowSignatureHelp;
use crate::{Editor, EditorSettings, ToggleAutoSignatureHelp};
use gpui::{AppContext, ViewContext};
use language::markdown::parse_markdown;
use multi_buffer::{Anchor, ToOffset};
use settings::Settings;
use std::ops::Range;
pub use popover::SignatureHelpPopover;
pub use state::SignatureHelpState;
// Language-specific settings may define quotes as "brackets", so filter them out separately.
const QUOTE_PAIRS: [(&'static str, &'static str); 3] = [("'", "'"), ("\"", "\""), ("`", "`")];
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SignatureHelpHiddenBy {
AutoClose,
Escape,
Selection,
}
impl Editor {
pub fn toggle_auto_signature_help_menu(
&mut self,
_: &ToggleAutoSignatureHelp,
cx: &mut ViewContext<Self>,
) {
self.auto_signature_help = self
.auto_signature_help
.map(|auto_signature_help| !auto_signature_help)
.or_else(|| Some(!EditorSettings::get_global(cx).auto_signature_help));
match self.auto_signature_help {
Some(auto_signature_help) if auto_signature_help => {
self.show_signature_help(&ShowSignatureHelp, cx);
}
Some(_) => {
self.hide_signature_help(cx, SignatureHelpHiddenBy::AutoClose);
}
None => {}
}
cx.notify();
}
pub(super) fn hide_signature_help(
&mut self,
cx: &mut ViewContext<Self>,
signature_help_hidden_by: SignatureHelpHiddenBy,
) -> bool {
if self.signature_help_state.is_shown() {
self.signature_help_state.kill_task();
self.signature_help_state.hide(signature_help_hidden_by);
cx.notify();
true
} else {
false
}
}
pub fn auto_signature_help_enabled(&self, cx: &AppContext) -> bool {
if let Some(auto_signature_help) = self.auto_signature_help {
auto_signature_help
} else {
EditorSettings::get_global(cx).auto_signature_help
}
}
pub(super) fn should_open_signature_help_automatically(
&mut self,
old_cursor_position: &Anchor,
backspace_pressed: bool,
cx: &mut ViewContext<Self>,
) -> bool {
if !(self.signature_help_state.is_shown() || self.auto_signature_help_enabled(cx)) {
return false;
}
let newest_selection = self.selections.newest::<usize>(cx);
let head = newest_selection.head();
// There are two cases where the head and tail of a selection are different: selecting multiple ranges and using backspace.
// If we dont exclude the backspace case, signature_help will blink every time backspace is pressed, so we need to prevent this.
if !newest_selection.is_empty() && !backspace_pressed && head != newest_selection.tail() {
self.signature_help_state
.hide(SignatureHelpHiddenBy::Selection);
return false;
}
let buffer_snapshot = self.buffer().read(cx).snapshot(cx);
let bracket_range = |position: usize| match (position, position + 1) {
(0, b) if b <= buffer_snapshot.len() => 0..b,
(0, b) => 0..b - 1,
(a, b) if b <= buffer_snapshot.len() => a - 1..b,
(a, b) => a - 1..b - 1,
};
let not_quote_like_brackets = |start: Range<usize>, end: Range<usize>| {
let text = buffer_snapshot.text();
let (text_start, text_end) = (text.get(start), text.get(end));
QUOTE_PAIRS
.into_iter()
.all(|(start, end)| text_start != Some(start) && text_end != Some(end))
};
let previous_position = old_cursor_position.to_offset(&buffer_snapshot);
let previous_brackets_range = bracket_range(previous_position);
let previous_brackets_surround = buffer_snapshot
.innermost_enclosing_bracket_ranges(
previous_brackets_range,
Some(&not_quote_like_brackets),
)
.filter(|(start_bracket_range, end_bracket_range)| {
start_bracket_range.start != previous_position
&& end_bracket_range.end != previous_position
});
let current_brackets_range = bracket_range(head);
let current_brackets_surround = buffer_snapshot
.innermost_enclosing_bracket_ranges(
current_brackets_range,
Some(&not_quote_like_brackets),
)
.filter(|(start_bracket_range, end_bracket_range)| {
start_bracket_range.start != head && end_bracket_range.end != head
});
match (previous_brackets_surround, current_brackets_surround) {
(None, None) => {
self.signature_help_state
.hide(SignatureHelpHiddenBy::AutoClose);
false
}
(Some(_), None) => {
self.signature_help_state
.hide(SignatureHelpHiddenBy::AutoClose);
false
}
(None, Some(_)) => true,
(Some(previous), Some(current)) => {
let condition = self.signature_help_state.hidden_by_selection()
|| previous != current
|| (previous == current && self.signature_help_state.is_shown());
if !condition {
self.signature_help_state
.hide(SignatureHelpHiddenBy::AutoClose);
}
condition
}
}
}
pub fn show_signature_help(&mut self, _: &ShowSignatureHelp, cx: &mut ViewContext<Self>) {
if self.pending_rename.is_some() {
return;
}
let position = self.selections.newest_anchor().head();
let Some((buffer, buffer_position)) =
self.buffer.read(cx).text_anchor_for_position(position, cx)
else {
return;
};
self.signature_help_state
.set_task(cx.spawn(move |editor, mut cx| async move {
let signature_help = editor
.update(&mut cx, |editor, cx| {
let language = editor.language_at(position, cx);
let project = editor.project.clone()?;
let (markdown, language_registry) = {
project.update(cx, |project, mut cx| {
let language_registry = project.languages().clone();
(
project.signature_help(&buffer, buffer_position, &mut cx),
language_registry,
)
})
};
Some((markdown, language_registry, language))
})
.ok()
.flatten();
let signature_help_popover = if let Some((
signature_help_task,
language_registry,
language,
)) = signature_help
{
// TODO allow multiple signature helps inside the same popover
if let Some(mut signature_help) = signature_help_task.await.into_iter().next() {
let mut parsed_content = parse_markdown(
signature_help.markdown.as_str(),
&language_registry,
language,
)
.await;
parsed_content
.highlights
.append(&mut signature_help.highlights);
Some(SignatureHelpPopover { parsed_content })
} else {
None
}
} else {
None
};
editor
.update(&mut cx, |editor, cx| {
let previous_popover = editor.signature_help_state.popover();
if previous_popover != signature_help_popover.as_ref() {
if let Some(signature_help_popover) = signature_help_popover {
editor
.signature_help_state
.set_popover(signature_help_popover);
} else {
editor
.signature_help_state
.hide(SignatureHelpHiddenBy::AutoClose);
}
cx.notify();
}
})
.ok();
}));
}
}

View File

@@ -1,48 +0,0 @@
use crate::{Editor, EditorStyle};
use gpui::{
div, AnyElement, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, Size,
StatefulInteractiveElement, Styled, ViewContext, WeakView,
};
use language::ParsedMarkdown;
use ui::StyledExt;
use workspace::Workspace;
#[derive(Clone, Debug)]
pub struct SignatureHelpPopover {
pub parsed_content: ParsedMarkdown,
}
impl PartialEq for SignatureHelpPopover {
fn eq(&self, other: &Self) -> bool {
let str_equality = self.parsed_content.text.as_str() == other.parsed_content.text.as_str();
let highlight_equality = self.parsed_content.highlights == other.parsed_content.highlights;
str_equality && highlight_equality
}
}
impl SignatureHelpPopover {
pub fn render(
&mut self,
style: &EditorStyle,
max_size: Size<Pixels>,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> AnyElement {
div()
.id("signature_help_popover")
.elevation_2(cx)
.overflow_y_scroll()
.max_w(max_size.width)
.max_h(max_size.height)
.on_mouse_move(|_, cx| cx.stop_propagation())
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
.child(div().p_2().child(crate::render_parsed_markdown(
"signature_help_popover_content",
&self.parsed_content,
style,
workspace,
cx,
)))
.into_any_element()
}
}

View File

@@ -1,65 +0,0 @@
use crate::signature_help::popover::SignatureHelpPopover;
use crate::signature_help::SignatureHelpHiddenBy;
use gpui::Task;
#[derive(Default, Debug)]
pub struct SignatureHelpState {
task: Option<Task<()>>,
popover: Option<SignatureHelpPopover>,
hidden_by: Option<SignatureHelpHiddenBy>,
backspace_pressed: bool,
}
impl SignatureHelpState {
pub fn set_task(&mut self, task: Task<()>) {
self.task = Some(task);
self.hidden_by = None;
}
pub fn kill_task(&mut self) {
self.task = None;
}
pub fn popover(&self) -> Option<&SignatureHelpPopover> {
self.popover.as_ref()
}
pub fn popover_mut(&mut self) -> Option<&mut SignatureHelpPopover> {
self.popover.as_mut()
}
pub fn backspace_pressed(&self) -> bool {
self.backspace_pressed
}
pub fn set_backspace_pressed(&mut self, backspace_pressed: bool) {
self.backspace_pressed = backspace_pressed;
}
pub fn set_popover(&mut self, popover: SignatureHelpPopover) {
self.popover = Some(popover);
self.hidden_by = None;
}
pub fn hide(&mut self, hidden_by: SignatureHelpHiddenBy) {
if self.hidden_by.is_none() {
self.popover = None;
self.hidden_by = Some(hidden_by);
}
}
pub fn hidden_by_selection(&self) -> bool {
self.hidden_by == Some(SignatureHelpHiddenBy::Selection)
}
pub fn is_shown(&self) -> bool {
self.popover.is_some()
}
}
#[cfg(test)]
impl SignatureHelpState {
pub fn task(&self) -> Option<&Task<()>> {
self.task.as_ref()
}
}

View File

@@ -49,9 +49,10 @@ pub fn is_supported_wasm_api_version(
/// Returns the Wasm API version range that is supported by the Wasm host.
#[inline(always)]
pub fn wasm_api_version_range(release_channel: ReleaseChannel) -> RangeInclusive<SemanticVersion> {
let max_version = match release_channel {
ReleaseChannel::Dev | ReleaseChannel::Nightly => latest::MAX_VERSION,
ReleaseChannel::Stable | ReleaseChannel::Preview => since_v0_0_6::MAX_VERSION,
let max_version = if release_channel == ReleaseChannel::Dev {
latest::MAX_VERSION
} else {
since_v0_0_6::MAX_VERSION
};
since_v0_0_1::MIN_VERSION..=max_version

View File

@@ -67,10 +67,7 @@ pub trait Fs: Send + Sync {
self.remove_file(path, options).await
}
async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
async fn load(&self, path: &Path) -> Result<String> {
Ok(String::from_utf8(self.load_bytes(path).await?)?)
}
async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>>;
async fn load(&self, path: &Path) -> Result<String>;
async fn atomic_write(&self, path: PathBuf, text: String) -> Result<()>;
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;
async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
@@ -321,11 +318,6 @@ impl Fs for RealFs {
let text = smol::unblock(|| std::fs::read_to_string(path)).await?;
Ok(text)
}
async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>> {
let path = path.to_path_buf();
let bytes = smol::unblock(|| std::fs::read(path)).await?;
Ok(bytes)
}
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
smol::unblock(move || {
@@ -1441,10 +1433,6 @@ impl Fs for FakeFs {
Ok(String::from_utf8(content.clone())?)
}
async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>> {
self.load_internal(path).await
}
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
self.simulate_random_delay().await;
let path = normalize_path(path.as_path());

View File

@@ -14,4 +14,3 @@ futures.workspace = true
http.workspace = true
serde.workspace = true
serde_json.workspace = true
chrono.workspace = true

View File

@@ -97,7 +97,6 @@ pub struct GenerateContentRequest {
pub contents: Vec<Content>,
pub generation_config: Option<GenerationConfig>,
pub safety_settings: Option<Vec<SafetySetting>>,
pub cached_content: Option<String>,
}
#[derive(Debug, Deserialize)]
@@ -268,115 +267,3 @@ pub struct CountTokensRequest {
pub struct CountTokensResponse {
pub total_tokens: usize,
}
pub async fn create_cached_content<T: HttpClient>(
client: &T,
api_url: &str,
api_key: &str,
request: CreateCachedContentRequest,
) -> Result<CreateCachedContentResponse> {
let uri = format!("{}/v1beta/cachedContents?key={}", api_url, api_key);
let request = serde_json::to_string(&request)?;
let mut response = client.post_json(&uri, request.into()).await?;
let mut text = String::new();
response.body_mut().read_to_string(&mut text).await?;
if response.status().is_success() {
Ok(serde_json::from_str::<CreateCachedContentResponse>(&text)?)
} else {
Err(anyhow!(
"error during createCachedContent, status code: {:?}, body: {}",
response.status(),
text
))
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateCachedContentRequest {
pub contents: Vec<Content>,
pub tools: Option<Vec<Tool>>,
pub ttl: Option<String>,
pub display_name: Option<String>,
pub model: String,
pub system_instruction: Option<Content>,
pub tool_config: Option<ToolConfig>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateCachedContentResponse {
pub name: String,
pub create_time: chrono::DateTime<chrono::Utc>,
pub update_time: chrono::DateTime<chrono::Utc>,
pub expire_time: Option<chrono::DateTime<chrono::Utc>>,
pub usage_metadata: UsageMetadata,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UsageMetadata {
pub total_token_count: u32,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Tool {
pub function_declarations: Vec<FunctionDeclaration>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FunctionDeclaration {
pub name: String,
pub description: String,
pub parameters: Schema,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Schema {
#[serde(rename = "type")]
pub schema_type: SchemaType,
pub format: Option<String>,
pub description: Option<String>,
pub nullable: Option<bool>,
pub enum_values: Option<Vec<String>>,
pub properties: Option<std::collections::HashMap<String, Box<Schema>>>,
pub required: Option<Vec<String>>,
pub items: Option<Box<Schema>>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum SchemaType {
TypeUnspecified,
String,
Number,
Integer,
Boolean,
Array,
Object,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ToolConfig {
pub function_calling_config: Option<FunctionCallingConfig>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FunctionCallingConfig {
pub mode: Option<FunctionCallingMode>,
pub allowed_function_names: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum FunctionCallingMode {
ModeUnspecified,
Auto,
Any,
None,
}

View File

@@ -134,22 +134,18 @@ x11rb = { version = "0.13.0", features = [
"resource_manager",
"sync",
] }
xkbcommon = { git = "https://github.com/ConradIrwin/xkbcommon-rs", rev = "2d4c4439160c7846ede0f0ece93bf73b1e613339", features = [
"wayland",
"x11",
] }
xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
xim = { git = "https://github.com/npmania/xim-rs", rev = "27132caffc5b9bc9c432ca4afad184ab6e7c16af", features = [
"x11rb-xcb",
"x11rb-client",
] }
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5a5c4d4", features = [
"source-fontconfig-dlopen",
] }
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5a5c4d4", features = ["source-fontconfig-dlopen"] }
x11-clipboard = "0.9.2"
[target.'cfg(windows)'.dependencies]
windows.workspace = true
windows-core = "0.57"
clipboard-win = "3.1.1"
[[example]]
name = "hello_world"

View File

@@ -193,16 +193,6 @@ impl TextInput {
.find_map(|(idx, _)| (idx > offset).then_some(idx))
.unwrap_or(self.content.len())
}
fn reset(&mut self) {
self.content = "".into();
self.selected_range = 0..0;
self.selection_reversed = false;
self.marked_range = None;
self.last_layout = None;
self.last_bounds = None;
self.is_selecting = false;
}
}
impl ViewInputHandler for TextInput {
@@ -504,47 +494,13 @@ struct InputExample {
recent_keystrokes: Vec<Keystroke>,
}
impl InputExample {
fn on_reset_click(&mut self, _: &MouseUpEvent, cx: &mut ViewContext<Self>) {
self.recent_keystrokes.clear();
self.text_input
.update(cx, |text_input, _cx| text_input.reset());
cx.notify();
}
}
impl Render for InputExample {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let num_keystrokes = self.recent_keystrokes.len();
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
div()
.bg(rgb(0xaaaaaa))
.flex()
.flex_col()
.size_full()
.child(
div()
.bg(white())
.border_b_1()
.border_color(black())
.flex()
.flex_row()
.justify_between()
.child(format!("Keystrokes: {}", num_keystrokes))
.child(
div()
.border_1()
.border_color(black())
.px_2()
.bg(yellow())
.child("Reset")
.hover(|style| {
style
.bg(yellow().blend(opaque_grey(0.5, 0.5)))
.cursor_pointer()
})
.on_mouse_up(MouseButton::Left, cx.listener(Self::on_reset_click)),
),
)
.child(self.text_input.clone())
.children(self.recent_keystrokes.iter().rev().map(|ks| {
format!(

View File

@@ -406,8 +406,6 @@ pub(crate) trait PlatformTextSystem: Send + Sync {
raster_bounds: Bounds<DevicePixels>,
) -> Result<(Size<DevicePixels>, Vec<u8>)>;
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
#[cfg(target_os = "windows")]
fn destroy(&self);
}
#[derive(PartialEq, Eq, Hash, Clone)]

View File

@@ -177,9 +177,6 @@ impl PlatformTextSystem for CosmicTextSystem {
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout {
self.0.write().layout_line(text, font_size, runs)
}
#[cfg(target_os = "windows")]
fn destroy(&self) {}
}
impl CosmicTextSystemState {

View File

@@ -24,7 +24,7 @@ use x11rb::xcb_ffi::XCBConnection;
use xim::{x11rb::X11rbClient, Client};
use xim::{AttributeName, InputStyle};
use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
use xkbcommon::xkb::{self as xkbc, LayoutIndex, ModMask};
use xkbcommon::xkb as xkbc;
use crate::platform::linux::LinuxClient;
use crate::platform::{LinuxCommon, PlatformWindow};
@@ -94,13 +94,6 @@ impl From<xim::ClientError> for EventHandlerError {
}
}
#[derive(Debug, Default, Clone)]
struct XKBStateNotiy {
depressed_layout: LayoutIndex,
latched_layout: LayoutIndex,
locked_layout: LayoutIndex,
}
pub struct X11ClientState {
pub(crate) loop_handle: LoopHandle<'static, X11Client>,
pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
@@ -120,7 +113,6 @@ pub struct X11ClientState {
pub(crate) mouse_focused_window: Option<xproto::Window>,
pub(crate) keyboard_focused_window: Option<xproto::Window>,
pub(crate) xkb: xkbc::State,
previous_xkb_state: XKBStateNotiy,
pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>,
pub(crate) xim_handler: Option<XimHandler>,
pub modifiers: Modifiers,
@@ -358,7 +350,6 @@ impl X11Client {
mouse_focused_window: None,
keyboard_focused_window: None,
xkb: xkb_state,
previous_xkb_state: XKBStateNotiy::default(),
ximc,
xim_handler,
@@ -631,11 +622,7 @@ impl X11Client {
event.latched_group as u32,
event.locked_group.into(),
);
state.previous_xkb_state = XKBStateNotiy {
depressed_layout: event.base_group as u32,
latched_layout: event.latched_group as u32,
locked_layout: event.locked_group.into(),
};
let modifiers = Modifiers::from_xkb(&state.xkb);
if state.modifiers == modifiers {
drop(state);
@@ -657,18 +644,11 @@ impl X11Client {
let modifiers = modifiers_from_state(event.state);
state.modifiers = modifiers;
state.pre_ime_key_down.take();
let keystroke = {
let code = event.detail.into();
let xkb_state = state.previous_xkb_state.clone();
state.xkb.update_mask(
event.state.bits() as ModMask,
0,
0,
xkb_state.depressed_layout,
xkb_state.latched_layout,
xkb_state.locked_layout,
);
let mut keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
state.xkb.update_key(code, xkbc::KeyDirection::Down);
let keysym = state.xkb.key_get_one_sym(code);
if keysym.is_modifier_key() {
return Some(());
@@ -727,16 +707,8 @@ impl X11Client {
let keystroke = {
let code = event.detail.into();
let xkb_state = state.previous_xkb_state.clone();
state.xkb.update_mask(
event.state.bits() as ModMask,
0,
0,
xkb_state.depressed_layout,
xkb_state.latched_layout,
xkb_state.locked_layout,
);
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
state.xkb.update_key(code, xkbc::KeyDirection::Up);
let keysym = state.xkb.key_get_one_sym(code);
if keysym.is_modifier_key() {
return Some(());
@@ -1197,13 +1169,10 @@ impl LinuxClient for X11Client {
let cursor = match state.cursor_cache.get(&style) {
Some(cursor) => *cursor,
None => {
let Some(cursor) = state
let cursor = state
.cursor_handle
.load_cursor(&state.xcb_connection, &style.to_icon_name())
.log_err()
else {
return;
};
.expect("failed to load cursor");
state.cursor_cache.insert(style, cursor);
cursor
}

View File

@@ -797,7 +797,7 @@ impl Platform for MacPlatform {
CursorStyle::OpenHand => msg_send![class!(NSCursor), openHandCursor],
CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), verticalResizeCursor],
CursorStyle::ResizeLeft => msg_send![class!(NSCursor), resizeLeftCursor],
CursorStyle::ResizeRight => msg_send![class!(NSCursor), resizeRightCursor],
CursorStyle::ResizeColumn => msg_send![class!(NSCursor), resizeLeftRightCursor],

View File

@@ -1,4 +1,4 @@
use std::{borrow::Cow, mem::ManuallyDrop, sync::Arc};
use std::{borrow::Cow, sync::Arc};
use ::util::ResultExt;
use anyhow::{anyhow, Result};
@@ -39,7 +39,7 @@ pub(crate) struct DirectWriteTextSystem(RwLock<DirectWriteState>);
struct DirectWriteComponent {
locale: String,
factory: IDWriteFactory5,
bitmap_factory: ManuallyDrop<IWICImagingFactory2>,
bitmap_factory: IWICImagingFactory2,
d2d1_factory: ID2D1Factory,
in_memory_loader: IDWriteInMemoryFontFileLoader,
builder: IDWriteFontSetBuilder1,
@@ -79,7 +79,6 @@ impl DirectWriteComponent {
let factory: IDWriteFactory5 = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED)?;
let bitmap_factory: IWICImagingFactory2 =
CoCreateInstance(&CLSID_WICImagingFactory2, None, CLSCTX_INPROC_SERVER)?;
let bitmap_factory = ManuallyDrop::new(bitmap_factory);
let d2d1_factory: ID2D1Factory =
D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, None)?;
// The `IDWriteInMemoryFontFileLoader` here is supported starting from
@@ -239,11 +238,6 @@ impl PlatformTextSystem for DirectWriteTextSystem {
..Default::default()
})
}
fn destroy(&self) {
let mut lock = self.0.write();
unsafe { ManuallyDrop::drop(&mut lock.components.bitmap_factory) };
}
}
impl DirectWriteState {
@@ -589,18 +583,6 @@ impl DirectWriteState {
DWRITE_MEASURING_MODE_NATURAL,
)?
};
// todo(windows)
// This is a walkaround, deleted when figured out.
let y_offset;
let extra_height;
if params.is_emoji {
y_offset = 0;
extra_height = 0;
} else {
// make some room for scaler.
y_offset = -1;
extra_height = 2;
}
if bounds.right < bounds.left {
Ok(Bounds {
@@ -611,13 +593,11 @@ impl DirectWriteState {
Ok(Bounds {
origin: point(
((bounds.left * params.scale_factor).ceil() as i32).into(),
((bounds.top * params.scale_factor).ceil() as i32 + y_offset).into(),
((bounds.top * params.scale_factor).ceil() as i32).into(),
),
size: size(
(((bounds.right - bounds.left) * params.scale_factor).ceil() as i32).into(),
(((bounds.bottom - bounds.top) * params.scale_factor).ceil() as i32
+ extra_height)
.into(),
(((bounds.bottom - bounds.top) * params.scale_factor).ceil() as i32).into(),
),
})
}

View File

@@ -82,7 +82,7 @@ pub(crate) fn handle_msg(
WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr),
WM_IME_COMPOSITION => handle_ime_composition(handle, lparam, state_ptr),
WM_SETCURSOR => handle_set_cursor(lparam, state_ptr),
WM_SETTINGCHANGE => handle_system_settings_changed(handle, state_ptr),
WM_SETTINGCHANGE => handle_system_settings_changed(state_ptr),
CURSOR_STYLE_CHANGED => handle_cursor_changed(lparam, state_ptr),
_ => None,
};
@@ -732,10 +732,7 @@ fn handle_dpi_changed_msg(
state_ptr: Rc<WindowsWindowStatePtr>,
) -> Option<isize> {
let new_dpi = wparam.loword() as f32;
let mut lock = state_ptr.state.borrow_mut();
lock.scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
lock.border_offset.udpate(handle).log_err();
drop(lock);
state_ptr.state.borrow_mut().scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
let rect = unsafe { &*(lparam.0 as *const RECT) };
let width = rect.right - rect.left;
@@ -804,9 +801,6 @@ fn handle_hit_test_msg(
lparam: LPARAM,
state_ptr: Rc<WindowsWindowStatePtr>,
) -> Option<isize> {
if !state_ptr.is_movable {
return None;
}
if !state_ptr.hide_title_bar {
return None;
}
@@ -1053,17 +1047,12 @@ fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Op
Some(1)
}
fn handle_system_settings_changed(
handle: HWND,
state_ptr: Rc<WindowsWindowStatePtr>,
) -> Option<isize> {
fn handle_system_settings_changed(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
let mut lock = state_ptr.state.borrow_mut();
// mouse wheel
lock.system_settings.mouse_wheel_settings.update();
// mouse double click
lock.click_state.system_update();
// window border offset
lock.border_offset.udpate(handle).log_err();
Some(0)
}

View File

@@ -12,6 +12,7 @@ use std::{
use ::util::ResultExt;
use anyhow::{anyhow, Context, Result};
use clipboard_win::{get_clipboard_string, set_clipboard_string};
use futures::channel::oneshot::{self, Receiver};
use itertools::Itertools;
use parking_lot::RwLock;
@@ -21,22 +22,9 @@ use windows::{
core::*,
Win32::{
Foundation::*,
Globalization::u_memcpy,
Graphics::Gdi::*,
Security::Credentials::*,
System::{
Com::*,
DataExchange::{
CloseClipboard, EmptyClipboard, GetClipboardData, OpenClipboard,
RegisterClipboardFormatW, SetClipboardData,
},
LibraryLoader::*,
Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GMEM_MOVEABLE},
Ole::*,
SystemInformation::*,
Threading::*,
Time::*,
},
System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*, Threading::*, Time::*},
UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
},
UI::ViewManagement::UISettings,
@@ -52,8 +40,6 @@ pub(crate) struct WindowsPlatform {
background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor,
text_system: Arc<dyn PlatformTextSystem>,
clipboard_hash_format: u32,
clipboard_metadata_format: u32,
}
pub(crate) struct WindowsPlatformState {
@@ -102,9 +88,6 @@ impl WindowsPlatform {
let icon = load_icon().unwrap_or_default();
let state = RefCell::new(WindowsPlatformState::new());
let raw_window_handles = RwLock::new(SmallVec::new());
let clipboard_hash_format = register_clipboard_format(CLIPBOARD_HASH_FORMAT).unwrap();
let clipboard_metadata_format =
register_clipboard_format(CLIPBOARD_METADATA_FORMAT).unwrap();
Self {
state,
@@ -113,8 +96,6 @@ impl WindowsPlatform {
background_executor,
foreground_executor,
text_system,
clipboard_hash_format,
clipboard_metadata_format,
}
}
@@ -307,7 +288,7 @@ impl Platform for WindowsPlatform {
self.icon,
self.foreground_executor.clone(),
lock.current_cursor,
)?;
);
drop(lock);
let handle = window.get_raw_handle();
self.raw_window_handles.write().push(handle);
@@ -517,15 +498,17 @@ impl Platform for WindowsPlatform {
}
fn write_to_clipboard(&self, item: ClipboardItem) {
write_to_clipboard(
item,
self.clipboard_hash_format,
self.clipboard_metadata_format,
);
if item.text.len() > 0 {
set_clipboard_string(item.text()).unwrap();
}
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
read_from_clipboard(self.clipboard_hash_format, self.clipboard_metadata_format)
let text = get_clipboard_string().ok()?;
Some(ClipboardItem {
text,
metadata: None,
})
}
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
@@ -603,8 +586,9 @@ impl Platform for WindowsPlatform {
impl Drop for WindowsPlatform {
fn drop(&mut self) {
self.text_system.destroy();
unsafe { OleUninitialize() };
unsafe {
OleUninitialize();
}
}
}
@@ -696,133 +680,3 @@ fn should_auto_hide_scrollbars() -> Result<bool> {
let ui_settings = UISettings::new()?;
Ok(ui_settings.AutoHideScrollBars()?)
}
fn register_clipboard_format(format: PCWSTR) -> Result<u32> {
let ret = unsafe { RegisterClipboardFormatW(format) };
if ret == 0 {
Err(anyhow::anyhow!(
"Error when registering clipboard format: {}",
std::io::Error::last_os_error()
))
} else {
Ok(ret)
}
}
fn write_to_clipboard(item: ClipboardItem, hash_format: u32, metadata_format: u32) {
write_to_clipboard_inner(item, hash_format, metadata_format).log_err();
unsafe { CloseClipboard().log_err() };
}
fn write_to_clipboard_inner(
item: ClipboardItem,
hash_format: u32,
metadata_format: u32,
) -> Result<()> {
unsafe {
OpenClipboard(None)?;
EmptyClipboard()?;
let encode_wide = item.text.encode_utf16().chain(Some(0)).collect_vec();
set_data_to_clipboard(&encode_wide, CF_UNICODETEXT.0 as u32)?;
if let Some(ref metadata) = item.metadata {
let hash_result = {
let hash = ClipboardItem::text_hash(&item.text);
hash.to_ne_bytes()
};
let encode_wide = std::slice::from_raw_parts(hash_result.as_ptr().cast::<u16>(), 4);
set_data_to_clipboard(encode_wide, hash_format)?;
let metadata_wide = metadata.encode_utf16().chain(Some(0)).collect_vec();
set_data_to_clipboard(&metadata_wide, metadata_format)?;
}
}
Ok(())
}
fn set_data_to_clipboard(data: &[u16], format: u32) -> Result<()> {
unsafe {
let global = GlobalAlloc(GMEM_MOVEABLE, data.len() * 2)?;
let handle = GlobalLock(global);
u_memcpy(handle as _, data.as_ptr(), data.len() as _);
let _ = GlobalUnlock(global);
SetClipboardData(format, HANDLE(global.0 as isize))?;
}
Ok(())
}
fn read_from_clipboard(hash_format: u32, metadata_format: u32) -> Option<ClipboardItem> {
let result = read_from_clipboard_inner(hash_format, metadata_format).log_err();
unsafe { CloseClipboard().log_err() };
result
}
fn read_from_clipboard_inner(hash_format: u32, metadata_format: u32) -> Result<ClipboardItem> {
unsafe {
OpenClipboard(None)?;
let text = {
let handle = GetClipboardData(CF_UNICODETEXT.0 as u32)?;
let text = PCWSTR(handle.0 as *const u16);
String::from_utf16_lossy(text.as_wide())
};
let mut item = ClipboardItem {
text,
metadata: None,
};
let Some(hash) = read_hash_from_clipboard(hash_format) else {
return Ok(item);
};
let Some(metadata) = read_metadata_from_clipboard(metadata_format) else {
return Ok(item);
};
if hash == ClipboardItem::text_hash(&item.text) {
item.metadata = Some(metadata);
}
Ok(item)
}
}
fn read_hash_from_clipboard(hash_format: u32) -> Option<u64> {
unsafe {
let handle = GetClipboardData(hash_format).log_err()?;
let raw_ptr = handle.0 as *const u16;
let hash_bytes: [u8; 8] = std::slice::from_raw_parts(raw_ptr.cast::<u8>(), 8)
.to_vec()
.try_into()
.log_err()?;
Some(u64::from_ne_bytes(hash_bytes))
}
}
fn read_metadata_from_clipboard(metadata_format: u32) -> Option<String> {
unsafe {
let handle = GetClipboardData(metadata_format).log_err()?;
let text = PCWSTR(handle.0 as *const u16);
Some(String::from_utf16_lossy(text.as_wide()))
}
}
// clipboard
pub const CLIPBOARD_HASH_FORMAT: PCWSTR = windows::core::w!("zed-text-hash");
pub const CLIPBOARD_METADATA_FORMAT: PCWSTR = windows::core::w!("zed-metadata");
#[cfg(test)]
mod tests {
use crate::{ClipboardItem, Platform, WindowsPlatform};
#[test]
fn test_clipboard() {
let platform = WindowsPlatform::new();
let item = ClipboardItem::new("你好".to_string());
platform.write_to_clipboard(item.clone());
assert_eq!(platform.read_from_clipboard(), Some(item));
let item = ClipboardItem::new("12345".to_string());
platform.write_to_clipboard(item.clone());
assert_eq!(platform.read_from_clipboard(), Some(item));
let item = ClipboardItem::new("abcdef".to_string()).with_metadata(vec![3, 4]);
platform.write_to_clipboard(item.clone());
assert_eq!(platform.read_from_clipboard(), Some(item));
}
}

View File

@@ -11,7 +11,7 @@ use std::{
};
use ::util::ResultExt;
use anyhow::{Context, Result};
use anyhow::Context;
use futures::channel::oneshot::{self, Receiver};
use itertools::Itertools;
use raw_window_handle as rwh;
@@ -35,7 +35,6 @@ pub struct WindowsWindowState {
pub origin: Point<Pixels>,
pub logical_size: Size<Pixels>,
pub fullscreen_restore_bounds: Bounds<Pixels>,
pub border_offset: WindowBorderOffset,
pub scale_factor: f32,
pub callbacks: Callbacks,
@@ -58,7 +57,6 @@ pub(crate) struct WindowsWindowStatePtr {
pub(crate) state: RefCell<WindowsWindowState>,
pub(crate) handle: AnyWindowHandle,
pub(crate) hide_title_bar: bool,
pub(crate) is_movable: bool,
pub(crate) executor: ForegroundExecutor,
}
@@ -69,7 +67,7 @@ impl WindowsWindowState {
cs: &CREATESTRUCTW,
current_cursor: HCURSOR,
display: WindowsDisplay,
) -> Result<Self> {
) -> Self {
let scale_factor = {
let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32
@@ -83,8 +81,7 @@ impl WindowsWindowState {
origin,
size: logical_size,
};
let border_offset = WindowBorderOffset::default();
let renderer = windows_renderer::windows_renderer(hwnd, transparent)?;
let renderer = windows_renderer::windows_renderer(hwnd, transparent);
let callbacks = Callbacks::default();
let input_handler = None;
let click_state = ClickState::new();
@@ -92,11 +89,10 @@ impl WindowsWindowState {
let nc_button_pressed = None;
let fullscreen = None;
Ok(Self {
Self {
origin,
logical_size,
fullscreen_restore_bounds,
border_offset,
scale_factor,
callbacks,
input_handler,
@@ -108,7 +104,7 @@ impl WindowsWindowState {
display,
fullscreen,
hwnd,
})
}
}
#[inline]
@@ -127,8 +123,7 @@ impl WindowsWindowState {
}
}
// Calculate the bounds used for saving and whether the window is maximized.
fn calculate_window_bounds(&self) -> (Bounds<Pixels>, bool) {
fn window_bounds(&self) -> WindowBounds {
let placement = unsafe {
let mut placement = WINDOWPLACEMENT {
length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
@@ -137,22 +132,22 @@ impl WindowsWindowState {
GetWindowPlacement(self.hwnd, &mut placement).log_err();
placement
};
(
calculate_client_rect(
placement.rcNormalPosition,
self.border_offset,
let physical_size = size(
DevicePixels(placement.rcNormalPosition.right - placement.rcNormalPosition.left),
DevicePixels(placement.rcNormalPosition.bottom - placement.rcNormalPosition.top),
);
let bounds = Bounds {
origin: logical_point(
placement.rcNormalPosition.left as f32,
placement.rcNormalPosition.top as f32,
self.scale_factor,
),
placement.showCmd == SW_SHOWMAXIMIZED.0 as u32,
)
}
fn window_bounds(&self) -> WindowBounds {
let (bounds, maximized) = self.calculate_window_bounds();
size: physical_size.to_pixels(self.scale_factor),
};
if self.is_fullscreen() {
WindowBounds::Fullscreen(self.fullscreen_restore_bounds)
} else if maximized {
} else if placement.showCmd == SW_SHOWMAXIMIZED.0 as u32 {
WindowBounds::Maximized(bounds)
} else {
WindowBounds::Windowed(bounds)
@@ -203,23 +198,22 @@ impl WindowsWindowState {
}
impl WindowsWindowStatePtr {
fn new(context: &WindowCreateContext, hwnd: HWND, cs: &CREATESTRUCTW) -> Result<Rc<Self>> {
fn new(context: &WindowCreateContext, hwnd: HWND, cs: &CREATESTRUCTW) -> Rc<Self> {
let state = RefCell::new(WindowsWindowState::new(
hwnd,
context.transparent,
cs,
context.current_cursor,
context.display,
)?);
));
Ok(Rc::new(Self {
Rc::new(Self {
state,
hwnd,
handle: context.handle,
hide_title_bar: context.hide_title_bar,
is_movable: context.is_movable,
executor: context.executor.clone(),
}))
})
}
}
@@ -236,12 +230,11 @@ pub(crate) struct Callbacks {
}
struct WindowCreateContext {
inner: Option<Result<Rc<WindowsWindowStatePtr>>>,
inner: Option<Rc<WindowsWindowStatePtr>>,
handle: AnyWindowHandle,
hide_title_bar: bool,
display: WindowsDisplay,
transparent: bool,
is_movable: bool,
executor: ForegroundExecutor,
current_cursor: HCURSOR,
}
@@ -253,7 +246,7 @@ impl WindowsWindow {
icon: HICON,
executor: ForegroundExecutor,
current_cursor: HCURSOR,
) -> Result<Self> {
) -> Self {
let classname = register_wnd_class(icon);
let hide_title_bar = params
.titlebar
@@ -268,14 +261,7 @@ impl WindowsWindow {
.map(|title| title.as_ref())
.unwrap_or(""),
);
let (dwexstyle, dwstyle) = if params.kind == WindowKind::PopUp {
(WS_EX_TOOLWINDOW, WINDOW_STYLE(0x0))
} else {
(
WS_EX_APPWINDOW,
WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX,
)
};
let dwstyle = WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
let hinstance = get_module_handle();
let display = if let Some(display_id) = params.display_id {
// if we obtain a display_id, then this ID must be valid.
@@ -289,14 +275,13 @@ impl WindowsWindow {
hide_title_bar,
display,
transparent: true,
is_movable: params.is_movable,
executor,
current_cursor,
};
let lpparam = Some(&context as *const _ as *const _);
let raw_hwnd = unsafe {
CreateWindowExW(
dwexstyle,
WS_EX_APPWINDOW,
classname,
&windowname,
dwstyle,
@@ -310,31 +295,32 @@ impl WindowsWindow {
lpparam,
)
};
let state_ptr = context.inner.take().unwrap()?;
register_drag_drop(state_ptr.clone())?;
let state_ptr = Rc::clone(context.inner.as_ref().unwrap());
register_drag_drop(state_ptr.clone());
let wnd = Self(state_ptr);
unsafe {
let mut placement = WINDOWPLACEMENT {
length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
..Default::default()
};
GetWindowPlacement(raw_hwnd, &mut placement)?;
GetWindowPlacement(raw_hwnd, &mut placement).log_err();
// the bounds may be not inside the display
let bounds = if display.check_given_bounds(params.bounds) {
params.bounds
} else {
display.default_bounds()
};
let mut lock = state_ptr.state.borrow_mut();
let bounds = bounds.to_device_pixels(lock.scale_factor);
lock.border_offset.udpate(raw_hwnd)?;
placement.rcNormalPosition = calcualte_window_rect(bounds, lock.border_offset);
drop(lock);
SetWindowPlacement(raw_hwnd, &placement)?;
let bounds = bounds.to_device_pixels(wnd.0.state.borrow().scale_factor);
placement.rcNormalPosition.left = bounds.left().0;
placement.rcNormalPosition.right = bounds.right().0;
placement.rcNormalPosition.top = bounds.top().0;
placement.rcNormalPosition.bottom = bounds.bottom().0;
SetWindowPlacement(raw_hwnd, &placement).log_err();
}
unsafe { ShowWindow(raw_hwnd, SW_SHOW).ok()? };
unsafe { ShowWindow(raw_hwnd, SW_SHOW).ok().log_err() };
Ok(Self(state_ptr))
wnd
}
}
@@ -550,6 +536,10 @@ impl PlatformWindow for WindowsWindow {
.executor
.spawn(async move {
let mut lock = state_ptr.state.borrow_mut();
lock.fullscreen_restore_bounds = Bounds {
origin: lock.origin,
size: lock.logical_size,
};
let StyleAndBounds {
style,
x,
@@ -559,8 +549,6 @@ impl PlatformWindow for WindowsWindow {
} = if let Some(state) = lock.fullscreen.take() {
state
} else {
let (window_bounds, _) = lock.calculate_window_bounds();
lock.fullscreen_restore_bounds = window_bounds;
let style =
WINDOW_STYLE(unsafe { get_window_long(state_ptr.hwnd, GWL_STYLE) } as _);
let mut rc = RECT::default();
@@ -865,32 +853,6 @@ struct StyleAndBounds {
cy: i32,
}
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct WindowBorderOffset {
width_offset: i32,
height_offset: i32,
}
impl WindowBorderOffset {
pub(crate) fn udpate(&mut self, hwnd: HWND) -> anyhow::Result<()> {
let window_rect = unsafe {
let mut rect = std::mem::zeroed();
GetWindowRect(hwnd, &mut rect)?;
rect
};
let client_rect = unsafe {
let mut rect = std::mem::zeroed();
GetClientRect(hwnd, &mut rect)?;
rect
};
self.width_offset =
(window_rect.right - window_rect.left) - (client_rect.right - client_rect.left);
self.height_offset =
(window_rect.bottom - window_rect.top) - (client_rect.bottom - client_rect.top);
Ok(())
}
}
fn register_wnd_class(icon_handle: HICON) -> PCWSTR {
const CLASS_NAME: PCWSTR = w!("Zed::Window");
@@ -921,14 +883,10 @@ unsafe extern "system" fn wnd_proc(
let cs = unsafe { &*cs };
let ctx = cs.lpCreateParams as *mut WindowCreateContext;
let ctx = unsafe { &mut *ctx };
let creation_result = WindowsWindowStatePtr::new(ctx, hwnd, cs);
if creation_result.is_err() {
ctx.inner = Some(creation_result);
return LRESULT(0);
}
let weak = Box::new(Rc::downgrade(creation_result.as_ref().unwrap()));
let state_ptr = WindowsWindowStatePtr::new(ctx, hwnd, cs);
let weak = Box::new(Rc::downgrade(&state_ptr));
unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };
ctx.inner = Some(creation_result);
ctx.inner = Some(state_ptr);
return LRESULT(1);
}
let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowStatePtr>;
@@ -976,7 +934,7 @@ fn get_module_handle() -> HMODULE {
}
}
fn register_drag_drop(state_ptr: Rc<WindowsWindowStatePtr>) -> Result<()> {
fn register_drag_drop(state_ptr: Rc<WindowsWindowStatePtr>) {
let window_handle = state_ptr.hwnd;
let handler = WindowsDragDropHandler(state_ptr);
// The lifetime of `IDropTarget` is handled by Windows, it wont release untill
@@ -985,54 +943,8 @@ fn register_drag_drop(state_ptr: Rc<WindowsWindowStatePtr>) -> Result<()> {
let drag_drop_handler: IDropTarget = handler.into();
unsafe {
RegisterDragDrop(window_handle, &drag_drop_handler)
.context("unable to register drag-drop event")?;
}
Ok(())
}
fn calcualte_window_rect(bounds: Bounds<DevicePixels>, border_offset: WindowBorderOffset) -> RECT {
// NOTE:
// The reason that not using `AdjustWindowRectEx()` here is
// that the size reported by this function is incorrect.
// You can test it, and there are similar discussions online.
// See: https://stackoverflow.com/questions/12423584/how-to-set-exact-client-size-for-overlapped-window-winapi
//
// So we manually calculate these values here.
let mut rect = RECT {
left: bounds.left().0,
top: bounds.top().0,
right: bounds.right().0,
bottom: bounds.bottom().0,
.expect("unable to register drag-drop event")
};
let left_offset = border_offset.width_offset / 2;
let top_offset = border_offset.height_offset / 2;
let right_offset = border_offset.width_offset - left_offset;
let bottom_offet = border_offset.height_offset - top_offset;
rect.left -= left_offset;
rect.top -= top_offset;
rect.right += right_offset;
rect.bottom += bottom_offet;
rect
}
fn calculate_client_rect(
rect: RECT,
border_offset: WindowBorderOffset,
scale_factor: f32,
) -> Bounds<Pixels> {
let left_offset = border_offset.width_offset / 2;
let top_offset = border_offset.height_offset / 2;
let right_offset = border_offset.width_offset - left_offset;
let bottom_offet = border_offset.height_offset - top_offset;
let left = rect.left + left_offset;
let top = rect.top + top_offset;
let right = rect.right - right_offset;
let bottom = rect.bottom - bottom_offet;
let physical_size = size(DevicePixels(right - left), DevicePixels(bottom - top));
Bounds {
origin: logical_point(left as f32, top as f32, scale_factor),
size: physical_size.to_pixels(scale_factor),
}
}
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
@@ -1050,7 +962,7 @@ mod windows_renderer {
platform::blade::{BladeRenderer, BladeSurfaceConfig},
};
pub(super) fn windows_renderer(hwnd: HWND, transparent: bool) -> anyhow::Result<BladeRenderer> {
pub(super) fn windows_renderer(hwnd: HWND, transparent: bool) -> BladeRenderer {
let raw = RawWindow { hwnd: hwnd.0 };
let gpu: Arc<gpu::Context> = Arc::new(
unsafe {
@@ -1063,14 +975,14 @@ mod windows_renderer {
},
)
}
.map_err(|e| anyhow::anyhow!("{:?}", e))?,
.unwrap(),
);
let config = BladeSurfaceConfig {
size: gpu::Extent::default(),
transparent,
};
Ok(BladeRenderer::new(gpu, config))
BladeRenderer::new(gpu, config)
}
struct RawWindow {

View File

@@ -212,8 +212,6 @@ impl Default for TextStyle {
// todo(linux) make this configurable or choose better default
font_family: if cfg!(target_os = "linux") {
"FreeMono".into()
} else if cfg!(target_os = "windows") {
"Segoe UI".into()
} else {
"Helvetica".into()
},

View File

@@ -190,10 +190,18 @@ pub trait Styled: Sized {
self
}
/// Sets the element to align flex items along the baseline of the container's cross axis.
/// [Docs](https://tailwindcss.com/docs/align-items#baseline)
fn items_baseline(mut self) -> Self {
self.style().align_items = Some(AlignItems::Baseline);
/// Sets the element to justify flex items along the container's main axis
/// such that there is an equal amount of space between each item.
/// [Docs](https://tailwindcss.com/docs/justify-content#space-between)
fn justify_between(mut self) -> Self {
self.style().justify_content = Some(JustifyContent::SpaceBetween);
self
}
/// Sets the element to justify flex items along the center of the container's main axis.
/// [Docs](https://tailwindcss.com/docs/justify-content#center)
fn justify_center(mut self) -> Self {
self.style().justify_content = Some(JustifyContent::Center);
self
}
@@ -211,21 +219,6 @@ pub trait Styled: Sized {
self
}
/// Sets the element to justify flex items along the center of the container's main axis.
/// [Docs](https://tailwindcss.com/docs/justify-content#center)
fn justify_center(mut self) -> Self {
self.style().justify_content = Some(JustifyContent::Center);
self
}
/// Sets the element to justify flex items along the container's main axis
/// such that there is an equal amount of space between each item.
/// [Docs](https://tailwindcss.com/docs/justify-content#space-between)
fn justify_between(mut self) -> Self {
self.style().justify_content = Some(JustifyContent::SpaceBetween);
self
}
/// Sets the element to justify items along the container's main axis such
/// that there is an equal amount of space on each side of each item.
/// [Docs](https://tailwindcss.com/docs/justify-content#space-around)

View File

@@ -66,7 +66,6 @@ impl TextSystem {
// We should allow GPUI users to provide their own fallback font stack.
font("Zed Plex Mono"),
font("Helvetica"),
font("Segoe UI"), // Windows
font("Cantarell"), // Gnome
font("Ubuntu"), // Gnome (Ubuntu)
font("Noto Sans"), // KDE

View File

@@ -109,13 +109,7 @@ fn paint_line(
wrap_boundaries: &[WrapBoundary],
cx: &mut WindowContext,
) -> Result<()> {
let line_bounds = Bounds::new(
origin,
size(
layout.width,
line_height * (wrap_boundaries.len() as f32 + 1.),
),
);
let line_bounds = Bounds::new(origin, size(layout.width, line_height));
cx.paint_layer(line_bounds, |cx| {
let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
let baseline_offset = point(px(0.), padding_top + layout.ascent);

View File

@@ -1,7 +1,6 @@
mod item;
mod to_markdown;
use futures::future::BoxFuture;
pub use item::*;
pub use to_markdown::convert_rustdoc_to_markdown;
@@ -12,7 +11,7 @@ use anyhow::{bail, Context, Result};
use async_trait::async_trait;
use collections::{HashSet, VecDeque};
use fs::Fs;
use futures::{AsyncReadExt, FutureExt};
use futures::AsyncReadExt;
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
use crate::{IndexedDocsDatabase, IndexedDocsProvider, PackageName, ProviderId};
@@ -24,16 +23,124 @@ struct RustdocItemWithHistory {
pub history: Vec<String>,
}
pub struct LocalRustdocProvider {
#[async_trait]
pub trait RustdocProvider {
async fn fetch_page(
&self,
package: &PackageName,
item: Option<&RustdocItem>,
) -> Result<Option<String>>;
}
pub struct RustdocIndexer {
provider: Box<dyn RustdocProvider + Send + Sync + 'static>,
}
impl RustdocIndexer {
pub fn new(provider: Box<dyn RustdocProvider + Send + Sync + 'static>) -> Self {
Self { provider }
}
}
#[async_trait]
impl IndexedDocsProvider for RustdocIndexer {
fn id(&self) -> ProviderId {
ProviderId::rustdoc()
}
fn database_path(&self) -> PathBuf {
paths::support_dir().join("docs/rust/rustdoc-db.1.mdb")
}
async fn index(&self, package: PackageName, database: Arc<IndexedDocsDatabase>) -> Result<()> {
let Some(package_root_content) = self.provider.fetch_page(&package, None).await? else {
return Ok(());
};
let (crate_root_markdown, items) =
convert_rustdoc_to_markdown(package_root_content.as_bytes())?;
database
.insert(package.to_string(), crate_root_markdown)
.await?;
let mut seen_items = HashSet::from_iter(items.clone());
let mut items_to_visit: VecDeque<RustdocItemWithHistory> =
VecDeque::from_iter(items.into_iter().map(|item| RustdocItemWithHistory {
item,
#[cfg(debug_assertions)]
history: Vec::new(),
}));
while let Some(item_with_history) = items_to_visit.pop_front() {
let item = &item_with_history.item;
let Some(result) = self
.provider
.fetch_page(&package, Some(&item))
.await
.with_context(|| {
#[cfg(debug_assertions)]
{
format!(
"failed to fetch {item:?}: {history:?}",
history = item_with_history.history
)
}
#[cfg(not(debug_assertions))]
{
format!("failed to fetch {item:?}")
}
})?
else {
continue;
};
let (markdown, referenced_items) = convert_rustdoc_to_markdown(result.as_bytes())?;
database
.insert(format!("{package}::{}", item.display()), markdown)
.await?;
let parent_item = item;
for mut item in referenced_items {
if seen_items.contains(&item) {
continue;
}
seen_items.insert(item.clone());
item.path.extend(parent_item.path.clone());
match parent_item.kind {
RustdocItemKind::Mod => {
item.path.push(parent_item.name.clone());
}
_ => {}
}
items_to_visit.push_back(RustdocItemWithHistory {
#[cfg(debug_assertions)]
history: {
let mut history = item_with_history.history.clone();
history.push(item.url_path());
history
},
item,
});
}
}
Ok(())
}
}
pub struct LocalProvider {
fs: Arc<dyn Fs>,
cargo_workspace_root: PathBuf,
}
impl LocalRustdocProvider {
pub fn id() -> ProviderId {
ProviderId("rustdoc".into())
}
impl LocalProvider {
pub fn new(fs: Arc<dyn Fs>, cargo_workspace_root: PathBuf) -> Self {
Self {
fs,
@@ -43,53 +150,25 @@ impl LocalRustdocProvider {
}
#[async_trait]
impl IndexedDocsProvider for LocalRustdocProvider {
fn id(&self) -> ProviderId {
Self::id()
}
impl RustdocProvider for LocalProvider {
async fn fetch_page(
&self,
crate_name: &PackageName,
item: Option<&RustdocItem>,
) -> Result<Option<String>> {
let mut local_cargo_doc_path = self.cargo_workspace_root.join("target/doc");
local_cargo_doc_path.push(crate_name.as_ref());
if let Some(item) = item {
local_cargo_doc_path.push(item.url_path());
} else {
local_cargo_doc_path.push("index.html");
}
fn database_path(&self) -> PathBuf {
paths::support_dir().join("docs/rust/rustdoc-db.1.mdb")
}
let Ok(contents) = self.fs.load(&local_cargo_doc_path).await else {
return Ok(None);
};
async fn index(&self, package: PackageName, database: Arc<IndexedDocsDatabase>) -> Result<()> {
index_rustdoc(package, database, {
move |crate_name, item| {
let fs = self.fs.clone();
let cargo_workspace_root = self.cargo_workspace_root.clone();
let crate_name = crate_name.clone();
let item = item.cloned();
async move {
let target_doc_path = cargo_workspace_root.join("target/doc");
let mut local_cargo_doc_path = target_doc_path.join(crate_name.as_ref());
if !fs.is_dir(&local_cargo_doc_path).await {
let cargo_doc_exists_at_all = fs.is_dir(&target_doc_path).await;
if cargo_doc_exists_at_all {
bail!(
"no docs directory for '{crate_name}'. if this is a valid crate name, try running `cargo doc`"
);
} else {
bail!("no cargo doc directory. run `cargo doc`");
}
}
if let Some(item) = item {
local_cargo_doc_path.push(item.url_path());
} else {
local_cargo_doc_path.push("index.html");
}
let Ok(contents) = fs.load(&local_cargo_doc_path).await else {
return Ok(None);
};
Ok(Some(contents))
}
.boxed()
}
})
.await
Ok(Some(contents))
}
}
@@ -98,152 +177,50 @@ pub struct DocsDotRsProvider {
}
impl DocsDotRsProvider {
pub fn id() -> ProviderId {
ProviderId("docs-rs".into())
}
pub fn new(http_client: Arc<HttpClientWithUrl>) -> Self {
Self { http_client }
}
}
#[async_trait]
impl IndexedDocsProvider for DocsDotRsProvider {
fn id(&self) -> ProviderId {
Self::id()
}
impl RustdocProvider for DocsDotRsProvider {
async fn fetch_page(
&self,
crate_name: &PackageName,
item: Option<&RustdocItem>,
) -> Result<Option<String>> {
let version = "latest";
let path = format!(
"{crate_name}/{version}/{crate_name}{item_path}",
item_path = item
.map(|item| format!("/{}", item.url_path()))
.unwrap_or_default()
);
fn database_path(&self) -> PathBuf {
paths::support_dir().join("docs/rust/docs-rs-db.1.mdb")
}
async fn index(&self, package: PackageName, database: Arc<IndexedDocsDatabase>) -> Result<()> {
index_rustdoc(package, database, {
move |crate_name, item| {
let http_client = self.http_client.clone();
let crate_name = crate_name.clone();
let item = item.cloned();
async move {
let version = "latest";
let path = format!(
"{crate_name}/{version}/{crate_name}{item_path}",
item_path = item
.map(|item| format!("/{}", item.url_path()))
.unwrap_or_default()
);
let mut response = http_client
.get(
&format!("https://docs.rs/{path}"),
AsyncBody::default(),
true,
)
.await?;
let mut body = Vec::new();
response
.body_mut()
.read_to_end(&mut body)
.await
.context("error reading docs.rs response body")?;
if response.status().is_client_error() {
let text = String::from_utf8_lossy(body.as_slice());
bail!(
"status error {}, response: {text:?}",
response.status().as_u16()
);
}
Ok(Some(String::from_utf8(body)?))
}
.boxed()
}
})
.await
}
}
async fn index_rustdoc(
package: PackageName,
database: Arc<IndexedDocsDatabase>,
fetch_page: impl Fn(&PackageName, Option<&RustdocItem>) -> BoxFuture<'static, Result<Option<String>>>
+ Send
+ Sync,
) -> Result<()> {
let Some(package_root_content) = fetch_page(&package, None).await? else {
return Ok(());
};
let (crate_root_markdown, items) =
convert_rustdoc_to_markdown(package_root_content.as_bytes())?;
database
.insert(package.to_string(), crate_root_markdown)
.await?;
let mut seen_items = HashSet::from_iter(items.clone());
let mut items_to_visit: VecDeque<RustdocItemWithHistory> =
VecDeque::from_iter(items.into_iter().map(|item| RustdocItemWithHistory {
item,
#[cfg(debug_assertions)]
history: Vec::new(),
}));
while let Some(item_with_history) = items_to_visit.pop_front() {
let item = &item_with_history.item;
let Some(result) = fetch_page(&package, Some(&item)).await.with_context(|| {
#[cfg(debug_assertions)]
{
format!(
"failed to fetch {item:?}: {history:?}",
history = item_with_history.history
)
}
#[cfg(not(debug_assertions))]
{
format!("failed to fetch {item:?}")
}
})?
else {
continue;
};
let (markdown, referenced_items) = convert_rustdoc_to_markdown(result.as_bytes())?;
database
.insert(format!("{package}::{}", item.display()), markdown)
let mut response = self
.http_client
.get(
&format!("https://docs.rs/{path}"),
AsyncBody::default(),
true,
)
.await?;
let parent_item = item;
for mut item in referenced_items {
if seen_items.contains(&item) {
continue;
}
let mut body = Vec::new();
response
.body_mut()
.read_to_end(&mut body)
.await
.context("error reading docs.rs response body")?;
seen_items.insert(item.clone());
item.path.extend(parent_item.path.clone());
match parent_item.kind {
RustdocItemKind::Mod => {
item.path.push(parent_item.name.clone());
}
_ => {}
}
items_to_visit.push_back(RustdocItemWithHistory {
#[cfg(debug_assertions)]
history: {
let mut history = item_with_history.history.clone();
history.push(item.url_path());
history
},
item,
});
if response.status().is_client_error() {
let text = String::from_utf8_lossy(body.as_slice());
bail!(
"status error {}, response: {text:?}",
response.status().as_u16()
);
}
}
Ok(())
Ok(Some(String::from_utf8(body)?))
}
}

View File

@@ -21,6 +21,12 @@ use crate::IndexedDocsRegistry;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Deref, Display)]
pub struct ProviderId(pub Arc<str>);
impl ProviderId {
pub fn rustdoc() -> Self {
Self("rustdoc".into())
}
}
/// The name of a package.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Deref, Display)]
pub struct PackageName(Arc<str>);
@@ -51,7 +57,6 @@ pub struct IndexedDocsStore {
provider: Box<dyn IndexedDocsProvider + Send + Sync + 'static>,
indexing_tasks_by_package:
RwLock<HashMap<PackageName, Shared<Task<Result<(), Arc<anyhow::Error>>>>>>,
latest_errors_by_package: RwLock<HashMap<PackageName, Arc<str>>>,
}
impl IndexedDocsStore {
@@ -81,14 +86,9 @@ impl IndexedDocsStore {
database_future,
provider,
indexing_tasks_by_package: RwLock::new(HashMap::default()),
latest_errors_by_package: RwLock::new(HashMap::default()),
}
}
pub fn latest_error_for_package(&self, package: &PackageName) -> Option<Arc<str>> {
self.latest_errors_by_package.read().get(package).cloned()
}
/// Returns whether the package with the given name is currently being indexed.
pub fn is_indexing(&self, package: &PackageName) -> bool {
self.indexing_tasks_by_package.read().contains_key(package)
@@ -103,15 +103,6 @@ impl IndexedDocsStore {
.await
}
pub async fn load_many_by_prefix(&self, prefix: String) -> Result<Vec<(String, MarkdownDocs)>> {
self.database_future
.clone()
.await
.map_err(|err| anyhow!(err))?
.load_many_by_prefix(prefix)
.await
}
pub fn index(
self: Arc<Self>,
package: PackageName,
@@ -134,31 +125,16 @@ impl IndexedDocsStore {
}
});
let index_task = {
let package = package.clone();
async {
let database = this
.database_future
.clone()
.await
.map_err(|err| anyhow!(err))?;
this.provider.index(package, database).await
}
let index_task = async {
let database = this
.database_future
.clone()
.await
.map_err(|err| anyhow!(err))?;
this.provider.index(package, database).await
};
let result = index_task.await.map_err(Arc::new);
match &result {
Ok(_) => {
this.latest_errors_by_package.write().remove(&package);
}
Err(err) => {
this.latest_errors_by_package
.write()
.insert(package, err.to_string().into());
}
}
result
index_task.await.map_err(Arc::new)
}
})
.shared();
@@ -266,28 +242,6 @@ impl IndexedDocsDatabase {
})
}
pub fn load_many_by_prefix(&self, prefix: String) -> Task<Result<Vec<(String, MarkdownDocs)>>> {
let env = self.env.clone();
let entries = self.entries;
self.executor.spawn(async move {
let txn = env.read_txn()?;
let results = entries
.iter(&txn)?
.filter_map(|entry| {
let (key, value) = entry.ok()?;
if key.starts_with(&prefix) {
Some((key, value))
} else {
None
}
})
.collect::<Vec<_>>();
Ok(results)
})
}
pub fn insert(&self, key: String, docs: String) -> Task<Result<()>> {
let env = self.env.clone();
let entries = self.entries;

View File

@@ -122,7 +122,7 @@ lazy_static! {
pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
LanguageConfig {
name: "Plain Text".into(),
soft_wrap: Some(SoftWrap::EditorWidth),
soft_wrap: Some(SoftWrap::PreferredLineLength),
..Default::default()
},
None,

View File

@@ -13,4 +13,5 @@ brackets = [
]
tab_size = 2
soft_wrap = "preferred_line_length"
prettier_parser_name = "markdown"

View File

@@ -1,13 +1,9 @@
use anyhow::Result;
use async_trait::async_trait;
use gpui::AppContext;
use gpui::AsyncAppContext;
use language::{ContextProvider, LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary;
use node_runtime::NodeRuntime;
use project::project_settings::ProjectSettings;
use serde_json::Value;
use settings::Settings;
use std::{
any::Any,
borrow::Cow,
@@ -29,8 +25,6 @@ pub struct PythonLspAdapter {
}
impl PythonLspAdapter {
const SERVER_NAME: &'static str = "pyright";
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
PythonLspAdapter { node }
}
@@ -39,18 +33,14 @@ impl PythonLspAdapter {
#[async_trait(?Send)]
impl LspAdapter for PythonLspAdapter {
fn name(&self) -> LanguageServerName {
LanguageServerName(Self::SERVER_NAME.into())
LanguageServerName("pyright".into())
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Any + Send>> {
Ok(Box::new(
self.node
.npm_package_latest_version(Self::SERVER_NAME)
.await?,
) as Box<_>)
Ok(Box::new(self.node.npm_package_latest_version("pyright").await?) as Box<_>)
}
async fn fetch_server_binary(
@@ -61,7 +51,7 @@ impl LspAdapter for PythonLspAdapter {
) -> Result<LanguageServerBinary> {
let latest_version = latest_version.downcast::<String>().unwrap();
let server_path = container_dir.join(SERVER_PATH);
let package_name = Self::SERVER_NAME;
let package_name = "pyright";
let should_install_language_server = self
.node
@@ -174,20 +164,6 @@ impl LspAdapter for PythonLspAdapter {
filter_range,
})
}
async fn workspace_configuration(
self: Arc<Self>,
_: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
) -> Result<Value> {
cx.update(|cx| {
ProjectSettings::get_global(cx)
.lsp
.get(Self::SERVER_NAME)
.and_then(|s| s.settings.clone())
.unwrap_or_default()
})
}
}
async fn get_cached_server_binary(

View File

@@ -575,11 +575,12 @@ fn retrieve_package_id_and_bin_name_from_metadata(
metadata: CargoMetadata,
abs_path: &Path,
) -> Option<(String, String)> {
let abs_path = abs_path.to_str()?;
for package in metadata.packages {
for target in package.targets {
let is_bin = target.kind.iter().any(|kind| kind == "bin");
let target_path = PathBuf::from(target.src_path);
if target_path == abs_path && is_bin {
if target.src_path == abs_path && is_bin {
return Some((package.id, target.name));
}
}

View File

@@ -68,22 +68,10 @@ pub struct TypeScriptLspAdapter {
impl TypeScriptLspAdapter {
const OLD_SERVER_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.js";
const NEW_SERVER_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.mjs";
const SERVER_NAME: &'static str = "typescript-language-server";
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
TypeScriptLspAdapter { node }
}
async fn tsdk_path(adapter: &Arc<dyn LspAdapterDelegate>) -> &'static str {
let is_yarn = adapter
.read_text_file(PathBuf::from(".yarn/sdks/typescript/lib/typescript.js"))
.await
.is_ok();
if is_yarn {
".yarn/sdks/typescript/lib"
} else {
"node_modules/typescript/lib"
}
}
}
struct TypeScriptVersions {
@@ -94,7 +82,7 @@ struct TypeScriptVersions {
#[async_trait(?Send)]
impl LspAdapter for TypeScriptLspAdapter {
fn name(&self) -> LanguageServerName {
LanguageServerName(Self::SERVER_NAME.into())
LanguageServerName("typescript-language-server".into())
}
async fn fetch_latest_server_version(
@@ -208,14 +196,13 @@ impl LspAdapter for TypeScriptLspAdapter {
async fn initialization_options(
self: Arc<Self>,
adapter: &Arc<dyn LspAdapterDelegate>,
_: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
let tsdk_path = Self::tsdk_path(adapter).await;
Ok(Some(json!({
"provideFormatter": true,
"hostInfo": "zed",
"tsserver": {
"path": tsdk_path,
"path": "node_modules/typescript/lib",
},
"preferences": {
"includeInlayParameterNameHints": "all",
@@ -233,17 +220,8 @@ impl LspAdapter for TypeScriptLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
_: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
_cx: &mut AsyncAppContext,
) -> Result<Value> {
let override_options = cx.update(|cx| {
ProjectSettings::get_global(cx)
.lsp
.get(Self::SERVER_NAME)
.and_then(|s| s.initialization_options.clone())
})?;
if let Some(options) = override_options {
return Ok(options);
}
Ok(json!({
"completions": {
"completeFunctionCalls": true

View File

@@ -5,9 +5,7 @@ use gpui::AsyncAppContext;
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::{CodeActionKind, LanguageServerBinary};
use node_runtime::NodeRuntime;
use project::project_settings::ProjectSettings;
use serde_json::{json, Value};
use settings::Settings;
use std::{
any::Any,
ffi::OsString,
@@ -30,18 +28,6 @@ impl VtslsLspAdapter {
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
VtslsLspAdapter { node }
}
async fn tsdk_path(adapter: &Arc<dyn LspAdapterDelegate>) -> &'static str {
let is_yarn = adapter
.read_text_file(PathBuf::from(".yarn/sdks/typescript/lib/typescript.js"))
.await
.is_ok();
if is_yarn {
".yarn/sdks/typescript/lib"
} else {
"node_modules/typescript/lib"
}
}
}
struct TypeScriptVersions {
@@ -49,11 +35,10 @@ struct TypeScriptVersions {
server_version: String,
}
const SERVER_NAME: &'static str = "vtsls";
#[async_trait(?Send)]
impl LspAdapter for VtslsLspAdapter {
fn name(&self) -> LanguageServerName {
LanguageServerName(SERVER_NAME.into())
LanguageServerName("vtsls".into())
}
async fn fetch_latest_server_version(
@@ -174,12 +159,11 @@ impl LspAdapter for VtslsLspAdapter {
async fn initialization_options(
self: Arc<Self>,
adapter: &Arc<dyn LspAdapterDelegate>,
_: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
let tsdk_path = Self::tsdk_path(&adapter).await;
Ok(Some(json!({
"typescript": {
"tsdk": tsdk_path,
"tsdk": "node_modules/typescript/lib",
"format": {
"enable": true
},
@@ -212,33 +196,22 @@ impl LspAdapter for VtslsLspAdapter {
"enableServerSideFuzzyMatch": true,
"entriesLimit": 5000,
}
},
"autoUseWorkspaceTsdk": true
}
}
})))
}
async fn workspace_configuration(
self: Arc<Self>,
adapter: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
_: &Arc<dyn LspAdapterDelegate>,
_cx: &mut AsyncAppContext,
) -> Result<Value> {
let override_options = cx.update(|cx| {
ProjectSettings::get_global(cx)
.lsp
.get(SERVER_NAME)
.and_then(|s| s.initialization_options.clone())
})?;
if let Some(options) = override_options {
return Ok(options);
}
let tsdk_path = Self::tsdk_path(&adapter).await;
Ok(json!({
"typescript": {
"suggest": {
"completeFunctionCalls": true
},
"tsdk": tsdk_path,
"tsdk": "node_modules/typescript/lib",
"format": {
"enable": true
},
@@ -271,8 +244,7 @@ impl LspAdapter for VtslsLspAdapter {
"enableServerSideFuzzyMatch": true,
"entriesLimit": 5000,
}
},
"autoUseWorkspaceTsdk": true
}
}
}))
}

View File

@@ -57,7 +57,7 @@ gpui = { workspace = true, features = ["test-support"] }
live_kit_server.workspace = true
nanoid.workspace = true
sha2.workspace = true
simplelog.workspace = true
simplelog = "0.9"
[build-dependencies]
serde.workspace = true

View File

@@ -645,24 +645,7 @@ impl LanguageServer {
on_type_formatting: Some(DynamicRegistrationClientCapabilities {
dynamic_registration: None,
}),
signature_help: Some(SignatureHelpClientCapabilities {
signature_information: Some(SignatureInformationSettings {
documentation_format: Some(vec![
MarkupKind::Markdown,
MarkupKind::PlainText,
]),
parameter_information: Some(ParameterInformationSettings {
label_offset_support: Some(true),
}),
active_parameter_support: Some(true),
}),
..SignatureHelpClientCapabilities::default()
}),
synchronization: Some(TextDocumentSyncClientCapabilities {
did_save: Some(true),
..TextDocumentSyncClientCapabilities::default()
}),
..TextDocumentClientCapabilities::default()
..Default::default()
}),
experimental: Some(json!({
"serverStatusNotification": true,

View File

@@ -1,5 +1,5 @@
use assets::Assets;
use gpui::{prelude::*, rgb, App, KeyBinding, StyleRefinement, Task, View, WindowOptions};
use gpui::{prelude::*, App, KeyBinding, Task, View, WindowOptions};
use language::{language_settings::AllLanguageSettings, LanguageRegistry};
use markdown::{Markdown, MarkdownStyle};
use node_runtime::FakeNodeRuntime;
@@ -105,49 +105,44 @@ pub fn main() {
cx.activate(true);
cx.open_window(WindowOptions::default(), |cx| {
cx.new_view(|cx| {
let markdown_style = MarkdownStyle {
base_text_style: gpui::TextStyle {
font_family: "Zed Plex Sans".into(),
color: cx.theme().colors().terminal_ansi_black,
..Default::default()
},
code_block: StyleRefinement::default()
.font_family("Zed Plex Mono")
.m(rems(1.))
.bg(rgb(0xAAAAAAA)),
inline_code: gpui::TextStyleRefinement {
font_family: Some("Zed Mono".into()),
color: Some(cx.theme().colors().editor_foreground),
background_color: Some(cx.theme().colors().editor_background),
..Default::default()
},
rule_color: Color::Muted.color(cx),
block_quote_border_color: Color::Muted.color(cx),
block_quote: gpui::TextStyleRefinement {
color: Some(Color::Muted.color(cx)),
..Default::default()
},
link: gpui::TextStyleRefinement {
color: Some(Color::Accent.color(cx)),
underline: Some(gpui::UnderlineStyle {
thickness: px(1.),
color: Some(Color::Accent.color(cx)),
wavy: false,
}),
..Default::default()
},
syntax: cx.theme().syntax().clone(),
selection_background_color: {
let mut selection = cx.theme().players().local().selection;
selection.fade_out(0.7);
selection
},
..Default::default()
};
MarkdownExample::new(
MARKDOWN_EXAMPLE.to_string(),
markdown_style,
MarkdownStyle {
code_block: gpui::TextStyleRefinement {
font_family: Some("Zed Plex Mono".into()),
color: Some(cx.theme().colors().editor_foreground),
background_color: Some(cx.theme().colors().editor_background),
..Default::default()
},
inline_code: gpui::TextStyleRefinement {
font_family: Some("Zed Plex Mono".into()),
// @nate: Could we add inline-code specific styles to the theme?
color: Some(cx.theme().colors().editor_foreground),
background_color: Some(cx.theme().colors().editor_background),
..Default::default()
},
rule_color: Color::Muted.color(cx),
block_quote_border_color: Color::Muted.color(cx),
block_quote: gpui::TextStyleRefinement {
color: Some(Color::Muted.color(cx)),
..Default::default()
},
link: gpui::TextStyleRefinement {
color: Some(Color::Accent.color(cx)),
underline: Some(gpui::UnderlineStyle {
thickness: px(1.),
color: Some(Color::Accent.color(cx)),
wavy: false,
}),
..Default::default()
},
syntax: cx.theme().syntax().clone(),
selection_background_color: {
let mut selection = cx.theme().players().local().selection;
selection.fade_out(0.7);
selection
},
},
language_registry,
cx,
)
@@ -168,8 +163,7 @@ impl MarkdownExample {
language_registry: Arc<LanguageRegistry>,
cx: &mut WindowContext,
) -> Self {
let markdown =
cx.new_view(|cx| Markdown::new(text, style, Some(language_registry), cx, None));
let markdown = cx.new_view(|cx| Markdown::new(text, style, Some(language_registry), cx));
Self { markdown }
}
}

View File

@@ -1,120 +0,0 @@
use assets::Assets;
use gpui::*;
use language::{language_settings::AllLanguageSettings, LanguageRegistry};
use markdown::{Markdown, MarkdownStyle};
use node_runtime::FakeNodeRuntime;
use settings::SettingsStore;
use std::sync::Arc;
use theme::LoadThemes;
use ui::div;
use ui::prelude::*;
const MARKDOWN_EXAMPLE: &'static str = r#"
this text should be selectable
wow so cool
## Heading 2
"#;
pub fn main() {
env_logger::init();
App::new().with_assets(Assets).run(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
language::init(cx);
SettingsStore::update(cx, |store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |_| {});
});
cx.bind_keys([KeyBinding::new("cmd-c", markdown::Copy, None)]);
let node_runtime = FakeNodeRuntime::new();
let language_registry = Arc::new(LanguageRegistry::new(
Task::ready(()),
cx.background_executor().clone(),
));
languages::init(language_registry.clone(), node_runtime, cx);
theme::init(LoadThemes::JustBase, cx);
Assets.load_fonts(cx).unwrap();
cx.activate(true);
let _ = cx.open_window(WindowOptions::default(), |cx| {
cx.new_view(|cx| {
let markdown_style = MarkdownStyle {
base_text_style: gpui::TextStyle {
font_family: "Zed Mono".into(),
color: cx.theme().colors().text,
..Default::default()
},
code_block: StyleRefinement {
text: Some(gpui::TextStyleRefinement {
font_family: Some("Zed Mono".into()),
background_color: Some(cx.theme().colors().editor_background),
..Default::default()
}),
margin: gpui::EdgesRefinement {
top: Some(Length::Definite(rems(4.).into())),
left: Some(Length::Definite(rems(4.).into())),
right: Some(Length::Definite(rems(4.).into())),
bottom: Some(Length::Definite(rems(4.).into())),
},
..Default::default()
},
inline_code: gpui::TextStyleRefinement {
font_family: Some("Zed Mono".into()),
background_color: Some(cx.theme().colors().editor_background),
..Default::default()
},
rule_color: Color::Muted.color(cx),
block_quote_border_color: Color::Muted.color(cx),
block_quote: gpui::TextStyleRefinement {
color: Some(Color::Muted.color(cx)),
..Default::default()
},
link: gpui::TextStyleRefinement {
color: Some(Color::Accent.color(cx)),
underline: Some(gpui::UnderlineStyle {
thickness: px(1.),
color: Some(Color::Accent.color(cx)),
wavy: false,
}),
..Default::default()
},
syntax: cx.theme().syntax().clone(),
selection_background_color: {
let mut selection = cx.theme().players().local().selection;
selection.fade_out(0.7);
selection
},
break_style: Default::default(),
heading: Default::default(),
};
let markdown = cx.new_view(|cx| {
Markdown::new(MARKDOWN_EXAMPLE.into(), markdown_style, None, cx, None)
});
HelloWorld { markdown }
})
});
});
}
struct HelloWorld {
markdown: View<Markdown>,
}
impl Render for HelloWorld {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.flex()
.bg(rgb(0x2e7d32))
.size(Length::Definite(Pixels(700.0).into()))
.justify_center()
.items_center()
.shadow_lg()
.border_1()
.border_color(rgb(0x0000ff))
.text_xl()
.text_color(rgb(0xffffff))
.child(div().child(self.markdown.clone()).p_20())
}
}

View File

@@ -1,17 +1,16 @@
pub mod parser;
mod parser;
use crate::parser::CodeBlockKind;
use futures::FutureExt;
use gpui::{
actions, point, quad, AnyElement, AppContext, Bounds, ClipboardItem, CursorStyle,
DispatchPhase, Edges, FocusHandle, FocusableView, FontStyle, FontWeight, GlobalElementId,
Hitbox, Hsla, KeyContext, Length, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent,
Point, Render, StrikethroughStyle, StyleRefinement, StyledText, Task, TextLayout, TextRun,
TextStyle, TextStyleRefinement, View,
Hitbox, Hsla, KeyContext, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent, Point,
Render, StrikethroughStyle, Style, StyledText, Task, TextLayout, TextRun, TextStyle,
TextStyleRefinement, View,
};
use language::{Language, LanguageRegistry, Rope};
use parser::{parse_markdown, MarkdownEvent, MarkdownTag, MarkdownTagEnd};
use std::{iter, mem, ops::Range, rc::Rc, sync::Arc};
use theme::SyntaxTheme;
use ui::prelude::*;
@@ -19,8 +18,7 @@ use util::{ResultExt, TryFutureExt};
#[derive(Clone)]
pub struct MarkdownStyle {
pub base_text_style: TextStyle,
pub code_block: StyleRefinement,
pub code_block: TextStyleRefinement,
pub inline_code: TextStyleRefinement,
pub block_quote: TextStyleRefinement,
pub link: TextStyleRefinement,
@@ -28,27 +26,8 @@ pub struct MarkdownStyle {
pub block_quote_border_color: Hsla,
pub syntax: Arc<SyntaxTheme>,
pub selection_background_color: Hsla,
pub break_style: StyleRefinement,
pub heading: StyleRefinement,
}
impl Default for MarkdownStyle {
fn default() -> Self {
Self {
base_text_style: Default::default(),
code_block: Default::default(),
inline_code: Default::default(),
block_quote: Default::default(),
link: Default::default(),
rule_color: Default::default(),
block_quote_border_color: Default::default(),
syntax: Arc::new(SyntaxTheme::default()),
selection_background_color: Default::default(),
break_style: Default::default(),
heading: Default::default(),
}
}
}
pub struct Markdown {
source: String,
selection: Selection,
@@ -60,7 +39,6 @@ pub struct Markdown {
pending_parse: Option<Task<Option<()>>>,
focus_handle: FocusHandle,
language_registry: Option<Arc<LanguageRegistry>>,
fallback_code_block_language: Option<String>,
}
actions!(markdown, [Copy]);
@@ -71,7 +49,6 @@ impl Markdown {
style: MarkdownStyle,
language_registry: Option<Arc<LanguageRegistry>>,
cx: &mut ViewContext<Self>,
fallback_code_block_language: Option<String>,
) -> Self {
let focus_handle = cx.focus_handle();
let mut this = Self {
@@ -85,7 +62,6 @@ impl Markdown {
pending_parse: None,
focus_handle,
language_registry,
fallback_code_block_language,
};
this.parse(cx);
this
@@ -113,14 +89,7 @@ impl Markdown {
&self.source
}
pub fn parsed_markdown(&self) -> &ParsedMarkdown {
&self.parsed_markdown
}
fn copy(&self, text: &RenderedText, cx: &mut ViewContext<Self>) {
if self.selection.end <= self.selection.start {
return;
}
let text = text.text_for_range(self.selection.start..self.selection.end);
cx.write_to_clipboard(ClipboardItem::new(text));
}
@@ -171,7 +140,6 @@ impl Render for Markdown {
cx.view().clone(),
self.style.clone(),
self.language_registry.clone(),
self.fallback_code_block_language.clone(),
)
}
}
@@ -217,21 +185,11 @@ impl Selection {
}
#[derive(Clone)]
pub struct ParsedMarkdown {
struct ParsedMarkdown {
source: SharedString,
events: Arc<[(Range<usize>, MarkdownEvent)]>,
}
impl ParsedMarkdown {
pub fn source(&self) -> &SharedString {
&self.source
}
pub fn events(&self) -> &Arc<[(Range<usize>, MarkdownEvent)]> {
return &self.events;
}
}
impl Default for ParsedMarkdown {
fn default() -> Self {
Self {
@@ -245,7 +203,6 @@ pub struct MarkdownElement {
markdown: View<Markdown>,
style: MarkdownStyle,
language_registry: Option<Arc<LanguageRegistry>>,
fallback_code_block_language: Option<String>,
}
impl MarkdownElement {
@@ -253,31 +210,19 @@ impl MarkdownElement {
markdown: View<Markdown>,
style: MarkdownStyle,
language_registry: Option<Arc<LanguageRegistry>>,
fallback_code_block_language: Option<String>,
) -> Self {
Self {
markdown,
style,
language_registry,
fallback_code_block_language,
}
}
fn load_language(&self, name: &str, cx: &mut WindowContext) -> Option<Arc<Language>> {
let language_test = self.language_registry.as_ref()?.language_for_name(name);
let language_name = match language_test.now_or_never() {
Some(Ok(_)) => String::from(name),
Some(Err(_)) if !name.is_empty() && self.fallback_code_block_language.is_some() => {
self.fallback_code_block_language.clone().unwrap()
}
_ => String::new(),
};
let language = self
.language_registry
.as_ref()?
.language_for_name(language_name.as_str())
.language_for_name(name)
.map(|language| language.ok())
.shared();
@@ -472,7 +417,7 @@ impl MarkdownElement {
.update(cx, |markdown, _| markdown.autoscroll_request.take())?;
let (position, line_height) = rendered_text.position_for_source_index(autoscroll_index)?;
let text_style = self.style.base_text_style.clone();
let text_style = cx.text_style();
let font_id = cx.text_system().resolve_font(&text_style.font());
let font_size = text_style.font_size.to_pixels(cx.rem_size());
let em_width = cx
@@ -517,26 +462,14 @@ impl Element for MarkdownElement {
_id: Option<&GlobalElementId>,
cx: &mut WindowContext,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
let mut builder = MarkdownElementBuilder::new(
self.style.base_text_style.clone(),
self.style.syntax.clone(),
);
let mut builder = MarkdownElementBuilder::new(cx.text_style(), self.style.syntax.clone());
let parsed_markdown = self.markdown.read(cx).parsed_markdown.clone();
let markdown_end = if let Some(last) = parsed_markdown.events.last() {
last.0.end
} else {
0
};
for (range, event) in parsed_markdown.events.iter() {
match event {
MarkdownEvent::Start(tag) => {
match tag {
MarkdownTag::Paragraph => {
builder.push_div(
div().mb_2().line_height(rems(1.3)),
range,
markdown_end,
);
builder.push_div(div().mb_2().line_height(rems(1.3)));
}
MarkdownTag::Heading { level, .. } => {
let mut heading = div().mb_2();
@@ -547,11 +480,7 @@ impl Element for MarkdownElement {
pulldown_cmark::HeadingLevel::H4 => heading.text_lg(),
_ => heading,
};
heading.style().refine(&self.style.heading);
builder.push_text_style(
self.style.heading.text_style().clone().unwrap_or_default(),
);
builder.push_div(heading, range, markdown_end);
builder.push_div(heading);
}
MarkdownTag::BlockQuote => {
builder.push_text_style(self.style.block_quote.clone());
@@ -561,8 +490,6 @@ impl Element for MarkdownElement {
.mb_2()
.border_l_4()
.border_color(self.style.block_quote_border_color),
range,
markdown_end,
);
}
MarkdownTag::CodeBlock(kind) => {
@@ -572,18 +499,17 @@ impl Element for MarkdownElement {
None
};
let mut d = div().w_full().rounded_lg();
d.style().refine(&self.style.code_block);
if let Some(code_block_text_style) = &self.style.code_block.text {
builder.push_text_style(code_block_text_style.to_owned());
}
builder.push_code_block(language);
builder.push_div(d, range, markdown_end);
builder.push_text_style(self.style.code_block.clone());
builder.push_div(div().rounded_lg().p_4().mb_2().w_full().when_some(
self.style.code_block.background_color,
|div, color| div.bg(color),
));
}
MarkdownTag::HtmlBlock => builder.push_div(div(), range, markdown_end),
MarkdownTag::HtmlBlock => builder.push_div(div()),
MarkdownTag::List(bullet_index) => {
builder.push_list(*bullet_index);
builder.push_div(div().pl_4(), range, markdown_end);
builder.push_div(div().pl_4());
}
MarkdownTag::Item => {
let bullet = if let Some(bullet_index) = builder.next_bullet_index() {
@@ -599,11 +525,9 @@ impl Element for MarkdownElement {
.items_start()
.gap_1()
.child(bullet),
range,
markdown_end,
);
// Without `w_0`, text doesn't wrap to the width of the container.
builder.push_div(div().flex_1().w_0(), range, markdown_end);
builder.push_div(div().flex_1().w_0());
}
MarkdownTag::Emphasis => builder.push_text_style(TextStyleRefinement {
font_style: Some(FontStyle::Italic),
@@ -628,7 +552,6 @@ impl Element for MarkdownElement {
builder.push_text_style(self.style.link.clone())
}
}
MarkdownTag::MetadataBlock(_) => {}
_ => log::error!("unsupported markdown tag {:?}", tag),
}
}
@@ -636,10 +559,7 @@ impl Element for MarkdownElement {
MarkdownTagEnd::Paragraph => {
builder.pop_div();
}
MarkdownTagEnd::Heading(_) => {
builder.pop_div();
builder.pop_text_style()
}
MarkdownTagEnd::Heading(_) => builder.pop_div(),
MarkdownTagEnd::BlockQuote => {
builder.pop_text_style();
builder.pop_div()
@@ -647,10 +567,8 @@ impl Element for MarkdownElement {
MarkdownTagEnd::CodeBlock => {
builder.trim_trailing_newline();
builder.pop_div();
builder.pop_text_style();
builder.pop_code_block();
if self.style.code_block.text.is_some() {
builder.pop_text_style();
}
}
MarkdownTagEnd::HtmlBlock => builder.pop_div(),
MarkdownTagEnd::List(_) => {
@@ -691,24 +609,18 @@ impl Element for MarkdownElement {
.border_b_1()
.my_2()
.border_color(self.style.rule_color),
range,
markdown_end,
);
builder.pop_div()
}
MarkdownEvent::SoftBreak => builder.push_text(" ", range.start),
MarkdownEvent::HardBreak => {
let mut d = div().py_3();
d.style().refine(&self.style.break_style);
builder.push_div(d, range, markdown_end);
builder.pop_div()
}
MarkdownEvent::SoftBreak => builder.push_text("\n", range.start),
MarkdownEvent::HardBreak => builder.push_text("\n", range.start),
_ => log::error!("unsupported markdown event {:?}", event),
}
}
let mut rendered_markdown = builder.build();
let child_layout_id = rendered_markdown.element.request_layout(cx);
let layout_id = cx.request_layout(gpui::Style::default(), [child_layout_id]);
let layout_id = cx.request_layout(Style::default(), [child_layout_id]);
(layout_id, rendered_markdown)
}
@@ -820,32 +732,8 @@ impl MarkdownElementBuilder {
self.text_style_stack.pop();
}
fn push_div(&mut self, mut div: Div, range: &Range<usize>, markdown_end: usize) {
fn push_div(&mut self, div: Div) {
self.flush_text();
if range.start == 0 {
//first element, remove top margin
div.style().refine(&StyleRefinement {
margin: gpui::EdgesRefinement {
top: Some(Length::Definite(px(0.).into())),
left: None,
right: None,
bottom: None,
},
..Default::default()
});
}
if range.end == markdown_end {
div.style().refine(&StyleRefinement {
margin: gpui::EdgesRefinement {
top: None,
left: None,
right: None,
bottom: Some(Length::Definite(rems(0.).into())),
},
..Default::default()
});
}
self.div_stack.push(div);
}

View File

@@ -7,22 +7,11 @@ use std::ops::Range;
pub fn parse_markdown(text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
let mut events = Vec::new();
let mut within_link = false;
let mut within_metadata = false;
for (pulldown_event, mut range) in Parser::new_ext(text, Options::all()).into_offset_iter() {
if within_metadata {
if let pulldown_cmark::Event::End(pulldown_cmark::TagEnd::MetadataBlock { .. }) =
pulldown_event
{
within_metadata = false;
}
continue;
}
match pulldown_event {
pulldown_cmark::Event::Start(tag) => {
match tag {
pulldown_cmark::Tag::Link { .. } => within_link = true,
pulldown_cmark::Tag::MetadataBlock { .. } => within_metadata = true,
_ => {}
if let pulldown_cmark::Tag::Link { .. } = tag {
within_link = true;
}
events.push((range, MarkdownEvent::Start(tag.into())))
}

View File

@@ -131,7 +131,11 @@ impl AnchorRangeExt for Range<Anchor> {
}
fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool {
self.end.cmp(&other.start, buffer).is_ge() && self.start.cmp(&other.end, buffer).is_le()
let start_cmp = self.start.cmp(&other.start, buffer);
let end_cmp = self.end.cmp(&other.end, buffer);
(start_cmp == Ordering::Less || start_cmp == Ordering::Equal)
&& (end_cmp == Ordering::Greater || end_cmp == Ordering::Equal)
}
fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<usize> {

View File

@@ -1129,7 +1129,7 @@ impl OutlinePanel {
EntryOwned::Entry(FsEntry::File(worktree_id, _, buffer_id, _)) => {
let project = self.project.read(cx);
let entry_id = project
.buffer_for_id(buffer_id, cx)
.buffer_for_id(buffer_id)
.and_then(|buffer| buffer.read(cx).entry_id(cx));
project
.worktree_for_id(worktree_id, cx)
@@ -1147,7 +1147,7 @@ impl OutlinePanel {
.remove(&CollapsedEntry::Excerpt(buffer_id, excerpt_id));
let project = self.project.read(cx);
let entry_id = project
.buffer_for_id(buffer_id, cx)
.buffer_for_id(buffer_id)
.and_then(|buffer| buffer.read(cx).entry_id(cx));
entry_id.and_then(|entry_id| {
@@ -1622,7 +1622,11 @@ impl OutlinePanel {
ExcerptOutlines::Invalidated(_) => ExcerptOutlines::NotFetched,
ExcerptOutlines::NotFetched => ExcerptOutlines::NotFetched,
},
None => ExcerptOutlines::NotFetched,
None => {
new_collapsed_entries
.insert(CollapsedEntry::Excerpt(buffer_id, excerpt_id));
ExcerptOutlines::NotFetched
}
};
new_excerpts.entry(buffer_id).or_default().insert(
excerpt_id,
@@ -1670,6 +1674,11 @@ impl OutlinePanel {
.insert(CollapsedEntry::ExternalFile(buffer_id));
}
}
for excerpt_id in &excerpts {
new_collapsed_entries
.insert(CollapsedEntry::Excerpt(buffer_id, *excerpt_id));
}
}
if let Some(worktree) = worktree {

View File

@@ -84,7 +84,7 @@ impl Prettier {
path_to_check.pop();
}
let mut closest_package_json_path = None;
let mut project_path_with_prettier_dependency = None;
loop {
if installed_prettiers.contains(&path_to_check) {
log::debug!("Found prettier path {path_to_check:?} in installed prettiers");
@@ -92,44 +92,61 @@ impl Prettier {
} else if let Some(package_json_contents) =
read_package_json(fs, &path_to_check).await?
{
if has_prettier_in_node_modules(fs, &path_to_check).await? {
log::debug!("Found prettier path {path_to_check:?} in the node_modules");
return Ok(ControlFlow::Continue(Some(path_to_check)));
} else {
match &closest_package_json_path {
None => closest_package_json_path = Some(path_to_check.clone()),
Some(closest_package_json_path) => {
match package_json_contents.get("workspaces") {
Some(serde_json::Value::Array(workspaces)) => {
let subproject_path = closest_package_json_path.strip_prefix(&path_to_check).expect("traversing path parents, should be able to strip prefix");
if workspaces.iter().filter_map(|value| {
if let serde_json::Value::String(s) = value {
Some(s.clone())
} else {
log::warn!("Skipping non-string 'workspaces' value: {value:?}");
None
}
}).any(|workspace_definition| {
workspace_definition == subproject_path.to_string_lossy() || PathMatcher::new(&[workspace_definition]).ok().map_or(false, |path_matcher| path_matcher.is_match(subproject_path))
}) {
anyhow::ensure!(has_prettier_in_node_modules(fs, &path_to_check).await?, "Path {path_to_check:?} is the workspace root for project in {closest_package_json_path:?}, but it has no prettier installed");
log::info!("Found prettier path {path_to_check:?} in the workspace root for project in {closest_package_json_path:?}");
return Ok(ControlFlow::Continue(Some(path_to_check)));
} else {
log::warn!("Skipping path {path_to_check:?} workspace root with workspaces {workspaces:?} that have no prettier installed");
}
},
Some(unknown) => log::error!("Failed to parse workspaces for {path_to_check:?} from package.json, got {unknown:?}. Skipping."),
None => log::warn!("Skipping path {path_to_check:?} that has no prettier dependency and no workspaces section in its package.json"),
}
}
if has_prettier_in_package_json(&package_json_contents) {
if has_prettier_in_node_modules(fs, &path_to_check).await? {
log::debug!("Found prettier path {path_to_check:?} in both package.json and node_modules");
return Ok(ControlFlow::Continue(Some(path_to_check)));
} else if project_path_with_prettier_dependency.is_none() {
project_path_with_prettier_dependency = Some(path_to_check.clone());
}
} else {
match package_json_contents.get("workspaces") {
Some(serde_json::Value::Array(workspaces)) => {
match &project_path_with_prettier_dependency {
Some(project_path_with_prettier_dependency) => {
let subproject_path = project_path_with_prettier_dependency.strip_prefix(&path_to_check).expect("traversing path parents, should be able to strip prefix");
if workspaces.iter().filter_map(|value| {
if let serde_json::Value::String(s) = value {
Some(s.clone())
} else {
log::warn!("Skipping non-string 'workspaces' value: {value:?}");
None
}
}).any(|workspace_definition| {
if let Some(path_matcher) = PathMatcher::new(&[workspace_definition.clone()]).ok() {
path_matcher.is_match(subproject_path)
} else {
workspace_definition == subproject_path.to_string_lossy()
}
}) {
anyhow::ensure!(has_prettier_in_node_modules(fs, &path_to_check).await?, "Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}, but it's not installed into workspace root's node_modules");
log::info!("Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}");
return Ok(ControlFlow::Continue(Some(path_to_check)));
} else {
log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but is not included in its package.json workspaces {workspaces:?}");
}
}
None => {
log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but has no prettier in its package.json");
}
}
},
Some(unknown) => log::error!("Failed to parse workspaces for {path_to_check:?} from package.json, got {unknown:?}. Skipping."),
None => log::warn!("Skipping path {path_to_check:?} that has no prettier dependency and no workspaces section in its package.json"),
}
}
}
if !path_to_check.pop() {
log::debug!("Found no prettier in ancestors of {locate_from:?}");
return Ok(ControlFlow::Continue(None));
match project_path_with_prettier_dependency {
Some(closest_prettier_discovered) => {
anyhow::bail!("No prettier found in node_modules for ancestors of {locate_from:?}, but discovered prettier package.json dependency in {closest_prettier_discovered:?}")
}
None => {
log::debug!("Found no prettier in ancestors of {locate_from:?}");
return Ok(ControlFlow::Continue(None));
}
}
}
}
}
@@ -431,6 +448,22 @@ async fn read_package_json(
Ok(None)
}
fn has_prettier_in_package_json(
package_json_contents: &HashMap<String, serde_json::Value>,
) -> bool {
if let Some(serde_json::Value::Object(o)) = package_json_contents.get("dependencies") {
if o.contains_key(PRETTIER_PACKAGE_NAME) {
return true;
}
}
if let Some(serde_json::Value::Object(o)) = package_json_contents.get("devDependencies") {
if o.contains_key(PRETTIER_PACKAGE_NAME) {
return true;
}
}
false
}
enum Format {}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
@@ -515,36 +548,40 @@ mod tests {
)
.await;
assert_eq!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/.config/zed/settings.json"),
)
.await
.unwrap(),
ControlFlow::Continue(None),
"Should find no prettier for path hierarchy without it"
assert!(
matches!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/.config/zed/settings.json"),
)
.await,
Ok(ControlFlow::Continue(None))
),
"Should successfully find no prettier for path hierarchy without it"
);
assert_eq!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/project/src/index.js")
)
.await.unwrap(),
ControlFlow::Continue(Some(PathBuf::from("/root/work/project"))),
"Should successfully find a prettier for path hierarchy that has node_modules with prettier, but no package.json mentions of it"
assert!(
matches!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/project/src/index.js")
)
.await,
Ok(ControlFlow::Continue(None))
),
"Should successfully find no prettier for path hierarchy that has node_modules with prettier, but no package.json mentions of it"
);
assert_eq!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/project/node_modules/expect/build/print.js")
)
.await
.unwrap(),
ControlFlow::Break(()),
assert!(
matches!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/project/node_modules/expect/build/print.js")
)
.await,
Ok(ControlFlow::Break(()))
),
"Should not format files inside node_modules/"
);
}
@@ -654,17 +691,18 @@ mod tests {
)
.await;
assert_eq!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/web_blog/pages/[slug].tsx")
)
.await
.unwrap(),
ControlFlow::Continue(None),
"Should find no prettier when node_modules don't have it"
);
match Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/web_blog/pages/[slug].tsx")
)
.await {
Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"),
Err(e) => {
let message = e.to_string();
assert!(message.contains("/root/work/web_blog"), "Error message should mention which project had prettier defined");
},
};
assert_eq!(
Prettier::locate_prettier_installation(

View File

@@ -1,928 +0,0 @@
use crate::ProjectPath;
use anyhow::{anyhow, Context as _, Result};
use collections::{hash_map, HashMap};
use futures::{channel::oneshot, StreamExt as _};
use gpui::{
AppContext, AsyncAppContext, Context as _, EventEmitter, Model, ModelContext, Task, WeakModel,
};
use language::{
proto::{deserialize_version, serialize_version, split_operations},
Buffer, Capability, Language, Operation,
};
use rpc::{
proto::{self, AnyProtoClient, PeerId},
ErrorExt as _, TypedEnvelope,
};
use std::{io, path::Path, sync::Arc};
use text::BufferId;
use util::{debug_panic, maybe, ResultExt as _};
use worktree::{File, ProjectEntryId, RemoteWorktree, Worktree};
/// A set of open buffers.
pub struct BufferStore {
retain_buffers: bool,
opened_buffers: HashMap<BufferId, OpenBuffer>,
local_buffer_ids_by_path: HashMap<ProjectPath, BufferId>,
local_buffer_ids_by_entry_id: HashMap<ProjectEntryId, BufferId>,
#[allow(clippy::type_complexity)]
loading_buffers_by_path: HashMap<
ProjectPath,
postage::watch::Receiver<Option<Result<Model<Buffer>, Arc<anyhow::Error>>>>,
>,
loading_remote_buffers_by_id: HashMap<BufferId, Model<Buffer>>,
remote_buffer_listeners:
HashMap<BufferId, Vec<oneshot::Sender<Result<Model<Buffer>, anyhow::Error>>>>,
}
enum OpenBuffer {
Strong(Model<Buffer>),
Weak(WeakModel<Buffer>),
Operations(Vec<Operation>),
}
pub enum BufferStoreEvent {
BufferAdded(Model<Buffer>),
BufferChangedFilePath {
buffer: Model<Buffer>,
old_file: Option<Arc<File>>,
},
BufferSaved {
buffer: Model<Buffer>,
has_changed_file: bool,
saved_version: clock::Global,
},
}
impl EventEmitter<BufferStoreEvent> for BufferStore {}
impl BufferStore {
/// Creates a buffer store, optionally retaining its buffers.
///
/// If `retain_buffers` is `true`, then buffers are owned by the buffer store
/// and won't be released unless they are explicitly removed, or `retain_buffers`
/// is set to `false` via `set_retain_buffers`. Otherwise, buffers are stored as
/// weak handles.
pub fn new(retain_buffers: bool) -> Self {
Self {
retain_buffers,
opened_buffers: Default::default(),
remote_buffer_listeners: Default::default(),
loading_remote_buffers_by_id: Default::default(),
local_buffer_ids_by_path: Default::default(),
local_buffer_ids_by_entry_id: Default::default(),
loading_buffers_by_path: Default::default(),
}
}
pub fn open_buffer(
&mut self,
project_path: ProjectPath,
worktree: Model<Worktree>,
cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Buffer>>> {
let existing_buffer = self.get_by_path(&project_path, cx);
if let Some(existing_buffer) = existing_buffer {
return Task::ready(Ok(existing_buffer));
}
let loading_watch = match self.loading_buffers_by_path.entry(project_path.clone()) {
// If the given path is already being loaded, then wait for that existing
// task to complete and return the same buffer.
hash_map::Entry::Occupied(e) => e.get().clone(),
// Otherwise, record the fact that this path is now being loaded.
hash_map::Entry::Vacant(entry) => {
let (mut tx, rx) = postage::watch::channel();
entry.insert(rx.clone());
let project_path = project_path.clone();
let load_buffer = match worktree.read(cx) {
Worktree::Local(_) => {
self.open_local_buffer_internal(project_path.path.clone(), worktree, cx)
}
Worktree::Remote(tree) => {
self.open_remote_buffer_internal(&project_path.path, tree, cx)
}
};
cx.spawn(move |this, mut cx| async move {
let load_result = load_buffer.await;
*tx.borrow_mut() = Some(this.update(&mut cx, |this, _| {
// Record the fact that the buffer is no longer loading.
this.loading_buffers_by_path.remove(&project_path);
let buffer = load_result.map_err(Arc::new)?;
Ok(buffer)
})?);
anyhow::Ok(())
})
.detach();
rx
}
};
cx.background_executor().spawn(async move {
Self::wait_for_loading_buffer(loading_watch)
.await
.map_err(|e| e.cloned())
})
}
fn open_local_buffer_internal(
&mut self,
path: Arc<Path>,
worktree: Model<Worktree>,
cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Buffer>>> {
let load_buffer = worktree.update(cx, |worktree, cx| {
let load_file = worktree.load_file(path.as_ref(), cx);
let reservation = cx.reserve_model();
let buffer_id = BufferId::from(reservation.entity_id().as_non_zero_u64());
cx.spawn(move |_, mut cx| async move {
let loaded = load_file.await?;
let text_buffer = cx
.background_executor()
.spawn(async move { text::Buffer::new(0, buffer_id, loaded.text) })
.await;
cx.insert_model(reservation, |_| {
Buffer::build(
text_buffer,
loaded.diff_base,
Some(loaded.file),
Capability::ReadWrite,
)
})
})
});
cx.spawn(move |this, mut cx| async move {
let buffer = match load_buffer.await {
Ok(buffer) => Ok(buffer),
Err(error) if is_not_found_error(&error) => cx.new_model(|cx| {
let buffer_id = BufferId::from(cx.entity_id().as_non_zero_u64());
let text_buffer = text::Buffer::new(0, buffer_id, "".into());
Buffer::build(
text_buffer,
None,
Some(Arc::new(File {
worktree,
path,
mtime: None,
entry_id: None,
is_local: true,
is_deleted: false,
is_private: false,
})),
Capability::ReadWrite,
)
}),
Err(e) => Err(e),
}?;
this.update(&mut cx, |this, cx| {
this.add_buffer(buffer.clone(), cx).log_err();
})?;
Ok(buffer)
})
}
fn open_remote_buffer_internal(
&self,
path: &Arc<Path>,
worktree: &RemoteWorktree,
cx: &ModelContext<Self>,
) -> Task<Result<Model<Buffer>>> {
let worktree_id = worktree.id().to_proto();
let project_id = worktree.project_id();
let client = worktree.client();
let path_string = path.clone().to_string_lossy().to_string();
cx.spawn(move |this, mut cx| async move {
let response = client
.request(proto::OpenBufferByPath {
project_id,
worktree_id,
path: path_string,
})
.await?;
let buffer_id = BufferId::new(response.buffer_id)?;
this.update(&mut cx, |this, cx| {
this.wait_for_remote_buffer(buffer_id, cx)
})?
.await
})
}
pub fn create_buffer(
&mut self,
remote_client: Option<(AnyProtoClient, u64)>,
cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Buffer>>> {
if let Some((remote_client, project_id)) = remote_client {
let create = remote_client.request(proto::OpenNewBuffer { project_id });
cx.spawn(|this, mut cx| async move {
let response = create.await?;
let buffer_id = BufferId::new(response.buffer_id)?;
this.update(&mut cx, |this, cx| {
this.wait_for_remote_buffer(buffer_id, cx)
})?
.await
})
} else {
Task::ready(Ok(self.create_local_buffer("", None, cx)))
}
}
pub fn create_local_buffer(
&mut self,
text: &str,
language: Option<Arc<Language>>,
cx: &mut ModelContext<Self>,
) -> Model<Buffer> {
let buffer = cx.new_model(|cx| {
Buffer::local(text, cx)
.with_language(language.unwrap_or_else(|| language::PLAIN_TEXT.clone()), cx)
});
self.add_buffer(buffer.clone(), cx).log_err();
buffer
}
pub fn save_buffer(
&mut self,
buffer: Model<Buffer>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
return Task::ready(Err(anyhow!("buffer doesn't have a file")));
};
match file.worktree.read(cx) {
Worktree::Local(_) => {
self.save_local_buffer(file.worktree.clone(), buffer, file.path.clone(), false, cx)
}
Worktree::Remote(tree) => self.save_remote_buffer(buffer, None, tree, cx),
}
}
pub fn save_buffer_as(
&mut self,
buffer: Model<Buffer>,
path: ProjectPath,
worktree: Model<Worktree>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let old_file = File::from_dyn(buffer.read(cx).file())
.cloned()
.map(Arc::new);
let task = match worktree.read(cx) {
Worktree::Local(_) => {
self.save_local_buffer(worktree, buffer.clone(), path.path, true, cx)
}
Worktree::Remote(tree) => {
self.save_remote_buffer(buffer.clone(), Some(path.to_proto()), tree, cx)
}
};
cx.spawn(|this, mut cx| async move {
task.await?;
this.update(&mut cx, |_, cx| {
cx.emit(BufferStoreEvent::BufferChangedFilePath { buffer, old_file });
})
})
}
fn save_local_buffer(
&self,
worktree: Model<Worktree>,
buffer_handle: Model<Buffer>,
path: Arc<Path>,
mut has_changed_file: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let buffer = buffer_handle.read(cx);
let text = buffer.as_rope().clone();
let line_ending = buffer.line_ending();
let version = buffer.version();
if buffer.file().is_some_and(|file| !file.is_created()) {
has_changed_file = true;
}
let save = worktree.update(cx, |worktree, cx| {
worktree.write_file(path.as_ref(), text, line_ending, cx)
});
cx.spawn(move |this, mut cx| async move {
let new_file = save.await?;
let mtime = new_file.mtime;
buffer_handle.update(&mut cx, |buffer, cx| {
if has_changed_file {
buffer.file_updated(new_file, cx);
}
buffer.did_save(version.clone(), mtime, cx);
})?;
this.update(&mut cx, |_, cx| {
cx.emit(BufferStoreEvent::BufferSaved {
buffer: buffer_handle,
has_changed_file,
saved_version: version,
})
})?;
Ok(())
})
}
fn save_remote_buffer(
&self,
buffer_handle: Model<Buffer>,
new_path: Option<proto::ProjectPath>,
tree: &RemoteWorktree,
cx: &ModelContext<Self>,
) -> Task<Result<()>> {
let buffer = buffer_handle.read(cx);
let buffer_id = buffer.remote_id().into();
let version = buffer.version();
let rpc = tree.client();
let project_id = tree.project_id();
cx.spawn(move |_, mut cx| async move {
let response = rpc
.request(proto::SaveBuffer {
project_id,
buffer_id,
new_path,
version: serialize_version(&version),
})
.await?;
let version = deserialize_version(&response.version);
let mtime = response.mtime.map(|mtime| mtime.into());
buffer_handle.update(&mut cx, |buffer, cx| {
buffer.did_save(version.clone(), mtime, cx);
})?;
Ok(())
})
}
fn add_buffer(&mut self, buffer: Model<Buffer>, cx: &mut ModelContext<Self>) -> Result<()> {
let remote_id = buffer.read(cx).remote_id();
let is_remote = buffer.read(cx).replica_id() != 0;
let open_buffer = if self.retain_buffers {
OpenBuffer::Strong(buffer.clone())
} else {
OpenBuffer::Weak(buffer.downgrade())
};
match self.opened_buffers.entry(remote_id) {
hash_map::Entry::Vacant(entry) => {
entry.insert(open_buffer);
}
hash_map::Entry::Occupied(mut entry) => {
if let OpenBuffer::Operations(operations) = entry.get_mut() {
buffer.update(cx, |b, cx| b.apply_ops(operations.drain(..), cx))?;
} else if entry.get().upgrade().is_some() {
if is_remote {
return Ok(());
} else {
debug_panic!("buffer {} was already registered", remote_id);
Err(anyhow!("buffer {} was already registered", remote_id))?;
}
}
entry.insert(open_buffer);
}
}
if let Some(senders) = self.remote_buffer_listeners.remove(&remote_id) {
for sender in senders {
sender.send(Ok(buffer.clone())).ok();
}
}
if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
if file.is_local {
self.local_buffer_ids_by_path.insert(
ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path.clone(),
},
remote_id,
);
if let Some(entry_id) = file.entry_id {
self.local_buffer_ids_by_entry_id
.insert(entry_id, remote_id);
}
}
}
cx.emit(BufferStoreEvent::BufferAdded(buffer));
Ok(())
}
pub fn buffers(&self) -> impl '_ + Iterator<Item = Model<Buffer>> {
self.opened_buffers
.values()
.filter_map(|buffer| buffer.upgrade())
}
pub fn loading_buffers(
&self,
) -> impl Iterator<
Item = (
&ProjectPath,
postage::watch::Receiver<Option<Result<Model<Buffer>, Arc<anyhow::Error>>>>,
),
> {
self.loading_buffers_by_path
.iter()
.map(|(path, rx)| (path, rx.clone()))
}
pub fn get_by_path(&self, path: &ProjectPath, cx: &AppContext) -> Option<Model<Buffer>> {
self.buffers().find_map(|buffer| {
let file = File::from_dyn(buffer.read(cx).file())?;
if file.worktree_id(cx) == path.worktree_id && &file.path == &path.path {
Some(buffer)
} else {
None
}
})
}
pub fn get(&self, buffer_id: BufferId) -> Option<Model<Buffer>> {
self.opened_buffers
.get(&buffer_id)
.and_then(|buffer| buffer.upgrade())
}
pub fn get_existing(&self, buffer_id: BufferId) -> Result<Model<Buffer>> {
self.get(buffer_id)
.ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))
}
pub fn get_possibly_incomplete(&self, buffer_id: BufferId) -> Option<Model<Buffer>> {
self.get(buffer_id)
.or_else(|| self.loading_remote_buffers_by_id.get(&buffer_id).cloned())
}
fn get_or_remove_by_path(
&mut self,
entry_id: ProjectEntryId,
project_path: &ProjectPath,
) -> Option<(BufferId, Model<Buffer>)> {
let buffer_id = match self.local_buffer_ids_by_entry_id.get(&entry_id) {
Some(&buffer_id) => buffer_id,
None => match self.local_buffer_ids_by_path.get(project_path) {
Some(&buffer_id) => buffer_id,
None => {
return None;
}
},
};
let buffer = if let Some(buffer) = self.get(buffer_id) {
buffer
} else {
self.opened_buffers.remove(&buffer_id);
self.local_buffer_ids_by_path.remove(project_path);
self.local_buffer_ids_by_entry_id.remove(&entry_id);
return None;
};
Some((buffer_id, buffer))
}
pub fn wait_for_remote_buffer(
&mut self,
id: BufferId,
cx: &mut AppContext,
) -> Task<Result<Model<Buffer>>> {
let buffer = self.get(id);
if let Some(buffer) = buffer {
return Task::ready(Ok(buffer));
}
let (tx, rx) = oneshot::channel();
self.remote_buffer_listeners.entry(id).or_default().push(tx);
cx.background_executor().spawn(async move { rx.await? })
}
pub fn buffer_version_info(
&self,
cx: &AppContext,
) -> (Vec<proto::BufferVersion>, Vec<BufferId>) {
let buffers = self
.buffers()
.map(|buffer| {
let buffer = buffer.read(cx);
proto::BufferVersion {
id: buffer.remote_id().into(),
version: language::proto::serialize_version(&buffer.version),
}
})
.collect();
let incomplete_buffer_ids = self
.loading_remote_buffers_by_id
.keys()
.copied()
.collect::<Vec<_>>();
(buffers, incomplete_buffer_ids)
}
pub fn disconnected_from_host(&mut self, cx: &mut AppContext) {
self.set_retain_buffers(false, cx);
for buffer in self.buffers() {
buffer.update(cx, |buffer, cx| {
buffer.set_capability(Capability::ReadOnly, cx)
});
}
// Wake up all futures currently waiting on a buffer to get opened,
// to give them a chance to fail now that we've disconnected.
self.remote_buffer_listeners.clear();
}
pub fn set_retain_buffers(&mut self, retain_buffers: bool, cx: &mut AppContext) {
self.retain_buffers = retain_buffers;
for open_buffer in self.opened_buffers.values_mut() {
if retain_buffers {
if let OpenBuffer::Weak(buffer) = open_buffer {
if let Some(buffer) = buffer.upgrade() {
*open_buffer = OpenBuffer::Strong(buffer);
}
}
} else {
if let Some(buffer) = open_buffer.upgrade() {
buffer.update(cx, |buffer, _| buffer.give_up_waiting());
}
if let OpenBuffer::Strong(buffer) = open_buffer {
*open_buffer = OpenBuffer::Weak(buffer.downgrade());
}
}
}
}
pub fn discard_incomplete(&mut self) {
self.opened_buffers
.retain(|_, buffer| !matches!(buffer, OpenBuffer::Operations(_)));
}
pub fn file_changed(
&mut self,
path: Arc<Path>,
entry_id: ProjectEntryId,
worktree_handle: &Model<worktree::Worktree>,
snapshot: &worktree::Snapshot,
cx: &mut ModelContext<Self>,
) -> Option<(Model<Buffer>, Arc<File>, Arc<File>)> {
let (buffer_id, buffer) = self.get_or_remove_by_path(
entry_id,
&ProjectPath {
worktree_id: snapshot.id(),
path,
},
)?;
let result = buffer.update(cx, |buffer, cx| {
let old_file = File::from_dyn(buffer.file())?;
if old_file.worktree != *worktree_handle {
return None;
}
let new_file = if let Some(entry) = old_file
.entry_id
.and_then(|entry_id| snapshot.entry_for_id(entry_id))
{
File {
is_local: true,
entry_id: Some(entry.id),
mtime: entry.mtime,
path: entry.path.clone(),
worktree: worktree_handle.clone(),
is_deleted: false,
is_private: entry.is_private,
}
} else if let Some(entry) = snapshot.entry_for_path(old_file.path.as_ref()) {
File {
is_local: true,
entry_id: Some(entry.id),
mtime: entry.mtime,
path: entry.path.clone(),
worktree: worktree_handle.clone(),
is_deleted: false,
is_private: entry.is_private,
}
} else {
File {
is_local: true,
entry_id: old_file.entry_id,
path: old_file.path.clone(),
mtime: old_file.mtime,
worktree: worktree_handle.clone(),
is_deleted: true,
is_private: old_file.is_private,
}
};
if new_file == *old_file {
return None;
}
let old_file = Arc::new(old_file.clone());
let new_file = Arc::new(new_file);
buffer.file_updated(new_file.clone(), cx);
Some((cx.handle(), old_file, new_file))
});
if let Some((buffer, old_file, new_file)) = &result {
if new_file.path != old_file.path {
self.local_buffer_ids_by_path.remove(&ProjectPath {
path: old_file.path.clone(),
worktree_id: old_file.worktree_id(cx),
});
self.local_buffer_ids_by_path.insert(
ProjectPath {
worktree_id: new_file.worktree_id(cx),
path: new_file.path.clone(),
},
buffer_id,
);
cx.emit(BufferStoreEvent::BufferChangedFilePath {
buffer: buffer.clone(),
old_file: Some(old_file.clone()),
});
}
if new_file.entry_id != old_file.entry_id {
if let Some(entry_id) = old_file.entry_id {
self.local_buffer_ids_by_entry_id.remove(&entry_id);
}
if let Some(entry_id) = new_file.entry_id {
self.local_buffer_ids_by_entry_id
.insert(entry_id, buffer_id);
}
}
}
result
}
pub fn buffer_changed_file(
&mut self,
buffer: Model<Buffer>,
cx: &mut AppContext,
) -> Option<()> {
let file = File::from_dyn(buffer.read(cx).file())?;
let remote_id = buffer.read(cx).remote_id();
if let Some(entry_id) = file.entry_id {
match self.local_buffer_ids_by_entry_id.get(&entry_id) {
Some(_) => {
return None;
}
None => {
self.local_buffer_ids_by_entry_id
.insert(entry_id, remote_id);
}
}
};
self.local_buffer_ids_by_path.insert(
ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path.clone(),
},
remote_id,
);
Some(())
}
pub async fn create_buffer_for_peer(
this: Model<Self>,
peer_id: PeerId,
buffer_id: BufferId,
project_id: u64,
client: AnyProtoClient,
cx: &mut AsyncAppContext,
) -> Result<()> {
let Some(buffer) = this.update(cx, |this, _| this.get(buffer_id))? else {
return Ok(());
};
let operations = buffer.update(cx, |b, cx| b.serialize_ops(None, cx))?;
let operations = operations.await;
let state = buffer.update(cx, |buffer, _| buffer.to_proto())?;
let initial_state = proto::CreateBufferForPeer {
project_id,
peer_id: Some(peer_id),
variant: Some(proto::create_buffer_for_peer::Variant::State(state)),
};
if client.send(initial_state).log_err().is_some() {
let client = client.clone();
cx.background_executor()
.spawn(async move {
let mut chunks = split_operations(operations).peekable();
while let Some(chunk) = chunks.next() {
let is_last = chunks.peek().is_none();
client.send(proto::CreateBufferForPeer {
project_id,
peer_id: Some(peer_id),
variant: Some(proto::create_buffer_for_peer::Variant::Chunk(
proto::BufferChunk {
buffer_id: buffer_id.into(),
operations: chunk,
is_last,
},
)),
})?;
}
anyhow::Ok(())
})
.await
.log_err();
}
Ok(())
}
pub fn handle_update_buffer(
&mut self,
envelope: TypedEnvelope<proto::UpdateBuffer>,
is_remote: bool,
cx: &mut AppContext,
) -> Result<proto::Ack> {
let payload = envelope.payload.clone();
let buffer_id = BufferId::new(payload.buffer_id)?;
let ops = payload
.operations
.into_iter()
.map(language::proto::deserialize_operation)
.collect::<Result<Vec<_>, _>>()?;
match self.opened_buffers.entry(buffer_id) {
hash_map::Entry::Occupied(mut e) => match e.get_mut() {
OpenBuffer::Strong(buffer) => {
buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))?;
}
OpenBuffer::Operations(operations) => operations.extend_from_slice(&ops),
OpenBuffer::Weak(_) => {}
},
hash_map::Entry::Vacant(e) => {
if !is_remote {
debug_panic!(
"received buffer update from {:?}",
envelope.original_sender_id
);
return Err(anyhow!("received buffer update for non-remote project"));
}
e.insert(OpenBuffer::Operations(ops));
}
}
Ok(proto::Ack {})
}
pub fn handle_create_buffer_for_peer(
&mut self,
envelope: TypedEnvelope<proto::CreateBufferForPeer>,
mut worktrees: impl Iterator<Item = Model<Worktree>>,
replica_id: u16,
capability: Capability,
cx: &mut ModelContext<Self>,
) -> Result<()> {
match envelope
.payload
.variant
.ok_or_else(|| anyhow!("missing variant"))?
{
proto::create_buffer_for_peer::Variant::State(mut state) => {
let buffer_id = BufferId::new(state.id)?;
let buffer_result = maybe!({
let mut buffer_file = None;
if let Some(file) = state.file.take() {
let worktree_id = worktree::WorktreeId::from_proto(file.worktree_id);
let worktree = worktrees
.find(|worktree| worktree.read(cx).id() == worktree_id)
.ok_or_else(|| {
anyhow!("no worktree found for id {}", file.worktree_id)
})?;
buffer_file = Some(Arc::new(File::from_proto(file, worktree.clone(), cx)?)
as Arc<dyn language::File>);
}
Buffer::from_proto(replica_id, capability, state, buffer_file)
});
match buffer_result {
Ok(buffer) => {
let buffer = cx.new_model(|_| buffer);
self.loading_remote_buffers_by_id.insert(buffer_id, buffer);
}
Err(error) => {
if let Some(listeners) = self.remote_buffer_listeners.remove(&buffer_id) {
for listener in listeners {
listener.send(Err(anyhow!(error.cloned()))).ok();
}
}
}
}
}
proto::create_buffer_for_peer::Variant::Chunk(chunk) => {
let buffer_id = BufferId::new(chunk.buffer_id)?;
let buffer = self
.loading_remote_buffers_by_id
.get(&buffer_id)
.cloned()
.ok_or_else(|| {
anyhow!(
"received chunk for buffer {} without initial state",
chunk.buffer_id
)
})?;
let result = maybe!({
let operations = chunk
.operations
.into_iter()
.map(language::proto::deserialize_operation)
.collect::<Result<Vec<_>>>()?;
buffer.update(cx, |buffer, cx| buffer.apply_ops(operations, cx))
});
if let Err(error) = result {
self.loading_remote_buffers_by_id.remove(&buffer_id);
if let Some(listeners) = self.remote_buffer_listeners.remove(&buffer_id) {
for listener in listeners {
listener.send(Err(error.cloned())).ok();
}
}
} else if chunk.is_last {
self.loading_remote_buffers_by_id.remove(&buffer_id);
self.add_buffer(buffer, cx)?;
}
}
}
Ok(())
}
pub async fn handle_save_buffer(
this: Model<Self>,
project_id: u64,
worktree: Option<Model<Worktree>>,
envelope: TypedEnvelope<proto::SaveBuffer>,
mut cx: AsyncAppContext,
) -> Result<proto::BufferSaved> {
let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
let buffer = this.update(&mut cx, |this, _| this.get_existing(buffer_id))??;
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(&envelope.payload.version))
})?
.await?;
let buffer_id = buffer.update(&mut cx, |buffer, _| buffer.remote_id())?;
if let Some(new_path) = envelope.payload.new_path {
let worktree = worktree.context("no such worktree")?;
let new_path = ProjectPath::from_proto(new_path);
this.update(&mut cx, |this, cx| {
this.save_buffer_as(buffer.clone(), new_path, worktree, cx)
})?
.await?;
} else {
this.update(&mut cx, |this, cx| this.save_buffer(buffer.clone(), cx))?
.await?;
}
buffer.update(&mut cx, |buffer, _| proto::BufferSaved {
project_id,
buffer_id: buffer_id.into(),
version: serialize_version(buffer.saved_version()),
mtime: buffer.saved_mtime().map(|time| time.into()),
})
}
pub async fn wait_for_loading_buffer(
mut receiver: postage::watch::Receiver<Option<Result<Model<Buffer>, Arc<anyhow::Error>>>>,
) -> Result<Model<Buffer>, Arc<anyhow::Error>> {
loop {
if let Some(result) = receiver.borrow().as_ref() {
match result {
Ok(buffer) => return Ok(buffer.to_owned()),
Err(e) => return Err(e.to_owned()),
}
}
receiver.next().await;
}
}
}
impl OpenBuffer {
fn upgrade(&self) -> Option<Model<Buffer>> {
match self {
OpenBuffer::Strong(handle) => Some(handle.clone()),
OpenBuffer::Weak(handle) => handle.upgrade(),
OpenBuffer::Operations(_) => None,
}
}
}
fn is_not_found_error(error: &anyhow::Error) -> bool {
error
.root_cause()
.downcast_ref::<io::Error>()
.is_some_and(|err| err.kind() == io::ErrorKind::NotFound)
}

View File

@@ -1,5 +1,3 @@
mod signature_help;
use crate::{
CodeAction, CoreCompletion, DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint,
InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location,
@@ -8,7 +6,6 @@ use crate::{
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use client::proto::{self, PeerId};
use clock::Global;
use futures::future;
use gpui::{AppContext, AsyncAppContext, Model};
use language::{
@@ -23,14 +20,9 @@ use lsp::{
DocumentHighlightKind, LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities,
OneOf, ServerCapabilities,
};
use signature_help::{lsp_to_proto_signature, proto_to_lsp_signature};
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
use text::{BufferId, LineEnding};
pub use signature_help::{
SignatureHelp, SIGNATURE_HELP_HIGHLIGHT_CURRENT, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD,
};
pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions {
lsp::FormattingOptions {
tab_size,
@@ -129,11 +121,6 @@ pub(crate) struct GetDocumentHighlights {
pub position: PointUtf16,
}
#[derive(Clone)]
pub(crate) struct GetSignatureHelp {
pub position: PointUtf16,
}
#[derive(Clone)]
pub(crate) struct GetHover {
pub position: PointUtf16,
@@ -410,18 +397,16 @@ impl LspCommand for PerformRename {
message: proto::PerformRenameResponse,
project: Model<Project>,
_: Model<Buffer>,
cx: AsyncAppContext,
mut cx: AsyncAppContext,
) -> Result<ProjectTransaction> {
let message = message
.transaction
.ok_or_else(|| anyhow!("missing transaction"))?;
Project::deserialize_project_transaction(
project.downgrade(),
message,
self.push_to_history,
cx,
)
.await
project
.update(&mut cx, |project, cx| {
project.deserialize_project_transaction(message, self.push_to_history, cx)
})?
.await
}
fn buffer_id_from_proto(message: &proto::PerformRename) -> Result<BufferId> {
@@ -1240,116 +1225,6 @@ impl LspCommand for GetDocumentHighlights {
}
}
#[async_trait(?Send)]
impl LspCommand for GetSignatureHelp {
type Response = Option<SignatureHelp>;
type LspRequest = lsp::SignatureHelpRequest;
type ProtoRequest = proto::GetSignatureHelp;
fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
capabilities.signature_help_provider.is_some()
}
fn to_lsp(
&self,
path: &Path,
_: &Buffer,
_: &Arc<LanguageServer>,
_cx: &AppContext,
) -> lsp::SignatureHelpParams {
let url_result = lsp::Url::from_file_path(path);
if url_result.is_err() {
log::error!("an invalid file path has been specified");
}
lsp::SignatureHelpParams {
text_document_position_params: lsp::TextDocumentPositionParams {
text_document: lsp::TextDocumentIdentifier {
uri: url_result.expect("invalid file path"),
},
position: point_to_lsp(self.position),
},
context: None,
work_done_progress_params: Default::default(),
}
}
async fn response_from_lsp(
self,
message: Option<lsp::SignatureHelp>,
_: Model<Project>,
buffer: Model<Buffer>,
_: LanguageServerId,
mut cx: AsyncAppContext,
) -> Result<Self::Response> {
let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
Ok(message.and_then(|message| SignatureHelp::new(message, language)))
}
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest {
let offset = buffer.point_utf16_to_offset(self.position);
proto::GetSignatureHelp {
project_id,
buffer_id: buffer.remote_id().to_proto(),
position: Some(serialize_anchor(&buffer.anchor_after(offset))),
version: serialize_version(&buffer.version()),
}
}
async fn from_proto(
payload: Self::ProtoRequest,
_: Model<Project>,
buffer: Model<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Self> {
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(&payload.version))
})?
.await
.with_context(|| format!("waiting for version for buffer {}", buffer.entity_id()))?;
let buffer_snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
Ok(Self {
position: payload
.position
.and_then(deserialize_anchor)
.context("invalid position")?
.to_point_utf16(&buffer_snapshot),
})
}
fn response_to_proto(
response: Self::Response,
_: &mut Project,
_: PeerId,
_: &Global,
_: &mut AppContext,
) -> proto::GetSignatureHelpResponse {
proto::GetSignatureHelpResponse {
signature_help: response
.map(|signature_help| lsp_to_proto_signature(signature_help.original_data)),
}
}
async fn response_from_proto(
self,
response: proto::GetSignatureHelpResponse,
_: Model<Project>,
buffer: Model<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Self::Response> {
let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
Ok(response
.signature_help
.map(|proto_help| proto_to_lsp_signature(proto_help))
.and_then(|lsp_help| SignatureHelp::new(lsp_help, language)))
}
fn buffer_id_from_proto(message: &Self::ProtoRequest) -> Result<BufferId> {
BufferId::new(message.buffer_id)
}
}
#[async_trait(?Send)]
impl LspCommand for GetHover {
type Response = Option<Hover>;

View File

@@ -1,636 +0,0 @@
use std::{ops::Range, sync::Arc};
use gpui::FontWeight;
use language::{
markdown::{MarkdownHighlight, MarkdownHighlightStyle},
Language,
};
use rpc::proto::{self, documentation};
pub const SIGNATURE_HELP_HIGHLIGHT_CURRENT: MarkdownHighlight =
MarkdownHighlight::Style(MarkdownHighlightStyle {
italic: false,
underline: false,
strikethrough: false,
weight: FontWeight::EXTRA_BOLD,
});
pub const SIGNATURE_HELP_HIGHLIGHT_OVERLOAD: MarkdownHighlight =
MarkdownHighlight::Style(MarkdownHighlightStyle {
italic: true,
underline: false,
strikethrough: false,
weight: FontWeight::NORMAL,
});
#[derive(Debug)]
pub struct SignatureHelp {
pub markdown: String,
pub highlights: Vec<(Range<usize>, MarkdownHighlight)>,
pub(super) original_data: lsp::SignatureHelp,
}
impl SignatureHelp {
pub fn new(help: lsp::SignatureHelp, language: Option<Arc<Language>>) -> Option<Self> {
let function_options_count = help.signatures.len();
let signature_information = help
.active_signature
.and_then(|active_signature| help.signatures.get(active_signature as usize))
.or_else(|| help.signatures.first())?;
let str_for_join = ", ";
let parameter_length = signature_information
.parameters
.as_ref()
.map_or(0, |parameters| parameters.len());
let mut highlight_start = 0;
let (markdown, mut highlights): (Vec<_>, Vec<_>) = signature_information
.parameters
.as_ref()?
.iter()
.enumerate()
.map(|(i, parameter_information)| {
let label = match parameter_information.label.clone() {
lsp::ParameterLabel::Simple(string) => string,
lsp::ParameterLabel::LabelOffsets(offset) => signature_information
.label
.chars()
.skip(offset[0] as usize)
.take((offset[1] - offset[0]) as usize)
.collect::<String>(),
};
let label_length = label.len();
let highlights = help.active_parameter.and_then(|active_parameter| {
if i == active_parameter as usize {
Some((
highlight_start..(highlight_start + label_length),
SIGNATURE_HELP_HIGHLIGHT_CURRENT,
))
} else {
None
}
});
if i != parameter_length {
highlight_start += label_length + str_for_join.len();
}
(label, highlights)
})
.unzip();
if markdown.is_empty() {
None
} else {
let markdown = markdown.join(str_for_join);
let language_name = language
.map(|n| n.name().to_lowercase())
.unwrap_or_default();
let markdown = if function_options_count >= 2 {
let suffix = format!("(+{} overload)", function_options_count - 1);
let highlight_start = markdown.len() + 1;
highlights.push(Some((
highlight_start..(highlight_start + suffix.len()),
SIGNATURE_HELP_HIGHLIGHT_OVERLOAD,
)));
format!("```{language_name}\n{markdown} {suffix}")
} else {
format!("```{language_name}\n{markdown}")
};
Some(Self {
markdown,
highlights: highlights.into_iter().flatten().collect(),
original_data: help,
})
}
}
}
pub fn lsp_to_proto_signature(lsp_help: lsp::SignatureHelp) -> proto::SignatureHelp {
proto::SignatureHelp {
signatures: lsp_help
.signatures
.into_iter()
.map(|signature| proto::SignatureInformation {
label: signature.label,
documentation: signature
.documentation
.map(|documentation| lsp_to_proto_documentation(documentation)),
parameters: signature
.parameters
.unwrap_or_default()
.into_iter()
.map(|parameter_info| proto::ParameterInformation {
label: Some(match parameter_info.label {
lsp::ParameterLabel::Simple(label) => {
proto::parameter_information::Label::Simple(label)
}
lsp::ParameterLabel::LabelOffsets(offsets) => {
proto::parameter_information::Label::LabelOffsets(
proto::LabelOffsets {
start: offsets[0],
end: offsets[1],
},
)
}
}),
documentation: parameter_info.documentation.map(lsp_to_proto_documentation),
})
.collect(),
active_parameter: signature.active_parameter,
})
.collect(),
active_signature: lsp_help.active_signature,
active_parameter: lsp_help.active_parameter,
}
}
fn lsp_to_proto_documentation(documentation: lsp::Documentation) -> proto::Documentation {
proto::Documentation {
content: Some(match documentation {
lsp::Documentation::String(string) => proto::documentation::Content::Value(string),
lsp::Documentation::MarkupContent(content) => {
proto::documentation::Content::MarkupContent(proto::MarkupContent {
is_markdown: matches!(content.kind, lsp::MarkupKind::Markdown),
value: content.value,
})
}
}),
}
}
pub fn proto_to_lsp_signature(proto_help: proto::SignatureHelp) -> lsp::SignatureHelp {
lsp::SignatureHelp {
signatures: proto_help
.signatures
.into_iter()
.map(|signature| lsp::SignatureInformation {
label: signature.label,
documentation: signature.documentation.and_then(proto_to_lsp_documentation),
parameters: Some(
signature
.parameters
.into_iter()
.filter_map(|parameter_info| {
Some(lsp::ParameterInformation {
label: match parameter_info.label? {
proto::parameter_information::Label::Simple(string) => {
lsp::ParameterLabel::Simple(string)
}
proto::parameter_information::Label::LabelOffsets(offsets) => {
lsp::ParameterLabel::LabelOffsets([
offsets.start,
offsets.end,
])
}
},
documentation: parameter_info
.documentation
.and_then(proto_to_lsp_documentation),
})
})
.collect(),
),
active_parameter: signature.active_parameter,
})
.collect(),
active_signature: proto_help.active_signature,
active_parameter: proto_help.active_parameter,
}
}
fn proto_to_lsp_documentation(documentation: proto::Documentation) -> Option<lsp::Documentation> {
let documentation = {
Some(match documentation.content? {
documentation::Content::Value(string) => lsp::Documentation::String(string),
documentation::Content::MarkupContent(markup) => {
lsp::Documentation::MarkupContent(if markup.is_markdown {
lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown,
value: markup.value,
}
} else {
lsp::MarkupContent {
kind: lsp::MarkupKind::PlainText,
value: markup.value,
}
})
}
})
};
documentation
}
#[cfg(test)]
mod tests {
use crate::lsp_command::signature_help::{
SignatureHelp, SIGNATURE_HELP_HIGHLIGHT_CURRENT, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD,
};
#[test]
fn test_create_signature_help_markdown_string_1() {
let signature_help = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn test(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(0),
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\nfoo: u8, bar: &str".to_string(),
vec![(0..7, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]
)
);
}
#[test]
fn test_create_signature_help_markdown_string_2() {
let signature_help = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn test(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(1),
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\nfoo: u8, bar: &str".to_string(),
vec![(9..18, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]
)
);
}
#[test]
fn test_create_signature_help_markdown_string_3() {
let signature_help = lsp::SignatureHelp {
signatures: vec![
lsp::SignatureInformation {
label: "fn test1(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
lsp::SignatureInformation {
label: "fn test2(hoge: String, fuga: bool)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
],
active_signature: Some(0),
active_parameter: Some(0),
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\nfoo: u8, bar: &str (+1 overload)".to_string(),
vec![
(0..7, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
(19..32, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
]
)
);
}
#[test]
fn test_create_signature_help_markdown_string_4() {
let signature_help = lsp::SignatureHelp {
signatures: vec![
lsp::SignatureInformation {
label: "fn test1(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
lsp::SignatureInformation {
label: "fn test2(hoge: String, fuga: bool)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
],
active_signature: Some(1),
active_parameter: Some(0),
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\nhoge: String, fuga: bool (+1 overload)".to_string(),
vec![
(0..12, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
(25..38, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
]
)
);
}
#[test]
fn test_create_signature_help_markdown_string_5() {
let signature_help = lsp::SignatureHelp {
signatures: vec![
lsp::SignatureInformation {
label: "fn test1(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
lsp::SignatureInformation {
label: "fn test2(hoge: String, fuga: bool)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
],
active_signature: Some(1),
active_parameter: Some(1),
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\nhoge: String, fuga: bool (+1 overload)".to_string(),
vec![
(14..24, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
(25..38, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
]
)
);
}
#[test]
fn test_create_signature_help_markdown_string_6() {
let signature_help = lsp::SignatureHelp {
signatures: vec![
lsp::SignatureInformation {
label: "fn test1(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
lsp::SignatureInformation {
label: "fn test2(hoge: String, fuga: bool)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
],
active_signature: Some(1),
active_parameter: None,
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\nhoge: String, fuga: bool (+1 overload)".to_string(),
vec![(25..38, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)]
)
);
}
#[test]
fn test_create_signature_help_markdown_string_7() {
let signature_help = lsp::SignatureHelp {
signatures: vec![
lsp::SignatureInformation {
label: "fn test1(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
lsp::SignatureInformation {
label: "fn test2(hoge: String, fuga: bool)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
lsp::SignatureInformation {
label: "fn test3(one: usize, two: u32)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("one: usize".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("two: u32".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
],
active_signature: Some(2),
active_parameter: Some(1),
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\none: usize, two: u32 (+2 overload)".to_string(),
vec![
(12..20, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
(21..34, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
]
)
);
}
#[test]
fn test_create_signature_help_markdown_string_8() {
let signature_help = lsp::SignatureHelp {
signatures: vec![],
active_signature: None,
active_parameter: None,
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_none());
}
#[test]
fn test_create_signature_help_markdown_string_9() {
let signature_help = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn test(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::LabelOffsets([8, 15]),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::LabelOffsets([17, 26]),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(0),
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\nfoo: u8, bar: &str".to_string(),
vec![(0..7, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]
)
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -20,23 +20,6 @@ pub struct ProjectSettings {
/// Configuration for Git-related features
#[serde(default)]
pub git: GitSettings,
/// Configuration for how direnv configuration should be loaded
#[serde(default)]
pub load_direnv: DirenvSettings,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DirenvSettings {
/// Load direnv configuration through a shell hook
#[default]
ShellHook,
/// Load direnv configuration directly using `direnv export json`
///
/// Warning: This option is experimental and might cause some inconsistent behaviour compared to using the shell hook.
/// If it does, please report it to GitHub
Direct,
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]

View File

@@ -322,12 +322,6 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
trigger_characters: Some(vec![".".to_string(), "::".to_string()]),
..Default::default()
}),
text_document_sync: Some(lsp::TextDocumentSyncCapability::Options(
lsp::TextDocumentSyncOptions {
save: Some(lsp::TextDocumentSyncSaveOptions::Supported(true)),
..Default::default()
},
)),
..Default::default()
},
..Default::default()
@@ -342,12 +336,6 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
trigger_characters: Some(vec![":".to_string()]),
..Default::default()
}),
text_document_sync: Some(lsp::TextDocumentSyncCapability::Options(
lsp::TextDocumentSyncOptions {
save: Some(lsp::TextDocumentSyncSaveOptions::Supported(true)),
..Default::default()
},
)),
..Default::default()
},
..Default::default()
@@ -3068,8 +3056,15 @@ async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) {
});
});
let remote =
cx.update(|cx| Worktree::remote(0, 1, metadata, project.read(cx).client().into(), cx));
let remote = cx.update(|cx| {
Worktree::remote(
0,
1,
metadata,
Box::new(CollabRemoteWorktreeClient(project.read(cx).client())),
cx,
)
});
cx.executor().run_until_parked();

View File

@@ -180,7 +180,6 @@ impl Project {
settings.max_scroll_history_lines,
window,
completion_tx,
cx,
)
.map(|builder| {
let terminal_handle = cx.new_model(|cx| builder.subscribe(cx));

View File

@@ -1,177 +0,0 @@
//! This module deals with everything related to path handling for Yarn, the package manager for Web ecosystem.
//! Yarn is a bit peculiar, because it references paths within .zip files, which we obviously can't handle.
//! It also uses virtual paths for peer dependencies.
//!
//! Long story short, before we attempt to resolve a path as a "real" path, we try to treat is as a yarn path;
//! for .zip handling, we unpack the contents into the temp directory (yes, this is bad, against the spirit of Yarn and what-not)
use std::{
ffi::OsStr,
path::{Path, PathBuf},
sync::Arc,
};
use anyhow::Result;
use collections::HashMap;
use fs::Fs;
use gpui::{AppContext, Context, Model, ModelContext, Task};
use util::ResultExt;
pub(crate) struct YarnPathStore {
temp_dirs: HashMap<Arc<Path>, tempfile::TempDir>,
fs: Arc<dyn Fs>,
}
/// Returns `None` when passed path is a malformed virtual path or it's not a virtual path at all.
fn resolve_virtual(path: &Path) -> Option<Arc<Path>> {
let components: Vec<_> = path.components().collect();
let mut non_virtual_path = PathBuf::new();
let mut i = 0;
let mut is_virtual = false;
while i < components.len() {
if let Some(os_str) = components[i].as_os_str().to_str() {
// Detect the __virtual__ segment
if os_str == "__virtual__" {
let pop_count = components
.get(i + 2)?
.as_os_str()
.to_str()?
.parse::<usize>()
.ok()?;
// Apply dirname operation pop_count times
for _ in 0..pop_count {
non_virtual_path.pop();
}
i += 3; // Skip hash and pop_count components
is_virtual = true;
continue;
}
}
non_virtual_path.push(&components[i]);
i += 1;
}
is_virtual.then(|| Arc::from(non_virtual_path))
}
impl YarnPathStore {
pub(crate) fn new(fs: Arc<dyn Fs>, cx: &mut AppContext) -> Model<Self> {
cx.new_model(|_| Self {
temp_dirs: Default::default(),
fs,
})
}
pub(crate) fn process_path(
&mut self,
path: &Path,
protocol: &str,
cx: &ModelContext<Self>,
) -> Task<Option<(Arc<Path>, Arc<Path>)>> {
let mut is_zip = protocol.eq("zip");
let path: &Path = if let Some(non_zip_part) = path
.as_os_str()
.as_encoded_bytes()
.strip_prefix("/zip:".as_bytes())
{
// typescript-language-server prepends the paths with zip:, which is messy.
is_zip = true;
Path::new(OsStr::new(
std::str::from_utf8(non_zip_part).expect("Invalid UTF-8"),
))
} else {
path
};
let as_virtual = resolve_virtual(&path);
let Some(path) = as_virtual.or_else(|| is_zip.then(|| Arc::from(path))) else {
return Task::ready(None);
};
if let Some(zip_file) = zip_path(&path) {
let zip_file: Arc<Path> = Arc::from(zip_file);
cx.spawn(|this, mut cx| async move {
let dir = this
.update(&mut cx, |this, _| {
this.temp_dirs
.get(&zip_file)
.map(|temp| temp.path().to_owned())
})
.ok()?;
let zip_root = if let Some(dir) = dir {
dir
} else {
let fs = this.update(&mut cx, |this, _| this.fs.clone()).ok()?;
let tempdir = dump_zip(zip_file.clone(), fs).await.log_err()?;
let new_path = tempdir.path().to_owned();
this.update(&mut cx, |this, _| {
this.temp_dirs.insert(zip_file.clone(), tempdir);
})
.ok()?;
new_path
};
// Rebase zip-path onto new temp path.
let as_relative = path.strip_prefix(zip_file).ok()?.into();
Some((zip_root.into(), as_relative))
})
} else {
Task::ready(None)
}
}
}
fn zip_path(path: &Path) -> Option<&Path> {
let path_str = path.to_str()?;
let zip_end = path_str.find(".zip/")?;
let zip_path = &path_str[..zip_end + 4]; // ".zip" is 4 characters long
Some(Path::new(zip_path))
}
async fn dump_zip(path: Arc<Path>, fs: Arc<dyn Fs>) -> Result<tempfile::TempDir> {
let dir = tempfile::tempdir()?;
let contents = fs.load_bytes(&path).await?;
node_runtime::extract_zip(dir.path(), futures::io::Cursor::new(contents)).await?;
Ok(dir)
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn test_resolve_virtual() {
let test_cases = vec![
(
"/path/to/some/folder/__virtual__/a0b1c2d3/0/subpath/to/file.dat",
Some(Path::new("/path/to/some/folder/subpath/to/file.dat")),
),
(
"/path/to/some/folder/__virtual__/e4f5a0b1/0/subpath/to/file.dat",
Some(Path::new("/path/to/some/folder/subpath/to/file.dat")),
),
(
"/path/to/some/folder/__virtual__/a0b1c2d3/1/subpath/to/file.dat",
Some(Path::new("/path/to/some/subpath/to/file.dat")),
),
(
"/path/to/some/folder/__virtual__/a0b1c2d3/3/subpath/to/file.dat",
Some(Path::new("/path/subpath/to/file.dat")),
),
("/path/to/nonvirtual/", None),
("/path/to/malformed/__virtual__", None),
("/path/to/malformed/__virtual__/a0b1c2d3", None),
(
"/path/to/malformed/__virtual__/a0b1c2d3/this-should-be-a-number",
None,
),
];
for (input, expected) in test_cases {
let input_path = Path::new(input);
let resolved_path = resolve_virtual(input_path);
assert_eq!(resolved_path.as_deref(), expected);
}
}
}

View File

@@ -2293,7 +2293,7 @@ impl ProjectPanel {
.right_0()
.top_0()
.bottom_0()
.w(px(12.))
.w_3()
.cursor_default()
.child(ProjectPanelScrollbar::new(
percentage as f32..end_offset as f32,

View File

@@ -19,7 +19,6 @@ doctest = false
[dependencies]
anyhow.workspace = true
collections.workspace = true
futures.workspace = true
prost.workspace = true
serde.workspace = true

View File

@@ -203,10 +203,6 @@ message Envelope {
CompleteWithLanguageModel complete_with_language_model = 166;
LanguageModelResponse language_model_response = 167;
CacheLanguageModelContent cache_language_model_content = 219;
CacheLanguageModelContentResponse cache_language_model_content_response = 220; // current max
CountTokensWithLanguageModel count_tokens_with_language_model = 168;
CountTokensResponse count_tokens_response = 169;
GetCachedEmbeddings get_cached_embeddings = 189;
@@ -266,10 +262,7 @@ message Envelope {
OpenContextResponse open_context_response = 213;
UpdateContext update_context = 214;
SynchronizeContexts synchronize_contexts = 215;
SynchronizeContextsResponse synchronize_contexts_response = 216;
GetSignatureHelp get_signature_help = 217;
GetSignatureHelpResponse get_signature_help_response = 218;
SynchronizeContextsResponse synchronize_contexts_response = 216; // current max
}
reserved 158 to 161;
@@ -941,55 +934,6 @@ message GetCodeActionsResponse {
repeated VectorClockEntry version = 2;
}
message GetSignatureHelp {
uint64 project_id = 1;
uint64 buffer_id = 2;
Anchor position = 3;
repeated VectorClockEntry version = 4;
}
message GetSignatureHelpResponse {
optional SignatureHelp signature_help = 1;
}
message SignatureHelp {
repeated SignatureInformation signatures = 1;
optional uint32 active_signature = 2;
optional uint32 active_parameter = 3;
}
message SignatureInformation {
string label = 1;
optional Documentation documentation = 2;
repeated ParameterInformation parameters = 3;
optional uint32 active_parameter = 4;
}
message Documentation {
oneof content {
string value = 1;
MarkupContent markup_content = 2;
}
}
enum MarkupKind {
PlainText = 0;
Markdown = 1;
}
message ParameterInformation {
oneof label {
string simple = 1;
LabelOffsets label_offsets = 2;
}
optional Documentation documentation = 3;
}
message LabelOffsets {
uint32 start = 1;
uint32 end = 2;
}
message GetHover {
uint64 project_id = 1;
uint64 buffer_id = 2;
@@ -2029,25 +1973,6 @@ message CompleteWithLanguageModel {
float temperature = 4;
repeated ChatCompletionTool tools = 5;
optional string tool_choice = 6;
repeated string cached_contents = 7;
}
message CacheLanguageModelContent {
string model = 1;
repeated LanguageModelRequestMessage messages = 2;
repeated string stop = 3;
float temperature = 4;
repeated ChatCompletionTool tools = 5;
optional string tool_choice = 6;
optional uint64 ttl_seconds = 7;
}
message CacheLanguageModelContentResponse {
string name = 1;
uint64 create_time = 2;
uint64 update_time = 3;
optional uint64 expire_time = 4;
uint32 total_token_count = 5;
}
// A tool presented to the language model for its use
@@ -2210,7 +2135,6 @@ message MultiLspQuery {
oneof request {
GetHover get_hover = 5;
GetCodeActions get_code_actions = 6;
GetSignatureHelp get_signature_help = 7;
}
}
@@ -2229,7 +2153,6 @@ message LspResponse {
oneof response {
GetHoverResponse get_hover_response = 1;
GetCodeActionsResponse get_code_actions_response = 2;
GetSignatureHelpResponse get_signature_help_response = 3;
}
}

View File

@@ -7,19 +7,18 @@ mod typed_envelope;
pub use error::*;
pub use typed_envelope::*;
use anyhow::anyhow;
use collections::HashMap;
use futures::{future::BoxFuture, Future};
pub use prost::{DecodeError, Message};
use serde::Serialize;
use std::any::{Any, TypeId};
use std::time::Instant;
use std::{
any::{Any, TypeId},
cmp,
fmt::{self, Debug},
iter, mem,
sync::Arc,
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
fmt::Debug,
iter,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use std::{fmt, mem};
include!(concat!(env!("OUT_DIR"), "/zed.messages.rs"));
@@ -60,51 +59,6 @@ pub enum MessagePriority {
Background,
}
pub trait ProtoClient: Send + Sync {
fn request(
&self,
envelope: Envelope,
request_type: &'static str,
) -> BoxFuture<'static, anyhow::Result<Envelope>>;
fn send(&self, envelope: Envelope) -> anyhow::Result<()>;
}
#[derive(Clone)]
pub struct AnyProtoClient(Arc<dyn ProtoClient>);
impl<T> From<Arc<T>> for AnyProtoClient
where
T: ProtoClient + 'static,
{
fn from(client: Arc<T>) -> Self {
Self(client)
}
}
impl AnyProtoClient {
pub fn new<T: ProtoClient + 'static>(client: Arc<T>) -> Self {
Self(client)
}
pub fn request<T: RequestMessage>(
&self,
request: T,
) -> impl Future<Output = anyhow::Result<T::Response>> {
let envelope = request.into_envelope(0, None, None);
let response = self.0.request(envelope, T::NAME);
async move {
T::Response::from_envelope(response.await?)
.ok_or_else(|| anyhow!("received response of the wrong type"))
}
}
pub fn send<T: EnvelopedMessage>(&self, request: T) -> anyhow::Result<()> {
let envelope = request.into_envelope(0, None, None);
self.0.send(envelope)
}
}
impl<T: EnvelopedMessage> AnyTypedEnvelope for TypedEnvelope<T> {
fn payload_type_id(&self) -> TypeId {
TypeId::of::<T>()
@@ -194,8 +148,6 @@ messages!(
(ApplyCompletionAdditionalEditsResponse, Background),
(BufferReloaded, Foreground),
(BufferSaved, Foreground),
(CacheLanguageModelContent, Background),
(CacheLanguageModelContentResponse, Background),
(Call, Foreground),
(CallCanceled, Foreground),
(CancelCall, Foreground),
@@ -252,8 +204,6 @@ messages!(
(GetProjectSymbolsResponse, Background),
(GetReferences, Background),
(GetReferencesResponse, Background),
(GetSignatureHelp, Background),
(GetSignatureHelpResponse, Background),
(GetSupermavenApiKey, Background),
(GetSupermavenApiKeyResponse, Background),
(GetTypeDefinition, Background),
@@ -402,7 +352,6 @@ request_messages!(
ApplyCompletionAdditionalEdits,
ApplyCompletionAdditionalEditsResponse
),
(CacheLanguageModelContent, CacheLanguageModelContentResponse),
(Call, Ack),
(CancelCall, Ack),
(CopyProjectEntry, ProjectEntryResponse),
@@ -433,7 +382,6 @@ request_messages!(
(GetPrivateUserInfo, GetPrivateUserInfoResponse),
(GetProjectSymbols, GetProjectSymbolsResponse),
(GetReferences, GetReferencesResponse),
(GetSignatureHelp, GetSignatureHelpResponse),
(GetSupermavenApiKey, GetSupermavenApiKeyResponse),
(GetTypeDefinition, GetTypeDefinitionResponse),
(LinkedEditingRange, LinkedEditingRangeResponse),
@@ -534,7 +482,6 @@ entity_messages!(
GetHover,
GetProjectSymbols,
GetReferences,
GetSignatureHelp,
GetTypeDefinition,
InlayHints,
JoinProject,

View File

@@ -102,21 +102,18 @@ impl Render for QuickActionBar {
inlay_hints_enabled,
supports_inlay_hints,
git_blame_inline_enabled,
auto_signature_help_enabled,
) = {
let editor = editor.read(cx);
let selection_menu_enabled = editor.selection_menu_enabled(cx);
let inlay_hints_enabled = editor.inlay_hints_enabled();
let supports_inlay_hints = editor.supports_inlay_hints(cx);
let git_blame_inline_enabled = editor.git_blame_inline_enabled();
let auto_signature_help_enabled = editor.auto_signature_help_enabled(cx);
(
selection_menu_enabled,
inlay_hints_enabled,
supports_inlay_hints,
git_blame_inline_enabled,
auto_signature_help_enabled,
)
};
@@ -268,23 +265,6 @@ impl Render for QuickActionBar {
},
);
menu = menu.toggleable_entry(
"Auto Signature Help",
auto_signature_help_enabled,
Some(editor::actions::ToggleAutoSignatureHelp.boxed_clone()),
{
let editor = editor.clone();
move |cx| {
editor.update(cx, |editor, cx| {
editor.toggle_auto_signature_help_menu(
&editor::actions::ToggleAutoSignatureHelp,
cx,
);
});
}
},
);
menu
});
cx.subscribe(&menu, |quick_action_bar, _, _: &DismissEvent, _cx| {

View File

@@ -114,31 +114,25 @@ impl DevServerProjects {
cx.notify();
});
let mut base_style = cx.text_style();
base_style.refine(&gpui::TextStyleRefinement {
color: Some(cx.theme().colors().editor_foreground),
..Default::default()
});
let markdown_style = MarkdownStyle {
base_text_style: base_style,
code_block: gpui::StyleRefinement {
text: Some(gpui::TextStyleRefinement {
font_family: Some("Zed Plex Mono".into()),
..Default::default()
}),
code_block: gpui::TextStyleRefinement {
font_family: Some("Zed Plex Mono".into()),
color: Some(cx.theme().colors().editor_foreground),
background_color: Some(cx.theme().colors().editor_background),
..Default::default()
},
inline_code: Default::default(),
block_quote: Default::default(),
link: gpui::TextStyleRefinement {
color: Some(Color::Accent.color(cx)),
..Default::default()
},
rule_color: Default::default(),
block_quote_border_color: Default::default(),
syntax: cx.theme().syntax().clone(),
selection_background_color: cx.theme().players().local().selection,
..Default::default()
};
let markdown =
cx.new_view(|cx| Markdown::new("".to_string(), markdown_style, None, cx, None));
let markdown = cx.new_view(|cx| Markdown::new("".to_string(), markdown_style, None, cx));
Self {
mode: Mode::Default(None),

View File

@@ -24,7 +24,6 @@ futures.workspace = true
image.workspace = true
language.workspace = true
log.workspace = true
multi_buffer.workspace = true
project.workspace = true
runtimelib.workspace = true
schemars.workspace = true

View File

@@ -11,8 +11,7 @@ use gpui::{
actions, prelude::*, AppContext, AsyncWindowContext, EntityId, EventEmitter, FocusHandle,
FocusOutEvent, FocusableView, Subscription, Task, View, WeakView,
};
use language::{Language, Point};
use multi_buffer::MultiBufferRow;
use language::Point;
use project::Fs;
use settings::{Settings as _, SettingsStore};
use std::{ops::Range, sync::Arc};
@@ -175,14 +174,34 @@ impl RuntimePanel {
let range = if selection.is_empty() {
let cursor = selection.head();
let cursor_row = multi_buffer_snapshot.offset_to_point(cursor).row;
let start_offset = multi_buffer_snapshot.point_to_offset(Point::new(cursor_row, 0));
let line_start = multi_buffer_snapshot.offset_to_point(cursor).row;
let mut start_offset = multi_buffer_snapshot.point_to_offset(Point::new(line_start, 0));
let end_point = Point::new(
cursor_row,
multi_buffer_snapshot.line_len(MultiBufferRow(cursor_row)),
);
let end_offset = start_offset.saturating_add(end_point.column as usize);
// Iterate backwards to find the start of the line
while start_offset > 0 {
let ch = multi_buffer_snapshot
.chars_at(start_offset - 1)
.next()
.unwrap_or('\0');
if ch == '\n' {
break;
}
start_offset -= 1;
}
let mut end_offset = cursor;
// Iterate forwards to find the end of the line
while end_offset < multi_buffer_snapshot.len() {
let ch = multi_buffer_snapshot
.chars_at(end_offset)
.next()
.unwrap_or('\0');
if ch == '\n' {
break;
}
end_offset += 1;
}
// Create a range from the start to the end of the line
start_offset..end_offset
@@ -197,7 +216,7 @@ impl RuntimePanel {
&self,
editor: WeakView<Editor>,
cx: &mut ViewContext<Self>,
) -> Option<(String, Arc<Language>, Range<Anchor>)> {
) -> Option<(String, Arc<str>, Range<Anchor>)> {
let editor = editor.upgrade()?;
let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
@@ -207,24 +226,30 @@ impl RuntimePanel {
.text_for_range(anchor_range.clone())
.collect::<String>();
let start_language = buffer.language_at(anchor_range.start)?;
let end_language = buffer.language_at(anchor_range.end)?;
if start_language != end_language {
return None;
}
let start_language = buffer.language_at(anchor_range.start);
let end_language = buffer.language_at(anchor_range.end);
Some((selected_text, start_language.clone(), anchor_range))
let language_name = if start_language == end_language {
start_language
.map(|language| language.code_fence_block_name())
.filter(|lang| **lang != *"markdown")?
} else {
// If the selection spans multiple languages, don't run it
return None;
};
Some((selected_text, language_name, anchor_range))
}
pub fn language(
&self,
editor: WeakView<Editor>,
cx: &mut ViewContext<Self>,
) -> Option<Arc<Language>> {
let editor = editor.upgrade()?;
let selection = editor.read(cx).selections.newest::<usize>(cx);
let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
buffer.language_at(selection.head()).cloned()
) -> Option<Arc<str>> {
match self.snippet(editor, cx) {
Some((_, language, _)) => Some(language),
None => None,
}
}
pub fn refresh_kernelspecs(&mut self, cx: &mut ViewContext<Self>) -> Task<anyhow::Result<()>> {
@@ -241,12 +266,11 @@ impl RuntimePanel {
pub fn kernelspec(
&self,
language: &Language,
language_name: &str,
cx: &mut ViewContext<Self>,
) -> Option<KernelSpecification> {
let settings = JupyterSettings::get_global(cx);
let language_name = language.code_fence_block_name();
let selected_kernel = settings.kernel_selections.get(language_name.as_ref());
let selected_kernel = settings.kernel_selections.get(language_name);
self.kernel_specifications
.iter()
@@ -272,7 +296,7 @@ impl RuntimePanel {
return Ok(());
}
let (selected_text, language, anchor_range) = match self.snippet(editor.clone(), cx) {
let (selected_text, language_name, anchor_range) = match self.snippet(editor.clone(), cx) {
Some(snippet) => snippet,
None => return Ok(()),
};
@@ -280,8 +304,8 @@ impl RuntimePanel {
let entity_id = editor.entity_id();
let kernel_specification = self
.kernelspec(&language, cx)
.with_context(|| format!("No kernel found for language: {}", language.name()))?;
.kernelspec(&language_name, cx)
.with_context(|| format!("No kernel found for language: {language_name}"))?;
let session = self.sessions.entry(entity_id).or_insert_with(|| {
let view =
@@ -296,6 +320,7 @@ impl RuntimePanel {
panel.sessions.remove(&shutdown_event.entity_id());
}
}
//
},
);
@@ -325,7 +350,7 @@ impl RuntimePanel {
pub enum SessionSupport {
ActiveSession(View<Session>),
Inactive(KernelSpecification),
RequiresSetup(Arc<str>),
RequiresSetup(String),
Unsupported,
}
@@ -352,12 +377,11 @@ impl RuntimePanel {
match kernelspec {
Some(kernelspec) => SessionSupport::Inactive(kernelspec),
None => {
// If no kernelspec but language is one of typescript or python
let language: String = language.to_lowercase();
// If no kernelspec but language is one of typescript, python, r, or julia
// then we return RequiresSetup
match language.name().as_ref() {
"TypeScript" | "Python" => {
SessionSupport::RequiresSetup(language.name())
}
match language.as_str() {
"typescript" | "python" => SessionSupport::RequiresSetup(language),
_ => SessionSupport::Unsupported,
}
}

View File

@@ -7,13 +7,10 @@ use editor::{
display_map::{
BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock,
},
Anchor, AnchorRangeExt as _, Editor, MultiBuffer, ToPoint,
Anchor, AnchorRangeExt as _, Editor,
};
use futures::{FutureExt as _, StreamExt as _};
use gpui::{
div, prelude::*, EventEmitter, Model, Render, Subscription, Task, View, ViewContext, WeakView,
};
use language::Point;
use gpui::{div, prelude::*, EventEmitter, Render, Task, View, ViewContext, WeakView};
use project::Fs;
use runtimelib::{
ExecuteRequest, InterruptRequest, JupyterMessage, JupyterMessageContent, KernelInfoRequest,
@@ -30,13 +27,11 @@ pub struct Session {
blocks: HashMap<String, EditorBlock>,
pub messaging_task: Task<()>,
pub kernel_specification: KernelSpecification,
_buffer_subscription: Subscription,
}
struct EditorBlock {
editor: WeakView<Editor>,
code_range: Range<Anchor>,
invalidation_anchor: Anchor,
block_id: BlockId,
execution_view: View<ExecutionView>,
}
@@ -50,25 +45,7 @@ impl EditorBlock {
) -> anyhow::Result<Self> {
let execution_view = cx.new_view(|cx| ExecutionView::new(status, cx));
let (block_id, invalidation_anchor) = editor.update(cx, |editor, cx| {
let buffer = editor.buffer().clone();
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let end_point = code_range.end.to_point(&buffer_snapshot);
let next_row_start = end_point + Point::new(1, 0);
if next_row_start > buffer_snapshot.max_point() {
buffer.update(cx, |buffer, cx| {
buffer.edit(
[(
buffer_snapshot.max_point()..buffer_snapshot.max_point(),
"\n",
)],
None,
cx,
)
});
}
let invalidation_anchor = buffer.read(cx).read(cx).anchor_before(next_row_start);
let block_id = editor.update(cx, |editor, cx| {
let block = BlockProperties {
position: code_range.end,
height: execution_view.num_lines(cx).saturating_add(1),
@@ -77,14 +54,12 @@ impl EditorBlock {
disposition: BlockDisposition::Below,
};
let block_id = editor.insert_blocks([block], None, cx)[0];
(block_id, invalidation_anchor)
editor.insert_blocks([block], None, cx)[0]
})?;
anyhow::Ok(Self {
editor,
code_range,
invalidation_anchor,
block_id,
execution_view,
})
@@ -204,55 +179,15 @@ impl Session {
})
.shared();
let subscription = match editor.upgrade() {
Some(editor) => {
let buffer = editor.read(cx).buffer().clone();
cx.subscribe(&buffer, Self::on_buffer_event)
}
None => Subscription::new(|| {}),
};
return Self {
editor,
kernel: Kernel::StartingKernel(pending_kernel),
messaging_task: Task::ready(()),
blocks: HashMap::default(),
kernel_specification,
_buffer_subscription: subscription,
};
}
fn on_buffer_event(
&mut self,
buffer: Model<MultiBuffer>,
event: &multi_buffer::Event,
cx: &mut ViewContext<Self>,
) {
if let multi_buffer::Event::Edited { .. } = event {
let snapshot = buffer.read(cx).snapshot(cx);
let mut blocks_to_remove: HashSet<BlockId> = HashSet::default();
self.blocks.retain(|_id, block| {
if block.invalidation_anchor.is_valid(&snapshot) {
true
} else {
blocks_to_remove.insert(block.block_id);
false
}
});
if !blocks_to_remove.is_empty() {
self.editor
.update(cx, |editor, cx| {
editor.remove_blocks(blocks_to_remove, None, cx);
})
.ok();
cx.notify();
}
}
}
fn send(&mut self, message: JupyterMessage, _cx: &mut ViewContext<Self>) -> anyhow::Result<()> {
match &mut self.kernel {
Kernel::RunningKernel(kernel) => {

View File

@@ -557,14 +557,6 @@ impl Peer {
Ok(())
}
pub fn send_dynamic(&self, receiver_id: ConnectionId, message: proto::Envelope) -> Result<()> {
let connection = self.connection_state(receiver_id)?;
connection
.outgoing_tx
.unbounded_send(proto::Message::Envelope(message))?;
Ok(())
}
pub fn forward_send<T: EnvelopedMessage>(
&self,
sender_id: ConnectionId,

View File

@@ -9,7 +9,7 @@ use any_vec::AnyVec;
use collections::HashMap;
use editor::{
actions::{Tab, TabPrev},
DisplayPoint, Editor, EditorElement, EditorSettings, EditorStyle,
DisplayPoint, Editor, EditorElement, EditorStyle,
};
use futures::channel::oneshot;
use gpui::{
@@ -777,15 +777,6 @@ impl BufferSearchBar {
.get(&searchable_item.downgrade())
.filter(|matches| !matches.is_empty())
{
// If 'wrapscan' is disabled, searches do not wrap around the end of the file.
if !EditorSettings::get_global(cx).search_wrap {
if (direction == Direction::Next && index + count >= matches.len())
|| (direction == Direction::Prev && index < count)
{
crate::show_no_more_matches(cx);
return;
}
}
let new_match_index = searchable_item
.match_index_for_direction(matches, index, direction, count, cx);

View File

@@ -8,8 +8,7 @@ use editor::{
actions::SelectAll,
items::active_match_index,
scroll::{Autoscroll, Axis},
Anchor, Editor, EditorElement, EditorEvent, EditorSettings, EditorStyle, MultiBuffer,
MAX_TAB_TITLE_LEN,
Anchor, Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer, MAX_TAB_TITLE_LEN,
};
use gpui::{
actions, div, Action, AnyElement, AnyView, AppContext, Context as _, Element, EntityId,
@@ -144,6 +143,7 @@ pub struct ProjectSearchView {
panels_with_errors: HashSet<InputPanel>,
active_match_index: Option<usize>,
search_id: usize,
query_editor_was_focused: bool,
included_files_editor: View<Editor>,
excluded_files_editor: View<Editor>,
filters_enabled: bool,
@@ -714,6 +714,7 @@ impl ProjectSearchView {
search_options: options,
panels_with_errors: HashSet::default(),
active_match_index: None,
query_editor_was_focused: false,
included_files_editor,
excluded_files_editor,
filters_enabled,
@@ -969,16 +970,6 @@ impl ProjectSearchView {
fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
if let Some(index) = self.active_match_index {
let match_ranges = self.model.read(cx).match_ranges.clone();
if !EditorSettings::get_global(cx).search_wrap {
if (direction == Direction::Next && index + 1 >= match_ranges.len())
|| (direction == Direction::Prev && index == 0)
{
crate::show_no_more_matches(cx);
return;
}
}
let new_index = self.results_editor.update(cx, |editor, cx| {
editor.match_index_for_direction(&match_ranges, index, direction, 1, cx)
});
@@ -998,6 +989,7 @@ impl ProjectSearchView {
self.query_editor.update(cx, |query_editor, cx| {
query_editor.select_all(&SelectAll, cx);
});
self.query_editor_was_focused = true;
let editor_handle = self.query_editor.focus_handle(cx);
cx.focus(&editor_handle);
}
@@ -1012,6 +1004,7 @@ impl ProjectSearchView {
let cursor = query_editor.selections.newest_anchor().head();
query_editor.change_selections(None, cx, |s| s.select_ranges([cursor..cursor]));
});
self.query_editor_was_focused = false;
let results_handle = self.results_editor.focus_handle(cx);
cx.focus(&results_handle);
}

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