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

View file

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

View file

@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.2 # syntax = docker/dockerfile:1.2
FROM rust:1.70-bullseye as builder FROM rust:1.71-bullseye as builder
WORKDIR app WORKDIR app
COPY . . 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": { "suffixes": {
"aac": "audio", "aac": "audio",
"bash": "terminal", "accdb": "storage",
"bmp": "image", "bak": "backup",
"c": "code", "bash": "terminal",
"conf": "settings", "bash_aliases": "terminal",
"cpp": "code", "bash_logout": "terminal",
"cc": "code", "bash_profile": "terminal",
"css": "code", "bashrc": "terminal",
"doc": "document", "bmp": "image",
"docx": "document", "c": "code",
"eslintrc": "eslint", "cc": "code",
"eslintrc.js": "eslint", "conf": "settings",
"eslintrc.json": "eslint", "cpp": "code",
"flac": "audio", "css": "code",
"fish": "terminal", "csv": "storage",
"gitattributes": "vcs", "dat": "storage",
"gitignore": "vcs", "db": "storage",
"gitmodules": "vcs", "dbf": "storage",
"gif": "image", "dll": "storage",
"go": "code", "doc": "document",
"h": "code", "docx": "document",
"handlebars": "code", "eslintrc": "eslint",
"hbs": "template", "eslintrc.js": "eslint",
"htm": "template", "eslintrc.json": "eslint",
"html": "template", "fmp": "storage",
"svelte": "template", "fp7": "storage",
"hpp": "code", "flac": "audio",
"ico": "image", "fish": "terminal",
"ini": "settings", "frm": "storage",
"java": "code", "gdb": "storage",
"jpeg": "image", "gitattributes": "vcs",
"jpg": "image", "gitignore": "vcs",
"js": "code", "gitmodules": "vcs",
"json": "storage", "gif": "image",
"lock": "lock", "go": "code",
"log": "log", "h": "code",
"md": "document", "handlebars": "code",
"mdx": "document", "hbs": "template",
"mp3": "audio", "htm": "template",
"mp4": "video", "html": "template",
"ods": "document", "ib": "storage",
"odp": "document", "ico": "image",
"odt": "document", "ini": "settings",
"ogg": "video", "java": "code",
"pdf": "document", "jpeg": "image",
"php": "code", "jpg": "image",
"png": "image", "js": "code",
"ppt": "document", "json": "storage",
"pptx": "document", "ldf": "storage",
"prettierrc": "prettier", "lock": "lock",
"prettierignore": "prettier", "log": "log",
"ps1": "terminal", "mdb": "storage",
"psd": "image", "md": "document",
"py": "code", "mdf": "storage",
"rb": "code", "mdx": "document",
"rkt": "code", "mp3": "audio",
"rs": "rust", "mp4": "video",
"rtf": "document", "myd": "storage",
"scm": "code", "myi": "storage",
"sh": "terminal", "ods": "document",
"bashrc": "terminal", "odp": "document",
"bash_profile": "terminal", "odt": "document",
"bash_aliases": "terminal", "ogg": "video",
"bash_logout": "terminal", "pdb": "storage",
"profile": "terminal", "pdf": "document",
"zshrc": "terminal", "php": "code",
"zshenv": "terminal", "png": "image",
"zsh_profile": "terminal", "ppt": "document",
"zsh_aliases": "terminal", "pptx": "document",
"zsh_histfile": "terminal", "prettierignore": "prettier",
"zlogin": "terminal", "prettierrc": "prettier",
"sql": "code", "profile": "terminal",
"svg": "image", "ps1": "terminal",
"swift": "code", "psd": "image",
"tiff": "image", "py": "code",
"toml": "toml", "rb": "code",
"ts": "typescript", "rkt": "code",
"tsx": "code", "rs": "rust",
"txt": "document", "rtf": "document",
"wav": "audio", "sav": "storage",
"webm": "video", "scm": "code",
"xls": "document", "sh": "terminal",
"xlsx": "document", "sqlite": "storage",
"xml": "template", "sdf": "storage",
"yaml": "settings", "svelte": "template",
"yml": "settings", "svg": "image",
"zsh": "terminal" "swift": "code",
}, "ts": "typescript",
"types": { "tsx": "code",
"audio": { "tiff": "image",
"icon": "icons/file_icons/audio.svg" "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": { "types": {
"icon": "icons/file_icons/code.svg" "audio": {
}, "icon": "icons/file_icons/audio.svg"
"collapsed_chevron": { },
"icon": "icons/file_icons/chevron_right.svg" "code": {
}, "icon": "icons/file_icons/code.svg"
"collapsed_folder": { },
"icon": "icons/file_icons/folder.svg" "collapsed_chevron": {
}, "icon": "icons/file_icons/chevron_right.svg"
"default": { },
"icon": "icons/file_icons/file.svg" "collapsed_folder": {
}, "icon": "icons/file_icons/folder.svg"
"document": { },
"icon": "icons/file_icons/book.svg" "default": {
}, "icon": "icons/file_icons/file.svg"
"eslint": { },
"icon": "icons/file_icons/eslint.svg" "document": {
}, "icon": "icons/file_icons/book.svg"
"expanded_chevron": { },
"icon": "icons/file_icons/chevron_down.svg" "eslint": {
}, "icon": "icons/file_icons/eslint.svg"
"expanded_folder": { },
"icon": "icons/file_icons/folder_open.svg" "expanded_chevron": {
}, "icon": "icons/file_icons/chevron_down.svg"
"image": { },
"icon": "icons/file_icons/image.svg" "expanded_folder": {
}, "icon": "icons/file_icons/folder_open.svg"
"lock": { },
"icon": "icons/file_icons/lock.svg" "image": {
}, "icon": "icons/file_icons/image.svg"
"log": { },
"icon": "icons/file_icons/info.svg" "lock": {
}, "icon": "icons/file_icons/lock.svg"
"prettier": { },
"icon": "icons/file_icons/prettier.svg" "log": {
}, "icon": "icons/file_icons/info.svg"
"rust": { },
"icon": "icons/file_icons/rust.svg" "prettier": {
}, "icon": "icons/file_icons/prettier.svg"
"settings": { },
"icon": "icons/file_icons/settings.svg" "rust": {
}, "icon": "icons/file_icons/rust.svg"
"storage": { },
"icon": "icons/file_icons/database.svg" "settings": {
}, "icon": "icons/file_icons/settings.svg"
"template": { },
"icon": "icons/file_icons/html.svg" "storage": {
}, "icon": "icons/file_icons/database.svg"
"terminal": { },
"icon": "icons/file_icons/terminal.svg" "template": {
}, "icon": "icons/file_icons/html.svg"
"toml": { },
"icon": "icons/file_icons/toml.svg" "terminal": {
}, "icon": "icons/file_icons/terminal.svg"
"typescript": { },
"icon": "icons/file_icons/typescript.svg" "toml": {
}, "icon": "icons/file_icons/toml.svg"
"vcs": { },
"icon": "icons/file_icons/git.svg" "typescript": {
}, "icon": "icons/file_icons/typescript.svg"
"video": { },
"icon": "icons/file_icons/video.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" "alt-enter": "search::SelectAllMatches"
} }
}, },
{
"context": "BufferSearchBar > Editor",
"bindings": {
"up": "search::PreviousHistoryQuery",
"down": "search::NextHistoryQuery"
}
},
{ {
"context": "ProjectSearchBar", "context": "ProjectSearchBar",
"bindings": { "bindings": {
"escape": "project_search::ToggleFocus" "escape": "project_search::ToggleFocus"
} }
}, },
{
"context": "ProjectSearchBar > Editor",
"bindings": {
"up": "search::PreviousHistoryQuery",
"down": "search::NextHistoryQuery"
}
},
{ {
"context": "ProjectSearchView", "context": "ProjectSearchView",
"bindings": { "bindings": {

View file

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

View file

@ -12,10 +12,7 @@ use client::{
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use fs::FakeFs; use fs::FakeFs;
use futures::{channel::oneshot, StreamExt as _}; use futures::{channel::oneshot, StreamExt as _};
use gpui::{ use gpui::{executor::Deterministic, ModelHandle, TestAppContext, WindowHandle};
elements::*, executor::Deterministic, AnyElement, Entity, ModelHandle, TestAppContext, View,
ViewContext, ViewHandle, WeakViewHandle,
};
use language::LanguageRegistry; use language::LanguageRegistry;
use parking_lot::Mutex; use parking_lot::Mutex;
use project::{Project, WorktreeId}; use project::{Project, WorktreeId};
@ -466,42 +463,8 @@ impl TestClient {
&self, &self,
project: &ModelHandle<Project>, project: &ModelHandle<Project>,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) -> ViewHandle<Workspace> { ) -> WindowHandle<Workspace> {
struct WorkspaceContainer { cx.add_window(|cx| Workspace::test_new(project.clone(), cx))
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
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,7 +7,7 @@ use gpui::{
elements::*, elements::*,
geometry::{rect::RectF, vector::vec2f}, geometry::{rect::RectF, vector::vec2f},
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions}, platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
AnyElement, AppContext, Entity, View, ViewContext, AnyElement, AppContext, Entity, View, ViewContext, WindowHandle,
}; };
use util::ResultExt; use util::ResultExt;
use workspace::AppState; 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 app_state = Arc::downgrade(app_state);
let mut incoming_call = ActiveCall::global(cx).read(cx).incoming(); let mut incoming_call = ActiveCall::global(cx).read(cx).incoming();
cx.spawn(|mut cx| async move { 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 { while let Some(incoming_call) = incoming_call.next().await {
for window_id in notification_windows.drain(..) { for window in notification_windows.drain(..) {
cx.remove_window(window_id); window.remove(&mut cx);
} }
if let Some(incoming_call) = incoming_call { 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() { for screen in cx.platform().screens() {
let screen_bounds = screen.bounds(); let screen_bounds = screen.bounds();
let (window_id, _) = cx.add_window( let window = cx.add_window(
WindowOptions { WindowOptions {
bounds: WindowBounds::Fixed(RectF::new( bounds: WindowBounds::Fixed(RectF::new(
screen_bounds.upper_right() 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()), |_| 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() { for screen in cx.platform().screens() {
let screen_bounds = screen.bounds(); let screen_bounds = screen.bounds();
let (window_id, _) = cx.add_window( let window = cx.add_window(
WindowOptions { WindowOptions {
bounds: WindowBounds::Fixed(RectF::new( bounds: WindowBounds::Fixed(RectF::new(
screen_bounds.upper_right() - vec2f(PADDING + window_size.x(), PADDING), 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 notification_windows
.entry(*project_id) .entry(*project_id)
.or_insert(Vec::new()) .or_insert(Vec::new())
.push(window_id); .push(window);
} }
} }
room::Event::RemoteProjectUnshared { project_id } => { room::Event::RemoteProjectUnshared { project_id } => {
if let Some(window_ids) = notification_windows.remove(&project_id) { if let Some(windows) = notification_windows.remove(&project_id) {
for window_id in window_ids { for window in windows {
cx.update_window(window_id, |cx| cx.remove_window()); window.remove(cx);
} }
} }
} }
room::Event::Left => { room::Event::Left => {
for (_, window_ids) in notification_windows.drain() { for (_, windows) in notification_windows.drain() {
for window_id in window_ids { for window in windows {
cx.update_window(window_id, |cx| cx.remove_window()); window.remove(cx);
} }
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -28,6 +28,7 @@ use blink_manager::BlinkManager;
use client::{ClickhouseEvent, TelemetrySettings}; use client::{ClickhouseEvent, TelemetrySettings};
use clock::{Global, ReplicaId}; use clock::{Global, ReplicaId};
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing};
use copilot::Copilot; use copilot::Copilot;
pub use display_map::DisplayPoint; pub use display_map::DisplayPoint;
use display_map::*; use display_map::*;
@ -89,7 +90,7 @@ use std::{
cmp::{self, Ordering, Reverse}, cmp::{self, Ordering, Reverse},
mem, mem,
num::NonZeroU32, num::NonZeroU32,
ops::{ControlFlow, Deref, DerefMut, Range}, ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
path::Path, path::Path,
sync::Arc, sync::Arc,
time::{Duration, Instant}, time::{Duration, Instant},
@ -231,6 +232,13 @@ actions!(
SortLinesCaseInsensitive, SortLinesCaseInsensitive,
ReverseLines, ReverseLines,
ShuffleLines, ShuffleLines,
ConvertToUpperCase,
ConvertToLowerCase,
ConvertToTitleCase,
ConvertToSnakeCase,
ConvertToKebabCase,
ConvertToUpperCamelCase,
ConvertToLowerCamelCase,
Transpose, Transpose,
Cut, Cut,
Copy, Copy,
@ -353,6 +361,13 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::sort_lines_case_insensitive); cx.add_action(Editor::sort_lines_case_insensitive);
cx.add_action(Editor::reverse_lines); cx.add_action(Editor::reverse_lines);
cx.add_action(Editor::shuffle_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_word_start);
cx.add_action(Editor::delete_to_previous_subword_start); cx.add_action(Editor::delete_to_previous_subword_start);
cx.add_action(Editor::delete_to_next_word_end); cx.add_action(Editor::delete_to_next_word_end);
@ -543,6 +558,7 @@ pub struct Editor {
show_local_selections: bool, show_local_selections: bool,
mode: EditorMode, mode: EditorMode,
show_gutter: bool, show_gutter: bool,
show_wrap_guides: Option<bool>,
placeholder_text: Option<Arc<str>>, placeholder_text: Option<Arc<str>>,
highlighted_rows: Option<Range<u32>>, highlighted_rows: Option<Range<u32>>,
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
@ -1375,6 +1391,7 @@ impl Editor {
show_local_selections: true, show_local_selections: true,
mode, mode,
show_gutter: mode == EditorMode::Full, show_gutter: mode == EditorMode::Full,
show_wrap_guides: None,
placeholder_text: None, placeholder_text: None,
highlighted_rows: None, highlighted_rows: None,
background_highlights: Default::default(), background_highlights: Default::default(),
@ -2706,7 +2723,7 @@ impl Editor {
.collect() .collect()
} }
fn excerpt_visible_offsets( pub fn excerpt_visible_offsets(
&self, &self,
restrict_to_languages: Option<&HashSet<Arc<Language>>>, restrict_to_languages: Option<&HashSet<Arc<Language>>>,
cx: &mut ViewContext<'_, '_, Editor>, cx: &mut ViewContext<'_, '_, Editor>,
@ -4219,7 +4236,7 @@ impl Editor {
_: &SortLinesCaseSensitive, _: &SortLinesCaseSensitive,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.manipulate_lines(cx, |text| text.sort()) self.manipulate_lines(cx, |lines| lines.sort())
} }
pub fn sort_lines_case_insensitive( pub fn sort_lines_case_insensitive(
@ -4227,7 +4244,7 @@ impl Editor {
_: &SortLinesCaseInsensitive, _: &SortLinesCaseInsensitive,
cx: &mut ViewContext<Self>, 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>) { pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext<Self>) {
@ -4265,19 +4282,19 @@ impl Editor {
let text = buffer let text = buffer
.text_for_range(start_point..end_point) .text_for_range(start_point..end_point)
.collect::<String>(); .collect::<String>();
let mut text = text.split("\n").collect_vec(); let mut lines = text.split("\n").collect_vec();
let text_len = text.len(); let lines_len = lines.len();
callback(&mut text); callback(&mut lines);
// This is a current limitation with selections. // 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. // If we wanted to support removing or adding lines, we'd need to fix the logic associated with selections.
debug_assert!( debug_assert!(
text.len() == text_len, lines.len() == lines_len,
"callback should not change the number of lines" "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 start_anchor = buffer.anchor_after(start_point);
let end_anchor = buffer.anchor_before(end_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>) { 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 display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot; let buffer = &display_map.buffer_snapshot;
@ -7187,6 +7295,10 @@ impl Editor {
pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> { pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
let mut wrap_guides = smallvec::smallvec![]; 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); let settings = self.buffer.read(cx).settings_at(0, cx);
if settings.show_wrap_guides { if settings.show_wrap_guides {
if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) { if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
@ -7244,6 +7356,11 @@ impl Editor {
cx.notify(); 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>) { 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(buffer) = self.buffer().read(cx).as_singleton() {
if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) { if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
@ -7432,6 +7549,78 @@ impl Editor {
results 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>( pub fn highlight_text<T: 'static>(
&mut self, &mut self,
ranges: Vec<Range<Anchor>>, 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... // We really need to rethink this ID system...
.with_tooltip::<BlockContextToolip>( .with_tooltip::<BlockContextToolip>(
cx.block_id, cx.block_id,
"Copy diagnostic message".to_string(), "Copy diagnostic message",
None, None,
tooltip_style, tooltip_style,
cx, 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}, platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
text_layout::{self, Line, RunStyle, TextLayoutCache}, text_layout::{self, Line, RunStyle, TextLayoutCache},
AnyElement, Axis, Border, CursorRegion, Element, EventContext, FontCache, LayoutContext, 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 itertools::Itertools;
use json::json; use json::json;
@ -508,13 +508,13 @@ impl EditorElement {
bounds: gutter_bounds, bounds: gutter_bounds,
background: Some(self.style.gutter_background), background: Some(self.style.gutter_background),
border: Border::new(0., Color::transparent_black()), border: Border::new(0., Color::transparent_black()),
corner_radius: 0., corner_radii: Default::default(),
}); });
scene.push_quad(Quad { scene.push_quad(Quad {
bounds: text_bounds, bounds: text_bounds,
background: Some(self.style.background), background: Some(self.style.background),
border: Border::new(0., Color::transparent_black()), border: Border::new(0., Color::transparent_black()),
corner_radius: 0., corner_radii: Default::default(),
}); });
if let EditorMode::Full = layout.mode { if let EditorMode::Full = layout.mode {
@ -542,7 +542,7 @@ impl EditorElement {
bounds: RectF::new(origin, size), bounds: RectF::new(origin, size),
background: Some(self.style.active_line_background), background: Some(self.style.active_line_background),
border: Border::default(), border: Border::default(),
corner_radius: 0., corner_radii: Default::default(),
}); });
} }
} }
@ -562,12 +562,24 @@ impl EditorElement {
bounds: RectF::new(origin, size), bounds: RectF::new(origin, size),
background: Some(self.style.highlighted_line_background), background: Some(self.style.highlighted_line_background),
border: Border::default(), 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() { 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 { let color = if *active {
self.style.active_wrap_guide self.style.active_wrap_guide
} else { } else {
@ -580,7 +592,7 @@ impl EditorElement {
), ),
background: Some(color), background: Some(color),
border: Border::new(0., Color::transparent_black()), border: Border::new(0., Color::transparent_black()),
corner_radius: 0., corner_radii: Default::default(),
}); });
} }
} }
@ -681,7 +693,7 @@ impl EditorElement {
bounds: highlight_bounds, bounds: highlight_bounds,
background: Some(diff_style.modified), background: Some(diff_style.modified),
border: Border::new(0., Color::transparent_black()), border: Border::new(0., Color::transparent_black()),
corner_radius: 1. * line_height, corner_radii: (1. * line_height).into(),
}); });
continue; continue;
@ -714,7 +726,7 @@ impl EditorElement {
bounds: highlight_bounds, bounds: highlight_bounds,
background: Some(diff_style.deleted), background: Some(diff_style.deleted),
border: Border::new(0., Color::transparent_black()), border: Border::new(0., Color::transparent_black()),
corner_radius: 1. * line_height, corner_radii: (1. * line_height).into(),
}); });
continue; continue;
@ -736,7 +748,7 @@ impl EditorElement {
bounds: highlight_bounds, bounds: highlight_bounds,
background: Some(color), background: Some(color),
border: Border::new(0., Color::transparent_black()), 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(); scene.pop_layer();
} }
fn scrollbar_left(&self, bounds: &RectF) -> f32 {
bounds.max_x() - self.style.theme.scrollbar.width
}
fn paint_scrollbar( fn paint_scrollbar(
&mut self, &mut self,
scene: &mut SceneBuilder, scene: &mut SceneBuilder,
@ -1074,7 +1090,7 @@ impl EditorElement {
let top = bounds.min_y(); let top = bounds.min_y();
let bottom = bounds.max_y(); let bottom = bounds.max_y();
let right = bounds.max_x(); let right = bounds.max_x();
let left = right - style.width; let left = self.scrollbar_left(&bounds);
let row_range = &layout.scrollbar_row_range; let row_range = &layout.scrollbar_row_range;
let max_row = layout.max_row as f32 + (row_range.end - row_range.start); 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 { if layout.is_singleton && scrollbar_settings.selections {
let start_anchor = Anchor::min(); let start_anchor = Anchor::min();
let end_anchor = Anchor::max(); let end_anchor = Anchor::max();
let mut start_row = None;
let mut end_row = None;
let color = scrollbar_theme.selections; let color = scrollbar_theme.selections;
let border = Border { let border = Border {
width: 1., width: 1.,
@ -1123,54 +1137,32 @@ impl EditorElement {
bottom: false, bottom: false,
left: true, left: true,
}; };
let mut push_region = |start, end| { let mut push_region = |start: DisplayPoint, end: DisplayPoint| {
if let (Some(start_display), Some(end_display)) = (start, end) { let start_y = y_for_row(start.row() as f32);
let start_y = y_for_row(start_display as f32); let mut end_y = y_for_row(end.row() as f32);
let mut end_y = y_for_row(end_display as f32); if end_y - start_y < 1. {
if end_y - start_y < 1. { 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 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 let background_ranges = editor
.background_highlights_in_range_for::<crate::items::BufferSearchHighlights>( .background_highlight_row_ranges::<crate::items::BufferSearchHighlights>(
start_anchor..end_anchor, start_anchor..end_anchor,
&layout.position_map.snapshot, &layout.position_map.snapshot,
&theme, 50000,
) );
{ for row in background_ranges {
let start_display = row.start; let start = row.start();
let end_display = row.end; let end = row.end();
push_region(*start, *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!();
}
} }
// 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 { if layout.is_singleton && scrollbar_settings.git_diff {
@ -1217,7 +1209,7 @@ impl EditorElement {
bounds, bounds,
background: Some(color), background: Some(color),
border, border,
corner_radius: style.thumb.corner_radius, corner_radii: style.thumb.corner_radii.into(),
}) })
} }
} }
@ -1226,7 +1218,7 @@ impl EditorElement {
bounds: thumb_bounds, bounds: thumb_bounds,
border: style.thumb.border, border: style.thumb.border,
background: style.thumb.background_color, 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, visible_bounds: RectF,
layout: &mut Self::LayoutState, layout: &mut Self::LayoutState,
editor: &mut Editor, editor: &mut Editor,
cx: &mut ViewContext<Editor>, cx: &mut PaintContext<Editor>,
) -> Self::PaintState { ) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
scene.push_layer(Some(visible_bounds)); scene.push_layer(Some(visible_bounds));
@ -2751,14 +2743,14 @@ impl Cursor {
bounds, bounds,
background: None, background: None,
border: Border::all(1., self.color), border: Border::all(1., self.color),
corner_radius: 0., corner_radii: Default::default(),
}); });
} else { } else {
scene.push_quad(Quad { scene.push_quad(Quad {
bounds, bounds,
background: Some(self.color), background: Some(self.color),
border: Default::default(), border: Default::default(),
corner_radius: 0., corner_radii: Default::default(),
}); });
} }
@ -2998,16 +2990,18 @@ mod tests {
use language::language_settings; use language::language_settings;
use log::info; use log::info;
use std::{num::NonZeroU32, sync::Arc}; use std::{num::NonZeroU32, sync::Arc};
use util::test::{generate_marked_text, sample_text}; use util::test::sample_text;
#[gpui::test] #[gpui::test]
fn test_layout_line_numbers(cx: &mut TestAppContext) { fn test_layout_line_numbers(cx: &mut TestAppContext) {
init_test(cx, |_| {}); init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| { let editor = cx
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx); .add_window(|cx| {
Editor::new(EditorMode::Full, buffer, None, None, 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 element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let layouts = editor.update(cx, |editor, cx| { let layouts = editor.update(cx, |editor, cx| {
@ -3023,10 +3017,12 @@ mod tests {
async fn test_vim_visual_selections(cx: &mut TestAppContext) { async fn test_vim_visual_selections(cx: &mut TestAppContext) {
init_test(cx, |_| {}); init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| { let editor = cx
let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx); .add_window(|cx| {
Editor::new(EditorMode::Full, buffer, None, None, 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 mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let (_, state) = editor.update(cx, |editor, cx| { let (_, state) = editor.update(cx, |editor, cx| {
editor.cursor_shape = CursorShape::Block; editor.cursor_shape = CursorShape::Block;
@ -3099,25 +3095,27 @@ mod tests {
// 13: bbbbbb // 13: bbbbbb
// 14: cccccc // 14: cccccc
// 15: dddddd // 15: dddddd
let (_, editor) = cx.add_window(|cx| { let editor = cx
let buffer = MultiBuffer::build_multi( .add_window(|cx| {
[ let buffer = MultiBuffer::build_multi(
( [
&(sample_text(8, 6, 'a') + "\n"), (
vec![ &(sample_text(8, 6, 'a') + "\n"),
Point::new(0, 0)..Point::new(3, 0), vec![
Point::new(4, 0)..Point::new(7, 0), 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)], &(sample_text(8, 6, 'a') + "\n"),
), vec![Point::new(1, 0)..Point::new(3, 0)],
], ),
cx, ],
); cx,
Editor::new(EditorMode::Full, buffer, None, None, 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 mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let (_, state) = editor.update(cx, |editor, cx| { let (_, state) = editor.update(cx, |editor, cx| {
editor.cursor_shape = CursorShape::Block; editor.cursor_shape = CursorShape::Block;
@ -3167,10 +3165,12 @@ mod tests {
fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) { fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
init_test(cx, |_| {}); init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| { let editor = cx
let buffer = MultiBuffer::build_simple("", cx); .add_window(|cx| {
Editor::new(EditorMode::Full, buffer, None, None, cx) let buffer = MultiBuffer::build_simple("", cx);
}); Editor::new(EditorMode::Full, buffer, None, None, cx)
})
.root(cx);
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
editor.set_placeholder_text("hello", cx); editor.set_placeholder_text("hello", cx);
@ -3221,7 +3221,14 @@ mod tests {
let mut scene = SceneBuilder::new(1.0); let mut scene = SceneBuilder::new(1.0);
let bounds = RectF::new(Default::default(), size); let bounds = RectF::new(Default::default(), size);
editor.update(cx, |editor, cx| { 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!( info!(
"Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'" "Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'"
); );
let (_, editor) = cx.add_window(|cx| { let editor = cx
let buffer = MultiBuffer::build_simple(&input_text, cx); .add_window(|cx| {
Editor::new(editor_mode, buffer, None, None, 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 mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let (_, layout_state) = editor.update(cx, |editor, cx| { let (_, layout_state) = editor.update(cx, |editor, cx| {

View file

@ -599,7 +599,7 @@ impl InfoPopover {
bounds, bounds,
background: Some(code_span_background_color), background: Some(code_span_background_color),
border: Default::default(), 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}, path::{Path, PathBuf},
}; };
use text::Selection; 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::{BreadcrumbText, FollowableItemHandle};
use workspace::{ use workspace::{
item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem}, item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
@ -546,9 +549,7 @@ impl Item for Editor {
.and_then(|f| f.as_local())? .and_then(|f| f.as_local())?
.abs_path(cx); .abs_path(cx);
let file_path = util::paths::compact(&file_path) let file_path = file_path.compact().to_string_lossy().to_string();
.to_string_lossy()
.to_string();
Some(file_path.into()) Some(file_path.into())
} }

View file

@ -13,7 +13,7 @@ use gpui::{
}; };
use language::{Bias, Point}; use language::{Bias, Point};
use util::ResultExt; use util::ResultExt;
use workspace::{item::Item, WorkspaceId}; use workspace::WorkspaceId;
use crate::{ use crate::{
display_map::{DisplaySnapshot, ToDisplayPoint}, display_map::{DisplaySnapshot, ToDisplayPoint},
@ -333,9 +333,7 @@ impl Editor {
cx, 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 { 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(): "" }})) .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
.await; .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 project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.find_or_create_local_worktree("/root", true, cx) project.find_or_create_local_worktree("/root", true, cx)
@ -98,7 +99,7 @@ impl<'a> EditorLspTestContext<'a> {
Self { Self {
cx: EditorTestContext { cx: EditorTestContext {
cx, cx,
window_id, window: window.into(),
editor, editor,
}, },
lsp, lsp,

View file

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

View file

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

View file

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

View file

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

View file

@ -22,6 +22,7 @@ sqlez = { path = "../sqlez" }
async-task = "4.0.3" async-task = "4.0.3"
backtrace = { version = "0.3", optional = true } backtrace = { version = "0.3", optional = true }
ctor.workspace = true ctor.workspace = true
derive_more.workspace = true
dhat = { version = "0.3", optional = true } dhat = { version = "0.3", optional = true }
env_logger = { version = "0.9", optional = true } env_logger = { version = "0.9", optional = true }
etagere = "0.2" 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(); let cx = app.0.clone();
move |action| { move |action| {
let mut cx = cx.borrow_mut(); let mut cx = cx.borrow_mut();
if let Some(main_window_id) = cx.platform.main_window_id() { if let Some(main_window) = cx.active_window() {
let dispatched = cx let dispatched = main_window
.update_window(main_window_id, |cx| { .update(&mut *cx, |cx| {
if let Some(view_id) = cx.focused_view_id() { if let Some(view_id) = cx.focused_view_id() {
cx.dispatch_action(Some(view_id), action); cx.dispatch_action(Some(view_id), action);
true true

View file

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

View file

@ -4,9 +4,9 @@ use crate::{
keymap_matcher::{Binding, Keystroke}, keymap_matcher::{Binding, Keystroke},
platform, platform,
platform::{Event, InputHandler, KeyDownEvent, Platform}, platform::{Event, InputHandler, KeyDownEvent, Platform},
Action, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache, Handle, Action, AnyWindowHandle, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache,
ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakHandle, Handle, ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle,
WindowContext, WeakHandle, WindowContext, WindowHandle,
}; };
use collections::BTreeMap; use collections::BTreeMap;
use futures::Future; use futures::Future;
@ -60,7 +60,7 @@ impl TestAppContext {
RefCounts::new(leak_detector), RefCounts::new(leak_detector),
(), (),
); );
cx.next_entity_id = first_entity_id; cx.next_id = first_entity_id;
let cx = TestAppContext { let cx = TestAppContext {
cx: Rc::new(RefCell::new(cx)), cx: Rc::new(RefCell::new(cx)),
foreground_platform, foreground_platform,
@ -72,8 +72,8 @@ impl TestAppContext {
cx cx
} }
pub fn dispatch_action<A: Action>(&mut self, window_id: usize, action: A) { pub fn dispatch_action<A: Action>(&mut self, window: AnyWindowHandle, action: A) {
self.update_window(window_id, |window| { self.update_window(window, |window| {
window.dispatch_action(window.focused_view_id(), &action); window.dispatch_action(window.focused_view_id(), &action);
}) })
.expect("window not found"); .expect("window not found");
@ -81,10 +81,10 @@ impl TestAppContext {
pub fn available_actions( pub fn available_actions(
&self, &self,
window_id: usize, window: AnyWindowHandle,
view_id: usize, view_id: usize,
) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> { ) -> 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() .unwrap_or_default()
} }
@ -92,33 +92,34 @@ impl TestAppContext {
self.update(|cx| cx.dispatch_global_action_any(&action)); self.update(|cx| cx.dispatch_global_action_any(&action));
} }
pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) { pub fn dispatch_keystroke(
let handled = self &mut self,
.cx window: AnyWindowHandle,
.borrow_mut() keystroke: Keystroke,
.update_window(window_id, |cx| { is_held: bool,
if cx.dispatch_keystroke(&keystroke) { ) {
return true; let handled = window.update(self, |cx| {
} if cx.dispatch_keystroke(&keystroke) {
return true;
}
if cx.dispatch_event( if cx.dispatch_event(
Event::KeyDown(KeyDownEvent { Event::KeyDown(KeyDownEvent {
keystroke: keystroke.clone(), keystroke: keystroke.clone(),
is_held, is_held,
}), }),
false, false,
) { ) {
return true; return true;
} }
false false
}) });
.unwrap_or(false);
if !handled && !keystroke.cmd && !keystroke.ctrl { if !handled && !keystroke.cmd && !keystroke.ctrl {
WindowInputHandler { WindowInputHandler {
app: self.cx.clone(), app: self.cx.clone(),
window_id, window,
} }
.replace_text_in_range(None, &keystroke.key) .replace_text_in_range(None, &keystroke.key)
} }
@ -126,18 +127,18 @@ impl TestAppContext {
pub fn read_window<T, F: FnOnce(&WindowContext) -> T>( pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
&self, &self,
window_id: usize, window: AnyWindowHandle,
callback: F, callback: F,
) -> Option<T> { ) -> 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>( pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self, &mut self,
window_id: usize, window: AnyWindowHandle,
callback: F, callback: F,
) -> Option<T> { ) -> 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> 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) 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 where
T: View, V: View,
F: FnOnce(&mut ViewContext<T>) -> T, F: FnOnce(&mut ViewContext<V>) -> V,
{ {
let (window_id, view) = self let window = self
.cx .cx
.borrow_mut() .borrow_mut()
.add_window(Default::default(), build_root_view); .add_window(Default::default(), build_root_view);
self.simulate_window_activation(Some(window_id)); window.simulate_activation(self);
(window_id, view) window
}
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")
} }
pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription
@ -190,8 +182,8 @@ impl TestAppContext {
self.cx.borrow_mut().subscribe_global(callback) self.cx.borrow_mut().subscribe_global(callback)
} }
pub fn window_ids(&self) -> Vec<usize> { pub fn windows(&self) -> Vec<AnyWindowHandle> {
self.cx.borrow().windows.keys().copied().collect() self.cx.borrow().windows().collect()
} }
pub fn remove_all_windows(&mut self) { pub fn remove_all_windows(&mut self) {
@ -261,76 +253,6 @@ impl TestAppContext {
self.foreground_platform.as_ref().did_prompt_for_new_path() 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>> { pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
self.cx.borrow().leak_detector() self.cx.borrow().leak_detector()
} }
@ -351,18 +273,6 @@ impl TestAppContext {
self.assert_dropped(weak); 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>) { pub fn set_condition_duration(&mut self, duration: Option<Duration>) {
self.condition_duration = duration; self.condition_duration = duration;
} }
@ -405,19 +315,39 @@ impl BorrowAppContext for TestAppContext {
} }
impl BorrowWindowContext 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 self.cx
.borrow() .borrow()
.read_window(window_id, f) .read_window(window, f)
.expect("window was closed") .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 self.cx
.borrow_mut() .borrow_mut()
.update_window(window_id, f) .update_window(window, f)
.expect("window was closed") .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> { 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> { impl<T: View> ViewHandle<T> {
pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> { pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
use postage::prelude::{Sink as _, Stream as _}; use postage::prelude::{Sink as _, Stream as _};

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ use std::marker::PhantomData;
use super::Element; use super::Element;
use crate::{ use crate::{
json::{self, json}, json::{self, json},
SceneBuilder, View, ViewContext, PaintContext, SceneBuilder, View, ViewContext,
}; };
use json::ToJson; use json::ToJson;
use pathfinder_geometry::{ use pathfinder_geometry::{
@ -56,7 +56,7 @@ where
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
view: &mut V, view: &mut V,
cx: &mut ViewContext<V>, cx: &mut PaintContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
self.0(scene, bounds, visible_bounds, view, cx) 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 serde_json::json;
use crate::{ use crate::{
json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
}; };
pub struct Clipped<V: View> { pub struct Clipped<V: View> {
@ -37,7 +38,7 @@ impl<V: View> Element<V> for Clipped<V> {
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
view: &mut V, view: &mut V,
cx: &mut ViewContext<V>, cx: &mut PaintContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
scene.paint_layer(Some(bounds), |scene| { scene.paint_layer(Some(bounds), |scene| {
self.child self.child

View file

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

View file

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

View file

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

View file

@ -2,7 +2,8 @@ use std::ops::Range;
use crate::{ use crate::{
geometry::{rect::RectF, vector::Vector2F}, 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; use serde_json::json;
@ -61,7 +62,7 @@ impl<V: View> Element<V> for Expanded<V> {
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
view: &mut V, view: &mut V,
cx: &mut ViewContext<V>, cx: &mut PaintContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
self.child self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx); .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::{ use crate::{
json::{self, ToJson, Value}, json::{self, ToJson, Value},
AnyElement, Axis, Element, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint, AnyElement, Axis, Element, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder,
Vector2FExt, View, ViewContext, SizeConstraint, Vector2FExt, View, ViewContext,
}; };
use pathfinder_geometry::{ use pathfinder_geometry::{
rect::RectF, rect::RectF,
@ -258,7 +258,7 @@ impl<V: View> Element<V> for Flex<V> {
visible_bounds: RectF, visible_bounds: RectF,
remaining_space: &mut Self::LayoutState, remaining_space: &mut Self::LayoutState,
view: &mut V, view: &mut V,
cx: &mut ViewContext<V>, cx: &mut PaintContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); 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, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
view: &mut V, view: &mut V,
cx: &mut ViewContext<V>, cx: &mut PaintContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
self.child self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx) .paint(scene, bounds.origin(), visible_bounds, view, cx)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,8 @@ use std::ops::Range;
use crate::{ use crate::{
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
json::{self, json, ToJson}, 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. /// 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, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
view: &mut V, view: &mut V,
cx: &mut ViewContext<V>, cx: &mut PaintContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
for child in &mut self.children { for child in &mut self.children {
scene.paint_layer(None, |scene| { scene.paint_layer(None, |scene| {

View file

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

View file

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

View file

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

View file

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

View file

@ -71,6 +71,32 @@ pub struct TextStyle {
pub underline: Underline, 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)] #[derive(JsonSchema)]
#[serde(remote = "Properties")] #[serde(remote = "Properties")]
pub struct PropertiesDef { pub struct PropertiesDef {

View file

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

View file

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

View file

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

View file

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

View file

@ -19,7 +19,10 @@ typedef struct {
float border_bottom; float border_bottom;
float border_left; float border_left;
vector_uchar4 border_color; 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; } GPUIQuad;
typedef enum { typedef enum {
@ -31,7 +34,10 @@ typedef enum {
typedef struct { typedef struct {
vector_float2 origin; vector_float2 origin;
vector_float2 size; 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; float sigma;
vector_uchar4 color; vector_uchar4 color;
} GPUIShadow; } GPUIShadow;
@ -89,7 +95,10 @@ typedef struct {
float border_bottom; float border_bottom;
float border_left; float border_left;
vector_uchar4 border_color; 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; uint8_t grayscale;
} GPUIImage; } GPUIImage;

View file

@ -43,7 +43,10 @@ struct QuadFragmentInput {
float border_bottom; float border_bottom;
float border_left; float border_left;
float4 border_color; 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 uchar grayscale; // only used in image shader
}; };
@ -51,12 +54,27 @@ float4 quad_sdf(QuadFragmentInput input) {
float2 half_size = input.size / 2.; float2 half_size = input.size / 2.;
float2 center = input.origin + half_size; float2 center = input.origin + half_size;
float2 center_to_point = input.position.xy - center; float2 center_to_point = input.position.xy - center;
float2 rounded_edge_to_point = abs(center_to_point) - half_size + input.corner_radius; float 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; 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 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; 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; float2 point_to_inset_corner = abs(center_to_point) - inset_size;
float border_width; float border_width;
if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) { 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_bottom,
quad.border_left, quad.border_left,
coloru_to_colorf(quad.border_color), 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, 0,
}; };
} }
@ -125,7 +146,10 @@ struct ShadowFragmentInput {
float4 position [[position]]; float4 position [[position]];
vector_float2 origin; vector_float2 origin;
vector_float2 size; 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; float sigma;
vector_uchar4 color; vector_uchar4 color;
}; };
@ -148,7 +172,10 @@ vertex ShadowFragmentInput shadow_vertex(
device_position, device_position,
shadow.origin, shadow.origin,
shadow.size, 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.sigma,
shadow.color, shadow.color,
}; };
@ -158,10 +185,24 @@ fragment float4 shadow_fragment(
ShadowFragmentInput input [[stage_in]] ShadowFragmentInput input [[stage_in]]
) { ) {
float sigma = input.sigma; float sigma = input.sigma;
float corner_radius = input.corner_radius;
float2 half_size = input.size / 2.; float2 half_size = input.size / 2.;
float2 center = input.origin + half_size; float2 center = input.origin + half_size;
float2 point = input.position.xy - center; 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 // The signal is only non-zero in a limited range, so don't waste samples
float low = point.y - half_size.y; float low = point.y - half_size.y;
@ -252,7 +293,10 @@ vertex QuadFragmentInput image_vertex(
image.border_bottom, image.border_bottom,
image.border_left, image.border_left,
coloru_to_colorf(image.border_color), 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, image.grayscale,
}; };
} }
@ -266,7 +310,7 @@ fragment float4 image_fragment(
if (input.grayscale) { if (input.grayscale) {
float grayscale = float grayscale =
0.2126 * input.background_color.r + 0.2126 * input.background_color.r +
0.7152 * input.background_color.g + 0.7152 * input.background_color.g +
0.0722 * input.background_color.b; 0.0722 * input.background_color.b;
input.background_color = float4(grayscale, grayscale, grayscale, input.background_color.a); 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, Event, InputHandler, KeyDownEvent, Modifiers, ModifiersChangedEvent, MouseButton,
MouseButtonEvent, MouseMovedEvent, Scene, WindowBounds, WindowKind, MouseButtonEvent, MouseMovedEvent, Scene, WindowBounds, WindowKind,
}, },
AnyWindowHandle,
}; };
use block::ConcreteBlock; use block::ConcreteBlock;
use cocoa::{ use cocoa::{
@ -282,7 +283,7 @@ struct InsertText {
} }
struct WindowState { struct WindowState {
id: usize, handle: AnyWindowHandle,
native_window: id, native_window: id,
kind: WindowKind, kind: WindowKind,
event_callback: Option<Box<dyn FnMut(Event) -> bool>>, 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( pub fn open(
id: usize, handle: AnyWindowHandle,
options: platform::WindowOptions, options: platform::WindowOptions,
executor: Rc<executor::Foreground>, executor: Rc<executor::Foreground>,
fonts: Arc<dyn platform::FontSystem>, fonts: Arc<dyn platform::FontSystem>,
@ -504,7 +505,7 @@ impl Window {
assert!(!native_view.is_null()); assert!(!native_view.is_null());
let window = Self(Rc::new(RefCell::new(WindowState { let window = Self(Rc::new(RefCell::new(WindowState {
id, handle,
native_window, native_window,
kind: options.kind, kind: options.kind,
event_callback: None, event_callback: None,
@ -621,13 +622,13 @@ impl Window {
} }
} }
pub fn main_window_id() -> Option<usize> { pub fn main_window() -> Option<AnyWindowHandle> {
unsafe { unsafe {
let app = NSApplication::sharedApplication(nil); let app = NSApplication::sharedApplication(nil);
let main_window: id = msg_send![app, mainWindow]; let main_window: id = msg_send![app, mainWindow];
if msg_send![main_window, isKindOfClass: WINDOW_CLASS] { if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
let id = get_window_state(&*main_window).borrow().id; let handle = get_window_state(&*main_window).borrow().handle;
Some(id) Some(handle)
} else { } else {
None None
} }
@ -635,7 +636,7 @@ impl Window {
} }
} }
impl Drop for Window { impl Drop for MacWindow {
fn drop(&mut self) { fn drop(&mut self) {
let this = self.0.borrow(); let this = self.0.borrow();
let window = this.native_window; 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 { fn bounds(&self) -> WindowBounds {
self.0.as_ref().borrow().bounds() self.0.as_ref().borrow().bounds()
} }
@ -881,7 +882,7 @@ impl platform::Window for Window {
fn is_topmost_for_position(&self, position: Vector2F) -> bool { fn is_topmost_for_position(&self, position: Vector2F) -> bool {
let self_borrow = self.0.borrow(); let self_borrow = self.0.borrow();
let self_id = self_borrow.id; let self_handle = self_borrow.handle;
unsafe { unsafe {
let app = NSApplication::sharedApplication(nil); 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_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS];
let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS]; let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS];
if is_panel == YES || is_window == YES { if is_panel == YES || is_window == YES {
let topmost_window_id = get_window_state(&*top_most_window).borrow().id; let topmost_window = get_window_state(&*top_most_window).borrow().handle;
topmost_window_id == self_id topmost_window == self_handle
} else { } else {
// Someone else's window is on top // Someone else's window is on top
false false
@ -1086,7 +1087,10 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
button: MouseButton::Left, button: MouseButton::Left,
modifiers: Modifiers { ctrl: true, .. }, modifiers: Modifiers { ctrl: true, .. },
.. ..
}) => return, }) => {
window_state_borrow.synthetic_drag_counter += 1;
return;
}
_ => None, _ => None,
}; };

View file

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

View file

@ -3,8 +3,10 @@ mod mouse_region;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
use collections::HashSet; use collections::HashSet;
use derive_more::Mul;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use serde_derive::Serialize;
use serde_json::json; use serde_json::json;
use std::{borrow::Cow, sync::Arc}; use std::{borrow::Cow, sync::Arc};
@ -65,13 +67,73 @@ pub struct Quad {
pub bounds: RectF, pub bounds: RectF,
pub background: Option<Color>, pub background: Option<Color>,
pub border: Border, 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)] #[derive(Debug)]
pub struct Shadow { pub struct Shadow {
pub bounds: RectF, pub bounds: RectF,
pub corner_radius: f32, pub corner_radii: CornerRadii,
pub sigma: f32, pub sigma: f32,
pub color: Color, pub color: Color,
} }
@ -177,7 +239,7 @@ pub struct PathVertex {
pub struct Image { pub struct Image {
pub bounds: RectF, pub bounds: RectF,
pub border: Border, pub border: Border,
pub corner_radius: f32, pub corner_radii: CornerRadii,
pub grayscale: bool, pub grayscale: bool,
pub data: Arc<ImageData>, 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 { pub struct MouseRegionId {
view_id: usize, view_id: usize,
tag: TypeId, tag: TypeId,

View file

@ -14,3 +14,5 @@ syn = "1.0"
quote = "1.0" quote = "1.0"
proc-macro2 = "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 // The name of the struct/enum
let name = input.ident; let name = input.ident;
let must_implement = format_ident!("{}MustImplementRenderElement", name);
let expanded = quote! { let expanded = quote! {
trait #must_implement : gpui::elements::RenderElement {}
impl #must_implement for #name {}
impl<V: gpui::View> gpui::elements::Element<V> for #name { impl<V: gpui::View> gpui::elements::Element<V> for #name {
type LayoutState = gpui::elements::AnyElement<V>; type LayoutState = gpui::elements::AnyElement<V>;
type PaintState = (); type PaintState = ();
@ -307,7 +311,7 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
visible_bounds: gpui::geometry::rect::RectF, visible_bounds: gpui::geometry::rect::RectF,
element: &mut gpui::elements::AnyElement<V>, element: &mut gpui::elements::AnyElement<V>,
view: &mut V, view: &mut V,
cx: &mut gpui::ViewContext<V>, cx: &mut gpui::PaintContext<V>,
) { ) {
element.paint(scene, bounds.origin(), visible_bounds, view, cx); element.paint(scene, bounds.origin(), visible_bounds, view, cx);
} }
@ -332,7 +336,7 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
_: &(), _: &(),
view: &V, view: &V,
cx: &gpui::ViewContext<V>, cx: &gpui::ViewContext<V>,
) -> serde_json::Value { ) -> gpui::serde_json::Value {
element.debug(view, cx) 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 theme::{SyntaxTheme, Theme};
use tree_sitter::{self, Query}; use tree_sitter::{self, Query};
use unicase::UniCase; 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}; use util::{merge_json_value_into, post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
@ -182,8 +182,8 @@ impl CachedLspAdapter {
self.adapter.workspace_configuration(cx) self.adapter.workspace_configuration(cx)
} }
pub async fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) { pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
self.adapter.process_diagnostics(params).await self.adapter.process_diagnostics(params)
} }
pub async fn process_completion(&self, completion_item: &mut lsp::CompletionItem) { pub async fn process_completion(&self, completion_item: &mut lsp::CompletionItem) {
@ -262,7 +262,7 @@ pub trait LspAdapter: 'static + Send + Sync {
container_dir: PathBuf, container_dir: PathBuf,
) -> Option<LanguageServerBinary>; ) -> 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) {} async fn process_completion(&self, _: &mut lsp::CompletionItem) {}
@ -777,7 +777,7 @@ impl LanguageRegistry {
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> { ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
let path = path.as_ref(); let path = path.as_ref();
let filename = path.file_name().and_then(|name| name.to_str()); 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]; let path_suffixes = [extension, filename];
self.get_or_load_language(|config| { self.get_or_load_language(|config| {
let path_matches = config let path_matches = config
@ -1487,12 +1487,6 @@ impl Language {
None 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) { pub async fn process_completion(self: &Arc<Self>, completion: &mut lsp::CompletionItem) {
for adapter in &self.adapters { for adapter in &self.adapters {
adapter.process_completion(completion).await; adapter.process_completion(completion).await;
@ -1756,7 +1750,7 @@ impl LspAdapter for Arc<FakeLspAdapter> {
unreachable!(); 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> { async fn disk_based_diagnostic_sources(&self) -> Vec<String> {
self.disk_based_diagnostics_sources.clone() 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>() .receive_notification::<lsp::notification::DidOpenTextDocument>()
.await; .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 { language_server.notify::<lsp::notification::LogMessage>(lsp::LogMessageParams {
message: "hello from the server".into(), message: "hello from the server".into(),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -115,6 +115,7 @@ actions!(
[ [
ExpandSelectedEntry, ExpandSelectedEntry,
CollapseSelectedEntry, CollapseSelectedEntry,
CollapseAllEntries,
NewDirectory, NewDirectory,
NewFile, NewFile,
Copy, Copy,
@ -140,6 +141,7 @@ pub fn init(assets: impl AssetSource, cx: &mut AppContext) {
file_associations::init(assets, cx); file_associations::init(assets, cx);
cx.add_action(ProjectPanel::expand_selected_entry); cx.add_action(ProjectPanel::expand_selected_entry);
cx.add_action(ProjectPanel::collapse_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_prev);
cx.add_action(ProjectPanel::select_next); cx.add_action(ProjectPanel::select_next);
cx.add_action(ProjectPanel::new_file); 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>) { 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(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) { if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) {
@ -1407,7 +1415,7 @@ impl ProjectPanel {
if cx if cx
.global::<DragAndDrop<Workspace>>() .global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id()) .currently_dragged::<ProjectEntryId>(cx.window())
.is_some() .is_some()
&& dragged_entry_destination && dragged_entry_destination
.as_ref() .as_ref()
@ -1451,7 +1459,7 @@ impl ProjectPanel {
.on_up(MouseButton::Left, move |_, this, cx| { .on_up(MouseButton::Left, move |_, this, cx| {
if let Some((_, dragged_entry)) = cx if let Some((_, dragged_entry)) = cx
.global::<DragAndDrop<Workspace>>() .global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id()) .currently_dragged::<ProjectEntryId>(cx.window())
{ {
this.move_entry( this.move_entry(
*dragged_entry, *dragged_entry,
@ -1464,7 +1472,7 @@ impl ProjectPanel {
.on_move(move |_, this, cx| { .on_move(move |_, this, cx| {
if cx if cx
.global::<DragAndDrop<Workspace>>() .global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id()) .currently_dragged::<ProjectEntryId>(cx.window())
.is_some() .is_some()
{ {
this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) { this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) {
@ -1718,7 +1726,7 @@ impl ClipboardEntry {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use gpui::{TestAppContext, ViewHandle}; use gpui::{AnyWindowHandle, TestAppContext, ViewHandle, WindowHandle};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use project::FakeFs; use project::FakeFs;
use serde_json::json; use serde_json::json;
@ -1772,7 +1780,9 @@ mod tests {
.await; .await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).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)); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..50, cx), visible_entries_as_strings(&panel, 0..50, cx),
@ -1860,7 +1870,8 @@ mod tests {
.await; .await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).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)); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "root1", 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 // Add a file with the root folder selected. The filename editor is placed
// before the first file in the root folder. // before the first file in the root folder.
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx)); 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); let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx)); assert!(panel.filename_editor.is_focused(cx));
}); });
@ -2211,7 +2222,8 @@ mod tests {
.await; .await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).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)); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "root1", 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 // Add a file with the root folder selected. The filename editor is placed
// before the first file in the root folder. // before the first file in the root folder.
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx)); 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); let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx)); assert!(panel.filename_editor.is_focused(cx));
}); });
@ -2311,7 +2323,9 @@ mod tests {
.await; .await;
let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).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)); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
panel.update(cx, |panel, cx| { panel.update(cx, |panel, cx| {
@ -2384,7 +2398,8 @@ mod tests {
.await; .await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).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)); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
toggle_expand_dir(&panel, "src/test", cx); toggle_expand_dir(&panel, "src/test", cx);
@ -2401,9 +2416,9 @@ mod tests {
" third.rs" " 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!( assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx), 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" "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); select_path(&panel, "src/test/second.rs", cx);
panel.update(cx, |panel, cx| panel.open_file(&Open, cx)); panel.update(cx, |panel, cx| panel.open_file(&Open, cx));
@ -2428,9 +2443,9 @@ mod tests {
" third.rs" " 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 let active_items = workspace
.read(cx) .read(cx)
.panes() .panes()
@ -2446,13 +2461,13 @@ mod tests {
.expect("Open item should be an editor"); .expect("Open item should be an editor");
open_editor.update(cx, |editor, cx| editor.set_text("Another text!", cx)); 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!( assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx), visible_entries_as_strings(&panel, 0..10, cx),
&["v src", " v test", " third.rs"], &["v src", " v test", " third.rs"],
"Project panel should have no deleted file, with one last file remaining" "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] #[gpui::test]
@ -2473,7 +2488,8 @@ mod tests {
.await; .await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).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)); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "src/", cx); select_path(&panel, "src/", cx);
@ -2484,7 +2500,7 @@ mod tests {
&["v src <== selected", " > test"] &["v src <== selected", " > test"]
); );
panel.update(cx, |panel, cx| panel.new_directory(&NewDirectory, cx)); 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); let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx)); assert!(panel.filename_editor.is_focused(cx));
}); });
@ -2515,7 +2531,7 @@ mod tests {
&["v src", " > test <== selected"] &["v src", " > test <== selected"]
); );
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx)); 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); let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx)); assert!(panel.filename_editor.is_focused(cx));
}); });
@ -2565,7 +2581,7 @@ mod tests {
], ],
); );
panel.update(cx, |panel, cx| panel.rename(&Rename, cx)); 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); let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx)); assert!(panel.filename_editor.is_focused(cx));
}); });
@ -2619,7 +2635,9 @@ mod tests {
.await; .await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).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 panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
let new_search_events_count = Arc::new(AtomicUsize::new(0)); 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( fn toggle_expand_dir(
panel: &ViewHandle<ProjectPanel>, panel: &ViewHandle<ProjectPanel>,
path: impl AsRef<Path>, path: impl AsRef<Path>,
@ -2801,13 +2878,11 @@ mod tests {
} }
fn ensure_single_file_is_opened( fn ensure_single_file_is_opened(
window_id: usize, window: WindowHandle<Workspace>,
workspace: &ViewHandle<Workspace>,
expected_path: &str, expected_path: &str,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) { ) {
cx.read_window(window_id, |cx| { window.update_root(cx, |workspace, cx| {
let workspace = workspace.read(cx);
let worktrees = workspace.worktrees(cx).collect::<Vec<_>>(); let worktrees = workspace.worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1); assert_eq!(worktrees.len(), 1);
let worktree_id = WorktreeId::from_usize(worktrees[0].id()); let worktree_id = WorktreeId::from_usize(worktrees[0].id());
@ -2829,12 +2904,12 @@ mod tests {
} }
fn submit_deletion( fn submit_deletion(
window_id: usize, window: AnyWindowHandle,
panel: &ViewHandle<ProjectPanel>, panel: &ViewHandle<ProjectPanel>,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) { ) {
assert!( assert!(
!cx.has_pending_prompt(window_id), !window.has_pending_prompt(cx),
"Should have no prompts before the deletion" "Should have no prompts before the deletion"
); );
panel.update(cx, |panel, cx| { panel.update(cx, |panel, cx| {
@ -2844,27 +2919,27 @@ mod tests {
.detach_and_log_err(cx); .detach_and_log_err(cx);
}); });
assert!( assert!(
cx.has_pending_prompt(window_id), window.has_pending_prompt(cx),
"Should have a prompt after the deletion" "Should have a prompt after the deletion"
); );
cx.simulate_prompt_answer(window_id, 0); window.simulate_prompt_answer(0, cx);
assert!( assert!(
!cx.has_pending_prompt(window_id), !window.has_pending_prompt(cx),
"Should have no prompts after prompt was replied to" "Should have no prompts after prompt was replied to"
); );
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
} }
fn ensure_no_open_items_and_panes( fn ensure_no_open_items_and_panes(
window_id: usize, window: AnyWindowHandle,
workspace: &ViewHandle<Workspace>, workspace: &ViewHandle<Workspace>,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) { ) {
assert!( assert!(
!cx.has_pending_prompt(window_id), !window.has_pending_prompt(cx),
"Should have no prompts after deletion operation closes the file" "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 let open_project_paths = workspace
.read(cx) .read(cx)
.panes() .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. // Create the project symbols view.
let symbols = cx.add_view(window_id, |cx| { let symbols = window.add_view(cx, |cx| {
ProjectSymbols::new( ProjectSymbols::new(
ProjectSymbolsDelegate::new(workspace.downgrade(), project.clone()), ProjectSymbolsDelegate::new(workspace.downgrade(), project.clone()),
cx, cx,

View file

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

View file

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

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
SearchOptions, SelectAllMatches, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, NextHistoryQuery, PreviousHistoryQuery, SearchHistory, SearchOptions, SelectAllMatches,
ToggleRegex, ToggleWholeWord, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex, ToggleWholeWord,
}; };
use collections::HashMap; use collections::HashMap;
use editor::Editor; 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_prev_match_on_pane);
cx.add_action(BufferSearchBar::select_all_matches_on_pane); cx.add_action(BufferSearchBar::select_all_matches_on_pane);
cx.add_action(BufferSearchBar::handle_editor_cancel); 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::<ToggleCaseSensitive>(SearchOptions::CASE_SENSITIVE, cx);
add_toggle_option_action::<ToggleWholeWord>(SearchOptions::WHOLE_WORD, cx); add_toggle_option_action::<ToggleWholeWord>(SearchOptions::WHOLE_WORD, cx);
add_toggle_option_action::<ToggleRegex>(SearchOptions::REGEX, 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 struct BufferSearchBar {
pub query_editor: ViewHandle<Editor>, query_editor: ViewHandle<Editor>,
active_searchable_item: Option<Box<dyn SearchableItemHandle>>, active_searchable_item: Option<Box<dyn SearchableItemHandle>>,
active_match_index: Option<usize>, active_match_index: Option<usize>,
active_searchable_item_subscription: Option<Subscription>, active_searchable_item_subscription: Option<Subscription>,
@ -76,6 +78,7 @@ pub struct BufferSearchBar {
default_options: SearchOptions, default_options: SearchOptions,
query_contains_error: bool, query_contains_error: bool,
dismissed: bool, dismissed: bool,
search_history: SearchHistory,
} }
impl Entity for BufferSearchBar { impl Entity for BufferSearchBar {
@ -106,6 +109,48 @@ impl View for BufferSearchBar {
.map(|active_searchable_item| active_searchable_item.supported_options()) .map(|active_searchable_item| active_searchable_item.supported_options())
.unwrap_or_default(); .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() Flex::row()
.with_child( .with_child(
Flex::row() Flex::row()
@ -258,6 +303,7 @@ impl BufferSearchBar {
pending_search: None, pending_search: None,
query_contains_error: false, query_contains_error: false,
dismissed: true, dismissed: true,
search_history: SearchHistory::default(),
} }
} }
@ -341,7 +387,7 @@ impl BufferSearchBar {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> oneshot::Receiver<()> { ) -> oneshot::Receiver<()> {
let options = options.unwrap_or(self.default_options); 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| { self.query_editor.update(cx, |query_editor, cx| {
query_editor.buffer().update(cx, |query_buffer, cx| { query_editor.buffer().update(cx, |query_buffer, cx| {
let len = query_buffer.len(cx); let len = query_buffer.len(cx);
@ -674,7 +720,7 @@ impl BufferSearchBar {
fn update_matches(&mut self, cx: &mut ViewContext<Self>) -> oneshot::Receiver<()> { fn update_matches(&mut self, cx: &mut ViewContext<Self>) -> oneshot::Receiver<()> {
let (done_tx, done_rx) = oneshot::channel(); 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(); self.pending_search.take();
if let Some(active_searchable_item) = self.active_searchable_item.as_ref() { if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
if query.is_empty() { 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 matches = active_searchable_item.find_matches(query, cx);
let active_searchable_item = active_searchable_item.downgrade(); let active_searchable_item = active_searchable_item.downgrade();
@ -720,6 +767,7 @@ impl BufferSearchBar {
.insert(active_searchable_item.downgrade(), matches); .insert(active_searchable_item.downgrade(), matches);
this.update_match_index(cx); this.update_match_index(cx);
this.search_history.add(query_text);
if !this.dismissed { if !this.dismissed {
let matches = this let matches = this
.searchable_items_with_matches .searchable_items_with_matches
@ -753,6 +801,28 @@ impl BufferSearchBar {
cx.notify(); 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)] #[cfg(test)]
@ -779,11 +849,10 @@ mod tests {
cx, 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 = window.add_view(cx, |cx| {
let search_bar = cx.add_view(window_id, |cx| {
let mut search_bar = BufferSearchBar::new(cx); let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx); search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx); search_bar.show(cx);
@ -1159,11 +1228,10 @@ mod tests {
"Should pick a query with multiple results" "Should pick a query with multiple results"
); );
let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx)); 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 = window.add_view(cx, |cx| {
let search_bar = cx.add_view(window_id, |cx| {
let mut search_bar = BufferSearchBar::new(cx); let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx); search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx); search_bar.show(cx);
@ -1179,12 +1247,13 @@ mod tests {
search_bar.activate_current_match(cx); search_bar.activate_current_match(cx);
}); });
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
assert!( assert!(
!editor.is_focused(cx), !editor.is_focused(cx),
"Initially, the editor should not be focused" "Initially, the editor should not be focused"
); );
}); });
let initial_selections = editor.update(cx, |editor, cx| { let initial_selections = editor.update(cx, |editor, cx| {
let initial_selections = editor.selections.display_ranges(cx); let initial_selections = editor.selections.display_ranges(cx);
assert_eq!( assert_eq!(
@ -1201,7 +1270,7 @@ mod tests {
cx.focus(search_bar.query_editor.as_any()); cx.focus(search_bar.query_editor.as_any());
search_bar.select_all_matches(&SelectAllMatches, cx); search_bar.select_all_matches(&SelectAllMatches, cx);
}); });
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
assert!( assert!(
editor.is_focused(cx), editor.is_focused(cx),
"Should focus editor after successful SelectAllMatches" "Should focus editor after successful SelectAllMatches"
@ -1225,7 +1294,7 @@ mod tests {
search_bar.update(cx, |search_bar, cx| { search_bar.update(cx, |search_bar, cx| {
search_bar.select_next_match(&SelectNextMatch, cx); search_bar.select_next_match(&SelectNextMatch, cx);
}); });
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
assert!( assert!(
editor.is_focused(cx), editor.is_focused(cx),
"Should still have editor focused after SelectNextMatch" "Should still have editor focused after SelectNextMatch"
@ -1254,7 +1323,7 @@ mod tests {
cx.focus(search_bar.query_editor.as_any()); cx.focus(search_bar.query_editor.as_any());
search_bar.select_all_matches(&SelectAllMatches, cx); search_bar.select_all_matches(&SelectAllMatches, cx);
}); });
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
assert!( assert!(
editor.is_focused(cx), editor.is_focused(cx),
"Should focus editor after successful SelectAllMatches" "Should focus editor after successful SelectAllMatches"
@ -1278,7 +1347,7 @@ mod tests {
search_bar.update(cx, |search_bar, cx| { search_bar.update(cx, |search_bar, cx| {
search_bar.select_prev_match(&SelectPrevMatch, cx); search_bar.select_prev_match(&SelectPrevMatch, cx);
}); });
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
assert!( assert!(
editor.is_focused(cx), editor.is_focused(cx),
"Should still have editor focused after SelectPrevMatch" "Should still have editor focused after SelectPrevMatch"
@ -1314,7 +1383,7 @@ mod tests {
search_bar.update(cx, |search_bar, cx| { search_bar.update(cx, |search_bar, cx| {
search_bar.select_all_matches(&SelectAllMatches, cx); search_bar.select_all_matches(&SelectAllMatches, cx);
}); });
cx.read_window(window_id, |cx| { window.read_with(cx, |cx| {
assert!( assert!(
!editor.is_focused(cx), !editor.is_focused(cx),
"Should not switch focus to editor if SelectAllMatches does not find any matches" "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