Merge branch 'main' into vim-visual-selection

This commit is contained in:
Conrad Irwin 2023-08-14 15:06:59 -06:00
commit fb90eada70
199 changed files with 6473 additions and 3725 deletions

5
.zed/settings.json Normal file
View File

@ -0,0 +1,5 @@
{
"JSON": {
"tab_size": 4
}
}

284
Cargo.lock generated
View File

@ -141,7 +141,7 @@ source = "git+https://github.com/alacritty/alacritty?rev=7b9f32300ee0a249c087230
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@ -187,9 +187,9 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "alsa"
version = "0.7.0"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8512c9117059663fb5606788fbca3619e2a91dac0e3fe516242eab1fa6be5e44"
checksum = "e2562ad8dcf0f789f65c6fdaad8a8a9708ed6b488e649da28c01656ad66b8b47"
dependencies = [
"alsa-sys",
"bitflags 1.3.2",
@ -215,9 +215,9 @@ checksum = "ec8ad6edb4840b78c5c3d88de606b22252d552b55f3a4699fbb10fc070ec3049"
[[package]]
name = "android-activity"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40bc1575e653f158cbdc6ebcd917b9564e66321c5325c232c3591269c257be69"
checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0"
dependencies = [
"android-properties",
"bitflags 1.3.2",
@ -507,7 +507,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@ -555,7 +555,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@ -598,7 +598,7 @@ checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@ -856,7 +856,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
"syn 2.0.27",
"syn 2.0.28",
"which",
]
@ -1040,7 +1040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
dependencies = [
"memchr",
"regex-automata 0.3.3",
"regex-automata 0.3.4",
"serde",
]
@ -1358,7 +1358,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@ -1425,7 +1425,7 @@ dependencies = [
"sum_tree",
"tempfile",
"thiserror",
"time 0.3.23",
"time 0.3.24",
"tiny_http",
"url",
"util",
@ -1527,7 +1527,7 @@ dependencies = [
"sha-1 0.9.8",
"sqlx",
"theme",
"time 0.3.23",
"time 0.3.24",
"tokio",
"tokio-tungstenite",
"toml 0.5.11",
@ -1649,6 +1649,21 @@ dependencies = [
"theme",
]
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "copilot"
version = "0.1.0"
@ -2042,9 +2057,9 @@ dependencies = [
[[package]]
name = "curl-sys"
version = "0.4.64+curl-8.2.0"
version = "0.4.65+curl-8.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f96069f0b1cb1241c838740659a771ef143363f52772a9ce1bd9c04c75eee0dc"
checksum = "961ba061c9ef2fe34bbd12b807152d96f0badd2bebe7b90ce6c8c8b7572a0986"
dependencies = [
"cc",
"libc",
@ -2124,6 +2139,28 @@ dependencies = [
"byteorder",
]
[[package]]
name = "deranged"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8810e7e2cf385b1e9b50d68264908ec367ba642c96d02edfe61c39e88e2a3c01"
dependencies = [
"serde",
]
[[package]]
name = "derive_more"
version = "0.99.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [
"convert_case 0.4.0",
"proc-macro2",
"quote",
"rustc_version 0.4.0",
"syn 1.0.109",
]
[[package]]
name = "dhat"
version = "0.3.2"
@ -2305,6 +2342,7 @@ dependencies = [
"clock",
"collections",
"context_menu",
"convert_case 0.6.0",
"copilot",
"ctor",
"db",
@ -2341,7 +2379,7 @@ dependencies = [
"tree-sitter",
"tree-sitter-html",
"tree-sitter-rust",
"tree-sitter-typescript 0.20.2 (git+https://github.com/tree-sitter/tree-sitter-typescript?rev=5d20856f34315b068c41edaee2ac8a100081d259)",
"tree-sitter-typescript",
"unindent",
"util",
"workspace",
@ -2425,9 +2463,9 @@ dependencies = [
[[package]]
name = "errno"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
dependencies = [
"errno-dragonfly",
"libc",
@ -2731,7 +2769,7 @@ dependencies = [
"smol",
"sum_tree",
"tempfile",
"time 0.3.23",
"time 0.3.24",
"util",
]
@ -2881,7 +2919,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@ -3033,9 +3071,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globset"
version = "0.4.11"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1391ab1f92ffcc08911957149833e682aa3fe252b9f45f966d2ef972274c97df"
checksum = "aca8bbd8e0707c1887a8bbb7e6b40e228f251ff5d62c8220a4a7a53c73aff006"
dependencies = [
"aho-corasick 1.0.2",
"bstr",
@ -3087,6 +3125,7 @@ dependencies = [
"core-graphics",
"core-text",
"ctor",
"derive_more",
"dhat",
"env_logger 0.9.3",
"etagere",
@ -3121,7 +3160,7 @@ dependencies = [
"smol",
"sqlez",
"sum_tree",
"time 0.3.23",
"time 0.3.24",
"tiny-skia",
"usvg",
"util",
@ -3133,6 +3172,7 @@ dependencies = [
name = "gpui_macros"
version = "0.1.0"
dependencies = [
"gpui",
"proc-macro2",
"quote",
"syn 1.0.109",
@ -3851,7 +3891,7 @@ dependencies = [
"text",
"theme",
"tree-sitter",
"tree-sitter-elixir 0.1.0 (git+https://github.com/elixir-lang/tree-sitter-elixir?rev=a2861e88a730287a60c11ea9299c033c7d076e30)",
"tree-sitter-elixir",
"tree-sitter-embedded-template",
"tree-sitter-heex",
"tree-sitter-html",
@ -3860,7 +3900,7 @@ dependencies = [
"tree-sitter-python",
"tree-sitter-ruby",
"tree-sitter-rust",
"tree-sitter-typescript 0.20.2 (git+https://github.com/tree-sitter/tree-sitter-typescript?rev=5d20856f34315b068c41edaee2ac8a100081d259)",
"tree-sitter-typescript",
"unicase",
"unindent",
"util",
@ -4034,9 +4074,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]]
name = "linux-raw-sys"
version = "0.4.3"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0"
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
[[package]]
name = "lipsum"
@ -4744,7 +4784,7 @@ dependencies = [
"proc-macro-crate 1.3.1",
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@ -4895,7 +4935,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@ -5097,7 +5137,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff"
dependencies = [
"rustc_version",
"rustc_version 0.3.3",
]
[[package]]
@ -5134,9 +5174,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "pest"
version = "2.7.1"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d2d1d55045829d65aad9d389139882ad623b33b904e7c9f1b10c5b8927298e5"
checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a"
dependencies = [
"thiserror",
"ucd-trie",
@ -5192,7 +5232,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@ -5230,7 +5270,7 @@ dependencies = [
"line-wrap",
"quick-xml",
"serde",
"time 0.3.23",
"time 0.3.24",
]
[[package]]
@ -5345,7 +5385,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62"
dependencies = [
"proc-macro2",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@ -5905,7 +5945,7 @@ checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
dependencies = [
"aho-corasick 1.0.2",
"memchr",
"regex-automata 0.3.3",
"regex-automata 0.3.4",
"regex-syntax 0.7.4",
]
@ -5920,9 +5960,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294"
dependencies = [
"aho-corasick 1.0.2",
"memchr",
@ -6217,7 +6257,7 @@ dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.27",
"syn 2.0.28",
"walkdir",
]
@ -6234,13 +6274,12 @@ dependencies = [
[[package]]
name = "rust_decimal"
version = "1.30.0"
version = "1.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0446843641c69436765a35a5a77088e28c2e6a12da93e84aa3ab1cd4aa5a042"
checksum = "4a2ab0025103a60ecaaf3abf24db1db240a4e1c15837090d2c32f625ac98abea"
dependencies = [
"arrayvec 0.7.4",
"borsh",
"bytecheck",
"byteorder",
"bytes 1.4.0",
"num-traits",
@ -6268,7 +6307,16 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
dependencies = [
"semver",
"semver 0.11.0",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver 1.0.18",
]
[[package]]
@ -6294,7 +6342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
dependencies = [
"bitflags 1.3.2",
"errno 0.3.1",
"errno 0.3.2",
"io-lifetimes 1.0.11",
"libc",
"linux-raw-sys 0.3.8",
@ -6308,9 +6356,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
dependencies = [
"bitflags 2.3.3",
"errno 0.3.1",
"errno 0.3.2",
"libc",
"linux-raw-sys 0.4.3",
"linux-raw-sys 0.4.5",
"windows-sys",
]
@ -6509,7 +6557,7 @@ dependencies = [
"serde_json",
"sqlx",
"thiserror",
"time 0.3.23",
"time 0.3.24",
"tracing",
"url",
"uuid 1.4.1",
@ -6537,7 +6585,7 @@ dependencies = [
"rust_decimal",
"sea-query-derive",
"serde_json",
"time 0.3.23",
"time 0.3.24",
"uuid 1.4.1",
]
@ -6552,7 +6600,7 @@ dependencies = [
"sea-query",
"serde_json",
"sqlx",
"time 0.3.23",
"time 0.3.24",
"uuid 1.4.1",
]
@ -6685,12 +6733,15 @@ dependencies = [
"theme",
"tiktoken-rs 0.5.0",
"tree-sitter",
"tree-sitter-cpp 0.20.2",
"tree-sitter-elixir 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tree-sitter-json 0.19.0",
"tree-sitter-cpp",
"tree-sitter-elixir",
"tree-sitter-json 0.20.0",
"tree-sitter-lua",
"tree-sitter-php",
"tree-sitter-ruby",
"tree-sitter-rust",
"tree-sitter-toml 0.20.0",
"tree-sitter-typescript 0.20.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tree-sitter-toml",
"tree-sitter-typescript",
"unindent",
"util",
"workspace",
@ -6705,6 +6756,12 @@ dependencies = [
"semver-parser",
]
[[package]]
name = "semver"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
[[package]]
name = "semver-parser"
version = "0.10.2"
@ -6722,22 +6779,22 @@ checksum = "5a9f47faea3cad316faa914d013d24f471cd90bfca1a0c70f05a3f42c6441e99"
[[package]]
name = "serde"
version = "1.0.175"
version = "1.0.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b"
checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.175"
version = "1.0.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4"
checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@ -6762,9 +6819,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.103"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b"
checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c"
dependencies = [
"indexmap 2.0.0",
"itoa 1.0.9",
@ -6786,13 +6843,13 @@ dependencies = [
[[package]]
name = "serde_repr"
version = "0.1.15"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e168eaaf71e8f9bd6037feb05190485708e019f4fd87d161b3c0a0d37daf85e5"
checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@ -7238,7 +7295,7 @@ dependencies = [
"sqlx-rt",
"stringprep",
"thiserror",
"time 0.3.23",
"time 0.3.24",
"tokio-stream",
"url",
"uuid 1.4.1",
@ -7487,9 +7544,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.27"
version = "2.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
dependencies = [
"proc-macro2",
"quote",
@ -7557,9 +7614,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "target-lexicon"
version = "0.12.10"
version = "0.12.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2faeef5759ab89935255b1a4cd98e0baf99d1085e37d36599c625dac49ae8e"
checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a"
[[package]]
name = "tempdir"
@ -7742,7 +7799,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@ -7815,10 +7872,11 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.23"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
checksum = "b79eabcd964882a646b3584543ccabeae7869e9ac32a46f6f22b7a5bd405308b"
dependencies = [
"deranged",
"itoa 1.0.9",
"serde",
"time-core",
@ -7833,9 +7891,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
[[package]]
name = "time-macros"
version = "0.2.10"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd"
dependencies = [
"time-core",
]
@ -7931,7 +7989,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@ -8153,7 +8211,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@ -8255,16 +8313,6 @@ dependencies = [
"tree-sitter",
]
[[package]]
name = "tree-sitter-cpp"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c88fd925d0333e63ac64e521f5bd79c53019e569ffbbccfeef346a326f459e9"
dependencies = [
"cc",
"tree-sitter",
]
[[package]]
name = "tree-sitter-css"
version = "0.19.0"
@ -8274,16 +8322,6 @@ dependencies = [
"tree-sitter",
]
[[package]]
name = "tree-sitter-elixir"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a9916f3e1c80b3c8aab8582604e97e8720cb9b893489b347cf999f80f9d469e"
dependencies = [
"cc",
"tree-sitter",
]
[[package]]
name = "tree-sitter-elixir"
version = "0.1.0"
@ -8471,26 +8509,6 @@ dependencies = [
"tree-sitter",
]
[[package]]
name = "tree-sitter-toml"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca517f578a98b23d20780247cc2688407fa81effad5b627a5a364ec3339b53e8"
dependencies = [
"cc",
"tree-sitter",
]
[[package]]
name = "tree-sitter-typescript"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "079c695c32d39ad089101c66393aeaca30e967fba3486a91f573d2f0e12d290a"
dependencies = [
"cc",
"tree-sitter",
]
[[package]]
name = "tree-sitter-typescript"
version = "0.20.2"
@ -8993,7 +9011,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
"wasm-bindgen-shared",
]
@ -9027,7 +9045,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -9040,9 +9058,9 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
[[package]]
name = "wasm-encoder"
version = "0.31.0"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06a3d1b4a575ffb873679402b2aedb3117555eb65c27b1b86c8a91e574bc2a2a"
checksum = "41763f20eafed1399fff1afb466496d3a959f58241436cfdc17e3f5ca954de16"
dependencies = [
"leb128",
]
@ -9264,9 +9282,9 @@ dependencies = [
[[package]]
name = "wast"
version = "62.0.0"
version = "62.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f7ee878019d69436895f019b65f62c33da63595d8e857cbdc87c13ecb29a32"
checksum = "b8ae06f09dbe377b889fbd620ff8fa21e1d49d1d9d364983c0cdbf9870cb9f1f"
dependencies = [
"leb128",
"memchr",
@ -9276,11 +9294,11 @@ dependencies = [
[[package]]
name = "wat"
version = "1.0.68"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "295572bf24aa5b685a971a83ad3e8b6e684aaad8a9be24bc7bf59bed84cc1c08"
checksum = "842e15861d203fb4a96d314b0751cdeaf0f6f8b35e8d81d2953af2af5e44e637"
dependencies = [
"wast 62.0.0",
"wast 62.0.1",
]
[[package]]
@ -9657,9 +9675,9 @@ dependencies = [
[[package]]
name = "winnow"
version = "0.5.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25b5872fa2e10bd067ae946f927e726d7d603eaeb6e02fa6a350e0722d2b8c11"
checksum = "8bd122eb777186e60c3fdf765a58ac76e41c582f1f535fbf3314434c6b58f3f7"
dependencies = [
"memchr",
]
@ -9843,7 +9861,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.98.0"
version = "0.100.0"
dependencies = [
"activity_indicator",
"ai",
@ -9930,9 +9948,9 @@ dependencies = [
"tree-sitter",
"tree-sitter-bash",
"tree-sitter-c",
"tree-sitter-cpp 0.20.0",
"tree-sitter-cpp",
"tree-sitter-css",
"tree-sitter-elixir 0.1.0 (git+https://github.com/elixir-lang/tree-sitter-elixir?rev=a2861e88a730287a60c11ea9299c033c7d076e30)",
"tree-sitter-elixir",
"tree-sitter-elm",
"tree-sitter-embedded-template",
"tree-sitter-glsl",
@ -9950,8 +9968,8 @@ dependencies = [
"tree-sitter-rust",
"tree-sitter-scheme",
"tree-sitter-svelte",
"tree-sitter-toml 0.5.1",
"tree-sitter-typescript 0.20.2 (git+https://github.com/tree-sitter/tree-sitter-typescript?rev=5d20856f34315b068c41edaee2ac8a100081d259)",
"tree-sitter-toml",
"tree-sitter-typescript",
"tree-sitter-yaml",
"unindent",
"url",
@ -9988,7 +10006,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]

View File

@ -79,6 +79,7 @@ resolver = "2"
anyhow = { version = "1.0.57" }
async-trait = { version = "0.1" }
ctor = { version = "0.1" }
derive_more = { version = "0.99.17" }
env_logger = { version = "0.9" }
futures = { version = "0.3" }
globset = { version = "0.4" }

View File

@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.2
FROM rust:1.70-bullseye as builder
FROM rust:1.71-bullseye as builder
WORKDIR app
COPY . .

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,93 @@
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -1,159 +1,179 @@
{
"suffixes": {
"aac": "audio",
"bash": "terminal",
"bmp": "image",
"c": "code",
"conf": "settings",
"cpp": "code",
"cc": "code",
"css": "code",
"doc": "document",
"docx": "document",
"eslintrc": "eslint",
"eslintrc.js": "eslint",
"eslintrc.json": "eslint",
"flac": "audio",
"fish": "terminal",
"gitattributes": "vcs",
"gitignore": "vcs",
"gitmodules": "vcs",
"gif": "image",
"go": "code",
"h": "code",
"handlebars": "code",
"hbs": "template",
"htm": "template",
"html": "template",
"svelte": "template",
"hpp": "code",
"ico": "image",
"ini": "settings",
"java": "code",
"jpeg": "image",
"jpg": "image",
"js": "code",
"json": "storage",
"lock": "lock",
"log": "log",
"md": "document",
"mdx": "document",
"mp3": "audio",
"mp4": "video",
"ods": "document",
"odp": "document",
"odt": "document",
"ogg": "video",
"pdf": "document",
"php": "code",
"png": "image",
"ppt": "document",
"pptx": "document",
"prettierrc": "prettier",
"prettierignore": "prettier",
"ps1": "terminal",
"psd": "image",
"py": "code",
"rb": "code",
"rkt": "code",
"rs": "rust",
"rtf": "document",
"scm": "code",
"sh": "terminal",
"bashrc": "terminal",
"bash_profile": "terminal",
"bash_aliases": "terminal",
"bash_logout": "terminal",
"profile": "terminal",
"zshrc": "terminal",
"zshenv": "terminal",
"zsh_profile": "terminal",
"zsh_aliases": "terminal",
"zsh_histfile": "terminal",
"zlogin": "terminal",
"sql": "code",
"svg": "image",
"swift": "code",
"tiff": "image",
"toml": "toml",
"ts": "typescript",
"tsx": "code",
"txt": "document",
"wav": "audio",
"webm": "video",
"xls": "document",
"xlsx": "document",
"xml": "template",
"yaml": "settings",
"yml": "settings",
"zsh": "terminal"
},
"types": {
"audio": {
"icon": "icons/file_icons/audio.svg"
"suffixes": {
"aac": "audio",
"accdb": "storage",
"bak": "backup",
"bash": "terminal",
"bash_aliases": "terminal",
"bash_logout": "terminal",
"bash_profile": "terminal",
"bashrc": "terminal",
"bmp": "image",
"c": "code",
"cc": "code",
"conf": "settings",
"cpp": "code",
"css": "code",
"csv": "storage",
"dat": "storage",
"db": "storage",
"dbf": "storage",
"dll": "storage",
"doc": "document",
"docx": "document",
"eslintrc": "eslint",
"eslintrc.js": "eslint",
"eslintrc.json": "eslint",
"fmp": "storage",
"fp7": "storage",
"flac": "audio",
"fish": "terminal",
"frm": "storage",
"gdb": "storage",
"gitattributes": "vcs",
"gitignore": "vcs",
"gitmodules": "vcs",
"gif": "image",
"go": "code",
"h": "code",
"handlebars": "code",
"hbs": "template",
"htm": "template",
"html": "template",
"ib": "storage",
"ico": "image",
"ini": "settings",
"java": "code",
"jpeg": "image",
"jpg": "image",
"js": "code",
"json": "storage",
"ldf": "storage",
"lock": "lock",
"log": "log",
"mdb": "storage",
"md": "document",
"mdf": "storage",
"mdx": "document",
"mp3": "audio",
"mp4": "video",
"myd": "storage",
"myi": "storage",
"ods": "document",
"odp": "document",
"odt": "document",
"ogg": "video",
"pdb": "storage",
"pdf": "document",
"php": "code",
"png": "image",
"ppt": "document",
"pptx": "document",
"prettierignore": "prettier",
"prettierrc": "prettier",
"profile": "terminal",
"ps1": "terminal",
"psd": "image",
"py": "code",
"rb": "code",
"rkt": "code",
"rs": "rust",
"rtf": "document",
"sav": "storage",
"scm": "code",
"sh": "terminal",
"sqlite": "storage",
"sdf": "storage",
"svelte": "template",
"svg": "image",
"swift": "code",
"ts": "typescript",
"tsx": "code",
"tiff": "image",
"toml": "toml",
"tsv": "storage",
"txt": "document",
"wav": "audio",
"webm": "video",
"xls": "document",
"xlsx": "document",
"xml": "template",
"yaml": "settings",
"yml": "settings",
"zlogin": "terminal",
"zsh": "terminal",
"zsh_aliases": "terminal",
"zshenv": "terminal",
"zsh_histfile": "terminal",
"zsh_profile": "terminal",
"zshrc": "terminal"
},
"code": {
"icon": "icons/file_icons/code.svg"
},
"collapsed_chevron": {
"icon": "icons/file_icons/chevron_right.svg"
},
"collapsed_folder": {
"icon": "icons/file_icons/folder.svg"
},
"default": {
"icon": "icons/file_icons/file.svg"
},
"document": {
"icon": "icons/file_icons/book.svg"
},
"eslint": {
"icon": "icons/file_icons/eslint.svg"
},
"expanded_chevron": {
"icon": "icons/file_icons/chevron_down.svg"
},
"expanded_folder": {
"icon": "icons/file_icons/folder_open.svg"
},
"image": {
"icon": "icons/file_icons/image.svg"
},
"lock": {
"icon": "icons/file_icons/lock.svg"
},
"log": {
"icon": "icons/file_icons/info.svg"
},
"prettier": {
"icon": "icons/file_icons/prettier.svg"
},
"rust": {
"icon": "icons/file_icons/rust.svg"
},
"settings": {
"icon": "icons/file_icons/settings.svg"
},
"storage": {
"icon": "icons/file_icons/database.svg"
},
"template": {
"icon": "icons/file_icons/html.svg"
},
"terminal": {
"icon": "icons/file_icons/terminal.svg"
},
"toml": {
"icon": "icons/file_icons/toml.svg"
},
"typescript": {
"icon": "icons/file_icons/typescript.svg"
},
"vcs": {
"icon": "icons/file_icons/git.svg"
},
"video": {
"icon": "icons/file_icons/video.svg"
"types": {
"audio": {
"icon": "icons/file_icons/audio.svg"
},
"code": {
"icon": "icons/file_icons/code.svg"
},
"collapsed_chevron": {
"icon": "icons/file_icons/chevron_right.svg"
},
"collapsed_folder": {
"icon": "icons/file_icons/folder.svg"
},
"default": {
"icon": "icons/file_icons/file.svg"
},
"document": {
"icon": "icons/file_icons/book.svg"
},
"eslint": {
"icon": "icons/file_icons/eslint.svg"
},
"expanded_chevron": {
"icon": "icons/file_icons/chevron_down.svg"
},
"expanded_folder": {
"icon": "icons/file_icons/folder_open.svg"
},
"image": {
"icon": "icons/file_icons/image.svg"
},
"lock": {
"icon": "icons/file_icons/lock.svg"
},
"log": {
"icon": "icons/file_icons/info.svg"
},
"prettier": {
"icon": "icons/file_icons/prettier.svg"
},
"rust": {
"icon": "icons/file_icons/rust.svg"
},
"settings": {
"icon": "icons/file_icons/settings.svg"
},
"storage": {
"icon": "icons/file_icons/database.svg"
},
"template": {
"icon": "icons/file_icons/html.svg"
},
"terminal": {
"icon": "icons/file_icons/terminal.svg"
},
"toml": {
"icon": "icons/file_icons/toml.svg"
},
"typescript": {
"icon": "icons/file_icons/typescript.svg"
},
"vcs": {
"icon": "icons/file_icons/git.svg"
},
"video": {
"icon": "icons/file_icons/video.svg"
}
}
}
}

View File

@ -227,12 +227,26 @@
"alt-enter": "search::SelectAllMatches"
}
},
{
"context": "BufferSearchBar > Editor",
"bindings": {
"up": "search::PreviousHistoryQuery",
"down": "search::NextHistoryQuery"
}
},
{
"context": "ProjectSearchBar",
"bindings": {
"escape": "project_search::ToggleFocus"
}
},
{
"context": "ProjectSearchBar > Editor",
"bindings": {
"up": "search::PreviousHistoryQuery",
"down": "search::NextHistoryQuery"
}
},
{
"context": "ProjectSearchView",
"bindings": {

View File

@ -362,7 +362,7 @@ impl AssistantPanel {
this.set_active_editor_index(this.prev_active_editor_index, cx);
}
})
.with_tooltip::<History>(1, "History".into(), None, tooltip_style, cx)
.with_tooltip::<History>(1, "History", None, tooltip_style, cx)
}
fn render_editor_tools(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement<Self>> {
@ -394,7 +394,7 @@ impl AssistantPanel {
})
.with_tooltip::<Split>(
1,
"Split Message".into(),
"Split Message",
Some(Box::new(Split)),
tooltip_style,
cx,
@ -416,13 +416,7 @@ impl AssistantPanel {
active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx));
}
})
.with_tooltip::<Assist>(
1,
"Assist".into(),
Some(Box::new(Assist)),
tooltip_style,
cx,
)
.with_tooltip::<Assist>(1, "Assist", Some(Box::new(Assist)), tooltip_style, cx)
}
fn render_quote_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
@ -446,7 +440,7 @@ impl AssistantPanel {
})
.with_tooltip::<QuoteSelection>(
1,
"Quote Selection".into(),
"Quote Selection",
Some(Box::new(QuoteSelection)),
tooltip_style,
cx,
@ -468,7 +462,7 @@ impl AssistantPanel {
})
.with_tooltip::<NewConversation>(
1,
"New Conversation".into(),
"New Conversation",
Some(Box::new(NewConversation)),
tooltip_style,
cx,
@ -498,11 +492,7 @@ impl AssistantPanel {
})
.with_tooltip::<ToggleZoom>(
0,
if self.zoomed {
"Zoom Out".into()
} else {
"Zoom In".into()
},
if self.zoomed { "Zoom Out" } else { "Zoom In" },
Some(Box::new(ToggleZoom)),
tooltip_style,
cx,
@ -1637,6 +1627,7 @@ impl ConversationEditor {
let mut editor = Editor::for_buffer(conversation.read(cx).buffer.clone(), None, cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_gutter(false, cx);
editor.set_show_wrap_guides(false, cx);
editor
});

View File

@ -12,10 +12,7 @@ use client::{
use collections::{HashMap, HashSet};
use fs::FakeFs;
use futures::{channel::oneshot, StreamExt as _};
use gpui::{
elements::*, executor::Deterministic, AnyElement, Entity, ModelHandle, TestAppContext, View,
ViewContext, ViewHandle, WeakViewHandle,
};
use gpui::{executor::Deterministic, ModelHandle, TestAppContext, WindowHandle};
use language::LanguageRegistry;
use parking_lot::Mutex;
use project::{Project, WorktreeId};
@ -466,42 +463,8 @@ impl TestClient {
&self,
project: &ModelHandle<Project>,
cx: &mut TestAppContext,
) -> ViewHandle<Workspace> {
struct WorkspaceContainer {
workspace: Option<WeakViewHandle<Workspace>>,
}
impl Entity for WorkspaceContainer {
type Event = ();
}
impl View for WorkspaceContainer {
fn ui_name() -> &'static str {
"WorkspaceContainer"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
if let Some(workspace) = self
.workspace
.as_ref()
.and_then(|workspace| workspace.upgrade(cx))
{
ChildView::new(&workspace, cx).into_any()
} else {
Empty::new().into_any()
}
}
}
// We use a workspace container so that we don't need to remove the window in order to
// drop the workspace and we can use a ViewHandle instead.
let (window_id, container) = cx.add_window(|_| WorkspaceContainer { workspace: None });
let workspace = cx.add_view(window_id, |cx| Workspace::test_new(project.clone(), cx));
container.update(cx, |container, cx| {
container.workspace = Some(workspace.downgrade());
cx.notify();
});
workspace
) -> WindowHandle<Workspace> {
cx.add_window(|cx| Workspace::test_new(project.clone(), cx))
}
}

View File

@ -7,8 +7,7 @@ use client::{User, RECEIVE_TIMEOUT};
use collections::HashSet;
use editor::{
test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion,
ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, ToggleCodeActions,
Undo,
ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToggleCodeActions, Undo,
};
use fs::{repository::GitFileStatus, FakeFs, Fs as _, LineEnding, RemoveOptions};
use futures::StreamExt as _;
@ -1208,7 +1207,7 @@ async fn test_share_project(
cx_c: &mut TestAppContext,
) {
deterministic.forbid_parking();
let (window_b, _) = cx_b.add_window(|_| EmptyView);
let window_b = cx_b.add_window(|_| EmptyView);
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
@ -1316,7 +1315,7 @@ async fn test_share_project(
.await
.unwrap();
let editor_b = cx_b.add_view(window_b, |cx| Editor::for_buffer(buffer_b, None, cx));
let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
// Client A sees client B's selection
deterministic.run_until_parked();
@ -1499,8 +1498,8 @@ async fn test_host_disconnect(
deterministic.run_until_parked();
assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
let (window_id_b, workspace_b) =
cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let window_b = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let workspace_b = window_b.root(cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "b.txt"), None, true, cx)
@ -1509,11 +1508,9 @@ async fn test_host_disconnect(
.unwrap()
.downcast::<Editor>()
.unwrap();
assert!(cx_b
.read_window(window_id_b, |cx| editor_b.is_focused(cx))
.unwrap());
assert!(window_b.read_with(cx_b, |cx| editor_b.is_focused(cx)));
editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
assert!(cx_b.is_window_edited(workspace_b.window_id()));
assert!(window_b.is_edited(cx_b));
// Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
server.forbid_connections();
@ -1525,10 +1522,10 @@ async fn test_host_disconnect(
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
// Ensure client B's edited state is reset and that the whole window is blurred.
cx_b.read_window(window_id_b, |cx| {
window_b.read_with(cx_b, |cx| {
assert_eq!(cx.focused_view_id(), None);
});
assert!(!cx_b.is_window_edited(workspace_b.window_id()));
assert!(!window_b.is_edited(cx_b));
// Ensure client B is not prompted to save edits when closing window after disconnecting.
let can_close = workspace_b
@ -3445,13 +3442,11 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.await
.unwrap();
let (window_a, _) = cx_a.add_window(|_| EmptyView);
let editor_a = cx_a.add_view(window_a, |cx| {
Editor::for_buffer(buffer_a, Some(project_a), cx)
});
let window_a = cx_a.add_window(|_| EmptyView);
let editor_a = window_a.add_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
let mut editor_cx_a = EditorTestContext {
cx: cx_a,
window_id: window_a,
window: window_a.into(),
editor: editor_a,
};
@ -3460,13 +3455,11 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.await
.unwrap();
let (window_b, _) = cx_b.add_window(|_| EmptyView);
let editor_b = cx_b.add_view(window_b, |cx| {
Editor::for_buffer(buffer_b, Some(project_b), cx)
});
let window_b = cx_b.add_window(|_| EmptyView);
let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
let mut editor_cx_b = EditorTestContext {
cx: cx_b,
window_id: window_b,
window: window_b.into(),
editor: editor_b,
};
@ -4205,8 +4198,8 @@ async fn test_collaborating_with_completion(
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
let (window_b, _) = cx_b.add_window(|_| EmptyView);
let editor_b = cx_b.add_view(window_b, |cx| {
let window_b = cx_b.add_window(|_| EmptyView);
let editor_b = window_b.add_view(cx_b, |cx| {
Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
});
@ -5316,7 +5309,8 @@ async fn test_collaborating_with_code_actions(
// Join the project as client B.
let project_b = client_b.build_remote_project(project_id, cx_b).await;
let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let window_b = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let workspace_b = window_b.root(cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
@ -5540,7 +5534,8 @@ async fn test_collaborating_with_renames(
.unwrap();
let project_b = client_b.build_remote_project(project_id, cx_b).await;
let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let window_b = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let workspace_b = window_b.root(cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "one.rs"), None, true, cx)
@ -5571,6 +5566,7 @@ async fn test_collaborating_with_renames(
.unwrap();
prepare_rename.await.unwrap();
editor_b.update(cx_b, |editor, cx| {
use editor::ToOffset;
let rename = editor.pending_rename().unwrap();
let buffer = editor.buffer().read(cx).snapshot(cx);
assert_eq!(
@ -6445,8 +6441,10 @@ async fn test_basic_following(
.await
.unwrap();
let workspace_a = client_a.build_workspace(&project_a, cx_a);
let workspace_b = client_b.build_workspace(&project_b, cx_b);
let window_a = client_a.build_workspace(&project_a, cx_a);
let workspace_a = window_a.root(cx_a);
let window_b = client_b.build_workspace(&project_b, cx_b);
let workspace_b = window_b.root(cx_b);
// Client A opens some editors.
let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
@ -6529,7 +6527,8 @@ async fn test_basic_following(
cx_c.foreground().run_until_parked();
let active_call_c = cx_c.read(ActiveCall::global);
let project_c = client_c.build_remote_project(project_id, cx_c).await;
let workspace_c = client_c.build_workspace(&project_c, cx_c);
let window_c = client_c.build_workspace(&project_c, cx_c);
let workspace_c = window_c.root(cx_c);
active_call_c
.update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
.await
@ -6547,7 +6546,7 @@ async fn test_basic_following(
cx_d.foreground().run_until_parked();
let active_call_d = cx_d.read(ActiveCall::global);
let project_d = client_d.build_remote_project(project_id, cx_d).await;
let workspace_d = client_d.build_workspace(&project_d, cx_d);
let workspace_d = client_d.build_workspace(&project_d, cx_d).root(cx_d);
active_call_d
.update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
.await
@ -6645,6 +6644,7 @@ async fn test_basic_following(
}
// Client C closes the project.
window_c.remove(cx_c);
cx_c.drop_last(workspace_c);
// Clients A and B see that client B is following A, and client C is not present in the followers.
@ -6874,9 +6874,7 @@ async fn test_basic_following(
});
// Client B activates a panel, and the previously-opened screen-sharing item gets activated.
let panel = cx_b.add_view(workspace_b.window_id(), |_| {
TestPanel::new(DockPosition::Left)
});
let panel = window_b.add_view(cx_b, |_| TestPanel::new(DockPosition::Left));
workspace_b.update(cx_b, |workspace, cx| {
workspace.add_panel(panel, cx);
workspace.toggle_panel_focus::<TestPanel>(cx);
@ -6904,7 +6902,7 @@ async fn test_basic_following(
// Client B activates an item that doesn't implement following,
// so the previously-opened screen-sharing item gets activated.
let unfollowable_item = cx_b.add_view(workspace_b.window_id(), |_| TestItem::new());
let unfollowable_item = window_b.add_view(cx_b, |_| TestItem::new());
workspace_b.update(cx_b, |workspace, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
@ -7066,10 +7064,10 @@ async fn test_following_tab_order(
.await
.unwrap();
let workspace_a = client_a.build_workspace(&project_a, cx_a);
let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
let workspace_b = client_b.build_workspace(&project_b, cx_b);
let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
let client_b_id = project_a.read_with(cx_a, |project, _| {
@ -7192,7 +7190,7 @@ async fn test_peers_following_each_other(
.unwrap();
// Client A opens some editors.
let workspace_a = client_a.build_workspace(&project_a, cx_a);
let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
let _editor_a1 = workspace_a
.update(cx_a, |workspace, cx| {
@ -7204,7 +7202,7 @@ async fn test_peers_following_each_other(
.unwrap();
// Client B opens an editor.
let workspace_b = client_b.build_workspace(&project_b, cx_b);
let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
let _editor_b1 = workspace_b
.update(cx_b, |workspace, cx| {
@ -7363,7 +7361,7 @@ async fn test_auto_unfollowing(
.unwrap();
// Client A opens some editors.
let workspace_a = client_a.build_workspace(&project_a, cx_a);
let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let _editor_a1 = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
@ -7374,7 +7372,7 @@ async fn test_auto_unfollowing(
.unwrap();
// Client B starts following client A.
let workspace_b = client_b.build_workspace(&project_b, cx_b);
let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
let leader_id = project_b.read_with(cx_b, |project, _| {
project.collaborators().values().next().unwrap().peer_id
@ -7502,14 +7500,14 @@ async fn test_peers_simultaneously_following_each_other(
client_a.fs.insert_tree("/a", json!({})).await;
let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
let workspace_a = client_a.build_workspace(&project_a, cx_a);
let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_remote_project(project_id, cx_b).await;
let workspace_b = client_b.build_workspace(&project_b, cx_b);
let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
deterministic.run_until_parked();
let client_a_id = project_b.read_with(cx_b, |project, _| {
@ -7601,8 +7599,8 @@ async fn test_on_input_format_from_host_to_guest(
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
let (window_a, _) = cx_a.add_window(|_| EmptyView);
let editor_a = cx_a.add_view(window_a, |cx| {
let window_a = cx_a.add_window(|_| EmptyView);
let editor_a = window_a.add_view(cx_a, |cx| {
Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)
});
@ -7730,8 +7728,8 @@ async fn test_on_input_format_from_guest_to_host(
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
let (window_b, _) = cx_b.add_window(|_| EmptyView);
let editor_b = cx_b.add_view(window_b, |cx| {
let window_b = cx_b.add_window(|_| EmptyView);
let editor_b = window_b.add_view(cx_b, |cx| {
Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
});
@ -7891,7 +7889,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.await
.unwrap();
let workspace_a = client_a.build_workspace(&project_a, cx_a);
let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
cx_a.foreground().start_waiting();
let _buffer_a = project_a
@ -7955,11 +7953,12 @@ async fn test_mutual_editor_inlay_hint_cache_update(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
inlay_cache.version, edits_made,
inlay_cache.version(),
edits_made,
"Host editor update the cache version after every cache/view change",
);
});
let workspace_b = client_b.build_workspace(&project_b, cx_b);
let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
@ -7978,7 +7977,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
inlay_cache.version, edits_made,
inlay_cache.version(),
edits_made,
"Guest editor update the cache version after every cache/view change"
);
});
@ -7998,7 +7998,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
"Host should get hints from the 1st edit and 1st LSP query"
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(inlay_cache.version, edits_made);
assert_eq!(inlay_cache.version(), edits_made);
});
editor_b.update(cx_b, |editor, _| {
assert_eq!(
@ -8012,7 +8012,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
"Guest should get hints the 1st edit and 2nd LSP query"
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(inlay_cache.version, edits_made);
assert_eq!(inlay_cache.version(), edits_made);
});
editor_a.update(cx_a, |editor, cx| {
@ -8037,7 +8037,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
4th query was made by guest (but not applied) due to cache invalidation logic"
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(inlay_cache.version, edits_made);
assert_eq!(inlay_cache.version(), edits_made);
});
editor_b.update(cx_b, |editor, _| {
assert_eq!(
@ -8053,7 +8053,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
"Guest should get hints from 3rd edit, 6th LSP query"
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(inlay_cache.version, edits_made);
assert_eq!(inlay_cache.version(), edits_made);
});
fake_language_server
@ -8079,7 +8079,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
inlay_cache.version, edits_made,
inlay_cache.version(),
edits_made,
"Host should accepted all edits and bump its cache version every time"
);
});
@ -8100,7 +8101,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
inlay_cache.version,
inlay_cache.version(),
edits_made,
"Guest should accepted all edits and bump its cache version every time"
);
@ -8198,8 +8199,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
.await
.unwrap();
let workspace_a = client_a.build_workspace(&project_a, cx_a);
let workspace_b = client_b.build_workspace(&project_b, cx_b);
let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
cx_a.foreground().start_waiting();
cx_b.foreground().start_waiting();
@ -8266,7 +8267,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
inlay_cache.version, 0,
inlay_cache.version(),
0,
"Host should not increment its cache version due to no changes",
);
});
@ -8281,7 +8283,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
inlay_cache.version, edits_made,
inlay_cache.version(),
edits_made,
"Guest editor update the cache version after every cache/view change"
);
});
@ -8298,7 +8301,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
inlay_cache.version, 0,
inlay_cache.version(),
0,
"Host should not increment its cache version due to no changes",
);
});
@ -8313,7 +8317,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
inlay_cache.version, edits_made,
inlay_cache.version(),
edits_made,
"Guest should accepted all edits and bump its cache version every time"
);
});
@ -8345,13 +8350,10 @@ fn room_participants(room: &ModelHandle<Room>, cx: &mut TestAppContext) -> RoomP
fn extract_hint_labels(editor: &Editor) -> Vec<String> {
let mut labels = Vec::new();
for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
let excerpt_hints = excerpt_hints.read();
for (_, inlay) in excerpt_hints.hints.iter() {
match &inlay.label {
project::InlayHintLabel::String(s) => labels.push(s.to_string()),
_ => unreachable!(),
}
for hint in editor.inlay_hint_cache().hints() {
match hint.label {
project::InlayHintLabel::String(s) => labels.push(s),
_ => unreachable!(),
}
}
labels

View File

@ -183,7 +183,7 @@ async fn apply_server_operation(
let username;
{
let mut plan = plan.lock();
let mut user = plan.user(user_id);
let user = plan.user(user_id);
if user.online {
return false;
}

View File

@ -15,8 +15,8 @@ use gpui::{
geometry::{rect::RectF, vector::vec2f, PathBuilder},
json::{self, ToJson},
platform::{CursorStyle, MouseButton},
AppContext, Entity, ImageData, LayoutContext, ModelHandle, SceneBuilder, Subscription, View,
ViewContext, ViewHandle, WeakViewHandle,
AppContext, Entity, ImageData, LayoutContext, ModelHandle, PaintContext, SceneBuilder,
Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
};
use picker::PickerEvent;
use project::{Project, RepositoryEntry};
@ -238,7 +238,7 @@ impl CollabTitlebarItem {
.left()
.with_tooltip::<RecentProjectsTooltip>(
0,
"Recent projects".into(),
"Recent projects",
Some(Box::new(recent_projects::OpenRecent)),
theme.tooltip.clone(),
cx,
@ -282,7 +282,7 @@ impl CollabTitlebarItem {
.left()
.with_tooltip::<BranchPopoverTooltip>(
0,
"Recent branches".into(),
"Recent branches",
Some(Box::new(ToggleVcsMenu)),
theme.tooltip.clone(),
cx,
@ -582,7 +582,7 @@ impl CollabTitlebarItem {
})
.with_tooltip::<ToggleContactsMenu>(
0,
"Show contacts menu".into(),
"Show contacts menu",
Some(Box::new(ToggleContactsMenu)),
theme.tooltip.clone(),
cx,
@ -633,7 +633,7 @@ impl CollabTitlebarItem {
})
.with_tooltip::<ToggleScreenSharing>(
0,
tooltip.into(),
tooltip,
Some(Box::new(ToggleScreenSharing)),
theme.tooltip.clone(),
cx,
@ -686,7 +686,7 @@ impl CollabTitlebarItem {
})
.with_tooltip::<ToggleMute>(
0,
tooltip.into(),
tooltip,
Some(Box::new(ToggleMute)),
theme.tooltip.clone(),
cx,
@ -734,7 +734,7 @@ impl CollabTitlebarItem {
})
.with_tooltip::<ToggleDeafen>(
0,
tooltip.into(),
tooltip,
Some(Box::new(ToggleDeafen)),
theme.tooltip.clone(),
cx,
@ -768,7 +768,7 @@ impl CollabTitlebarItem {
})
.with_tooltip::<LeaveCall>(
0,
tooltip.into(),
tooltip,
Some(Box::new(LeaveCall)),
theme.tooltip.clone(),
cx,
@ -1312,7 +1312,7 @@ impl Element<CollabTitlebarItem> for AvatarRibbon {
_: RectF,
_: &mut Self::LayoutState,
_: &mut CollabTitlebarItem,
_: &mut ViewContext<CollabTitlebarItem>,
_: &mut PaintContext<CollabTitlebarItem>,
) -> Self::PaintState {
let mut path = PathBuilder::new();
path.reset(bounds.lower_left());

View File

@ -305,18 +305,18 @@ impl ContactList {
github_login
);
let mut answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
let window_id = cx.window_id();
let window = cx.window();
cx.spawn(|_, mut cx| async move {
if answer.next().await == Some(0) {
if let Err(e) = user_store
.update(&mut cx, |store, cx| store.remove_contact(user_id, cx))
.await
{
cx.prompt(
window_id,
window.prompt(
PromptLevel::Info,
&format!("Failed to remove contact: {}", e),
&["Ok"],
&mut cx,
);
}
}
@ -837,7 +837,7 @@ impl ContactList {
),
background: Some(tree_branch.color),
border: gpui::Border::default(),
corner_radius: 0.,
corner_radii: Default::default(),
});
scene.push_quad(gpui::Quad {
bounds: RectF::from_points(
@ -846,7 +846,7 @@ impl ContactList {
),
background: Some(tree_branch.color),
border: gpui::Border::default(),
corner_radius: 0.,
corner_radii: Default::default(),
});
}))
.constrained()
@ -934,7 +934,7 @@ impl ContactList {
),
background: Some(tree_branch.color),
border: gpui::Border::default(),
corner_radius: 0.,
corner_radii: Default::default(),
});
scene.push_quad(gpui::Quad {
bounds: RectF::from_points(
@ -943,7 +943,7 @@ impl ContactList {
),
background: Some(tree_branch.color),
border: gpui::Border::default(),
corner_radius: 0.,
corner_radii: Default::default(),
});
}))
.constrained()
@ -1345,7 +1345,7 @@ impl View for ContactList {
})
.with_tooltip::<AddContact>(
0,
"Search for new contact".into(),
"Search for new contact",
None,
theme.tooltip.clone(),
cx,

View File

@ -7,7 +7,7 @@ use gpui::{
},
json::ToJson,
serde_json::{self, json},
AnyElement, Axis, Element, LayoutContext, SceneBuilder, ViewContext,
AnyElement, Axis, Element, LayoutContext, PaintContext, SceneBuilder, ViewContext,
};
use crate::CollabTitlebarItem;
@ -54,7 +54,7 @@ impl Element<CollabTitlebarItem> for FacePile {
visible_bounds: RectF,
_layout: &mut Self::LayoutState,
view: &mut CollabTitlebarItem,
cx: &mut ViewContext<CollabTitlebarItem>,
cx: &mut PaintContext<CollabTitlebarItem>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();

View File

@ -7,7 +7,7 @@ use gpui::{
elements::*,
geometry::{rect::RectF, vector::vec2f},
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
AnyElement, AppContext, Entity, View, ViewContext,
AnyElement, AppContext, Entity, View, ViewContext, WindowHandle,
};
use util::ResultExt;
use workspace::AppState;
@ -16,10 +16,10 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
let app_state = Arc::downgrade(app_state);
let mut incoming_call = ActiveCall::global(cx).read(cx).incoming();
cx.spawn(|mut cx| async move {
let mut notification_windows = Vec::new();
let mut notification_windows: Vec<WindowHandle<IncomingCallNotification>> = Vec::new();
while let Some(incoming_call) = incoming_call.next().await {
for window_id in notification_windows.drain(..) {
cx.remove_window(window_id);
for window in notification_windows.drain(..) {
window.remove(&mut cx);
}
if let Some(incoming_call) = incoming_call {
@ -31,7 +31,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
for screen in cx.platform().screens() {
let screen_bounds = screen.bounds();
let (window_id, _) = cx.add_window(
let window = cx.add_window(
WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(
screen_bounds.upper_right()
@ -49,7 +49,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|_| IncomingCallNotification::new(incoming_call.clone(), app_state.clone()),
);
notification_windows.push(window_id);
notification_windows.push(window);
}
}
}

View File

@ -26,7 +26,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
for screen in cx.platform().screens() {
let screen_bounds = screen.bounds();
let (window_id, _) = cx.add_window(
let window = cx.add_window(
WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(
screen_bounds.upper_right() - vec2f(PADDING + window_size.x(), PADDING),
@ -52,20 +52,20 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
notification_windows
.entry(*project_id)
.or_insert(Vec::new())
.push(window_id);
.push(window);
}
}
room::Event::RemoteProjectUnshared { project_id } => {
if let Some(window_ids) = notification_windows.remove(&project_id) {
for window_id in window_ids {
cx.update_window(window_id, |cx| cx.remove_window());
if let Some(windows) = notification_windows.remove(&project_id) {
for window in windows {
window.remove(cx);
}
}
}
room::Event::Left => {
for (_, window_ids) in notification_windows.drain() {
for window_id in window_ids {
cx.update_window(window_id, |cx| cx.remove_window());
for (_, windows) in notification_windows.drain() {
for window in windows {
window.remove(cx);
}
}
}

View File

@ -20,11 +20,11 @@ pub fn init(cx: &mut AppContext) {
{
status_indicator = Some(cx.add_status_bar_item(|_| SharingStatusIndicator));
}
} else if let Some((window_id, _)) = status_indicator.take() {
cx.update_window(window_id, |cx| cx.remove_window());
} else if let Some(window) = status_indicator.take() {
window.update(cx, |cx| cx.remove_window());
}
} else if let Some((window_id, _)) = status_indicator.take() {
cx.update_window(window_id, |cx| cx.remove_window());
} else if let Some(window) = status_indicator.take() {
window.update(cx, |cx| cx.remove_window());
}
})
.detach();

View File

@ -1,8 +1,8 @@
use collections::CommandPaletteFilter;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
actions, elements::*, keymap_matcher::Keystroke, Action, AppContext, Element, MouseState,
ViewContext,
actions, anyhow::anyhow, elements::*, keymap_matcher::Keystroke, Action, AnyWindowHandle,
AppContext, Element, MouseState, ViewContext,
};
use picker::{Picker, PickerDelegate, PickerEvent};
use std::cmp;
@ -28,7 +28,7 @@ pub struct CommandPaletteDelegate {
pub enum Event {
Dismissed,
Confirmed {
window_id: usize,
window: AnyWindowHandle,
focused_view_id: usize,
action: Box<dyn Action>,
},
@ -80,12 +80,13 @@ impl PickerDelegate for CommandPaletteDelegate {
query: String,
cx: &mut ViewContext<Picker<Self>>,
) -> gpui::Task<()> {
let window_id = cx.window_id();
let view_id = self.focused_view_id;
let window = cx.window();
cx.spawn(move |picker, mut cx| async move {
let actions = cx
.available_actions(window_id, view_id)
let actions = window
.available_actions(view_id, &cx)
.into_iter()
.flatten()
.filter_map(|(name, action, bindings)| {
let filtered = cx.read(|cx| {
if cx.has_global::<CommandPaletteFilter>() {
@ -162,13 +163,15 @@ impl PickerDelegate for CommandPaletteDelegate {
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
if !self.matches.is_empty() {
let window_id = cx.window_id();
let window = cx.window();
let focused_view_id = self.focused_view_id;
let action_ix = self.matches[self.selected_ix].candidate_id;
let action = self.actions.remove(action_ix).action;
cx.app_context()
.spawn(move |mut cx| async move {
cx.dispatch_action(window_id, focused_view_id, action.as_ref())
window
.dispatch_action(focused_view_id, action.as_ref(), &mut cx)
.ok_or_else(|| anyhow!("window was closed"))
})
.detach_and_log_err(cx);
}
@ -295,8 +298,9 @@ mod tests {
let app_state = init_test(cx);
let project = Project::test(app_state.fs.clone(), [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let editor = cx.add_view(window_id, |cx| {
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let editor = window.add_view(cx, |cx| {
let mut editor = Editor::single_line(None, cx);
editor.set_text("abc", cx);
editor

View File

@ -1,5 +1,5 @@
use gpui::{
anyhow,
anyhow::{self, anyhow},
elements::*,
geometry::vector::Vector2F,
keymap_matcher::KeymapContext,
@ -218,12 +218,14 @@ impl ContextMenu {
if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) {
match action {
ContextMenuItemAction::Action(action) => {
let window_id = cx.window_id();
let window = cx.window();
let view_id = self.parent_view_id;
let action = action.boxed_clone();
cx.app_context()
.spawn(|mut cx| async move {
cx.dispatch_action(window_id, view_id, action.as_ref())
window
.dispatch_action(view_id, action.as_ref(), &mut cx)
.ok_or_else(|| anyhow!("window was closed"))
})
.detach_and_log_err(cx);
}
@ -480,17 +482,19 @@ impl ContextMenu {
.on_down(MouseButton::Left, |_, _, _| {}) // Capture these events
.on_click(MouseButton::Left, move |_, menu, cx| {
menu.cancel(&Default::default(), cx);
let window_id = cx.window_id();
let window = cx.window();
match &action {
ContextMenuItemAction::Action(action) => {
let action = action.boxed_clone();
cx.app_context()
.spawn(|mut cx| async move {
cx.dispatch_action(
window_id,
view_id,
action.as_ref(),
)
window
.dispatch_action(
view_id,
action.as_ref(),
&mut cx,
)
.ok_or_else(|| anyhow!("window was closed"))
})
.detach_and_log_err(cx);
}

View File

@ -4,7 +4,7 @@ use gpui::{
geometry::rect::RectF,
platform::{WindowBounds, WindowKind, WindowOptions},
AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext,
ViewHandle,
WindowHandle,
};
use theme::ui::modal;
@ -18,43 +18,43 @@ const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
pub fn init(cx: &mut AppContext) {
if let Some(copilot) = Copilot::global(cx) {
let mut code_verification: Option<ViewHandle<CopilotCodeVerification>> = None;
let mut verification_window: Option<WindowHandle<CopilotCodeVerification>> = None;
cx.observe(&copilot, move |copilot, cx| {
let status = copilot.read(cx).status();
match &status {
crate::Status::SigningIn { prompt } => {
if let Some(code_verification_handle) = code_verification.as_mut() {
let window_id = code_verification_handle.window_id();
let updated = cx.update_window(window_id, |cx| {
code_verification_handle.update(cx, |code_verification, cx| {
code_verification.set_status(status.clone(), cx)
});
cx.activate_window();
});
if updated.is_none() {
code_verification = Some(create_copilot_auth_window(cx, &status));
if let Some(window) = verification_window.as_mut() {
let updated = window
.root(cx)
.map(|root| {
root.update(cx, |verification, cx| {
verification.set_status(status.clone(), cx);
cx.activate_window();
})
})
.is_some();
if !updated {
verification_window = Some(create_copilot_auth_window(cx, &status));
}
} else if let Some(_prompt) = prompt {
code_verification = Some(create_copilot_auth_window(cx, &status));
verification_window = Some(create_copilot_auth_window(cx, &status));
}
}
Status::Authorized | Status::Unauthorized => {
if let Some(code_verification) = code_verification.as_ref() {
let window_id = code_verification.window_id();
cx.update_window(window_id, |cx| {
code_verification.update(cx, |code_verification, cx| {
code_verification.set_status(status, cx)
if let Some(window) = verification_window.as_ref() {
if let Some(verification) = window.root(cx) {
verification.update(cx, |verification, cx| {
verification.set_status(status, cx);
cx.platform().activate(true);
cx.activate_window();
});
cx.platform().activate(true);
cx.activate_window();
});
}
}
}
_ => {
if let Some(code_verification) = code_verification.take() {
cx.update_window(code_verification.window_id(), |cx| cx.remove_window());
if let Some(code_verification) = verification_window.take() {
code_verification.update(cx, |cx| cx.remove_window());
}
}
}
@ -66,7 +66,7 @@ pub fn init(cx: &mut AppContext) {
fn create_copilot_auth_window(
cx: &mut AppContext,
status: &Status,
) -> ViewHandle<CopilotCodeVerification> {
) -> WindowHandle<CopilotCodeVerification> {
let window_size = theme::current(cx).copilot.modal.dimensions();
let window_options = WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
@ -78,10 +78,9 @@ fn create_copilot_auth_window(
is_movable: true,
screen: None,
};
let (_, view) = cx.add_window(window_options, |_cx| {
cx.add_window(window_options, |_cx| {
CopilotCodeVerification::new(status.clone())
});
view
})
}
pub struct CopilotCodeVerification {

View File

@ -140,7 +140,7 @@ impl View for CopilotButton {
})
.with_tooltip::<Self>(
0,
"GitHub Copilot".into(),
"GitHub Copilot",
None,
theme.tooltip.clone(),
cx,

View File

@ -855,7 +855,8 @@ mod tests {
let language_server_id = LanguageServerId(0);
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
// Create some diagnostics
project.update(cx, |project, cx| {
@ -942,7 +943,7 @@ mod tests {
});
// Open the project diagnostics view while there are already diagnostics.
let view = cx.add_view(window_id, |cx| {
let view = window.add_view(cx, |cx| {
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
});
@ -1248,9 +1249,10 @@ mod tests {
let server_id_1 = LanguageServerId(100);
let server_id_2 = LanguageServerId(101);
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let view = cx.add_view(window_id, |cx| {
let view = window.add_view(cx, |cx| {
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
});

View File

@ -173,7 +173,7 @@ impl View for DiagnosticIndicator {
})
.with_tooltip::<Summary>(
0,
"Project Diagnostics".to_string(),
"Project Diagnostics",
Some(Box::new(crate::Deploy)),
tooltip_style,
cx,

View File

@ -6,7 +6,7 @@ use gpui::{
geometry::{rect::RectF, vector::Vector2F},
platform::{CursorStyle, MouseButton},
scene::{MouseDown, MouseDrag},
AnyElement, Element, View, ViewContext, WeakViewHandle, WindowContext,
AnyElement, AnyWindowHandle, Element, View, ViewContext, WeakViewHandle, WindowContext,
};
const DEAD_ZONE: f32 = 4.;
@ -21,7 +21,7 @@ enum State<V: View> {
region: RectF,
},
Dragging {
window_id: usize,
window: AnyWindowHandle,
position: Vector2F,
region_offset: Vector2F,
region: RectF,
@ -49,14 +49,14 @@ impl<V: View> Clone for State<V> {
region,
},
State::Dragging {
window_id,
window,
position,
region_offset,
region,
payload,
render,
} => Self::Dragging {
window_id: window_id.clone(),
window: window.clone(),
position: position.clone(),
region_offset: region_offset.clone(),
region: region.clone(),
@ -87,16 +87,16 @@ impl<V: View> DragAndDrop<V> {
self.containers.insert(handle);
}
pub fn currently_dragged<T: Any>(&self, window_id: usize) -> Option<(Vector2F, Rc<T>)> {
pub fn currently_dragged<T: Any>(&self, window: AnyWindowHandle) -> Option<(Vector2F, Rc<T>)> {
self.currently_dragged.as_ref().and_then(|state| {
if let State::Dragging {
position,
payload,
window_id: window_dragged_from,
window: window_dragged_from,
..
} = state
{
if &window_id != window_dragged_from {
if &window != window_dragged_from {
return None;
}
@ -126,9 +126,9 @@ impl<V: View> DragAndDrop<V> {
cx: &mut WindowContext,
render: Rc<impl 'static + Fn(&T, &mut ViewContext<V>) -> AnyElement<V>>,
) {
let window_id = cx.window_id();
let window = cx.window();
cx.update_global(|this: &mut Self, cx| {
this.notify_containers_for_window(window_id, cx);
this.notify_containers_for_window(window, cx);
match this.currently_dragged.as_ref() {
Some(&State::Down {
@ -141,7 +141,7 @@ impl<V: View> DragAndDrop<V> {
}) => {
if (event.position - (region.origin() + region_offset)).length() > DEAD_ZONE {
this.currently_dragged = Some(State::Dragging {
window_id,
window,
region_offset,
region,
position: event.position,
@ -163,7 +163,7 @@ impl<V: View> DragAndDrop<V> {
..
}) => {
this.currently_dragged = Some(State::Dragging {
window_id,
window,
region_offset,
region,
position: event.position,
@ -188,14 +188,14 @@ impl<V: View> DragAndDrop<V> {
State::Down { .. } => None,
State::DeadZone { .. } => None,
State::Dragging {
window_id,
window,
region_offset,
position,
region,
payload,
render,
} => {
if cx.window_id() != window_id {
if cx.window() != window {
return None;
}
@ -260,27 +260,27 @@ impl<V: View> DragAndDrop<V> {
pub fn cancel_dragging<P: Any>(&mut self, cx: &mut WindowContext) {
if let Some(State::Dragging {
payload, window_id, ..
payload, window, ..
}) = &self.currently_dragged
{
if payload.is::<P>() {
let window_id = *window_id;
let window = *window;
self.currently_dragged = Some(State::Canceled);
self.notify_containers_for_window(window_id, cx);
self.notify_containers_for_window(window, cx);
}
}
}
fn finish_dragging(&mut self, cx: &mut WindowContext) {
if let Some(State::Dragging { window_id, .. }) = self.currently_dragged.take() {
self.notify_containers_for_window(window_id, cx);
if let Some(State::Dragging { window, .. }) = self.currently_dragged.take() {
self.notify_containers_for_window(window, cx);
}
}
fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut WindowContext) {
fn notify_containers_for_window(&mut self, window: AnyWindowHandle, cx: &mut WindowContext) {
self.containers.retain(|container| {
if let Some(container) = container.upgrade(cx) {
if container.window_id() == window_id {
if container.window() == window {
container.update(cx, |_, cx| cx.notify());
}
true

View File

@ -47,6 +47,7 @@ workspace = { path = "../workspace" }
aho-corasick = "0.7"
anyhow.workspace = true
convert_case = "0.6.0"
futures.workspace = true
indoc = "1.0.4"
itertools = "0.10"
@ -56,12 +57,12 @@ ordered-float.workspace = true
parking_lot.workspace = true
postage.workspace = true
pulldown-cmark = { version = "0.9.2", default-features = false }
rand.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
smallvec.workspace = true
smol.workspace = true
rand.workspace = true
tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-html = { workspace = true, optional = true }

View File

@ -397,7 +397,7 @@ impl InlayMap {
buffer_snapshot: MultiBufferSnapshot,
mut buffer_edits: Vec<text::Edit<usize>>,
) -> (InlaySnapshot, Vec<InlayEdit>) {
let mut snapshot = &mut self.snapshot;
let snapshot = &mut self.snapshot;
if buffer_edits.is_empty() {
if snapshot.buffer.trailing_excerpt_update_count()
@ -572,7 +572,6 @@ impl InlayMap {
})
.collect();
let buffer_snapshot = snapshot.buffer.clone();
drop(snapshot);
let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits);
(snapshot, edits)
}
@ -635,7 +634,6 @@ impl InlayMap {
}
log::info!("removing inlays: {:?}", to_remove);
drop(snapshot);
let (snapshot, edits) = self.splice(to_remove, to_insert);
(snapshot, edits)
}

View File

@ -28,6 +28,7 @@ use blink_manager::BlinkManager;
use client::{ClickhouseEvent, TelemetrySettings};
use clock::{Global, ReplicaId};
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing};
use copilot::Copilot;
pub use display_map::DisplayPoint;
use display_map::*;
@ -89,7 +90,7 @@ use std::{
cmp::{self, Ordering, Reverse},
mem,
num::NonZeroU32,
ops::{ControlFlow, Deref, DerefMut, Range},
ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
path::Path,
sync::Arc,
time::{Duration, Instant},
@ -231,6 +232,13 @@ actions!(
SortLinesCaseInsensitive,
ReverseLines,
ShuffleLines,
ConvertToUpperCase,
ConvertToLowerCase,
ConvertToTitleCase,
ConvertToSnakeCase,
ConvertToKebabCase,
ConvertToUpperCamelCase,
ConvertToLowerCamelCase,
Transpose,
Cut,
Copy,
@ -353,6 +361,13 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::sort_lines_case_insensitive);
cx.add_action(Editor::reverse_lines);
cx.add_action(Editor::shuffle_lines);
cx.add_action(Editor::convert_to_upper_case);
cx.add_action(Editor::convert_to_lower_case);
cx.add_action(Editor::convert_to_title_case);
cx.add_action(Editor::convert_to_snake_case);
cx.add_action(Editor::convert_to_kebab_case);
cx.add_action(Editor::convert_to_upper_camel_case);
cx.add_action(Editor::convert_to_lower_camel_case);
cx.add_action(Editor::delete_to_previous_word_start);
cx.add_action(Editor::delete_to_previous_subword_start);
cx.add_action(Editor::delete_to_next_word_end);
@ -543,6 +558,7 @@ pub struct Editor {
show_local_selections: bool,
mode: EditorMode,
show_gutter: bool,
show_wrap_guides: Option<bool>,
placeholder_text: Option<Arc<str>>,
highlighted_rows: Option<Range<u32>>,
#[allow(clippy::type_complexity)]
@ -1375,6 +1391,7 @@ impl Editor {
show_local_selections: true,
mode,
show_gutter: mode == EditorMode::Full,
show_wrap_guides: None,
placeholder_text: None,
highlighted_rows: None,
background_highlights: Default::default(),
@ -2706,7 +2723,7 @@ impl Editor {
.collect()
}
fn excerpt_visible_offsets(
pub fn excerpt_visible_offsets(
&self,
restrict_to_languages: Option<&HashSet<Arc<Language>>>,
cx: &mut ViewContext<'_, '_, Editor>,
@ -4219,7 +4236,7 @@ impl Editor {
_: &SortLinesCaseSensitive,
cx: &mut ViewContext<Self>,
) {
self.manipulate_lines(cx, |text| text.sort())
self.manipulate_lines(cx, |lines| lines.sort())
}
pub fn sort_lines_case_insensitive(
@ -4227,7 +4244,7 @@ impl Editor {
_: &SortLinesCaseInsensitive,
cx: &mut ViewContext<Self>,
) {
self.manipulate_lines(cx, |text| text.sort_by_key(|line| line.to_lowercase()))
self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase()))
}
pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext<Self>) {
@ -4265,19 +4282,19 @@ impl Editor {
let text = buffer
.text_for_range(start_point..end_point)
.collect::<String>();
let mut text = text.split("\n").collect_vec();
let mut lines = text.split("\n").collect_vec();
let text_len = text.len();
callback(&mut text);
let lines_len = lines.len();
callback(&mut lines);
// This is a current limitation with selections.
// If we wanted to support removing or adding lines, we'd need to fix the logic associated with selections.
debug_assert!(
text.len() == text_len,
lines.len() == lines_len,
"callback should not change the number of lines"
);
edits.push((start_point..end_point, text.join("\n")));
edits.push((start_point..end_point, lines.join("\n")));
let start_anchor = buffer.anchor_after(start_point);
let end_anchor = buffer.anchor_before(end_point);
@ -4304,6 +4321,97 @@ impl Editor {
});
}
pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_uppercase())
}
pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_lowercase())
}
pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_case(Case::Title))
}
pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_case(Case::Snake))
}
pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_case(Case::Kebab))
}
pub fn convert_to_upper_camel_case(
&mut self,
_: &ConvertToUpperCamelCase,
cx: &mut ViewContext<Self>,
) {
self.manipulate_text(cx, |text| text.to_case(Case::UpperCamel))
}
pub fn convert_to_lower_camel_case(
&mut self,
_: &ConvertToLowerCamelCase,
cx: &mut ViewContext<Self>,
) {
self.manipulate_text(cx, |text| text.to_case(Case::Camel))
}
fn manipulate_text<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
where
Fn: FnMut(&str) -> String,
{
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx).snapshot(cx);
let mut new_selections = Vec::new();
let mut edits = Vec::new();
let mut selection_adjustment = 0i32;
for selection in self.selections.all::<usize>(cx) {
let selection_is_empty = selection.is_empty();
let (start, end) = if selection_is_empty {
let word_range = movement::surrounding_word(
&display_map,
selection.start.to_display_point(&display_map),
);
let start = word_range.start.to_offset(&display_map, Bias::Left);
let end = word_range.end.to_offset(&display_map, Bias::Left);
(start, end)
} else {
(selection.start, selection.end)
};
let text = buffer.text_for_range(start..end).collect::<String>();
let old_length = text.len() as i32;
let text = callback(&text);
new_selections.push(Selection {
start: (start as i32 - selection_adjustment) as usize,
end: ((start + text.len()) as i32 - selection_adjustment) as usize,
goal: SelectionGoal::None,
..selection
});
selection_adjustment += old_length - text.len() as i32;
edits.push((start..end, text));
}
self.transact(cx, |this, cx| {
this.buffer.update(cx, |buffer, cx| {
buffer.edit(edits, None, cx);
});
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select(new_selections);
});
this.request_autoscroll(Autoscroll::fit(), cx);
});
}
pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;
@ -7187,6 +7295,10 @@ impl Editor {
pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
let mut wrap_guides = smallvec::smallvec![];
if self.show_wrap_guides == Some(false) {
return wrap_guides;
}
let settings = self.buffer.read(cx).settings_at(0, cx);
if settings.show_wrap_guides {
if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
@ -7244,6 +7356,11 @@ impl Editor {
cx.notify();
}
pub fn set_show_wrap_guides(&mut self, show_gutter: bool, cx: &mut ViewContext<Self>) {
self.show_wrap_guides = Some(show_gutter);
cx.notify();
}
pub fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext<Self>) {
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
@ -7432,6 +7549,78 @@ impl Editor {
results
}
pub fn background_highlight_row_ranges<T: 'static>(
&self,
search_range: Range<Anchor>,
display_snapshot: &DisplaySnapshot,
count: usize,
) -> Vec<RangeInclusive<DisplayPoint>> {
let mut results = Vec::new();
let buffer = &display_snapshot.buffer_snapshot;
let Some((_, ranges)) = self.background_highlights
.get(&TypeId::of::<T>()) else {
return vec![];
};
let start_ix = match ranges.binary_search_by(|probe| {
let cmp = probe.end.cmp(&search_range.start, buffer);
if cmp.is_gt() {
Ordering::Greater
} else {
Ordering::Less
}
}) {
Ok(i) | Err(i) => i,
};
let mut push_region = |start: Option<Point>, end: Option<Point>| {
if let (Some(start_display), Some(end_display)) = (start, end) {
results.push(
start_display.to_display_point(display_snapshot)
..=end_display.to_display_point(display_snapshot),
);
}
};
let mut start_row: Option<Point> = None;
let mut end_row: Option<Point> = None;
if ranges.len() > count {
return vec![];
}
for range in &ranges[start_ix..] {
if range.start.cmp(&search_range.end, buffer).is_ge() {
break;
}
let end = range.end.to_point(buffer);
if let Some(current_row) = &end_row {
if end.row == current_row.row {
continue;
}
}
let start = range.start.to_point(buffer);
if start_row.is_none() {
assert_eq!(end_row, None);
start_row = Some(start);
end_row = Some(end);
continue;
}
if let Some(current_end) = end_row.as_mut() {
if start.row > current_end.row + 1 {
push_region(start_row, end_row);
start_row = Some(start);
end_row = Some(end);
} else {
// Merge two hunks.
*current_end = end;
}
} else {
unreachable!();
}
}
// We might still have a hunk that was not rendered (if there was a search hit on the last line)
push_region(start_row, end_row);
results
}
pub fn highlight_text<T: 'static>(
&mut self,
ranges: Vec<Range<Anchor>>,
@ -8496,7 +8685,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
// We really need to rethink this ID system...
.with_tooltip::<BlockContextToolip>(
cx.block_id,
"Copy diagnostic message".to_string(),
"Copy diagnostic message",
None,
tooltip_style,
cx,

File diff suppressed because it is too large Load Diff

View File

@ -32,7 +32,7 @@ use gpui::{
platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
text_layout::{self, Line, RunStyle, TextLayoutCache},
AnyElement, Axis, Border, CursorRegion, Element, EventContext, FontCache, LayoutContext,
MouseRegion, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext,
MouseRegion, PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext,
};
use itertools::Itertools;
use json::json;
@ -508,13 +508,13 @@ impl EditorElement {
bounds: gutter_bounds,
background: Some(self.style.gutter_background),
border: Border::new(0., Color::transparent_black()),
corner_radius: 0.,
corner_radii: Default::default(),
});
scene.push_quad(Quad {
bounds: text_bounds,
background: Some(self.style.background),
border: Border::new(0., Color::transparent_black()),
corner_radius: 0.,
corner_radii: Default::default(),
});
if let EditorMode::Full = layout.mode {
@ -542,7 +542,7 @@ impl EditorElement {
bounds: RectF::new(origin, size),
background: Some(self.style.active_line_background),
border: Border::default(),
corner_radius: 0.,
corner_radii: Default::default(),
});
}
}
@ -562,12 +562,24 @@ impl EditorElement {
bounds: RectF::new(origin, size),
background: Some(self.style.highlighted_line_background),
border: Border::default(),
corner_radius: 0.,
corner_radii: Default::default(),
});
}
let scroll_left =
layout.position_map.snapshot.scroll_position().x() * layout.position_map.em_width;
for (wrap_position, active) in layout.wrap_guides.iter() {
let x = text_bounds.origin_x() + wrap_position + layout.position_map.em_width / 2.;
let x =
(text_bounds.origin_x() + wrap_position + layout.position_map.em_width / 2.)
- scroll_left;
if x < text_bounds.origin_x()
|| (layout.show_scrollbars && x > self.scrollbar_left(&bounds))
{
continue;
}
let color = if *active {
self.style.active_wrap_guide
} else {
@ -580,7 +592,7 @@ impl EditorElement {
),
background: Some(color),
border: Border::new(0., Color::transparent_black()),
corner_radius: 0.,
corner_radii: Default::default(),
});
}
}
@ -681,7 +693,7 @@ impl EditorElement {
bounds: highlight_bounds,
background: Some(diff_style.modified),
border: Border::new(0., Color::transparent_black()),
corner_radius: 1. * line_height,
corner_radii: (1. * line_height).into(),
});
continue;
@ -714,7 +726,7 @@ impl EditorElement {
bounds: highlight_bounds,
background: Some(diff_style.deleted),
border: Border::new(0., Color::transparent_black()),
corner_radius: 1. * line_height,
corner_radii: (1. * line_height).into(),
});
continue;
@ -736,7 +748,7 @@ impl EditorElement {
bounds: highlight_bounds,
background: Some(color),
border: Border::new(0., Color::transparent_black()),
corner_radius: diff_style.corner_radius * line_height,
corner_radii: (diff_style.corner_radius * line_height).into(),
});
}
}
@ -1056,6 +1068,10 @@ impl EditorElement {
scene.pop_layer();
}
fn scrollbar_left(&self, bounds: &RectF) -> f32 {
bounds.max_x() - self.style.theme.scrollbar.width
}
fn paint_scrollbar(
&mut self,
scene: &mut SceneBuilder,
@ -1074,7 +1090,7 @@ impl EditorElement {
let top = bounds.min_y();
let bottom = bounds.max_y();
let right = bounds.max_x();
let left = right - style.width;
let left = self.scrollbar_left(&bounds);
let row_range = &layout.scrollbar_row_range;
let max_row = layout.max_row as f32 + (row_range.end - row_range.start);
@ -1111,8 +1127,6 @@ impl EditorElement {
if layout.is_singleton && scrollbar_settings.selections {
let start_anchor = Anchor::min();
let end_anchor = Anchor::max();
let mut start_row = None;
let mut end_row = None;
let color = scrollbar_theme.selections;
let border = Border {
width: 1.,
@ -1123,54 +1137,32 @@ impl EditorElement {
bottom: false,
left: true,
};
let mut push_region = |start, end| {
if let (Some(start_display), Some(end_display)) = (start, end) {
let start_y = y_for_row(start_display as f32);
let mut end_y = y_for_row(end_display as f32);
if end_y - start_y < 1. {
end_y = start_y + 1.;
}
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
scene.push_quad(Quad {
bounds,
background: Some(color),
border,
corner_radius: style.thumb.corner_radius,
})
let mut push_region = |start: DisplayPoint, end: DisplayPoint| {
let start_y = y_for_row(start.row() as f32);
let mut end_y = y_for_row(end.row() as f32);
if end_y - start_y < 1. {
end_y = start_y + 1.;
}
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
scene.push_quad(Quad {
bounds,
background: Some(color),
border,
corner_radii: style.thumb.corner_radii.into(),
})
};
for (row, _) in &editor
.background_highlights_in_range_for::<crate::items::BufferSearchHighlights>(
let background_ranges = editor
.background_highlight_row_ranges::<crate::items::BufferSearchHighlights>(
start_anchor..end_anchor,
&layout.position_map.snapshot,
&theme,
)
{
let start_display = row.start;
let end_display = row.end;
if start_row.is_none() {
assert_eq!(end_row, None);
start_row = Some(start_display.row());
end_row = Some(end_display.row());
continue;
}
if let Some(current_end) = end_row.as_mut() {
if start_display.row() > *current_end + 1 {
push_region(start_row, end_row);
start_row = Some(start_display.row());
end_row = Some(end_display.row());
} else {
// Merge two hunks.
*current_end = end_display.row();
}
} else {
unreachable!();
}
50000,
);
for row in background_ranges {
let start = row.start();
let end = row.end();
push_region(*start, *end);
}
// We might still have a hunk that was not rendered (if there was a search hit on the last line)
push_region(start_row, end_row);
}
if layout.is_singleton && scrollbar_settings.git_diff {
@ -1217,7 +1209,7 @@ impl EditorElement {
bounds,
background: Some(color),
border,
corner_radius: style.thumb.corner_radius,
corner_radii: style.thumb.corner_radii.into(),
})
}
}
@ -1226,7 +1218,7 @@ impl EditorElement {
bounds: thumb_bounds,
border: style.thumb.border,
background: style.thumb.background_color,
corner_radius: style.thumb.corner_radius,
corner_radii: style.thumb.corner_radii.into(),
});
}
@ -2481,7 +2473,7 @@ impl Element<Editor> for EditorElement {
visible_bounds: RectF,
layout: &mut Self::LayoutState,
editor: &mut Editor,
cx: &mut ViewContext<Editor>,
cx: &mut PaintContext<Editor>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
scene.push_layer(Some(visible_bounds));
@ -2751,14 +2743,14 @@ impl Cursor {
bounds,
background: None,
border: Border::all(1., self.color),
corner_radius: 0.,
corner_radii: Default::default(),
});
} else {
scene.push_quad(Quad {
bounds,
background: Some(self.color),
border: Default::default(),
corner_radius: 0.,
corner_radii: Default::default(),
});
}
@ -2998,16 +2990,18 @@ mod tests {
use language::language_settings;
use log::info;
use std::{num::NonZeroU32, sync::Arc};
use util::test::{generate_marked_text, sample_text};
use util::test::sample_text;
#[gpui::test]
fn test_layout_line_numbers(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
});
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
})
.root(cx);
let element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let layouts = editor.update(cx, |editor, cx| {
@ -3023,10 +3017,12 @@ mod tests {
async fn test_vim_visual_selections(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
});
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
})
.root(cx);
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let (_, state) = editor.update(cx, |editor, cx| {
editor.cursor_shape = CursorShape::Block;
@ -3099,25 +3095,27 @@ mod tests {
// 13: bbbbbb
// 14: cccccc
// 15: dddddd
let (_, editor) = cx.add_window(|cx| {
let buffer = MultiBuffer::build_multi(
[
(
&(sample_text(8, 6, 'a') + "\n"),
vec![
Point::new(0, 0)..Point::new(3, 0),
Point::new(4, 0)..Point::new(7, 0),
],
),
(
&(sample_text(8, 6, 'a') + "\n"),
vec![Point::new(1, 0)..Point::new(3, 0)],
),
],
cx,
);
Editor::new(EditorMode::Full, buffer, None, None, cx)
});
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_multi(
[
(
&(sample_text(8, 6, 'a') + "\n"),
vec![
Point::new(0, 0)..Point::new(3, 0),
Point::new(4, 0)..Point::new(7, 0),
],
),
(
&(sample_text(8, 6, 'a') + "\n"),
vec![Point::new(1, 0)..Point::new(3, 0)],
),
],
cx,
);
Editor::new(EditorMode::Full, buffer, None, None, cx)
})
.root(cx);
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let (_, state) = editor.update(cx, |editor, cx| {
editor.cursor_shape = CursorShape::Block;
@ -3167,10 +3165,12 @@ mod tests {
fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple("", cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
});
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("", cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
})
.root(cx);
editor.update(cx, |editor, cx| {
editor.set_placeholder_text("hello", cx);
@ -3221,7 +3221,14 @@ mod tests {
let mut scene = SceneBuilder::new(1.0);
let bounds = RectF::new(Default::default(), size);
editor.update(cx, |editor, cx| {
element.paint(&mut scene, bounds, bounds, &mut state, editor, cx);
element.paint(
&mut scene,
bounds,
bounds,
&mut state,
editor,
&mut PaintContext::new(cx),
);
});
}
@ -3377,10 +3384,12 @@ mod tests {
info!(
"Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'"
);
let (_, editor) = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&input_text, cx);
Editor::new(editor_mode, buffer, None, None, cx)
});
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&input_text, cx);
Editor::new(editor_mode, buffer, None, None, cx)
})
.root(cx);
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let (_, layout_state) = editor.update(cx, |editor, cx| {

View File

@ -599,7 +599,7 @@ impl InfoPopover {
bounds,
background: Some(code_span_background_color),
border: Default::default(),
corner_radius: 2.0,
corner_radii: (2.0).into(),
});
}
},

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,10 @@ use std::{
path::{Path, PathBuf},
};
use text::Selection;
use util::{paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
use util::{
paths::{PathExt, FILE_ROW_COLUMN_DELIMITER},
ResultExt, TryFutureExt,
};
use workspace::item::{BreadcrumbText, FollowableItemHandle};
use workspace::{
item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
@ -546,9 +549,7 @@ impl Item for Editor {
.and_then(|f| f.as_local())?
.abs_path(cx);
let file_path = util::paths::compact(&file_path)
.to_string_lossy()
.to_string();
let file_path = file_path.compact().to_string_lossy().to_string();
Some(file_path.into())
}

View File

@ -13,7 +13,7 @@ use gpui::{
};
use language::{Bias, Point};
use util::ResultExt;
use workspace::{item::Item, WorkspaceId};
use workspace::WorkspaceId;
use crate::{
display_map::{DisplaySnapshot, ToDisplayPoint},
@ -333,9 +333,7 @@ impl Editor {
cx,
);
if !self.is_singleton(cx) {
self.refresh_inlays(InlayRefreshReason::NewLinesShown, cx);
}
self.refresh_inlays(InlayRefreshReason::NewLinesShown, cx);
}
pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {

View File

@ -69,7 +69,8 @@ impl<'a> EditorLspTestContext<'a> {
.insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
.await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
project
.update(cx, |project, cx| {
project.find_or_create_local_worktree("/root", true, cx)
@ -98,7 +99,7 @@ impl<'a> EditorLspTestContext<'a> {
Self {
cx: EditorTestContext {
cx,
window_id,
window: window.into(),
editor,
},
lsp,

View File

@ -3,7 +3,8 @@ use crate::{
};
use futures::Future;
use gpui::{
keymap_matcher::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle,
keymap_matcher::Keystroke, AnyWindowHandle, AppContext, ContextHandle, ModelContext,
ViewContext, ViewHandle,
};
use indoc::indoc;
use language::{Buffer, BufferSnapshot};
@ -21,7 +22,7 @@ use super::build_editor;
pub struct EditorTestContext<'a> {
pub cx: &'a mut gpui::TestAppContext,
pub window_id: usize,
pub window: AnyWindowHandle,
pub editor: ViewHandle<Editor>,
}
@ -32,16 +33,14 @@ impl<'a> EditorTestContext<'a> {
let buffer = project
.update(cx, |project, cx| project.create_buffer("", None, cx))
.unwrap();
let (window_id, editor) = cx.update(|cx| {
cx.add_window(Default::default(), |cx| {
cx.focus_self();
build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
})
let window = cx.add_window(|cx| {
cx.focus_self();
build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
});
let editor = window.root(cx);
Self {
cx,
window_id,
window: window.into(),
editor,
}
}
@ -113,7 +112,8 @@ impl<'a> EditorTestContext<'a> {
let keystroke_under_test_handle =
self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
let keystroke = Keystroke::parse(keystroke_text).unwrap();
self.cx.dispatch_keystroke(self.window_id, keystroke, false);
self.cx.dispatch_keystroke(self.window, keystroke, false);
keystroke_under_test_handle
}

View File

@ -66,7 +66,7 @@ impl View for DeployFeedbackButton {
})
.with_tooltip::<Self>(
0,
"Send Feedback".into(),
"Send Feedback",
Some(Box::new(GiveFeedback)),
theme.tooltip.clone(),
cx,

View File

@ -80,7 +80,7 @@ impl View for SubmitFeedbackButton {
.with_margin_left(theme.feedback.button_margin)
.with_tooltip::<Self>(
0,
"cmd-s".into(),
"cmd-s",
Some(Box::new(SubmitFeedback)),
theme.tooltip.clone(),
cx,

View File

@ -617,8 +617,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
cx.dispatch_action(window_id, Toggle);
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
cx.dispatch_action(window.into(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
finder
@ -631,8 +632,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window_id, SelectNext);
cx.dispatch_action(window_id, Confirm);
cx.dispatch_action(window.into(), SelectNext);
cx.dispatch_action(window.into(), Confirm);
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
.await;
@ -671,8 +672,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
cx.dispatch_action(window_id, Toggle);
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
cx.dispatch_action(window.into(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
let file_query = &first_file_name[..3];
@ -704,8 +706,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window_id, SelectNext);
cx.dispatch_action(window_id, Confirm);
cx.dispatch_action(window.into(), SelectNext);
cx.dispatch_action(window.into(), Confirm);
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
.await;
@ -754,8 +756,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
cx.dispatch_action(window_id, Toggle);
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
cx.dispatch_action(window.into(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
let file_query = &first_file_name[..3];
@ -787,8 +790,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window_id, SelectNext);
cx.dispatch_action(window_id, Confirm);
cx.dispatch_action(window.into(), SelectNext);
cx.dispatch_action(window.into(), Confirm);
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
.await;
@ -837,19 +840,23 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = cx.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let finder = cx
.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
),
cx,
),
cx,
)
});
)
})
.root(cx);
let query = test_path_like("hi");
finder
@ -931,19 +938,23 @@ mod tests {
cx,
)
.await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = cx.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let finder = cx
.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
),
cx,
),
cx,
)
});
)
})
.root(cx);
finder
.update(cx, |f, cx| {
f.delegate_mut().spawn_search(test_path_like("hi"), cx)
@ -967,19 +978,23 @@ mod tests {
cx,
)
.await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = cx.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let finder = cx
.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
),
cx,
),
cx,
)
});
)
})
.root(cx);
// Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file.
@ -1015,61 +1030,6 @@ mod tests {
finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 0));
}
#[gpui::test]
async fn test_multiple_matches_with_same_relative_path(cx: &mut TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/root",
json!({
"dir1": { "a.txt": "" },
"dir2": { "a.txt": "" }
}),
)
.await;
let project = Project::test(
app_state.fs.clone(),
["/root/dir1".as_ref(), "/root/dir2".as_ref()],
cx,
)
.await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = cx.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
),
cx,
)
});
// Run a search that matches two files with the same relative path.
finder
.update(cx, |f, cx| {
f.delegate_mut().spawn_search(test_path_like("a.t"), cx)
})
.await;
// Can switch between different matches with the same relative path.
finder.update(cx, |finder, cx| {
let delegate = finder.delegate_mut();
assert_eq!(delegate.matches.len(), 2);
assert_eq!(delegate.selected_index(), 0);
delegate.set_selected_index(1, cx);
assert_eq!(delegate.selected_index(), 1);
delegate.set_selected_index(0, cx);
assert_eq!(delegate.selected_index(), 0);
});
}
#[gpui::test]
async fn test_path_distance_ordering(cx: &mut TestAppContext) {
let app_state = init_test(cx);
@ -1089,7 +1049,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
@ -1103,18 +1065,20 @@ mod tests {
worktree_id,
path: Arc::from(Path::new("/root/dir2/b.txt")),
}));
let (_, finder) = cx.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
b_path,
Vec::new(),
let finder = cx
.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
b_path,
Vec::new(),
cx,
),
cx,
),
cx,
)
});
)
})
.root(cx);
finder
.update(cx, |f, cx| {
@ -1151,19 +1115,23 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = cx.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let finder = cx
.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
),
cx,
),
cx,
)
});
)
})
.root(cx);
finder
.update(cx, |f, cx| {
f.delegate_mut().spawn_search(test_path_like("dir"), cx)
@ -1198,7 +1166,8 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
@ -1216,7 +1185,7 @@ mod tests {
"fir",
1,
"first.rs",
window_id,
window.into(),
&workspace,
&deterministic,
cx,
@ -1231,7 +1200,7 @@ mod tests {
"sec",
1,
"second.rs",
window_id,
window.into(),
&workspace,
&deterministic,
cx,
@ -1253,7 +1222,7 @@ mod tests {
"thi",
1,
"third.rs",
window_id,
window.into(),
&workspace,
&deterministic,
cx,
@ -1285,7 +1254,7 @@ mod tests {
"sec",
1,
"second.rs",
window_id,
window.into(),
&workspace,
&deterministic,
cx,
@ -1324,7 +1293,7 @@ mod tests {
"thi",
1,
"third.rs",
window_id,
window.into(),
&workspace,
&deterministic,
cx,
@ -1404,7 +1373,8 @@ mod tests {
.detach();
deterministic.run_until_parked();
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1,);
@ -1439,7 +1409,7 @@ mod tests {
"sec",
1,
"second.rs",
window_id,
window.into(),
&workspace,
&deterministic,
cx,
@ -1461,7 +1431,7 @@ mod tests {
"fir",
1,
"first.rs",
window_id,
window.into(),
&workspace,
&deterministic,
cx,
@ -1493,12 +1463,12 @@ mod tests {
input: &str,
expected_matches: usize,
expected_editor_title: &str,
window_id: usize,
window: gpui::AnyWindowHandle,
workspace: &ViewHandle<Workspace>,
deterministic: &gpui::executor::Deterministic,
cx: &mut gpui::TestAppContext,
) -> Vec<FoundPath> {
cx.dispatch_action(window_id, Toggle);
cx.dispatch_action(window, Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
finder
.update(cx, |finder, cx| {
@ -1515,8 +1485,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window_id, SelectNext);
cx.dispatch_action(window_id, Confirm);
cx.dispatch_action(window, SelectNext);
cx.dispatch_action(window, Confirm);
deterministic.run_until_parked();
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())

View File

@ -135,7 +135,7 @@ impl Entity for GoToLine {
fn release(&mut self, cx: &mut AppContext) {
let scroll_position = self.prev_scroll_position.take();
cx.update_window(self.active_editor.window_id(), |cx| {
self.active_editor.window().update(cx, |cx| {
self.active_editor.update(cx, |editor, cx| {
editor.highlight_rows(None);
if let Some(scroll_position) = scroll_position {

View File

@ -22,6 +22,7 @@ sqlez = { path = "../sqlez" }
async-task = "4.0.3"
backtrace = { version = "0.3", optional = true }
ctor.workspace = true
derive_more.workspace = true
dhat = { version = "0.3", optional = true }
env_logger = { version = "0.9", optional = true }
etagere = "0.2"

View File

@ -0,0 +1,155 @@
use gpui::{
color::Color, geometry::rect::RectF, scene::Shadow, AnyElement, App, Element, Entity, Quad,
View,
};
use log::LevelFilter;
use pathfinder_geometry::vector::vec2f;
use simplelog::SimpleLogger;
fn main() {
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
App::new(()).unwrap().run(|cx| {
cx.platform().activate(true);
cx.add_window(Default::default(), |_| CornersView);
});
}
struct CornersView;
impl Entity for CornersView {
type Event = ();
}
impl View for CornersView {
fn ui_name() -> &'static str {
"CornersView"
}
fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> AnyElement<CornersView> {
CornersElement.into_any()
}
}
struct CornersElement;
impl<V: View> gpui::Element<V> for CornersElement {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: gpui::SizeConstraint,
_: &mut V,
_: &mut gpui::LayoutContext<V>,
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
(constraint.max, ())
}
fn paint(
&mut self,
scene: &mut gpui::SceneBuilder,
bounds: pathfinder_geometry::rect::RectF,
_: pathfinder_geometry::rect::RectF,
_: &mut Self::LayoutState,
_: &mut V,
_: &mut gpui::PaintContext<V>,
) -> Self::PaintState {
scene.push_quad(Quad {
bounds,
background: Some(Color::white()),
..Default::default()
});
scene.push_layer(None);
scene.push_quad(Quad {
bounds: RectF::new(vec2f(100., 100.), vec2f(100., 100.)),
background: Some(Color::red()),
border: Default::default(),
corner_radii: gpui::scene::CornerRadii {
top_left: 20.,
..Default::default()
},
});
scene.push_quad(Quad {
bounds: RectF::new(vec2f(200., 100.), vec2f(100., 100.)),
background: Some(Color::green()),
border: Default::default(),
corner_radii: gpui::scene::CornerRadii {
top_right: 20.,
..Default::default()
},
});
scene.push_quad(Quad {
bounds: RectF::new(vec2f(100., 200.), vec2f(100., 100.)),
background: Some(Color::blue()),
border: Default::default(),
corner_radii: gpui::scene::CornerRadii {
bottom_left: 20.,
..Default::default()
},
});
scene.push_quad(Quad {
bounds: RectF::new(vec2f(200., 200.), vec2f(100., 100.)),
background: Some(Color::yellow()),
border: Default::default(),
corner_radii: gpui::scene::CornerRadii {
bottom_right: 20.,
..Default::default()
},
});
scene.push_shadow(Shadow {
bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)),
corner_radii: gpui::scene::CornerRadii {
bottom_right: 20.,
..Default::default()
},
sigma: 20.0,
color: Color::black(),
});
scene.push_layer(None);
scene.push_quad(Quad {
bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)),
background: Some(Color::red()),
border: Default::default(),
corner_radii: gpui::scene::CornerRadii {
bottom_right: 20.,
..Default::default()
},
});
scene.pop_layer();
scene.pop_layer();
}
fn rect_for_text_range(
&self,
_: std::ops::Range<usize>,
_: pathfinder_geometry::rect::RectF,
_: pathfinder_geometry::rect::RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
_: &V,
_: &gpui::ViewContext<V>,
) -> Option<pathfinder_geometry::rect::RectF> {
unimplemented!()
}
fn debug(
&self,
_: pathfinder_geometry::rect::RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
_: &V,
_: &gpui::ViewContext<V>,
) -> serde_json::Value {
unimplemented!()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -77,9 +77,9 @@ pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform,
let cx = app.0.clone();
move |action| {
let mut cx = cx.borrow_mut();
if let Some(main_window_id) = cx.platform.main_window_id() {
let dispatched = cx
.update_window(main_window_id, |cx| {
if let Some(main_window) = cx.active_window() {
let dispatched = main_window
.update(&mut *cx, |cx| {
if let Some(view_id) = cx.focused_view_id() {
cx.dispatch_action(Some(view_id), action);
true

View File

@ -9,7 +9,7 @@ use collections::{hash_map::Entry, HashMap, HashSet};
#[cfg(any(test, feature = "test-support"))]
use crate::util::post_inc;
use crate::ElementStateId;
use crate::{AnyWindowHandle, ElementStateId};
lazy_static! {
static ref LEAK_BACKTRACE: bool =
@ -26,7 +26,7 @@ pub struct RefCounts {
entity_counts: HashMap<usize, usize>,
element_state_counts: HashMap<ElementStateId, ElementStateRefCount>,
dropped_models: HashSet<usize>,
dropped_views: HashSet<(usize, usize)>,
dropped_views: HashSet<(AnyWindowHandle, usize)>,
dropped_element_states: HashSet<ElementStateId>,
#[cfg(any(test, feature = "test-support"))]
@ -55,12 +55,12 @@ impl RefCounts {
}
}
pub fn inc_view(&mut self, window_id: usize, view_id: usize) {
pub fn inc_view(&mut self, window: AnyWindowHandle, view_id: usize) {
match self.entity_counts.entry(view_id) {
Entry::Occupied(mut entry) => *entry.get_mut() += 1,
Entry::Vacant(entry) => {
entry.insert(1);
self.dropped_views.remove(&(window_id, view_id));
self.dropped_views.remove(&(window, view_id));
}
}
}
@ -94,12 +94,12 @@ impl RefCounts {
}
}
pub fn dec_view(&mut self, window_id: usize, view_id: usize) {
pub fn dec_view(&mut self, window: AnyWindowHandle, view_id: usize) {
let count = self.entity_counts.get_mut(&view_id).unwrap();
*count -= 1;
if *count == 0 {
self.entity_counts.remove(&view_id);
self.dropped_views.insert((window_id, view_id));
self.dropped_views.insert((window, view_id));
}
}
@ -120,7 +120,7 @@ impl RefCounts {
&mut self,
) -> (
HashSet<usize>,
HashSet<(usize, usize)>,
HashSet<(AnyWindowHandle, usize)>,
HashSet<ElementStateId>,
) {
(

View File

@ -4,9 +4,9 @@ use crate::{
keymap_matcher::{Binding, Keystroke},
platform,
platform::{Event, InputHandler, KeyDownEvent, Platform},
Action, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache, Handle,
ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakHandle,
WindowContext,
Action, AnyWindowHandle, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache,
Handle, ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle,
WeakHandle, WindowContext, WindowHandle,
};
use collections::BTreeMap;
use futures::Future;
@ -60,7 +60,7 @@ impl TestAppContext {
RefCounts::new(leak_detector),
(),
);
cx.next_entity_id = first_entity_id;
cx.next_id = first_entity_id;
let cx = TestAppContext {
cx: Rc::new(RefCell::new(cx)),
foreground_platform,
@ -72,8 +72,8 @@ impl TestAppContext {
cx
}
pub fn dispatch_action<A: Action>(&mut self, window_id: usize, action: A) {
self.update_window(window_id, |window| {
pub fn dispatch_action<A: Action>(&mut self, window: AnyWindowHandle, action: A) {
self.update_window(window, |window| {
window.dispatch_action(window.focused_view_id(), &action);
})
.expect("window not found");
@ -81,10 +81,10 @@ impl TestAppContext {
pub fn available_actions(
&self,
window_id: usize,
window: AnyWindowHandle,
view_id: usize,
) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
self.read_window(window_id, |cx| cx.available_actions(view_id))
self.read_window(window, |cx| cx.available_actions(view_id))
.unwrap_or_default()
}
@ -92,33 +92,34 @@ impl TestAppContext {
self.update(|cx| cx.dispatch_global_action_any(&action));
}
pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
let handled = self
.cx
.borrow_mut()
.update_window(window_id, |cx| {
if cx.dispatch_keystroke(&keystroke) {
return true;
}
pub fn dispatch_keystroke(
&mut self,
window: AnyWindowHandle,
keystroke: Keystroke,
is_held: bool,
) {
let handled = window.update(self, |cx| {
if cx.dispatch_keystroke(&keystroke) {
return true;
}
if cx.dispatch_event(
Event::KeyDown(KeyDownEvent {
keystroke: keystroke.clone(),
is_held,
}),
false,
) {
return true;
}
if cx.dispatch_event(
Event::KeyDown(KeyDownEvent {
keystroke: keystroke.clone(),
is_held,
}),
false,
) {
return true;
}
false
})
.unwrap_or(false);
false
});
if !handled && !keystroke.cmd && !keystroke.ctrl {
WindowInputHandler {
app: self.cx.clone(),
window_id,
window,
}
.replace_text_in_range(None, &keystroke.key)
}
@ -126,18 +127,18 @@ impl TestAppContext {
pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
&self,
window_id: usize,
window: AnyWindowHandle,
callback: F,
) -> Option<T> {
self.cx.borrow().read_window(window_id, callback)
self.cx.borrow().read_window(window, callback)
}
pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self,
window_id: usize,
window: AnyWindowHandle,
callback: F,
) -> Option<T> {
self.cx.borrow_mut().update_window(window_id, callback)
self.cx.borrow_mut().update_window(window, callback)
}
pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
@ -148,26 +149,17 @@ impl TestAppContext {
self.cx.borrow_mut().add_model(build_model)
}
pub fn add_window<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
pub fn add_window<V, F>(&mut self, build_root_view: F) -> WindowHandle<V>
where
T: View,
F: FnOnce(&mut ViewContext<T>) -> T,
V: View,
F: FnOnce(&mut ViewContext<V>) -> V,
{
let (window_id, view) = self
let window = self
.cx
.borrow_mut()
.add_window(Default::default(), build_root_view);
self.simulate_window_activation(Some(window_id));
(window_id, view)
}
pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T>
where
T: View,
F: FnOnce(&mut ViewContext<T>) -> T,
{
self.update_window(window_id, |cx| cx.add_view(build_view))
.expect("window not found")
window.simulate_activation(self);
window
}
pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription
@ -190,8 +182,8 @@ impl TestAppContext {
self.cx.borrow_mut().subscribe_global(callback)
}
pub fn window_ids(&self) -> Vec<usize> {
self.cx.borrow().windows.keys().copied().collect()
pub fn windows(&self) -> Vec<AnyWindowHandle> {
self.cx.borrow().windows().collect()
}
pub fn remove_all_windows(&mut self) {
@ -261,76 +253,6 @@ impl TestAppContext {
self.foreground_platform.as_ref().did_prompt_for_new_path()
}
pub fn simulate_prompt_answer(&self, window_id: usize, answer: usize) {
use postage::prelude::Sink as _;
let mut done_tx = self
.platform_window_mut(window_id)
.pending_prompts
.borrow_mut()
.pop_front()
.expect("prompt was not called");
done_tx.try_send(answer).ok();
}
pub fn has_pending_prompt(&self, window_id: usize) -> bool {
let window = self.platform_window_mut(window_id);
let prompts = window.pending_prompts.borrow_mut();
!prompts.is_empty()
}
pub fn current_window_title(&self, window_id: usize) -> Option<String> {
self.platform_window_mut(window_id).title.clone()
}
pub fn simulate_window_close(&self, window_id: usize) -> bool {
let handler = self
.platform_window_mut(window_id)
.should_close_handler
.take();
if let Some(mut handler) = handler {
let should_close = handler();
self.platform_window_mut(window_id).should_close_handler = Some(handler);
should_close
} else {
false
}
}
pub fn simulate_window_resize(&self, window_id: usize, size: Vector2F) {
let mut window = self.platform_window_mut(window_id);
window.size = size;
let mut handlers = mem::take(&mut window.resize_handlers);
drop(window);
for handler in &mut handlers {
handler();
}
self.platform_window_mut(window_id).resize_handlers = handlers;
}
pub fn simulate_window_activation(&self, to_activate: Option<usize>) {
self.cx.borrow_mut().update(|cx| {
let other_window_ids = cx
.windows
.keys()
.filter(|window_id| Some(**window_id) != to_activate)
.copied()
.collect::<Vec<_>>();
for window_id in other_window_ids {
cx.window_changed_active_status(window_id, false)
}
if let Some(to_activate) = to_activate {
cx.window_changed_active_status(to_activate, true)
}
});
}
pub fn is_window_edited(&self, window_id: usize) -> bool {
self.platform_window_mut(window_id).edited
}
pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
self.cx.borrow().leak_detector()
}
@ -351,18 +273,6 @@ impl TestAppContext {
self.assert_dropped(weak);
}
fn platform_window_mut(&self, window_id: usize) -> std::cell::RefMut<platform::test::Window> {
std::cell::RefMut::map(self.cx.borrow_mut(), |state| {
let window = state.windows.get_mut(&window_id).unwrap();
let test_window = window
.platform_window
.as_any_mut()
.downcast_mut::<platform::test::Window>()
.unwrap();
test_window
})
}
pub fn set_condition_duration(&mut self, duration: Option<Duration>) {
self.condition_duration = duration;
}
@ -405,19 +315,39 @@ impl BorrowAppContext for TestAppContext {
}
impl BorrowWindowContext for TestAppContext {
fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
type Result<T> = T;
fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
self.cx
.borrow()
.read_window(window_id, f)
.read_window(window, f)
.expect("window was closed")
}
fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&WindowContext) -> Option<T>,
{
BorrowWindowContext::read_window(self, window, f)
}
fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self,
window: AnyWindowHandle,
f: F,
) -> T {
self.cx
.borrow_mut()
.update_window(window_id, f)
.update_window(window, f)
.expect("window was closed")
}
fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&mut WindowContext) -> Option<T>,
{
BorrowWindowContext::update_window(self, window, f)
}
}
impl<T: Entity> ModelHandle<T> {
@ -532,6 +462,71 @@ impl<T: Entity> ModelHandle<T> {
}
}
impl AnyWindowHandle {
pub fn has_pending_prompt(&self, cx: &mut TestAppContext) -> bool {
let window = self.platform_window_mut(cx);
let prompts = window.pending_prompts.borrow_mut();
!prompts.is_empty()
}
pub fn current_title(&self, cx: &mut TestAppContext) -> Option<String> {
self.platform_window_mut(cx).title.clone()
}
pub fn simulate_close(&self, cx: &mut TestAppContext) -> bool {
let handler = self.platform_window_mut(cx).should_close_handler.take();
if let Some(mut handler) = handler {
let should_close = handler();
self.platform_window_mut(cx).should_close_handler = Some(handler);
should_close
} else {
false
}
}
pub fn simulate_resize(&self, size: Vector2F, cx: &mut TestAppContext) {
let mut window = self.platform_window_mut(cx);
window.size = size;
let mut handlers = mem::take(&mut window.resize_handlers);
drop(window);
for handler in &mut handlers {
handler();
}
self.platform_window_mut(cx).resize_handlers = handlers;
}
pub fn is_edited(&self, cx: &mut TestAppContext) -> bool {
self.platform_window_mut(cx).edited
}
pub fn simulate_prompt_answer(&self, answer: usize, cx: &mut TestAppContext) {
use postage::prelude::Sink as _;
let mut done_tx = self
.platform_window_mut(cx)
.pending_prompts
.borrow_mut()
.pop_front()
.expect("prompt was not called");
done_tx.try_send(answer).ok();
}
fn platform_window_mut<'a>(
&self,
cx: &'a mut TestAppContext,
) -> std::cell::RefMut<'a, platform::test::Window> {
std::cell::RefMut::map(cx.cx.borrow_mut(), |state| {
let window = state.windows.get_mut(&self).unwrap();
let test_window = window
.platform_window
.as_any_mut()
.downcast_mut::<platform::test::Window>()
.unwrap();
test_window
})
}
}
impl<T: View> ViewHandle<T> {
pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
use postage::prelude::{Sink as _, Stream as _};

View File

@ -13,9 +13,10 @@ use crate::{
},
text_layout::TextLayoutCache,
util::post_inc,
Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect,
Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, SceneBuilder, Subscription,
View, ViewContext, ViewHandle, WindowInvalidation,
Action, AnyView, AnyViewHandle, AnyWindowHandle, AppContext, BorrowAppContext,
BorrowWindowContext, Effect, Element, Entity, Handle, LayoutContext, MouseRegion,
MouseRegionId, PaintContext, SceneBuilder, Subscription, View, ViewContext, ViewHandle,
WindowInvalidation,
};
use anyhow::{anyhow, bail, Result};
use collections::{HashMap, HashSet};
@ -51,8 +52,8 @@ pub struct Window {
cursor_regions: Vec<CursorRegion>,
mouse_regions: Vec<(MouseRegion, usize)>,
last_mouse_moved_event: Option<Event>,
pub(crate) hovered_region_ids: HashSet<MouseRegionId>,
pub(crate) clicked_region_ids: HashSet<MouseRegionId>,
pub(crate) hovered_region_ids: Vec<MouseRegionId>,
pub(crate) clicked_region_ids: Vec<MouseRegionId>,
pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>,
mouse_position: Vector2F,
text_layout_cache: TextLayoutCache,
@ -60,7 +61,7 @@ pub struct Window {
impl Window {
pub fn new<V, F>(
window_id: usize,
handle: AnyWindowHandle,
platform_window: Box<dyn platform::Window>,
cx: &mut AppContext,
build_view: F,
@ -92,7 +93,7 @@ impl Window {
appearance,
};
let mut window_context = WindowContext::mutable(cx, &mut window, window_id);
let mut window_context = WindowContext::mutable(cx, &mut window, handle);
let root_view = window_context.add_view(|cx| build_view(cx));
if let Some(invalidation) = window_context.window.invalidation.take() {
window_context.invalidate(invalidation, appearance);
@ -113,7 +114,7 @@ impl Window {
pub struct WindowContext<'a> {
pub(crate) app_context: Reference<'a, AppContext>,
pub(crate) window: Reference<'a, Window>,
pub(crate) window_id: usize,
pub(crate) window_handle: AnyWindowHandle,
pub(crate) removed: bool,
}
@ -142,42 +143,66 @@ impl BorrowAppContext for WindowContext<'_> {
}
impl BorrowWindowContext for WindowContext<'_> {
fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
if self.window_id == window_id {
type Result<T> = T;
fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, handle: AnyWindowHandle, f: F) -> T {
if self.window_handle == handle {
f(self)
} else {
panic!("read_with called with id of window that does not belong to this context")
}
}
fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
if self.window_id == window_id {
fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&WindowContext) -> Option<T>,
{
BorrowWindowContext::read_window(self, window, f)
}
fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self,
handle: AnyWindowHandle,
f: F,
) -> T {
if self.window_handle == handle {
f(self)
} else {
panic!("update called with id of window that does not belong to this context")
}
}
fn update_window_optional<T, F>(&mut self, handle: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&mut WindowContext) -> Option<T>,
{
BorrowWindowContext::update_window(self, handle, f)
}
}
impl<'a> WindowContext<'a> {
pub fn mutable(
app_context: &'a mut AppContext,
window: &'a mut Window,
window_id: usize,
handle: AnyWindowHandle,
) -> Self {
Self {
app_context: Reference::Mutable(app_context),
window: Reference::Mutable(window),
window_id,
window_handle: handle,
removed: false,
}
}
pub fn immutable(app_context: &'a AppContext, window: &'a Window, window_id: usize) -> Self {
pub fn immutable(
app_context: &'a AppContext,
window: &'a Window,
handle: AnyWindowHandle,
) -> Self {
Self {
app_context: Reference::Immutable(app_context),
window: Reference::Immutable(window),
window_id,
window_handle: handle,
removed: false,
}
}
@ -186,8 +211,8 @@ impl<'a> WindowContext<'a> {
self.removed = true;
}
pub fn window_id(&self) -> usize {
self.window_id
pub fn window(&self) -> AnyWindowHandle {
self.window_handle
}
pub fn app_context(&mut self) -> &mut AppContext {
@ -210,10 +235,10 @@ impl<'a> WindowContext<'a> {
where
F: FnOnce(&mut dyn AnyView, &mut Self) -> T,
{
let window_id = self.window_id;
let mut view = self.views.remove(&(window_id, view_id))?;
let handle = self.window_handle;
let mut view = self.views.remove(&(handle, view_id))?;
let result = f(view.as_mut(), self);
self.views.insert((window_id, view_id), view);
self.views.insert((handle, view_id), view);
Some(result)
}
@ -238,9 +263,9 @@ impl<'a> WindowContext<'a> {
}
pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut WindowContext)) {
let window_id = self.window_id;
let handle = self.window_handle;
self.app_context.defer(move |cx| {
cx.update_window(window_id, |cx| callback(cx));
cx.update_window(handle, |cx| callback(cx));
})
}
@ -280,10 +305,10 @@ impl<'a> WindowContext<'a> {
H: Handle<E>,
F: 'static + FnMut(H, &E::Event, &mut WindowContext) -> bool,
{
let window_id = self.window_id;
let window_handle = self.window_handle;
self.app_context
.subscribe_internal(handle, move |emitter, event, cx| {
cx.update_window(window_id, |cx| callback(emitter, event, cx))
cx.update_window(window_handle, |cx| callback(emitter, event, cx))
.unwrap_or(false)
})
}
@ -292,17 +317,17 @@ impl<'a> WindowContext<'a> {
where
F: 'static + FnMut(bool, &mut WindowContext) -> bool,
{
let window_id = self.window_id;
let handle = self.window_handle;
let subscription_id = post_inc(&mut self.next_subscription_id);
self.pending_effects
.push_back(Effect::WindowActivationObservation {
window_id,
window: handle,
subscription_id,
callback: Box::new(callback),
});
Subscription::WindowActivationObservation(
self.window_activation_observations
.subscribe(window_id, subscription_id),
.subscribe(handle, subscription_id),
)
}
@ -310,17 +335,17 @@ impl<'a> WindowContext<'a> {
where
F: 'static + FnMut(bool, &mut WindowContext) -> bool,
{
let window_id = self.window_id;
let window = self.window_handle;
let subscription_id = post_inc(&mut self.next_subscription_id);
self.pending_effects
.push_back(Effect::WindowFullscreenObservation {
window_id,
window,
subscription_id,
callback: Box::new(callback),
});
Subscription::WindowActivationObservation(
self.window_activation_observations
.subscribe(window_id, subscription_id),
.subscribe(window, subscription_id),
)
}
@ -328,17 +353,17 @@ impl<'a> WindowContext<'a> {
where
F: 'static + FnMut(WindowBounds, Uuid, &mut WindowContext) -> bool,
{
let window_id = self.window_id;
let window = self.window_handle;
let subscription_id = post_inc(&mut self.next_subscription_id);
self.pending_effects
.push_back(Effect::WindowBoundsObservation {
window_id,
window,
subscription_id,
callback: Box::new(callback),
});
Subscription::WindowBoundsObservation(
self.window_bounds_observations
.subscribe(window_id, subscription_id),
.subscribe(window, subscription_id),
)
}
@ -347,13 +372,13 @@ impl<'a> WindowContext<'a> {
F: 'static
+ FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut WindowContext) -> bool,
{
let window_id = self.window_id;
let window = self.window_handle;
let subscription_id = post_inc(&mut self.next_subscription_id);
self.keystroke_observations
.add_callback(window_id, subscription_id, Box::new(callback));
.add_callback(window, subscription_id, Box::new(callback));
Subscription::KeystrokeObservation(
self.keystroke_observations
.subscribe(window_id, subscription_id),
.subscribe(window, subscription_id),
)
}
@ -361,11 +386,11 @@ impl<'a> WindowContext<'a> {
&self,
view_id: usize,
) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
let window_id = self.window_id;
let handle = self.window_handle;
let mut contexts = Vec::new();
let mut handler_depths_by_action_id = HashMap::<TypeId, usize>::default();
for (depth, view_id) in self.ancestors(view_id).enumerate() {
if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
if let Some(view_metadata) = self.views_metadata.get(&(handle, view_id)) {
contexts.push(view_metadata.keymap_context.clone());
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
handler_depths_by_action_id
@ -410,13 +435,13 @@ impl<'a> WindowContext<'a> {
}
pub(crate) fn dispatch_keystroke(&mut self, keystroke: &Keystroke) -> bool {
let window_id = self.window_id;
let handle = self.window_handle;
if let Some(focused_view_id) = self.focused_view_id() {
let dispatch_path = self
.ancestors(focused_view_id)
.filter_map(|view_id| {
self.views_metadata
.get(&(window_id, view_id))
.get(&(handle, view_id))
.map(|view| (view_id, view.keymap_context.clone()))
})
.collect();
@ -441,15 +466,10 @@ impl<'a> WindowContext<'a> {
}
};
self.keystroke(
window_id,
keystroke.clone(),
handled_by,
match_result.clone(),
);
self.keystroke(handle, keystroke.clone(), handled_by, match_result.clone());
keystroke_handled
} else {
self.keystroke(window_id, keystroke.clone(), None, MatchResult::None);
self.keystroke(handle, keystroke.clone(), None, MatchResult::None);
false
}
}
@ -457,7 +477,7 @@ impl<'a> WindowContext<'a> {
pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
let mut mouse_events = SmallVec::<[_; 2]>::new();
let mut notified_views: HashSet<usize> = Default::default();
let window_id = self.window_id;
let handle = self.window_handle;
// 1. Handle platform event. Keyboard events get dispatched immediately, while mouse events
// get mapped into the mouse-specific MouseEvent type.
@ -658,6 +678,7 @@ impl<'a> WindowContext<'a> {
let mut highest_z_index = None;
let mouse_position = self.window.mouse_position.clone();
let window = &mut *self.window;
let prev_hovered_regions = mem::take(&mut window.hovered_region_ids);
for (region, z_index) in window.mouse_regions.iter().rev() {
// Allow mouse regions to appear transparent to hovers
if !region.hoverable {
@ -676,7 +697,11 @@ impl<'a> WindowContext<'a> {
// highest_z_index is set.
if contains_mouse && z_index == highest_z_index.unwrap() {
//Ensure that hover entrance events aren't sent twice
if window.hovered_region_ids.insert(region.id()) {
if let Err(ix) = window.hovered_region_ids.binary_search(&region.id()) {
window.hovered_region_ids.insert(ix, region.id());
}
// window.hovered_region_ids.insert(region.id());
if !prev_hovered_regions.contains(&region.id()) {
valid_regions.push(region.clone());
if region.notify_on_hover {
notified_views.insert(region.id().view_id());
@ -684,7 +709,7 @@ impl<'a> WindowContext<'a> {
}
} else {
// Ensure that hover exit events aren't sent twice
if window.hovered_region_ids.remove(&region.id()) {
if prev_hovered_regions.contains(&region.id()) {
valid_regions.push(region.clone());
if region.notify_on_hover {
notified_views.insert(region.id().view_id());
@ -821,19 +846,19 @@ impl<'a> WindowContext<'a> {
}
for view_id in notified_views {
self.notify_view(window_id, view_id);
self.notify_view(handle, view_id);
}
any_event_handled
}
pub(crate) fn dispatch_key_down(&mut self, event: &KeyDownEvent) -> bool {
let window_id = self.window_id;
let handle = self.window_handle;
if let Some(focused_view_id) = self.window.focused_view_id {
for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
if let Some(mut view) = self.views.remove(&(handle, view_id)) {
let handled = view.key_down(event, self, view_id);
self.views.insert((window_id, view_id), view);
self.views.insert((handle, view_id), view);
if handled {
return true;
}
@ -847,12 +872,12 @@ impl<'a> WindowContext<'a> {
}
pub(crate) fn dispatch_key_up(&mut self, event: &KeyUpEvent) -> bool {
let window_id = self.window_id;
let handle = self.window_handle;
if let Some(focused_view_id) = self.window.focused_view_id {
for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
if let Some(mut view) = self.views.remove(&(handle, view_id)) {
let handled = view.key_up(event, self, view_id);
self.views.insert((window_id, view_id), view);
self.views.insert((handle, view_id), view);
if handled {
return true;
}
@ -866,12 +891,12 @@ impl<'a> WindowContext<'a> {
}
pub(crate) fn dispatch_modifiers_changed(&mut self, event: &ModifiersChangedEvent) -> bool {
let window_id = self.window_id;
let handle = self.window_handle;
if let Some(focused_view_id) = self.window.focused_view_id {
for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
if let Some(mut view) = self.views.remove(&(handle, view_id)) {
let handled = view.modifiers_changed(event, self, view_id);
self.views.insert((window_id, view_id), view);
self.views.insert((handle, view_id), view);
if handled {
return true;
}
@ -906,14 +931,14 @@ impl<'a> WindowContext<'a> {
}
pub fn render_view(&mut self, params: RenderParams) -> Result<Box<dyn AnyRootElement>> {
let window_id = self.window_id;
let handle = self.window_handle;
let view_id = params.view_id;
let mut view = self
.views
.remove(&(window_id, view_id))
.remove(&(handle, view_id))
.ok_or_else(|| anyhow!("view not found"))?;
let element = view.render(self, view_id);
self.views.insert((window_id, view_id), view);
self.views.insert((handle, view_id), view);
Ok(element)
}
@ -941,9 +966,9 @@ impl<'a> WindowContext<'a> {
} else if old_parent_id == new_parent_id {
current_view_id = *old_parent_id.unwrap();
} else {
let window_id = self.window_id;
let handle = self.window_handle;
for view_id_to_notify in view_ids_to_notify {
self.notify_view(window_id, view_id_to_notify);
self.notify_view(handle, view_id_to_notify);
}
break;
}
@ -1111,7 +1136,7 @@ impl<'a> WindowContext<'a> {
}
pub fn focus(&mut self, view_id: Option<usize>) {
self.app_context.focus(self.window_id, view_id);
self.app_context.focus(self.window_handle, view_id);
}
pub fn window_bounds(&self) -> WindowBounds {
@ -1151,17 +1176,6 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.prompt(level, msg, answers)
}
pub fn replace_root_view<V, F>(&mut self, build_root_view: F) -> ViewHandle<V>
where
V: View,
F: FnOnce(&mut ViewContext<V>) -> V,
{
let root_view = self.add_view(|cx| build_root_view(cx));
self.window.root_view = Some(root_view.clone().into_any());
self.window.focused_view_id = Some(root_view.id());
root_view
}
pub fn add_view<T, F>(&mut self, build_view: F) -> ViewHandle<T>
where
T: View,
@ -1175,26 +1189,26 @@ impl<'a> WindowContext<'a> {
T: View,
F: FnOnce(&mut ViewContext<T>) -> Option<T>,
{
let window_id = self.window_id;
let view_id = post_inc(&mut self.next_entity_id);
let handle = self.window_handle;
let view_id = post_inc(&mut self.next_id);
let mut cx = ViewContext::mutable(self, view_id);
let handle = if let Some(view) = build_view(&mut cx) {
let mut keymap_context = KeymapContext::default();
view.update_keymap_context(&mut keymap_context, cx.app_context());
self.views_metadata.insert(
(window_id, view_id),
(handle, view_id),
ViewMetadata {
type_id: TypeId::of::<T>(),
keymap_context,
},
);
self.views.insert((window_id, view_id), Box::new(view));
self.views.insert((handle, view_id), Box::new(view));
self.window
.invalidation
.get_or_insert_with(Default::default)
.updated
.insert(view_id);
Some(ViewHandle::new(window_id, view_id, &self.ref_counts))
Some(ViewHandle::new(handle, view_id, &self.ref_counts))
} else {
None
};
@ -1371,7 +1385,7 @@ pub struct ChildView {
impl ChildView {
pub fn new(view: &AnyViewHandle, cx: &AppContext) -> Self {
let view_name = cx.view_ui_name(view.window_id(), view.id()).unwrap();
let view_name = cx.view_ui_name(view.window, view.id()).unwrap();
Self {
view_id: view.id(),
view_name,
@ -1420,7 +1434,7 @@ impl<V: View> Element<V> for ChildView {
visible_bounds: RectF,
_: &mut Self::LayoutState,
_: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) {
if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
rendered_view

View File

@ -2,11 +2,11 @@ use std::{cell::RefCell, ops::Range, rc::Rc};
use pathfinder_geometry::rect::RectF;
use crate::{platform::InputHandler, window::WindowContext, AnyView, AppContext};
use crate::{platform::InputHandler, window::WindowContext, AnyView, AnyWindowHandle, AppContext};
pub struct WindowInputHandler {
pub app: Rc<RefCell<AppContext>>,
pub window_id: usize,
pub window: AnyWindowHandle,
}
impl WindowInputHandler {
@ -21,13 +21,12 @@ impl WindowInputHandler {
//
// See https://github.com/zed-industries/community/issues/444
let mut app = self.app.try_borrow_mut().ok()?;
app.update_window(self.window_id, |cx| {
self.window.update_optional(&mut *app, |cx| {
let view_id = cx.window.focused_view_id?;
let view = cx.views.get(&(self.window_id, view_id))?;
let view = cx.views.get(&(self.window, view_id))?;
let result = f(view.as_ref(), &cx);
Some(result)
})
.flatten()
}
fn update_focused_view<T, F>(&mut self, f: F) -> Option<T>
@ -35,11 +34,12 @@ impl WindowInputHandler {
F: FnOnce(&mut dyn AnyView, &mut WindowContext, usize) -> T,
{
let mut app = self.app.try_borrow_mut().ok()?;
app.update_window(self.window_id, |cx| {
let view_id = cx.window.focused_view_id?;
cx.update_any_view(view_id, |view, cx| f(view, cx, view_id))
})
.flatten()
self.window
.update(&mut *app, |cx| {
let view_id = cx.window.focused_view_id?;
cx.update_any_view(view_id, |view, cx| f(view, cx, view_id))
})
.flatten()
}
}
@ -83,9 +83,8 @@ impl InputHandler for WindowInputHandler {
}
fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
self.app
.borrow()
.read_window(self.window_id, |cx| cx.rect_for_text_range(range_utf16))
.flatten()
self.window.read_optional_with(&*self.app.borrow(), |cx| {
cx.rect_for_text_range(range_utf16)
})
}
}

View File

@ -33,8 +33,8 @@ use crate::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json, Action, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle,
WindowContext,
json, Action, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext,
WeakViewHandle, WindowContext,
};
use anyhow::{anyhow, Result};
use collections::HashMap;
@ -61,7 +61,7 @@ pub trait Element<V: View>: 'static {
visible_bounds: RectF,
layout: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState;
fn rect_for_text_range(
@ -170,7 +170,7 @@ pub trait Element<V: View>: 'static {
fn with_tooltip<Tag: 'static>(
self,
id: usize,
text: String,
text: impl Into<Cow<'static, str>>,
action: Option<Box<dyn Action>>,
style: TooltipStyle,
cx: &mut ViewContext<V>,
@ -178,7 +178,7 @@ pub trait Element<V: View>: 'static {
where
Self: 'static + Sized,
{
Tooltip::new::<Tag, V>(id, text, action, style, self.into_any(), cx)
Tooltip::new::<Tag>(id, text, action, style, self.into_any(), cx)
}
fn resizable(
@ -201,6 +201,10 @@ pub trait Element<V: View>: 'static {
}
}
pub trait RenderElement {
fn render<V: View>(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
}
trait AnyElementState<V: View> {
fn layout(
&mut self,
@ -298,7 +302,14 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
mut layout,
} => {
let bounds = RectF::new(origin, size);
let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx);
let paint = element.paint(
scene,
bounds,
visible_bounds,
&mut layout,
view,
&mut PaintContext::new(cx),
);
ElementState::PostPaint {
element,
constraint,
@ -316,7 +327,14 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
..
} => {
let bounds = RectF::new(origin, bounds.size());
let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx);
let paint = element.paint(
scene,
bounds,
visible_bounds,
&mut layout,
view,
&mut PaintContext::new(cx),
);
ElementState::PostPaint {
element,
constraint,
@ -513,7 +531,7 @@ impl<V: View> Element<V> for AnyElement<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState {
self.paint(scene, bounds.origin(), visible_bounds, view, cx);
}

View File

@ -1,6 +1,7 @@
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
};
use json::ToJson;
@ -69,7 +70,7 @@ impl<V: View> Element<V> for Align<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState {
let my_center = bounds.size() / 2.;
let my_target = my_center + my_center * self.alignment;

View File

@ -3,7 +3,7 @@ use std::marker::PhantomData;
use super::Element;
use crate::{
json::{self, json},
SceneBuilder, View, ViewContext,
PaintContext, SceneBuilder, View, ViewContext,
};
use json::ToJson;
use pathfinder_geometry::{
@ -56,7 +56,7 @@ where
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState {
self.0(scene, bounds, visible_bounds, view, cx)
}

View File

@ -4,7 +4,8 @@ use pathfinder_geometry::{rect::RectF, vector::Vector2F};
use serde_json::json;
use crate::{
json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
};
pub struct Clipped<V: View> {
@ -37,7 +38,7 @@ impl<V: View> Element<V> for Clipped<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState {
scene.paint_layer(Some(bounds), |scene| {
self.child

View File

@ -5,7 +5,8 @@ use serde_json::json;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
};
pub struct ConstrainedBox<V: View> {
@ -156,7 +157,7 @@ impl<V: View> Element<V> for ConstrainedBox<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState {
scene.paint_layer(Some(visible_bounds), |scene| {
self.child

View File

@ -9,8 +9,9 @@ use crate::{
},
json::ToJson,
platform::CursorStyle,
scene::{self, Border, CursorRegion, Quad},
AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
scene::{self, Border, CornerRadii, CursorRegion, Quad},
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -29,7 +30,8 @@ pub struct ContainerStyle {
#[serde(default)]
pub border: Border,
#[serde(default)]
pub corner_radius: f32,
#[serde(alias = "corner_radius")]
pub corner_radii: CornerRadii,
#[serde(default)]
pub shadow: Option<Shadow>,
#[serde(default)]
@ -132,7 +134,10 @@ impl<V: View> Container<V> {
}
pub fn with_corner_radius(mut self, radius: f32) -> Self {
self.style.corner_radius = radius;
self.style.corner_radii.top_left = radius;
self.style.corner_radii.top_right = radius;
self.style.corner_radii.bottom_right = radius;
self.style.corner_radii.bottom_left = radius;
self
}
@ -214,7 +219,7 @@ impl<V: View> Element<V> for Container<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState {
let quad_bounds = RectF::from_points(
bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
@ -224,7 +229,7 @@ impl<V: View> Element<V> for Container<V> {
if let Some(shadow) = self.style.shadow.as_ref() {
scene.push_shadow(scene::Shadow {
bounds: quad_bounds + shadow.offset,
corner_radius: self.style.corner_radius,
corner_radii: self.style.corner_radii,
sigma: shadow.blur,
color: shadow.color,
});
@ -247,7 +252,7 @@ impl<V: View> Element<V> for Container<V> {
bounds: quad_bounds,
background: self.style.background_color,
border: Default::default(),
corner_radius: self.style.corner_radius,
corner_radii: self.style.corner_radii.into(),
});
self.child
@ -258,7 +263,7 @@ impl<V: View> Element<V> for Container<V> {
bounds: quad_bounds,
background: self.style.overlay_color,
border: self.style.border,
corner_radius: self.style.corner_radius,
corner_radii: self.style.corner_radii.into(),
});
scene.pop_layer();
} else {
@ -266,7 +271,7 @@ impl<V: View> Element<V> for Container<V> {
bounds: quad_bounds,
background: self.style.background_color,
border: self.style.border,
corner_radius: self.style.corner_radius,
corner_radii: self.style.corner_radii.into(),
});
let child_origin = child_origin
@ -283,7 +288,7 @@ impl<V: View> Element<V> for Container<V> {
bounds: quad_bounds,
background: self.style.overlay_color,
border: Default::default(),
corner_radius: 0.,
corner_radii: self.style.corner_radii.into(),
});
scene.pop_layer();
}
@ -327,7 +332,7 @@ impl ToJson for ContainerStyle {
"padding": self.padding.to_json(),
"background_color": self.background_color.to_json(),
"border": self.border.to_json(),
"corner_radius": self.corner_radius,
"corner_radius": self.corner_radii,
"shadow": self.shadow.to_json(),
})
}

View File

@ -6,7 +6,7 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
LayoutContext, SceneBuilder, View, ViewContext,
LayoutContext, PaintContext, SceneBuilder, View, ViewContext,
};
use crate::{Element, SizeConstraint};
@ -57,7 +57,7 @@ impl<V: View> Element<V> for Empty {
_: RectF,
_: &mut Self::LayoutState,
_: &mut V,
_: &mut ViewContext<V>,
_: &mut PaintContext<V>,
) -> Self::PaintState {
}

View File

@ -2,7 +2,8 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
};
use serde_json::json;
@ -61,7 +62,7 @@ impl<V: View> Element<V> for Expanded<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);

View File

@ -2,8 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
use crate::{
json::{self, ToJson, Value},
AnyElement, Axis, Element, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint,
Vector2FExt, View, ViewContext,
AnyElement, Axis, Element, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder,
SizeConstraint, Vector2FExt, View, ViewContext,
};
use pathfinder_geometry::{
rect::RectF,
@ -258,7 +258,7 @@ impl<V: View> Element<V> for Flex<V> {
visible_bounds: RectF,
remaining_space: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
@ -449,7 +449,7 @@ impl<V: View> Element<V> for FlexItem<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx)

View File

@ -3,7 +3,8 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::json,
AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
};
pub struct Hook<V: View> {
@ -52,7 +53,7 @@ impl<V: View> Element<V> for Hook<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);

View File

@ -5,8 +5,8 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
scene, Border, Element, ImageData, LayoutContext, SceneBuilder, SizeConstraint, View,
ViewContext,
scene, Border, Element, ImageData, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
View, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -97,13 +97,13 @@ impl<V: View> Element<V> for Image {
_: RectF,
layout: &mut Self::LayoutState,
_: &mut V,
_: &mut ViewContext<V>,
_: &mut PaintContext<V>,
) -> Self::PaintState {
if let Some(data) = layout {
scene.push_image(scene::Image {
bounds,
border: self.style.border,
corner_radius: self.style.corner_radius,
corner_radii: self.style.corner_radius.into(),
grayscale: self.style.grayscale,
data: data.clone(),
});

View File

@ -66,7 +66,7 @@ impl<V: View> Element<V> for KeystrokeLabel {
visible_bounds: RectF,
element: &mut AnyElement<V>,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) {
element.paint(scene, bounds.origin(), visible_bounds, view, cx);
}

View File

@ -8,7 +8,7 @@ use crate::{
},
json::{ToJson, Value},
text_layout::{Line, RunStyle},
Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -163,7 +163,7 @@ impl<V: View> Element<V> for Label {
visible_bounds: RectF,
line: &mut Self::LayoutState,
_: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
line.paint(

View File

@ -4,8 +4,8 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::json,
AnyElement, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
ViewContext,
AnyElement, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder, SizeConstraint,
View, ViewContext,
};
use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree};
@ -255,7 +255,7 @@ impl<V: View> Element<V> for List<V> {
visible_bounds: RectF,
scroll_top: &mut ListOffset,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
scene.push_layer(Some(visible_bounds));
@ -647,7 +647,7 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
#[cfg(test)]
mod tests {
use super::*;
use crate::{elements::Empty, geometry::vector::vec2f, Entity};
use crate::{elements::Empty, geometry::vector::vec2f, Entity, PaintContext};
use rand::prelude::*;
use std::env;
@ -988,7 +988,7 @@ mod tests {
_: RectF,
_: &mut (),
_: &mut V,
_: &mut ViewContext<V>,
_: &mut PaintContext<V>,
) {
unimplemented!()
}

View File

@ -10,8 +10,8 @@ use crate::{
CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
},
AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, SceneBuilder,
SizeConstraint, View, ViewContext,
AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, PaintContext,
SceneBuilder, SizeConstraint, View, ViewContext,
};
use serde_json::json;
use std::{marker::PhantomData, ops::Range};
@ -256,7 +256,7 @@ impl<Tag, V: View> Element<V> for MouseEventHandler<Tag, V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState {
if self.above {
self.child

View File

@ -3,8 +3,8 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
ViewContext,
AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
SizeConstraint, View, ViewContext,
};
use serde_json::json;
@ -143,7 +143,7 @@ impl<V: View> Element<V> for Overlay<V> {
_: RectF,
size: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) {
let (anchor_position, mut bounds) = match self.position_mode {
OverlayPositionMode::Window => {

View File

@ -7,8 +7,8 @@ use crate::{
geometry::rect::RectF,
platform::{CursorStyle, MouseButton},
scene::MouseDrag,
AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
ViewContext,
AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
SizeConstraint, View, ViewContext,
};
#[derive(Copy, Clone, Debug)]
@ -125,7 +125,7 @@ impl<V: View> Element<V> for Resizable<V> {
visible_bounds: pathfinder_geometry::rect::RectF,
constraint: &mut SizeConstraint,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState {
scene.push_stacking_context(None, None);

View File

@ -3,7 +3,8 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::{self, json, ToJson},
AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
};
/// Element which renders it's children in a stack on top of each other.
@ -57,7 +58,7 @@ impl<V: View> Element<V> for Stack<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState {
for child in &mut self.children {
scene.paint_layer(None, |scene| {

View File

@ -1,5 +1,6 @@
use super::constrain_size_preserving_aspect_ratio;
use crate::json::ToJson;
use crate::PaintContext;
use crate::{
color::Color,
geometry::{
@ -73,7 +74,7 @@ impl<V: View> Element<V> for Svg {
_visible_bounds: RectF,
svg: &mut Self::LayoutState,
_: &mut V,
_: &mut ViewContext<V>,
_: &mut PaintContext<V>,
) {
if let Some(svg) = svg.clone() {
scene.push_icon(scene::Icon {

View File

@ -7,8 +7,8 @@ use crate::{
},
json::{ToJson, Value},
text_layout::{Line, RunStyle, ShapedBoundary},
AppContext, Element, FontCache, LayoutContext, SceneBuilder, SizeConstraint, TextLayoutCache,
View, ViewContext,
AppContext, Element, FontCache, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
TextLayoutCache, View, ViewContext,
};
use log::warn;
use serde_json::json;
@ -171,7 +171,7 @@ impl<V: View> Element<V> for Text {
visible_bounds: RectF,
layout: &mut Self::LayoutState,
_: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState {
let mut origin = bounds.origin();
let empty = Vec::new();

View File

@ -6,12 +6,13 @@ use crate::{
fonts::TextStyle,
geometry::{rect::RectF, vector::Vector2F},
json::json,
Action, Axis, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint, Task, View,
ViewContext,
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
Task, View, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
use std::{
borrow::Cow,
cell::{Cell, RefCell},
ops::Range,
rc::Rc,
@ -52,9 +53,9 @@ pub struct KeystrokeStyle {
}
impl<V: View> Tooltip<V> {
pub fn new<Tag: 'static, T: View>(
pub fn new<Tag: 'static>(
id: usize,
text: String,
text: impl Into<Cow<'static, str>>,
action: Option<Box<dyn Action>>,
style: TooltipStyle,
child: AnyElement<V>,
@ -66,6 +67,8 @@ impl<V: View> Tooltip<V> {
let state_handle = cx.default_element_state::<ElementState<Tag>, Rc<TooltipState>>(id);
let state = state_handle.read(cx).clone();
let text = text.into();
let tooltip = if state.visible.get() {
let mut collapsed_tooltip = Self::render_tooltip(
focused_view_id,
@ -127,7 +130,7 @@ impl<V: View> Tooltip<V> {
pub fn render_tooltip(
focused_view_id: Option<usize>,
text: String,
text: impl Into<Cow<'static, str>>,
style: TooltipStyle,
action: Option<Box<dyn Action>>,
measure: bool,
@ -194,7 +197,7 @@ impl<V: View> Element<V> for Tooltip<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);

View File

@ -6,7 +6,7 @@ use crate::{
},
json::{self, json},
platform::ScrollWheelEvent,
AnyElement, LayoutContext, MouseRegion, SceneBuilder, View, ViewContext,
AnyElement, LayoutContext, MouseRegion, PaintContext, SceneBuilder, View, ViewContext,
};
use json::ToJson;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@ -278,7 +278,7 @@ impl<V: View> Element<V> for UniformList<V> {
visible_bounds: RectF,
layout: &mut Self::LayoutState,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) -> Self::PaintState {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();

View File

@ -71,6 +71,32 @@ pub struct TextStyle {
pub underline: Underline,
}
impl TextStyle {
pub fn refine(self, refinement: TextStyleRefinement) -> TextStyle {
TextStyle {
color: refinement.color.unwrap_or(self.color),
font_family_name: refinement
.font_family_name
.unwrap_or_else(|| self.font_family_name.clone()),
font_family_id: refinement.font_family_id.unwrap_or(self.font_family_id),
font_id: refinement.font_id.unwrap_or(self.font_id),
font_size: refinement.font_size.unwrap_or(self.font_size),
font_properties: refinement.font_properties.unwrap_or(self.font_properties),
underline: refinement.underline.unwrap_or(self.underline),
}
}
}
pub struct TextStyleRefinement {
pub color: Option<Color>,
pub font_family_name: Option<Arc<str>>,
pub font_family_id: Option<FamilyId>,
pub font_id: Option<FontId>,
pub font_size: Option<f32>,
pub font_properties: Option<Properties>,
pub underline: Option<Underline>,
}
#[derive(JsonSchema)]
#[serde(remote = "Properties")]
pub struct PropertiesDef {

View File

@ -19,7 +19,7 @@ use crate::{
},
keymap_matcher::KeymapMatcher,
text_layout::{LineLayout, RunStyle},
Action, ClipboardItem, Menu, Scene,
Action, AnyWindowHandle, ClipboardItem, Menu, Scene,
};
use anyhow::{anyhow, bail, Result};
use async_task::Runnable;
@ -58,13 +58,13 @@ pub trait Platform: Send + Sync {
fn open_window(
&self,
id: usize,
handle: AnyWindowHandle,
options: WindowOptions,
executor: Rc<executor::Foreground>,
) -> Box<dyn Window>;
fn main_window_id(&self) -> Option<usize>;
fn main_window(&self) -> Option<AnyWindowHandle>;
fn add_status_item(&self, id: usize) -> Box<dyn Window>;
fn add_status_item(&self, handle: AnyWindowHandle) -> Box<dyn Window>;
fn write_to_clipboard(&self, item: ClipboardItem);
fn read_from_clipboard(&self) -> Option<ClipboardItem>;

View File

@ -21,7 +21,7 @@ pub use fonts::FontSystem;
use platform::{MacForegroundPlatform, MacPlatform};
pub use renderer::Surface;
use std::{ops::Range, rc::Rc, sync::Arc};
use window::Window;
use window::MacWindow;
use crate::executor;

View File

@ -1,12 +1,12 @@
use super::{
event::key_to_native, screen::Screen, status_item::StatusItem, BoolExt as _, Dispatcher,
FontSystem, Window,
FontSystem, MacWindow,
};
use crate::{
executor,
keymap_matcher::KeymapMatcher,
platform::{self, AppVersion, CursorStyle, Event},
Action, ClipboardItem, Menu, MenuItem,
Action, AnyWindowHandle, ClipboardItem, Menu, MenuItem,
};
use anyhow::{anyhow, Result};
use block::ConcreteBlock;
@ -590,18 +590,18 @@ impl platform::Platform for MacPlatform {
fn open_window(
&self,
id: usize,
handle: AnyWindowHandle,
options: platform::WindowOptions,
executor: Rc<executor::Foreground>,
) -> Box<dyn platform::Window> {
Box::new(Window::open(id, options, executor, self.fonts()))
Box::new(MacWindow::open(handle, options, executor, self.fonts()))
}
fn main_window_id(&self) -> Option<usize> {
Window::main_window_id()
fn main_window(&self) -> Option<AnyWindowHandle> {
MacWindow::main_window()
}
fn add_status_item(&self, _id: usize) -> Box<dyn platform::Window> {
fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
Box::new(StatusItem::add(self.fonts()))
}

View File

@ -509,10 +509,14 @@ impl Renderer {
};
for (ix, shadow) in shadows.iter().enumerate() {
let shape_bounds = shadow.bounds * scale_factor;
let corner_radii = shadow.corner_radii * scale_factor;
let shader_shadow = shaders::GPUIShadow {
origin: shape_bounds.origin().to_float2(),
size: shape_bounds.size().to_float2(),
corner_radius: shadow.corner_radius * scale_factor,
corner_radius_top_left: corner_radii.top_left,
corner_radius_top_right: corner_radii.top_right,
corner_radius_bottom_right: corner_radii.bottom_right,
corner_radius_bottom_left: corner_radii.bottom_left,
sigma: shadow.sigma,
color: shadow.color.to_uchar4(),
};
@ -586,7 +590,10 @@ impl Renderer {
border_bottom: border_width * (quad.border.bottom as usize as f32),
border_left: border_width * (quad.border.left as usize as f32),
border_color: quad.border.color.to_uchar4(),
corner_radius: quad.corner_radius * scale_factor,
corner_radius_top_left: quad.corner_radii.top_left * scale_factor,
corner_radius_top_right: quad.corner_radii.top_right * scale_factor,
corner_radius_bottom_right: quad.corner_radii.bottom_right * scale_factor,
corner_radius_bottom_left: quad.corner_radii.bottom_left * scale_factor,
};
unsafe {
*(buffer_contents.add(ix)) = shader_quad;
@ -738,7 +745,7 @@ impl Renderer {
for image in images {
let origin = image.bounds.origin() * scale_factor;
let target_size = image.bounds.size() * scale_factor;
let corner_radius = image.corner_radius * scale_factor;
let corner_radii = image.corner_radii * scale_factor;
let border_width = image.border.width * scale_factor;
let (alloc_id, atlas_bounds) = self.image_cache.render(&image.data);
images_by_atlas
@ -754,7 +761,10 @@ impl Renderer {
border_bottom: border_width * (image.border.bottom as usize as f32),
border_left: border_width * (image.border.left as usize as f32),
border_color: image.border.color.to_uchar4(),
corner_radius,
corner_radius_top_left: corner_radii.top_left,
corner_radius_top_right: corner_radii.top_right,
corner_radius_bottom_right: corner_radii.bottom_right,
corner_radius_bottom_left: corner_radii.bottom_left,
grayscale: image.grayscale as u8,
});
}
@ -777,7 +787,10 @@ impl Renderer {
border_bottom: 0.,
border_left: 0.,
border_color: Default::default(),
corner_radius: 0.,
corner_radius_top_left: 0.,
corner_radius_top_right: 0.,
corner_radius_bottom_right: 0.,
corner_radius_bottom_left: 0.,
grayscale: false as u8,
});
} else {

View File

@ -19,7 +19,10 @@ typedef struct {
float border_bottom;
float border_left;
vector_uchar4 border_color;
float corner_radius;
float corner_radius_top_left;
float corner_radius_top_right;
float corner_radius_bottom_right;
float corner_radius_bottom_left;
} GPUIQuad;
typedef enum {
@ -31,7 +34,10 @@ typedef enum {
typedef struct {
vector_float2 origin;
vector_float2 size;
float corner_radius;
float corner_radius_top_left;
float corner_radius_top_right;
float corner_radius_bottom_right;
float corner_radius_bottom_left;
float sigma;
vector_uchar4 color;
} GPUIShadow;
@ -89,7 +95,10 @@ typedef struct {
float border_bottom;
float border_left;
vector_uchar4 border_color;
float corner_radius;
float corner_radius_top_left;
float corner_radius_top_right;
float corner_radius_bottom_right;
float corner_radius_bottom_left;
uint8_t grayscale;
} GPUIImage;

View File

@ -43,7 +43,10 @@ struct QuadFragmentInput {
float border_bottom;
float border_left;
float4 border_color;
float corner_radius;
float corner_radius_top_left;
float corner_radius_top_right;
float corner_radius_bottom_right;
float corner_radius_bottom_left;
uchar grayscale; // only used in image shader
};
@ -51,12 +54,27 @@ float4 quad_sdf(QuadFragmentInput input) {
float2 half_size = input.size / 2.;
float2 center = input.origin + half_size;
float2 center_to_point = input.position.xy - center;
float2 rounded_edge_to_point = abs(center_to_point) - half_size + input.corner_radius;
float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - input.corner_radius;
float corner_radius;
if (center_to_point.x < 0.) {
if (center_to_point.y < 0.) {
corner_radius = input.corner_radius_top_left;
} else {
corner_radius = input.corner_radius_bottom_left;
}
} else {
if (center_to_point.y < 0.) {
corner_radius = input.corner_radius_top_right;
} else {
corner_radius = input.corner_radius_bottom_right;
}
}
float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - corner_radius;
float vertical_border = center_to_point.x <= 0. ? input.border_left : input.border_right;
float horizontal_border = center_to_point.y <= 0. ? input.border_top : input.border_bottom;
float2 inset_size = half_size - input.corner_radius - float2(vertical_border, horizontal_border);
float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
float2 point_to_inset_corner = abs(center_to_point) - inset_size;
float border_width;
if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
@ -110,7 +128,10 @@ vertex QuadFragmentInput quad_vertex(
quad.border_bottom,
quad.border_left,
coloru_to_colorf(quad.border_color),
quad.corner_radius,
quad.corner_radius_top_left,
quad.corner_radius_top_right,
quad.corner_radius_bottom_right,
quad.corner_radius_bottom_left,
0,
};
}
@ -125,7 +146,10 @@ struct ShadowFragmentInput {
float4 position [[position]];
vector_float2 origin;
vector_float2 size;
float corner_radius;
float corner_radius_top_left;
float corner_radius_top_right;
float corner_radius_bottom_right;
float corner_radius_bottom_left;
float sigma;
vector_uchar4 color;
};
@ -148,7 +172,10 @@ vertex ShadowFragmentInput shadow_vertex(
device_position,
shadow.origin,
shadow.size,
shadow.corner_radius,
shadow.corner_radius_top_left,
shadow.corner_radius_top_right,
shadow.corner_radius_bottom_right,
shadow.corner_radius_bottom_left,
shadow.sigma,
shadow.color,
};
@ -158,10 +185,24 @@ fragment float4 shadow_fragment(
ShadowFragmentInput input [[stage_in]]
) {
float sigma = input.sigma;
float corner_radius = input.corner_radius;
float2 half_size = input.size / 2.;
float2 center = input.origin + half_size;
float2 point = input.position.xy - center;
float2 center_to_point = input.position.xy - center;
float corner_radius;
if (center_to_point.x < 0.) {
if (center_to_point.y < 0.) {
corner_radius = input.corner_radius_top_left;
} else {
corner_radius = input.corner_radius_bottom_left;
}
} else {
if (center_to_point.y < 0.) {
corner_radius = input.corner_radius_top_right;
} else {
corner_radius = input.corner_radius_bottom_right;
}
}
// The signal is only non-zero in a limited range, so don't waste samples
float low = point.y - half_size.y;
@ -252,7 +293,10 @@ vertex QuadFragmentInput image_vertex(
image.border_bottom,
image.border_left,
coloru_to_colorf(image.border_color),
image.corner_radius,
image.corner_radius_top_left,
image.corner_radius_top_right,
image.corner_radius_bottom_right,
image.corner_radius_bottom_left,
image.grayscale,
};
}
@ -266,7 +310,7 @@ fragment float4 image_fragment(
if (input.grayscale) {
float grayscale =
0.2126 * input.background_color.r +
0.7152 * input.background_color.g +
0.7152 * input.background_color.g +
0.0722 * input.background_color.b;
input.background_color = float4(grayscale, grayscale, grayscale, input.background_color.a);
}

View File

@ -13,6 +13,7 @@ use crate::{
Event, InputHandler, KeyDownEvent, Modifiers, ModifiersChangedEvent, MouseButton,
MouseButtonEvent, MouseMovedEvent, Scene, WindowBounds, WindowKind,
},
AnyWindowHandle,
};
use block::ConcreteBlock;
use cocoa::{
@ -282,7 +283,7 @@ struct InsertText {
}
struct WindowState {
id: usize,
handle: AnyWindowHandle,
native_window: id,
kind: WindowKind,
event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
@ -422,11 +423,11 @@ impl WindowState {
}
}
pub struct Window(Rc<RefCell<WindowState>>);
pub struct MacWindow(Rc<RefCell<WindowState>>);
impl Window {
impl MacWindow {
pub fn open(
id: usize,
handle: AnyWindowHandle,
options: platform::WindowOptions,
executor: Rc<executor::Foreground>,
fonts: Arc<dyn platform::FontSystem>,
@ -504,7 +505,7 @@ impl Window {
assert!(!native_view.is_null());
let window = Self(Rc::new(RefCell::new(WindowState {
id,
handle,
native_window,
kind: options.kind,
event_callback: None,
@ -621,13 +622,13 @@ impl Window {
}
}
pub fn main_window_id() -> Option<usize> {
pub fn main_window() -> Option<AnyWindowHandle> {
unsafe {
let app = NSApplication::sharedApplication(nil);
let main_window: id = msg_send![app, mainWindow];
if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
let id = get_window_state(&*main_window).borrow().id;
Some(id)
let handle = get_window_state(&*main_window).borrow().handle;
Some(handle)
} else {
None
}
@ -635,7 +636,7 @@ impl Window {
}
}
impl Drop for Window {
impl Drop for MacWindow {
fn drop(&mut self) {
let this = self.0.borrow();
let window = this.native_window;
@ -649,7 +650,7 @@ impl Drop for Window {
}
}
impl platform::Window for Window {
impl platform::Window for MacWindow {
fn bounds(&self) -> WindowBounds {
self.0.as_ref().borrow().bounds()
}
@ -881,7 +882,7 @@ impl platform::Window for Window {
fn is_topmost_for_position(&self, position: Vector2F) -> bool {
let self_borrow = self.0.borrow();
let self_id = self_borrow.id;
let self_handle = self_borrow.handle;
unsafe {
let app = NSApplication::sharedApplication(nil);
@ -898,8 +899,8 @@ impl platform::Window for Window {
let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS];
let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS];
if is_panel == YES || is_window == YES {
let topmost_window_id = get_window_state(&*top_most_window).borrow().id;
topmost_window_id == self_id
let topmost_window = get_window_state(&*top_most_window).borrow().handle;
topmost_window == self_handle
} else {
// Someone else's window is on top
false
@ -1086,7 +1087,10 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
button: MouseButton::Left,
modifiers: Modifiers { ctrl: true, .. },
..
}) => return,
}) => {
window_state_borrow.synthetic_drag_counter += 1;
return;
}
_ => None,
};

View File

@ -5,7 +5,7 @@ use crate::{
vector::{vec2f, Vector2F},
},
keymap_matcher::KeymapMatcher,
Action, ClipboardItem, Menu,
Action, AnyWindowHandle, ClipboardItem, Menu,
};
use anyhow::{anyhow, Result};
use collections::VecDeque;
@ -102,7 +102,7 @@ pub struct Platform {
fonts: Arc<dyn super::FontSystem>,
current_clipboard_item: Mutex<Option<ClipboardItem>>,
cursor: Mutex<CursorStyle>,
active_window_id: Arc<Mutex<Option<usize>>>,
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
}
impl Platform {
@ -112,7 +112,7 @@ impl Platform {
fonts: Arc::new(super::current::FontSystem::new()),
current_clipboard_item: Default::default(),
cursor: Mutex::new(CursorStyle::Arrow),
active_window_id: Default::default(),
active_window: Default::default(),
}
}
}
@ -146,30 +146,30 @@ impl super::Platform for Platform {
fn open_window(
&self,
id: usize,
handle: AnyWindowHandle,
options: super::WindowOptions,
_executor: Rc<super::executor::Foreground>,
) -> Box<dyn super::Window> {
*self.active_window_id.lock() = Some(id);
*self.active_window.lock() = Some(handle);
Box::new(Window::new(
id,
handle,
match options.bounds {
WindowBounds::Maximized | WindowBounds::Fullscreen => vec2f(1024., 768.),
WindowBounds::Fixed(rect) => rect.size(),
},
self.active_window_id.clone(),
self.active_window.clone(),
))
}
fn main_window_id(&self) -> Option<usize> {
self.active_window_id.lock().clone()
fn main_window(&self) -> Option<AnyWindowHandle> {
self.active_window.lock().clone()
}
fn add_status_item(&self, id: usize) -> Box<dyn crate::platform::Window> {
fn add_status_item(&self, handle: AnyWindowHandle) -> Box<dyn crate::platform::Window> {
Box::new(Window::new(
id,
handle,
vec2f(24., 24.),
self.active_window_id.clone(),
self.active_window.clone(),
))
}
@ -256,7 +256,7 @@ impl super::Screen for Screen {
}
pub struct Window {
id: usize,
handle: AnyWindowHandle,
pub(crate) size: Vector2F,
scale_factor: f32,
current_scene: Option<crate::Scene>,
@ -270,13 +270,17 @@ pub struct Window {
pub(crate) title: Option<String>,
pub(crate) edited: bool,
pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
active_window_id: Arc<Mutex<Option<usize>>>,
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
}
impl Window {
pub fn new(id: usize, size: Vector2F, active_window_id: Arc<Mutex<Option<usize>>>) -> Self {
pub fn new(
handle: AnyWindowHandle,
size: Vector2F,
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
) -> Self {
Self {
id,
handle,
size,
event_handlers: Default::default(),
resize_handlers: Default::default(),
@ -290,7 +294,7 @@ impl Window {
title: None,
edited: false,
pending_prompts: Default::default(),
active_window_id,
active_window,
}
}
@ -342,7 +346,7 @@ impl super::Window for Window {
}
fn activate(&self) {
*self.active_window_id.lock() = Some(self.id);
*self.active_window.lock() = Some(self.handle);
}
fn set_title(&mut self, title: &str) {

View File

@ -3,8 +3,10 @@ mod mouse_region;
#[cfg(debug_assertions)]
use collections::HashSet;
use derive_more::Mul;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
use serde_json::json;
use std::{borrow::Cow, sync::Arc};
@ -65,13 +67,73 @@ pub struct Quad {
pub bounds: RectF,
pub background: Option<Color>,
pub border: Border,
pub corner_radius: f32,
pub corner_radii: CornerRadii,
}
#[derive(Default, Debug, Mul, Clone, Copy, Serialize, JsonSchema)]
pub struct CornerRadii {
pub top_left: f32,
pub top_right: f32,
pub bottom_right: f32,
pub bottom_left: f32,
}
impl<'de> Deserialize<'de> for CornerRadii {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
pub struct CornerRadiiHelper {
pub top_left: Option<f32>,
pub top_right: Option<f32>,
pub bottom_right: Option<f32>,
pub bottom_left: Option<f32>,
}
#[derive(Deserialize)]
#[serde(untagged)]
enum RadiusOrRadii {
Radius(f32),
Radii(CornerRadiiHelper),
}
let json = RadiusOrRadii::deserialize(deserializer)?;
let result = match json {
RadiusOrRadii::Radius(radius) => CornerRadii::from(radius),
RadiusOrRadii::Radii(CornerRadiiHelper {
top_left,
top_right,
bottom_right,
bottom_left,
}) => CornerRadii {
top_left: top_left.unwrap_or(0.0),
top_right: top_right.unwrap_or(0.0),
bottom_right: bottom_right.unwrap_or(0.0),
bottom_left: bottom_left.unwrap_or(0.0),
},
};
Ok(result)
}
}
impl From<f32> for CornerRadii {
fn from(radius: f32) -> Self {
Self {
top_left: radius,
top_right: radius,
bottom_right: radius,
bottom_left: radius,
}
}
}
#[derive(Debug)]
pub struct Shadow {
pub bounds: RectF,
pub corner_radius: f32,
pub corner_radii: CornerRadii,
pub sigma: f32,
pub color: Color,
}
@ -177,7 +239,7 @@ pub struct PathVertex {
pub struct Image {
pub bounds: RectF,
pub border: Border,
pub corner_radius: f32,
pub corner_radii: CornerRadii,
pub grayscale: bool,
pub data: Arc<ImageData>,
}

View File

@ -177,7 +177,7 @@ impl MouseRegion {
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
pub struct MouseRegionId {
view_id: usize,
tag: TypeId,

View File

@ -14,3 +14,5 @@ syn = "1.0"
quote = "1.0"
proc-macro2 = "1.0"
[dev-dependencies]
gpui = { path = "../gpui" }

View File

@ -283,8 +283,12 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
// The name of the struct/enum
let name = input.ident;
let must_implement = format_ident!("{}MustImplementRenderElement", name);
let expanded = quote! {
trait #must_implement : gpui::elements::RenderElement {}
impl #must_implement for #name {}
impl<V: gpui::View> gpui::elements::Element<V> for #name {
type LayoutState = gpui::elements::AnyElement<V>;
type PaintState = ();
@ -307,7 +311,7 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
visible_bounds: gpui::geometry::rect::RectF,
element: &mut gpui::elements::AnyElement<V>,
view: &mut V,
cx: &mut gpui::ViewContext<V>,
cx: &mut gpui::PaintContext<V>,
) {
element.paint(scene, bounds.origin(), visible_bounds, view, cx);
}
@ -332,7 +336,7 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
_: &(),
view: &V,
cx: &gpui::ViewContext<V>,
) -> serde_json::Value {
) -> gpui::serde_json::Value {
element.debug(view, cx)
}
}

View File

@ -0,0 +1,14 @@
use gpui::{elements::RenderElement, View, ViewContext};
use gpui_macros::Element;
#[test]
fn test_derive_render_element() {
#[derive(Element)]
struct TestElement {}
impl RenderElement for TestElement {
fn render<V: View>(&mut self, _: &mut V, _: &mut ViewContext<V>) -> gpui::AnyElement<V> {
unimplemented!()
}
}
}

View File

@ -45,7 +45,7 @@ use syntax_map::SyntaxSnapshot;
use theme::{SyntaxTheme, Theme};
use tree_sitter::{self, Query};
use unicase::UniCase;
use util::http::HttpClient;
use util::{http::HttpClient, paths::PathExt};
use util::{merge_json_value_into, post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
#[cfg(any(test, feature = "test-support"))]
@ -182,8 +182,8 @@ impl CachedLspAdapter {
self.adapter.workspace_configuration(cx)
}
pub async fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
self.adapter.process_diagnostics(params).await
pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
self.adapter.process_diagnostics(params)
}
pub async fn process_completion(&self, completion_item: &mut lsp::CompletionItem) {
@ -262,7 +262,7 @@ pub trait LspAdapter: 'static + Send + Sync {
container_dir: PathBuf,
) -> Option<LanguageServerBinary>;
async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
async fn process_completion(&self, _: &mut lsp::CompletionItem) {}
@ -777,7 +777,7 @@ impl LanguageRegistry {
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
let path = path.as_ref();
let filename = path.file_name().and_then(|name| name.to_str());
let extension = path.extension().and_then(|name| name.to_str());
let extension = path.extension_or_hidden_file_name();
let path_suffixes = [extension, filename];
self.get_or_load_language(|config| {
let path_matches = config
@ -1487,12 +1487,6 @@ impl Language {
None
}
pub async fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) {
for adapter in &self.adapters {
adapter.process_diagnostics(diagnostics).await;
}
}
pub async fn process_completion(self: &Arc<Self>, completion: &mut lsp::CompletionItem) {
for adapter in &self.adapters {
adapter.process_completion(completion).await;
@ -1756,7 +1750,7 @@ impl LspAdapter for Arc<FakeLspAdapter> {
unreachable!();
}
async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
async fn disk_based_diagnostic_sources(&self) -> Vec<String> {
self.disk_based_diagnostics_sources.clone()

View File

@ -61,7 +61,9 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
.receive_notification::<lsp::notification::DidOpenTextDocument>()
.await;
let (_, log_view) = cx.add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx));
let log_view = cx
.add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx))
.root(cx);
language_server.notify::<lsp::notification::LogMessage>(lsp::LogMessageParams {
message: "hello from the server".into(),

View File

@ -58,11 +58,14 @@ fn build_bridge(swift_target: &SwiftTarget) {
"cargo:rerun-if-changed={}/Package.resolved",
SWIFT_PACKAGE_NAME
);
let swift_package_root = swift_package_root();
let swift_target_folder = swift_target_folder();
if !Command::new("swift")
.arg("build")
.args(["--configuration", &env::var("PROFILE").unwrap()])
.args(["--triple", &swift_target.target.triple])
.args(["--build-path".into(), swift_target_folder])
.current_dir(&swift_package_root)
.status()
.unwrap()
@ -128,6 +131,12 @@ fn swift_package_root() -> PathBuf {
env::current_dir().unwrap().join(SWIFT_PACKAGE_NAME)
}
fn swift_target_folder() -> PathBuf {
env::current_dir()
.unwrap()
.join(format!("../../target/{SWIFT_PACKAGE_NAME}"))
}
fn copy_dir(source: &Path, destination: &Path) {
assert!(
Command::new("rm")
@ -155,8 +164,7 @@ fn copy_dir(source: &Path, destination: &Path) {
impl SwiftTarget {
fn out_dir_path(&self) -> PathBuf {
swift_package_root()
.join(".build")
swift_target_folder()
.join(&self.target.unversioned_triple)
.join(env::var("PROFILE").unwrap())
}

View File

@ -434,7 +434,9 @@ impl LanguageServer {
..Default::default()
}),
inlay_hint: Some(InlayHintClientCapabilities {
resolve_support: None,
resolve_support: Some(InlayHintResolveClientCapabilities {
properties: vec!["textEdits".to_string(), "tooltip".to_string()],
}),
dynamic_registration: Some(false),
}),
..Default::default()

View File

@ -1954,7 +1954,7 @@ impl LspCommand for InlayHints {
_: &mut Project,
_: PeerId,
buffer_version: &clock::Global,
cx: &mut AppContext,
_: &mut AppContext,
) -> proto::InlayHintsResponse {
proto::InlayHintsResponse {
hints: response
@ -1963,51 +1963,17 @@ impl LspCommand for InlayHints {
position: Some(language::proto::serialize_anchor(&response_hint.position)),
padding_left: response_hint.padding_left,
padding_right: response_hint.padding_right,
label: Some(proto::InlayHintLabel {
label: Some(match response_hint.label {
InlayHintLabel::String(s) => proto::inlay_hint_label::Label::Value(s),
InlayHintLabel::LabelParts(label_parts) => {
proto::inlay_hint_label::Label::LabelParts(proto::InlayHintLabelParts {
parts: label_parts.into_iter().map(|label_part| proto::InlayHintLabelPart {
value: label_part.value,
tooltip: label_part.tooltip.map(|tooltip| {
let proto_tooltip = match tooltip {
InlayHintLabelPartTooltip::String(s) => proto::inlay_hint_label_part_tooltip::Content::Value(s),
InlayHintLabelPartTooltip::MarkupContent(markup_content) => proto::inlay_hint_label_part_tooltip::Content::MarkupContent(proto::MarkupContent {
kind: markup_content.kind,
value: markup_content.value,
}),
};
proto::InlayHintLabelPartTooltip {content: Some(proto_tooltip)}
}),
location: label_part.location.map(|location| proto::Location {
start: Some(serialize_anchor(&location.range.start)),
end: Some(serialize_anchor(&location.range.end)),
buffer_id: location.buffer.read(cx).remote_id(),
}),
}).collect()
})
}
}),
}),
kind: response_hint.kind.map(|kind| kind.name().to_string()),
tooltip: response_hint.tooltip.map(|response_tooltip| {
let proto_tooltip = match response_tooltip {
InlayHintTooltip::String(s) => {
proto::inlay_hint_tooltip::Content::Value(s)
}
InlayHintTooltip::MarkupContent(markup_content) => {
proto::inlay_hint_tooltip::Content::MarkupContent(
proto::MarkupContent {
kind: markup_content.kind,
value: markup_content.value,
},
)
}
};
proto::InlayHintTooltip {
content: Some(proto_tooltip),
}
// Do not pass extra data such as tooltips to clients: host can put tooltip data from the cache during resolution.
tooltip: None,
// Similarly, do not pass label parts to clients: host can return a detailed list during resolution.
label: Some(proto::InlayHintLabel {
label: Some(proto::inlay_hint_label::Label::Value(
match response_hint.label {
InlayHintLabel::String(s) => s,
InlayHintLabel::LabelParts(_) => response_hint.text(),
},
)),
}),
})
.collect(),

View File

@ -2769,24 +2769,21 @@ impl Project {
language_server
.on_notification::<lsp::notification::PublishDiagnostics, _>({
let adapter = adapter.clone();
move |mut params, cx| {
move |mut params, mut cx| {
let this = this;
let adapter = adapter.clone();
cx.spawn(|mut cx| async move {
adapter.process_diagnostics(&mut params).await;
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| {
this.update_diagnostics(
server_id,
params,
&adapter.disk_based_diagnostic_sources,
cx,
)
.log_err();
});
}
})
.detach();
adapter.process_diagnostics(&mut params);
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| {
this.update_diagnostics(
server_id,
params,
&adapter.disk_based_diagnostic_sources,
cx,
)
.log_err();
});
}
}
})
.detach();

View File

@ -1,5 +1,5 @@
use crate::Project;
use gpui::{ModelContext, ModelHandle, WeakModelHandle};
use gpui::{AnyWindowHandle, ModelContext, ModelHandle, WeakModelHandle};
use std::path::PathBuf;
use terminal::{Terminal, TerminalBuilder, TerminalSettings};
@ -11,7 +11,7 @@ impl Project {
pub fn create_terminal(
&mut self,
working_directory: Option<PathBuf>,
window_id: usize,
window: AnyWindowHandle,
cx: &mut ModelContext<Self>,
) -> anyhow::Result<ModelHandle<Terminal>> {
if self.is_remote() {
@ -27,7 +27,7 @@ impl Project {
settings.env.clone(),
Some(settings.blinking.clone()),
settings.alternate_scroll,
window_id,
window,
)
.map(|builder| {
let terminal_handle = cx.add_model(|cx| builder.subscribe(cx));

View File

@ -2369,7 +2369,7 @@ impl BackgroundScannerState {
}
// Remove any git repositories whose .git entry no longer exists.
let mut snapshot = &mut self.snapshot;
let snapshot = &mut self.snapshot;
let mut repositories = mem::take(&mut snapshot.git_repositories);
let mut repository_entries = mem::take(&mut snapshot.repository_entries);
repositories.retain(|work_directory_id, _| {

View File

@ -4,7 +4,7 @@ use collections::HashMap;
use gpui::{AppContext, AssetSource};
use serde_derive::Deserialize;
use util::iife;
use util::{iife, paths::PathExt};
#[derive(Deserialize, Debug)]
struct TypeConfig {
@ -48,14 +48,7 @@ impl FileAssociations {
// FIXME: Associate a type with the languages and have the file's langauge
// override these associations
iife!({
let suffix = path
.file_name()
.and_then(|os_str| os_str.to_str())
.and_then(|file_name| {
file_name
.find('.')
.and_then(|dot_index| file_name.get(dot_index + 1..))
})?;
let suffix = path.icon_suffix()?;
this.suffixes
.get(suffix)

View File

@ -115,6 +115,7 @@ actions!(
[
ExpandSelectedEntry,
CollapseSelectedEntry,
CollapseAllEntries,
NewDirectory,
NewFile,
Copy,
@ -140,6 +141,7 @@ pub fn init(assets: impl AssetSource, cx: &mut AppContext) {
file_associations::init(assets, cx);
cx.add_action(ProjectPanel::expand_selected_entry);
cx.add_action(ProjectPanel::collapse_selected_entry);
cx.add_action(ProjectPanel::collapse_all_entries);
cx.add_action(ProjectPanel::select_prev);
cx.add_action(ProjectPanel::select_next);
cx.add_action(ProjectPanel::new_file);
@ -514,6 +516,12 @@ impl ProjectPanel {
}
}
pub fn collapse_all_entries(&mut self, _: &CollapseAllEntries, cx: &mut ViewContext<Self>) {
self.expanded_dir_ids.clear();
self.update_visible_entries(None, cx);
cx.notify();
}
fn toggle_expanded(&mut self, entry_id: ProjectEntryId, cx: &mut ViewContext<Self>) {
if let Some(worktree_id) = self.project.read(cx).worktree_id_for_entry(entry_id, cx) {
if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) {
@ -1407,7 +1415,7 @@ impl ProjectPanel {
if cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id())
.currently_dragged::<ProjectEntryId>(cx.window())
.is_some()
&& dragged_entry_destination
.as_ref()
@ -1451,7 +1459,7 @@ impl ProjectPanel {
.on_up(MouseButton::Left, move |_, this, cx| {
if let Some((_, dragged_entry)) = cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id())
.currently_dragged::<ProjectEntryId>(cx.window())
{
this.move_entry(
*dragged_entry,
@ -1464,7 +1472,7 @@ impl ProjectPanel {
.on_move(move |_, this, cx| {
if cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id())
.currently_dragged::<ProjectEntryId>(cx.window())
.is_some()
{
this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) {
@ -1718,7 +1726,7 @@ impl ClipboardEntry {
#[cfg(test)]
mod tests {
use super::*;
use gpui::{TestAppContext, ViewHandle};
use gpui::{AnyWindowHandle, TestAppContext, ViewHandle, WindowHandle};
use pretty_assertions::assert_eq;
use project::FakeFs;
use serde_json::json;
@ -1772,7 +1780,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
assert_eq!(
visible_entries_as_strings(&panel, 0..50, cx),
@ -1860,7 +1870,8 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "root1", cx);
@ -1882,7 +1893,7 @@ mod tests {
// Add a file with the root folder selected. The filename editor is placed
// before the first file in the root folder.
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
cx.read_window(window_id, |cx| {
window.read_with(cx, |cx| {
let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx));
});
@ -2211,7 +2222,8 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "root1", cx);
@ -2233,7 +2245,7 @@ mod tests {
// Add a file with the root folder selected. The filename editor is placed
// before the first file in the root folder.
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
cx.read_window(window_id, |cx| {
window.read_with(cx, |cx| {
let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx));
});
@ -2311,7 +2323,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
panel.update(cx, |panel, cx| {
@ -2384,7 +2398,8 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
toggle_expand_dir(&panel, "src/test", cx);
@ -2401,9 +2416,9 @@ mod tests {
" third.rs"
]
);
ensure_single_file_is_opened(window_id, &workspace, "test/first.rs", cx);
ensure_single_file_is_opened(window, "test/first.rs", cx);
submit_deletion(window_id, &panel, cx);
submit_deletion(window.into(), &panel, cx);
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&[
@ -2414,7 +2429,7 @@ mod tests {
],
"Project panel should have no deleted file, no other file is selected in it"
);
ensure_no_open_items_and_panes(window_id, &workspace, cx);
ensure_no_open_items_and_panes(window.into(), &workspace, cx);
select_path(&panel, "src/test/second.rs", cx);
panel.update(cx, |panel, cx| panel.open_file(&Open, cx));
@ -2428,9 +2443,9 @@ mod tests {
" third.rs"
]
);
ensure_single_file_is_opened(window_id, &workspace, "test/second.rs", cx);
ensure_single_file_is_opened(window, "test/second.rs", cx);
cx.update_window(window_id, |cx| {
window.update(cx, |cx| {
let active_items = workspace
.read(cx)
.panes()
@ -2446,13 +2461,13 @@ mod tests {
.expect("Open item should be an editor");
open_editor.update(cx, |editor, cx| editor.set_text("Another text!", cx));
});
submit_deletion(window_id, &panel, cx);
submit_deletion(window.into(), &panel, cx);
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&["v src", " v test", " third.rs"],
"Project panel should have no deleted file, with one last file remaining"
);
ensure_no_open_items_and_panes(window_id, &workspace, cx);
ensure_no_open_items_and_panes(window.into(), &workspace, cx);
}
#[gpui::test]
@ -2473,7 +2488,8 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "src/", cx);
@ -2484,7 +2500,7 @@ mod tests {
&["v src <== selected", " > test"]
);
panel.update(cx, |panel, cx| panel.new_directory(&NewDirectory, cx));
cx.read_window(window_id, |cx| {
window.read_with(cx, |cx| {
let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx));
});
@ -2515,7 +2531,7 @@ mod tests {
&["v src", " > test <== selected"]
);
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
cx.read_window(window_id, |cx| {
window.read_with(cx, |cx| {
let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx));
});
@ -2565,7 +2581,7 @@ mod tests {
],
);
panel.update(cx, |panel, cx| panel.rename(&Rename, cx));
cx.read_window(window_id, |cx| {
window.read_with(cx, |cx| {
let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx));
});
@ -2619,7 +2635,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
let new_search_events_count = Arc::new(AtomicUsize::new(0));
@ -2678,6 +2696,65 @@ mod tests {
);
}
#[gpui::test]
async fn test_collapse_all_entries(cx: &mut gpui::TestAppContext) {
init_test_with_editor(cx);
let fs = FakeFs::new(cx.background());
fs.insert_tree(
"/project_root",
json!({
"dir_1": {
"nested_dir": {
"file_a.py": "# File contents",
"file_b.py": "# File contents",
"file_c.py": "# File contents",
},
"file_1.py": "# File contents",
"file_2.py": "# File contents",
"file_3.py": "# File contents",
},
"dir_2": {
"file_1.py": "# File contents",
"file_2.py": "# File contents",
"file_3.py": "# File contents",
}
}),
)
.await;
let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await;
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
panel.update(cx, |panel, cx| {
panel.collapse_all_entries(&CollapseAllEntries, cx)
});
cx.foreground().run_until_parked();
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&["v project_root", " > dir_1", " > dir_2",]
);
// Open dir_1 and make sure nested_dir was collapsed when running collapse_all_entries
toggle_expand_dir(&panel, "project_root/dir_1", cx);
cx.foreground().run_until_parked();
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&[
"v project_root",
" v dir_1 <== selected",
" > nested_dir",
" file_1.py",
" file_2.py",
" file_3.py",
" > dir_2",
]
);
}
fn toggle_expand_dir(
panel: &ViewHandle<ProjectPanel>,
path: impl AsRef<Path>,
@ -2801,13 +2878,11 @@ mod tests {
}
fn ensure_single_file_is_opened(
window_id: usize,
workspace: &ViewHandle<Workspace>,
window: WindowHandle<Workspace>,
expected_path: &str,
cx: &mut TestAppContext,
) {
cx.read_window(window_id, |cx| {
let workspace = workspace.read(cx);
window.update_root(cx, |workspace, cx| {
let worktrees = workspace.worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
let worktree_id = WorktreeId::from_usize(worktrees[0].id());
@ -2829,12 +2904,12 @@ mod tests {
}
fn submit_deletion(
window_id: usize,
window: AnyWindowHandle,
panel: &ViewHandle<ProjectPanel>,
cx: &mut TestAppContext,
) {
assert!(
!cx.has_pending_prompt(window_id),
!window.has_pending_prompt(cx),
"Should have no prompts before the deletion"
);
panel.update(cx, |panel, cx| {
@ -2844,27 +2919,27 @@ mod tests {
.detach_and_log_err(cx);
});
assert!(
cx.has_pending_prompt(window_id),
window.has_pending_prompt(cx),
"Should have a prompt after the deletion"
);
cx.simulate_prompt_answer(window_id, 0);
window.simulate_prompt_answer(0, cx);
assert!(
!cx.has_pending_prompt(window_id),
!window.has_pending_prompt(cx),
"Should have no prompts after prompt was replied to"
);
cx.foreground().run_until_parked();
}
fn ensure_no_open_items_and_panes(
window_id: usize,
window: AnyWindowHandle,
workspace: &ViewHandle<Workspace>,
cx: &mut TestAppContext,
) {
assert!(
!cx.has_pending_prompt(window_id),
!window.has_pending_prompt(cx),
"Should have no prompts after deletion operation closes the file"
);
cx.read_window(window_id, |cx| {
window.read_with(cx, |cx| {
let open_project_paths = workspace
.read(cx)
.panes()
@ -2878,3 +2953,4 @@ mod tests {
});
}
}
// TODO - a workspace command?

View File

@ -326,10 +326,11 @@ mod tests {
},
);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
// Create the project symbols view.
let symbols = cx.add_view(window_id, |cx| {
let symbols = window.add_view(cx, |cx| {
ProjectSymbols::new(
ProjectSymbolsDelegate::new(workspace.downgrade(), project.clone()),
cx,

View File

@ -5,6 +5,7 @@ use gpui::{
elements::{Label, LabelStyle},
AnyElement, Element, View,
};
use util::paths::PathExt;
use workspace::WorkspaceLocation;
pub struct HighlightedText {
@ -61,7 +62,7 @@ impl HighlightedWorkspaceLocation {
.paths()
.iter()
.map(|path| {
let path = util::paths::compact(&path);
let path = path.compact();
let highlighted_text = Self::highlights_for_path(
path.as_ref(),
&string_match.positions,

View File

@ -11,6 +11,7 @@ use highlighted_workspace_location::HighlightedWorkspaceLocation;
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate, PickerEvent};
use std::sync::Arc;
use util::paths::PathExt;
use workspace::{
notifications::simple_message_notification::MessageNotification, Workspace, WorkspaceLocation,
WORKSPACE_DB,
@ -134,7 +135,7 @@ impl PickerDelegate for RecentProjectsDelegate {
let combined_string = location
.paths()
.iter()
.map(|path| util::paths::compact(&path).to_string_lossy().into_owned())
.map(|path| path.compact().to_string_lossy().into_owned())
.collect::<Vec<_>>()
.join("");
StringMatchCandidate::new(id, combined_string)

View File

@ -1,6 +1,6 @@
use crate::{
SearchOptions, SelectAllMatches, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive,
ToggleRegex, ToggleWholeWord,
NextHistoryQuery, PreviousHistoryQuery, SearchHistory, SearchOptions, SelectAllMatches,
SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex, ToggleWholeWord,
};
use collections::HashMap;
use editor::Editor;
@ -46,6 +46,8 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(BufferSearchBar::select_prev_match_on_pane);
cx.add_action(BufferSearchBar::select_all_matches_on_pane);
cx.add_action(BufferSearchBar::handle_editor_cancel);
cx.add_action(BufferSearchBar::next_history_query);
cx.add_action(BufferSearchBar::previous_history_query);
add_toggle_option_action::<ToggleCaseSensitive>(SearchOptions::CASE_SENSITIVE, cx);
add_toggle_option_action::<ToggleWholeWord>(SearchOptions::WHOLE_WORD, cx);
add_toggle_option_action::<ToggleRegex>(SearchOptions::REGEX, cx);
@ -65,7 +67,7 @@ fn add_toggle_option_action<A: Action>(option: SearchOptions, cx: &mut AppContex
}
pub struct BufferSearchBar {
pub query_editor: ViewHandle<Editor>,
query_editor: ViewHandle<Editor>,
active_searchable_item: Option<Box<dyn SearchableItemHandle>>,
active_match_index: Option<usize>,
active_searchable_item_subscription: Option<Subscription>,
@ -76,6 +78,7 @@ pub struct BufferSearchBar {
default_options: SearchOptions,
query_contains_error: bool,
dismissed: bool,
search_history: SearchHistory,
}
impl Entity for BufferSearchBar {
@ -106,6 +109,48 @@ impl View for BufferSearchBar {
.map(|active_searchable_item| active_searchable_item.supported_options())
.unwrap_or_default();
let previous_query_keystrokes =
cx.binding_for_action(&PreviousHistoryQuery {})
.map(|binding| {
binding
.keystrokes()
.iter()
.map(|k| k.to_string())
.collect::<Vec<_>>()
});
let next_query_keystrokes = cx.binding_for_action(&NextHistoryQuery {}).map(|binding| {
binding
.keystrokes()
.iter()
.map(|k| k.to_string())
.collect::<Vec<_>>()
});
let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) {
(Some(previous_query_keystrokes), Some(next_query_keystrokes)) => {
format!(
"Search ({}/{} for previous/next query)",
previous_query_keystrokes.join(" "),
next_query_keystrokes.join(" ")
)
}
(None, Some(next_query_keystrokes)) => {
format!(
"Search ({} for next query)",
next_query_keystrokes.join(" ")
)
}
(Some(previous_query_keystrokes), None) => {
format!(
"Search ({} for previous query)",
previous_query_keystrokes.join(" ")
)
}
(None, None) => String::new(),
};
self.query_editor.update(cx, |editor, cx| {
editor.set_placeholder_text(new_placeholder_text, cx);
});
Flex::row()
.with_child(
Flex::row()
@ -258,6 +303,7 @@ impl BufferSearchBar {
pending_search: None,
query_contains_error: false,
dismissed: true,
search_history: SearchHistory::default(),
}
}
@ -341,7 +387,7 @@ impl BufferSearchBar {
cx: &mut ViewContext<Self>,
) -> oneshot::Receiver<()> {
let options = options.unwrap_or(self.default_options);
if query != self.query_editor.read(cx).text(cx) || self.search_options != options {
if query != self.query(cx) || self.search_options != options {
self.query_editor.update(cx, |query_editor, cx| {
query_editor.buffer().update(cx, |query_buffer, cx| {
let len = query_buffer.len(cx);
@ -674,7 +720,7 @@ impl BufferSearchBar {
fn update_matches(&mut self, cx: &mut ViewContext<Self>) -> oneshot::Receiver<()> {
let (done_tx, done_rx) = oneshot::channel();
let query = self.query_editor.read(cx).text(cx);
let query = self.query(cx);
self.pending_search.take();
if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
if query.is_empty() {
@ -707,6 +753,7 @@ impl BufferSearchBar {
)
};
let query_text = query.as_str().to_string();
let matches = active_searchable_item.find_matches(query, cx);
let active_searchable_item = active_searchable_item.downgrade();
@ -720,6 +767,7 @@ impl BufferSearchBar {
.insert(active_searchable_item.downgrade(), matches);
this.update_match_index(cx);
this.search_history.add(query_text);
if !this.dismissed {
let matches = this
.searchable_items_with_matches
@ -753,6 +801,28 @@ impl BufferSearchBar {
cx.notify();
}
}
fn next_history_query(&mut self, _: &NextHistoryQuery, cx: &mut ViewContext<Self>) {
if let Some(new_query) = self.search_history.next().map(str::to_string) {
let _ = self.search(&new_query, Some(self.search_options), cx);
} else {
self.search_history.reset_selection();
let _ = self.search("", Some(self.search_options), cx);
}
}
fn previous_history_query(&mut self, _: &PreviousHistoryQuery, cx: &mut ViewContext<Self>) {
if self.query(cx).is_empty() {
if let Some(new_query) = self.search_history.current().map(str::to_string) {
let _ = self.search(&new_query, Some(self.search_options), cx);
return;
}
}
if let Some(new_query) = self.search_history.previous().map(str::to_string) {
let _ = self.search(&new_query, Some(self.search_options), cx);
}
}
}
#[cfg(test)]
@ -779,11 +849,10 @@ mod tests {
cx,
)
});
let (window_id, _root_view) = cx.add_window(|_| EmptyView);
let window = cx.add_window(|_| EmptyView);
let editor = window.add_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
let search_bar = cx.add_view(window_id, |cx| {
let search_bar = window.add_view(cx, |cx| {
let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx);
@ -1159,11 +1228,10 @@ mod tests {
"Should pick a query with multiple results"
);
let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
let (window_id, _root_view) = cx.add_window(|_| EmptyView);
let window = cx.add_window(|_| EmptyView);
let editor = window.add_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
let search_bar = cx.add_view(window_id, |cx| {
let search_bar = window.add_view(cx, |cx| {
let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx);
@ -1179,12 +1247,13 @@ mod tests {
search_bar.activate_current_match(cx);
});
cx.read_window(window_id, |cx| {
window.read_with(cx, |cx| {
assert!(
!editor.is_focused(cx),
"Initially, the editor should not be focused"
);
});
let initial_selections = editor.update(cx, |editor, cx| {
let initial_selections = editor.selections.display_ranges(cx);
assert_eq!(
@ -1201,7 +1270,7 @@ mod tests {
cx.focus(search_bar.query_editor.as_any());
search_bar.select_all_matches(&SelectAllMatches, cx);
});
cx.read_window(window_id, |cx| {
window.read_with(cx, |cx| {
assert!(
editor.is_focused(cx),
"Should focus editor after successful SelectAllMatches"
@ -1225,7 +1294,7 @@ mod tests {
search_bar.update(cx, |search_bar, cx| {
search_bar.select_next_match(&SelectNextMatch, cx);
});
cx.read_window(window_id, |cx| {
window.read_with(cx, |cx| {
assert!(
editor.is_focused(cx),
"Should still have editor focused after SelectNextMatch"
@ -1254,7 +1323,7 @@ mod tests {
cx.focus(search_bar.query_editor.as_any());
search_bar.select_all_matches(&SelectAllMatches, cx);
});
cx.read_window(window_id, |cx| {
window.read_with(cx, |cx| {
assert!(
editor.is_focused(cx),
"Should focus editor after successful SelectAllMatches"
@ -1278,7 +1347,7 @@ mod tests {
search_bar.update(cx, |search_bar, cx| {
search_bar.select_prev_match(&SelectPrevMatch, cx);
});
cx.read_window(window_id, |cx| {
window.read_with(cx, |cx| {
assert!(
editor.is_focused(cx),
"Should still have editor focused after SelectPrevMatch"
@ -1314,7 +1383,7 @@ mod tests {
search_bar.update(cx, |search_bar, cx| {
search_bar.select_all_matches(&SelectAllMatches, cx);
});
cx.read_window(window_id, |cx| {
window.read_with(cx, |cx| {
assert!(
!editor.is_focused(cx),
"Should not switch focus to editor if SelectAllMatches does not find any matches"
@ -1333,4 +1402,154 @@ mod tests {
);
});
}
#[gpui::test]
async fn test_search_query_history(cx: &mut TestAppContext) {
crate::project_search::tests::init_test(cx);
let buffer_text = r#"
A regular expression (shortened as regex or regexp;[1] also referred to as
rational expression[2][3]) is a sequence of characters that specifies a search
pattern in text. Usually such patterns are used by string-searching algorithms
for "find" or "find and replace" operations on strings, or for input validation.
"#
.unindent();
let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
let window = cx.add_window(|_| EmptyView);
let editor = window.add_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
let search_bar = window.add_view(cx, |cx| {
let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx);
search_bar
});
// Add 3 search items into the history.
search_bar
.update(cx, |search_bar, cx| search_bar.search("a", None, cx))
.await
.unwrap();
search_bar
.update(cx, |search_bar, cx| search_bar.search("b", None, cx))
.await
.unwrap();
search_bar
.update(cx, |search_bar, cx| {
search_bar.search("c", Some(SearchOptions::CASE_SENSITIVE), cx)
})
.await
.unwrap();
// Ensure that the latest search is active.
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "c");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
// Next history query after the latest should set the query to the empty string.
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
// First previous query for empty current query should set the query to the latest.
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "c");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
// Further previous items should go over the history in reverse order.
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "b");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
// Previous items should never go behind the first history item.
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "a");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "a");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
// Next items should go over the history in the original order.
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "b");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
search_bar
.update(cx, |search_bar, cx| search_bar.search("ba", None, cx))
.await
.unwrap();
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "ba");
assert_eq!(search_bar.search_options, SearchOptions::NONE);
});
// New search input should add another entry to history and move the selection to the end of the history.
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "c");
assert_eq!(search_bar.search_options, SearchOptions::NONE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "b");
assert_eq!(search_bar.search_options, SearchOptions::NONE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "c");
assert_eq!(search_bar.search_options, SearchOptions::NONE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "ba");
assert_eq!(search_bar.search_options, SearchOptions::NONE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "");
assert_eq!(search_bar.search_options, SearchOptions::NONE);
});
}
}

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